diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 2f1446c6e5..1a4243ecae 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -94,6 +94,7 @@ jobs: uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Create the Keystore if: ${{ !github.event.pull_request.head.repo.fork }} @@ -219,6 +220,7 @@ jobs: uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Install Flutter dependencies working-directory: ./mobile diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index f2b3e3c248..1346d05112 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - 'open-api/**' + - 'mobile/lib/utils/openapi_patching.dart' - '.github/workflows/check-openapi.yml' concurrency: @@ -29,3 +30,36 @@ jobs: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json fail-on: ERR + + check-mobile-patches: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Mise + uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 + with: + github_token: ${{ github.token }} + working_directory: ./mobile + + - name: Get packages + working-directory: ./mobile + run: flutter pub get + + - 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/static_analysis.yml b/.github/workflows/static_analysis.yml index 872d0c08c7..f3ab31d630 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -64,6 +64,7 @@ jobs: uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Install dependencies run: flutter pub get diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fdaee15a59..b0ec88d5f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -560,6 +560,7 @@ jobs: uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2 with: github_token: ${{ steps.token.outputs.token }} + working_directory: ./mobile - name: Install dependencies run: flutter pub get diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index b53356139f..3c90c26cf9 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.your-domain.com`) + traefik.http.routers.immich.rule: Host(`immich.example.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 6938cfadd6..104c16c0a2 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -90,7 +90,7 @@ immich-admin list-users [ { id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53', - email: 'immich@example.com.com', + email: 'immich@example.com', name: 'Immich Admin', storageLabel: 'admin', externalPath: null, diff --git a/docs/docs/guides/database-gui.md b/docs/docs/guides/database-gui.md index 67b658f838..f9e90c166c 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: user-name@domain-name.com + PGADMIN_DEFAULT_EMAIL: admin@example.com PGADMIN_DEFAULT_PASSWORD: strong-password volumes: - pgadmin-data:/var/lib/pgadmin diff --git a/e2e/src/specs/server/api/asset.e2e-spec.ts b/e2e/src/specs/server/api/asset.e2e-spec.ts index 010b096c4d..7f89cb515d 100644 --- a/e2e/src/specs/server/api/asset.e2e-spec.ts +++ b/e2e/src/specs/server/api/asset.e2e-spec.ts @@ -492,20 +492,6 @@ describe('/asset', () => { expect(status).toEqual(200); }); - it('should set the negative rating', async () => { - const { status, body } = await request(app) - .put(`/assets/${user1Assets[0].id}`) - .set('Authorization', `Bearer ${user1.accessToken}`) - .send({ rating: -1 }); - expect(body).toMatchObject({ - id: user1Assets[0].id, - exifInfo: expect.objectContaining({ - rating: -1, - }), - }); - expect(status).toEqual(200); - }); - it('should return tagged people', async () => { const { status, body } = await request(app) .put(`/assets/${user1Assets[0].id}`) diff --git a/e2e/src/specs/server/api/search.e2e-spec.ts b/e2e/src/specs/server/api/search.e2e-spec.ts index 09d33b735b..0b86053f78 100644 --- a/e2e/src/specs/server/api/search.e2e-spec.ts +++ b/e2e/src/specs/server/api/search.e2e-spec.ts @@ -259,17 +259,6 @@ describe('/search', () => { assets: [assetHeic], }), }, - { - should: "should search city ('')", - deferred: () => ({ - dto: { - city: '', - visibility: AssetVisibility.Timeline, - includeNull: true, - }, - assets: [assetLast], - }), - }, { should: 'should search city (null)', deferred: () => ({ @@ -291,18 +280,6 @@ 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: () => ({ @@ -324,17 +301,6 @@ describe('/search', () => { assets: [assetFalcon], }), }, - { - should: "should search country ('')", - deferred: () => ({ - dto: { - country: '', - visibility: AssetVisibility.Timeline, - includeNull: true, - }, - assets: [assetLast], - }), - }, { should: 'should search country (null)', deferred: () => ({ diff --git a/i18n/en.json b/i18n/en.json index f4ad3001c2..adf73aac01 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -699,6 +699,7 @@ "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", @@ -1689,6 +1690,7 @@ "not_selected": "Not selected", "notes": "Notes", "nothing_here_yet": "Nothing here yet", + "notification_backup_reliability": "Enable notifications to improve background backup reliability", "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", diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py index 5145be0045..be574c6397 100644 --- a/machine-learning/test_main.py +++ b/machine-learning/test_main.py @@ -816,6 +816,10 @@ class TestFaceRecognition: 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", min_score=0.0, cache_dir="test_cache") num_faces = 2 @@ -860,6 +864,10 @@ class TestFaceRecognition: ) mocker.patch("immich_ml.models.base.InferenceModel.download") mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) 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" @@ -894,6 +902,10 @@ class TestFaceRecognition: ) mocker.patch("immich_ml.models.base.InferenceModel.download") mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + mocker.patch( + "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", + return_value=["CPUExecutionProvider"], + ) path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] @@ -996,6 +1008,10 @@ 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") diff --git a/mise.lock b/mise.lock index fa63fda5e0..df7819caa1 100644 --- a/mise.lock +++ b/mise.lock @@ -1,74 +1,5 @@ # @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.1" -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.1-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.1-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.1-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.1-stable.tar.xz" - -[tools."aqua:flutter/flutter"."platforms.macos-arm64"] -checksum = "blake3:15069c982a30ca0189a83edb5627b69d91485ad94fb74d2de8585b43364e9e8e" -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.1-stable.zip" - -[tools."aqua:flutter/flutter"."platforms.macos-x64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.1-stable.zip" - -[tools."aqua:flutter/flutter"."platforms.windows-x64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.1-stable.zip" - -[[tools.flutter]] -version = "3.41.9-stable" -backend = "asdf:flutter" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[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"."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"."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"."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"."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."github:extism/cli"]] version = "1.6.3" backend = "github:extism/cli" @@ -225,30 +156,6 @@ checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2 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."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" @@ -321,6 +228,34 @@ url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11. version = "10.33.4" backend = "aqua:pnpm/pnpm" +[tools.pnpm."platforms.linux-arm64"] +checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64" + +[tools.pnpm."platforms.linux-arm64-musl"] +checksum = "sha256:d29649c7380b5cd522f574208fbd35335846686498f45004604d3f5b8658b5cb" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-arm64" + +[tools.pnpm."platforms.linux-x64"] +checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64" + +[tools.pnpm."platforms.linux-x64-musl"] +checksum = "sha256:ff1795595535a10d0dfe327303f3dd02377be141190b1f5756de68edde2cf813" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-linux-x64" + +[tools.pnpm."platforms.macos-arm64"] +checksum = "sha256:7aae186a04e1ffaa0047d43cd07d68a98dec303304f28be52234ba955d26c671" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-arm64" + +[tools.pnpm."platforms.macos-x64"] +checksum = "sha256:3b0c97b9f794cdda293949a8ee0e0151ca08f512f4a832408386221c7c73eec6" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-macos-x64" + +[tools.pnpm."platforms.windows-x64"] +checksum = "sha256:3268b2f29defe0dce8a3a26c0ef01488f0d4aa4872923173186ef618ab7d68ef" +url = "https://github.com/pnpm/pnpm/releases/download/v10.33.4/pnpm-win-x64.exe" + [[tools.terragrunt]] version = "1.0.3" backend = "aqua:gruntwork-io/terragrunt" diff --git a/mise.toml b/mise.toml index b501f15f0c..65ed2377be 100644 --- a/mise.toml +++ b/mise.toml @@ -16,28 +16,14 @@ config_roots = [ [tools] node = "24.15.0" -"aqua:flutter/flutter" = "3.44.1" pnpm = "10.33.4" terragrunt = "1.0.3" opentofu = "1.11.6" -java = "21.0.2" "npm:oazapfts" = "7.5.0" "github:extism/cli" = "1.6.3" "github:webassembly/binaryen" = "version_124" "github:extism/js-pdk" = "1.6.0" -[tools."github:CQLabs/homebrew-dcm"] -version = "1.37.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" } - [tools."github:jellyfin/jellyfin-ffmpeg"] version = "7.1.3-6" diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 436d8c492d..1b8d2a97fb 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -89,6 +89,20 @@ + + + + + + + + + + + + + + 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 b4cd705b05..fc9ab28fa2 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,6 +1,7 @@ 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 @@ -22,6 +23,7 @@ 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 @@ -31,6 +33,11 @@ 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) @@ -55,6 +62,7 @@ class MainActivity : FlutterFragmentActivity() { BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx)) ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx)) + flutterEngine.plugins.add(ViewIntentPlugin()) flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(nativeSyncApiImpl) flutterEngine.plugins.add(permissionApiImpl) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt index 48a1a72037..5f7bf806b4 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt @@ -47,18 +47,44 @@ class FlutterError ( override val message: String? = null, val details: Any? = null ) : RuntimeException() + +enum class PermissionStatus(val raw: Int) { + GRANTED(0), + DENIED(1), + PERMANENTLY_DENIED(2); + + companion object { + fun ofRaw(raw: Int): PermissionStatus? { + return values().firstOrNull { it.raw == raw } + } + } +} private open class PermissionApiPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return super.readValueOfType(type, buffer) + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + PermissionStatus.ofRaw(it.toInt()) + } + } + else -> super.readValueOfType(type, buffer) + } } override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - super.writeValue(stream, value) + when (value) { + is PermissionStatus -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + else -> super.writeValue(stream, value) + } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface PermissionApi { + fun isIgnoringBatteryOptimizations(): PermissionStatus fun hasManageMediaPermission(): Boolean fun requestManageMediaPermission(callback: (Result) -> Unit) fun manageMediaPermission(callback: (Result) -> Unit) @@ -72,6 +98,21 @@ interface PermissionApi { @JvmOverloads fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.isIgnoringBatteryOptimizations()) + } catch (exception: Throwable) { + PermissionApiPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec) if (api != 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 index c3443bb06d..4e4ce7b424 100644 --- 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 @@ -1,13 +1,26 @@ 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() 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 index 345302026d..02f1cb237d 100644 --- 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 @@ -542,16 +542,17 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface NativeSyncApi { - fun shouldFullSync(): Boolean - fun getMediaChanges(): SyncDelta + fun shouldFullSync(callback: (Result) -> Unit) + fun getMediaChanges(callback: (Result) -> Unit) fun checkpointSync() fun clearSyncCheckpoint() - fun getAssetIdsForAlbum(albumId: String): List - fun getAlbums(): List + fun getAssetIdsForAlbum(albumId: String, callback: (Result>) -> Unit) + fun getAlbums(callback: (Result>) -> Unit) fun getAssetsCountSince(albumId: String, timestamp: Long): Long - fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result>) -> Unit) fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) fun cancelHashing() + fun cancelSync() fun getTrashedAssets(): Map> fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) fun getCloudIdForAssetIds(assetIds: List): List @@ -570,27 +571,33 @@ interface NativeSyncApi { 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) + api.shouldFullSync{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getMediaChanges()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getMediaChanges{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -629,32 +636,38 @@ interface NativeSyncApi { } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec) 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) + api.getAssetIdsForAlbum(albumIdArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = try { - listOf(api.getAlbums()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) + api.getAlbums{ result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -679,18 +692,21 @@ interface NativeSyncApi { } } run { - val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec) 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) + api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } } - reply.reply(wrapped) } } else { channel.setMessageHandler(null) @@ -733,6 +749,22 @@ interface NativeSyncApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.cancelSync() + 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) { 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 6d2c35d78f..180e23286c 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,7 +4,11 @@ import android.content.Context class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), NativeSyncApi { - override fun shouldFullSync(): Boolean { + override fun shouldFullSync(callback: (Result) -> Unit) { + runSync(callback) { shouldFullSync() } + } + + private fun shouldFullSync(): Boolean { return true } @@ -18,7 +22,11 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na // No-op for Android 10 and below } - override fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(callback: (Result) -> Unit) { + runSync(callback) { getMediaChanges() } + } + + private 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 ca54c9f823..4785b751c0 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,6 +7,8 @@ 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) @@ -35,7 +37,11 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun shouldFullSync(): Boolean = + override fun shouldFullSync(callback: (Result) -> Unit) { + runSync(callback) { shouldFullSync() } + } + + private fun shouldFullSync(): Boolean = MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null) override fun checkpointSync() { @@ -49,7 +55,11 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(callback: (Result) -> Unit) { + runSync(callback) { getMediaChanges() } + } + + private suspend fun getMediaChanges(): SyncDelta { val genMap = getSavedGenerationMap() val currentVolumes = MediaStore.getExternalVolumeNames(ctx) val changed = mutableListOf() @@ -58,6 +68,7 @@ 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 1f5ff2529e..18b771a613 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 @@ -45,12 +45,14 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa 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 @@ -295,7 +297,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa return PlatformAssetPlaybackStyle.IMAGE } - fun getAlbums(): List { + fun getAlbums(callback: (Result>) -> Unit) { + runSync(callback) { getAlbums() } + } + + private suspend fun getAlbums(): List { val albums = mutableListOf() val albumsCount = mutableMapOf() @@ -322,6 +328,7 @@ 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) @@ -342,7 +349,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa .sortedBy { it.id } } - fun getAssetIdsForAlbum(albumId: String): List { + fun getAssetIdsForAlbum(albumId: String, callback: (Result>) -> Unit) { + runSync(callback) { getAssetIdsForAlbum(albumId) } + } + + private fun getAssetIdsForAlbum(albumId: String): List { val projection = arrayOf(MediaStore.MediaColumns._ID) return getCursor( @@ -366,7 +377,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa )?.use { cursor -> cursor.count.toLong() } ?: 0L - fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result>) -> Unit) { + runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) } + } + + private fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION" val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS) @@ -451,6 +466,24 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa 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 (e: 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) } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt new file mode 100644 index 0000000000..1d5af15cb4 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt @@ -0,0 +1,292 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.viewintent + +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 ViewIntentPigeonUtils { + + 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 doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + 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) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } + +} + +/** + * 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 +) : RuntimeException() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ViewIntentPayload ( + val path: String? = null, + val mimeType: String, + val localAssetId: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): ViewIntentPayload { + val path = pigeonVar_list[0] as String? + val mimeType = pigeonVar_list[1] as String + val localAssetId = pigeonVar_list[2] as String? + return ViewIntentPayload(path, mimeType, localAssetId) + } + } + fun toList(): List { + return listOf( + path, + mimeType, + localAssetId, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as ViewIntentPayload + return ViewIntentPigeonUtils.deepEquals(this.path, other.path) && ViewIntentPigeonUtils.deepEquals(this.mimeType, other.mimeType) && ViewIntentPigeonUtils.deepEquals(this.localAssetId, other.localAssetId) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.path) + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.mimeType) + result = 31 * result + ViewIntentPigeonUtils.deepHash(this.localAssetId) + return result + } +} +private open class ViewIntentPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + ViewIntentPayload.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is ViewIntentPayload -> { + 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 ViewIntentHostApi { + fun consumeViewIntent(callback: (Result) -> Unit) + + companion object { + /** The codec used by ViewIntentHostApi. */ + val codec: MessageCodec by lazy { + ViewIntentPigeonCodec() + } + /** Sets up an instance of `ViewIntentHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: ViewIntentHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.consumeViewIntent{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(ViewIntentPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(ViewIntentPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} 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 new file mode 100644 index 0000000000..a1e1fea3dd --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt @@ -0,0 +1,201 @@ +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/drift_schemas/main/drift_schema_v28.json b/mobile/drift_schemas/main/drift_schema_v28.json new file mode 100644 index 0000000000..c635d0a6a2 --- /dev/null +++ b/mobile/drift_schemas/main/drift_schema_v28.json @@ -0,0 +1,3391 @@ +{ + "_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/integration_test/background_sync_teardown_test.dart b/mobile/integration_test/background_sync_teardown_test.dart new file mode 100644 index 0000000000..0f125b7fcc --- /dev/null +++ b/mobile/integration_test/background_sync_teardown_test.dart @@ -0,0 +1,154 @@ +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.value, + 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/test_utils/fake_immich_server.dart b/mobile/integration_test/test_utils/fake_immich_server.dart new file mode 100644 index 0000000000..c434f83bc5 --- /dev/null +++ b/mobile/integration_test/test_utils/fake_immich_server.dart @@ -0,0 +1,115 @@ +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(); + + 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; + + 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(); + if (!_streamOpened.isCompleted) { + _streamOpened.complete(SyncStream._(request.response)); + } + } + + 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 { + if (_streamOpened.isCompleted) { + await (await _streamOpened.future).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/ios/Runner/Background/BackgroundWorker.swift b/mobile/ios/Runner/Background/BackgroundWorker.swift index c5b5e1778a..ad583065f0 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.swift @@ -121,8 +121,8 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { /** * Cancels the currently running background task, either due to timeout or external request. - * 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. + * 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. */ func close() { if isComplete { @@ -132,12 +132,6 @@ 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/Permission/PermissionApi.g.swift b/mobile/ios/Runner/Permission/PermissionApi.g.swift index 53ad9e5b11..988e9b56dd 100644 --- a/mobile/ios/Runner/Permission/PermissionApi.g.swift +++ b/mobile/ios/Runner/Permission/PermissionApi.g.swift @@ -11,6 +11,24 @@ import Foundation #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] } @@ -46,8 +64,57 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } + +enum PermissionStatus: Int { + case granted = 0 + case denied = 1 + case permanentlyDenied = 2 +} + +private class PermissionApiPigeonCodecReader: 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 PermissionStatus(rawValue: enumResultAsInt) + } + return nil + default: + return super.readValue(ofType: type) + } + } +} + +private class PermissionApiPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? PermissionStatus { + super.writeByte(129) + super.writeValue(value.rawValue) + } else { + super.writeValue(value) + } + } +} + +private class PermissionApiPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return PermissionApiPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return PermissionApiPigeonCodecWriter(data: data) + } +} + +class PermissionApiPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = PermissionApiPigeonCodec(readerWriter: PermissionApiPigeonCodecReaderWriter()) +} + + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol PermissionApi { + func isIgnoringBatteryOptimizations() throws -> PermissionStatus func hasManageMediaPermission() throws -> Bool func requestManageMediaPermission(completion: @escaping (Result) -> Void) func manageMediaPermission(completion: @escaping (Result) -> Void) @@ -55,10 +122,23 @@ protocol PermissionApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class PermissionApiSetup { - static var codec: FlutterStandardMessageCodec { FlutterStandardMessageCodec.sharedInstance() } + static var codec: FlutterStandardMessageCodec { PermissionApiPigeonCodec.shared } /// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let isIgnoringBatteryOptimizationsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isIgnoringBatteryOptimizationsChannel.setMessageHandler { _, reply in + do { + let result = try api.isIgnoringBatteryOptimizations() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isIgnoringBatteryOptimizationsChannel.setMessageHandler(nil) + } let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { hasManageMediaPermissionChannel.setMessageHandler { _, reply in diff --git a/mobile/ios/Runner/Permission/PermissionApiImpl.swift b/mobile/ios/Runner/Permission/PermissionApiImpl.swift index e725b742fd..3d8e89486d 100644 --- a/mobile/ios/Runner/Permission/PermissionApiImpl.swift +++ b/mobile/ios/Runner/Permission/PermissionApiImpl.swift @@ -1,6 +1,10 @@ import Foundation class PermissionApiImpl: PermissionApi { + func isIgnoringBatteryOptimizations() throws -> PermissionStatus { + return PermissionStatus.granted; + } + func hasManageMediaPermission() throws -> Bool { return false } diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift index d18a153bb7..a752785c5b 100644 --- a/mobile/ios/Runner/Sync/Messages.g.swift +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -526,16 +526,17 @@ class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol NativeSyncApi { - func shouldFullSync() throws -> Bool - func getMediaChanges() throws -> SyncDelta + func shouldFullSync(completion: @escaping (Result) -> Void) + func getMediaChanges(completion: @escaping (Result) -> Void) func checkpointSync() throws func clearSyncCheckpoint() throws - func getAssetIdsForAlbum(albumId: String) throws -> [String] - func getAlbums() throws -> [PlatformAlbum] + func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void) + func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void) func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 - func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void) func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) func cancelHashing() throws + func cancelSync() throws func getTrashedAssets() throws -> [String: [PlatformAsset]] func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] @@ -555,26 +556,28 @@ class NativeSyncApiSetup { 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)) + api.shouldFullSync { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + 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) + let getMediaChangesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getMediaChangesChannel.setMessageHandler { _, reply in - do { - let result = try api.getMediaChanges() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getMediaChanges { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -606,33 +609,33 @@ class NativeSyncApiSetup { } 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) + let getAssetIdsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 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)) + api.getAssetIdsForAlbum(albumId: albumIdArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + 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) + let getAlbumsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { getAlbumsChannel.setMessageHandler { _, reply in - do { - let result = try api.getAlbums() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) + api.getAlbums { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -656,19 +659,19 @@ class NativeSyncApiSetup { } 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) + let getAssetsForAlbumChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) 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)) + api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } } } } else { @@ -707,6 +710,19 @@ class NativeSyncApiSetup { } else { cancelHashingChannel.setMessageHandler(nil) } + let cancelSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelSyncChannel.setMessageHandler { _, reply in + do { + try api.cancelSync() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cancelSyncChannel.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) diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index e6903defeb..ddfd023690 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -39,6 +39,9 @@ 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 @@ -71,7 +74,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken) } - func shouldFullSync() -> Bool { + func shouldFullSync(completion: @escaping (Result) -> Void) { + runSync(completion) { $0.shouldFullSync() } + } + + private func shouldFullSync() -> Bool { guard #available(iOS 16, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized, let storedToken = getChangeToken() else { @@ -87,12 +94,17 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return false } - func getAlbums() throws -> [PlatformAlbum] { + func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void) { + runSync(completion) { try $0.getAlbums() } + } + + private func getAlbums() throws -> [PlatformAlbum] { var albums: [PlatformAlbum] = [] - albumTypes.forEach { type in + for type in albumTypes { let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) for i in 0.. SyncDelta { + func getMediaChanges(completion: @escaping (Result) -> Void) { + runSync(completion) { try $0.getMediaChanges() } + } + + private func getMediaChanges() throws -> SyncDelta { guard #available(iOS 16, *) else { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil) } @@ -146,51 +162,49 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:]) } - do { - let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) + 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 } - var updatedAssets: Set = [] - var deletedAssets: Set = [] + let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers) + deletedAssets.formUnion(details.deletedLocalIdentifiers) - for change in changes { - guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue } + 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 [:] @@ -213,7 +227,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return albumAssets } - func getAssetIdsForAlbum(albumId: String) throws -> [String] { + func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void) { + runSync(completion) { try $0.getAssetIdsForAlbum(albumId: albumId) } + } + + private func getAssetIdsForAlbum(albumId: String) throws -> [String] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -223,9 +241,14 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { let options = PHFetchOptions() options.includeHiddenAssets = false let assets = getAssetsFromAlbum(in: album, options: options) - assets.enumerateObjects { (asset, _, _) in + assets.enumerateObjects { (asset, _, stop) in + if Task.isCancelled { + stop.pointee = true + return + } ids.append(asset.localIdentifier) } + try Task.checkCancellation() return ids } @@ -243,7 +266,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return Int64(assets.count) } - func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { + 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] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -262,9 +289,14 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { } var assets: [PlatformAsset] = [] - result.enumerateObjects { (asset, _, _) in + result.enumerateObjects { (asset, _, stop) in + if Task.isCancelled { + stop.pointee = true + return + } assets.append(asset.toPlatformAsset()) } + try Task.checkCancellation() return assets } @@ -324,6 +356,31 @@ 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? diff --git a/mobile/lib/constants/enums.dart b/mobile/lib/constants/enums.dart index 473bd52b03..902b40b395 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -22,3 +22,5 @@ enum AssetDateAggregation { start, end } enum SlideshowLook { contain, cover, blurredBackground } enum SlideshowDirection { forward, backward, shuffle } + +enum PartnerDirection { sharedBy, sharedWith } diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart index 9ed70d61d6..d1a7fc5546 100644 --- a/mobile/lib/domain/models/user.model.dart +++ b/mobile/lib/domain/models/user.model.dart @@ -237,3 +237,125 @@ class PartnerUserDto { return id.hashCode ^ email.hashCode ^ name.hashCode ^ inTimeline.hashCode ^ profileImagePath.hashCode; } } + +class User { + 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 bool inTimeline; + + const Partner({ + required super.id, + required super.name, + required super.email, + required super.profileChangedAt, + required super.hasProfileImage, + super.avatarColor, + this.inTimeline = false, + }); + + 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, + ); + + @override + String toString() { + return 'Partner(user: ${super.toString()}, inTimeline: $inTimeline)'; + } + + @override + bool operator ==(covariant Partner other) { + if (identical(this, other)) { + return true; + } + + return super == other && other.inTimeline == inTimeline; + } + + @override + int get hashCode => Object.hash(super.hashCode, inTimeline); +} diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index d28f7ff14b..529ec770c4 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -113,9 +113,35 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { @override Future onIosUpload(bool isRefresh, int? maxSeconds) async { - final hashTimeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); - final backupTimeout = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null; - return _backgroundLoop(hashTimeout: hashTimeout, backupTimeout: backupTimeout, debugLabel: 'iOS background upload'); + _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); + final sw = Stopwatch()..start(); + try { + final budget = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null; + + final sync = _ref?.read(backgroundSyncProvider); + if (sync == null) { + 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([sync.syncLocal(), sync.syncRemote(), sync.hashAssets(), _handleBackup()]); + if (budget != null) { + await all.timeout(budget, onTimeout: () => []); + } else { + await all; + } + } catch (error, stack) { + _logger.severe("Failed to complete iOS background upload", error, stack); + } finally { + sw.stop(); + _logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s"); + await _cleanup(); + } } Future _backgroundLoop({ @@ -188,20 +214,14 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { if (!_cancellationToken.isCompleted) { _cancellationToken.complete(); } - final cleanupFutures = [ - nativeSyncApi?.cancelHashing(), - workerManagerPatch.dispose().catchError((_) async { - // Discard any errors on the dispose call - return; - }), - LogService.I.dispose(), - Store.dispose(), - backgroundSyncManager?.cancel(), - _drift.optimize(allTables: true), - ]; - - await Future.wait(cleanupFutures.nonNulls); + // Workers share one sqlite connection, so DB teardown must wait until every worker has stopped using it. + await Future.wait([ + if (backgroundSyncManager != null) backgroundSyncManager.cancel(), + if (nativeSyncApi != null) nativeSyncApi.cancelHashing(), + ]); + await workerManagerPatch.dispose().catchError((_) async {}); + await Future.wait([LogService.I.dispose(), Store.dispose(), _drift.optimize(allTables: true)]); await _drift.close(); await _driftLogger.close(); diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index e2938a79ad..e4c332b283 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -1,3 +1,5 @@ +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'; @@ -17,7 +19,7 @@ class HashService { final DriftLocalAssetRepository _localAssetRepository; final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final NativeSyncApi _nativeSyncApi; - final bool Function()? _cancelChecker; + final Completer? _cancellation; final _log = Logger('HashService'); HashService({ @@ -25,11 +27,15 @@ class HashService { required this._localAssetRepository, required this._trashedLocalAssetRepository, required this._nativeSyncApi, - this._cancelChecker, + this._cancellation, int? batchSize, - }) : _batchSize = batchSize ?? kBatchHashFileLimit; + }) : _batchSize = batchSize ?? kBatchHashFileLimit { + // Stop the in-flight native hash call promptly on cancellation; the loops + // below also observe [isCancelled] to bail between batches. + _cancellation?.future.then((_) => _nativeSyncApi.cancelHashing().onError(_log.warning)); + } - bool get isCancelled => _cancelChecker?.call() ?? false; + bool get isCancelled => _cancellation?.isCompleted ?? false; Future hashAssets() async { _log.info("Starting hashing of assets"); diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 77ded0ba4d..feb104f90d 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -2,6 +2,7 @@ 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'; @@ -17,6 +18,8 @@ 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 DriftLocalAlbumRepository _localAlbumRepository; // ignore: unused_field @@ -25,6 +28,7 @@ class LocalSyncService { final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final AssetMediaRepository _assetMediaRepository; final IPermissionRepository _permissionRepository; + final Completer? _cancellation; final Logger _log = Logger("DeviceSyncService"); LocalSyncService({ @@ -34,7 +38,12 @@ class LocalSyncService { required this._trashedLocalAssetRepository, required this._assetMediaRepository, required this._permissionRepository, - }); + this._cancellation, + }) { + _cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning)); + } + + bool get _isCancelled => _cancellation?.isCompleted ?? false; Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); @@ -81,6 +90,10 @@ 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); } @@ -91,6 +104,10 @@ class LocalSyncService { // does not include changes for cloud albums. 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."); @@ -102,6 +119,12 @@ 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 { @@ -129,12 +152,21 @@ 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}"); @@ -162,6 +194,9 @@ 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}"); diff --git a/mobile/lib/domain/services/log.service.dart b/mobile/lib/domain/services/log.service.dart index 216f030b12..b612b3ce91 100644 --- a/mobile/lib/domain/services/log.service.dart +++ b/mobile/lib/domain/services/log.service.dart @@ -112,10 +112,16 @@ class LogService { return _flushBuffer(); } - Future dispose() { + Future dispose() async { _flushTimer?.cancel(); - _logSubscription.cancel(); - return _flushBuffer(); + _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; + } } Future _flushBuffer() async { diff --git a/mobile/lib/domain/services/partner.service.dart b/mobile/lib/domain/services/partner.service.dart index ce1bd9557b..63985823aa 100644 --- a/mobile/lib/domain/services/partner.service.dart +++ b/mobile/lib/domain/services/partner.service.dart @@ -1,51 +1,42 @@ +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:immich_mobile/utils/debug_print.dart'; +import 'package:stream_transform/stream_transform.dart'; -class DriftPartnerService { - final DriftPartnerRepository _driftPartnerRepository; +class PartnerService { + final UserRepository _userRepository; + final PartnerRepository _partnerRepository; final PartnerApiRepository _partnerApiRepository; - const DriftPartnerService(this._driftPartnerRepository, this._partnerApiRepository); + const PartnerService(this._userRepository, this._partnerRepository, this._partnerApiRepository); - Future> getSharedWith(String userId) { - return _driftPartnerRepository.getSharedWith(userId); + 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> getSharedBy(String userId) { - return _driftPartnerRepository.getSharedBy(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.update(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 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 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); + Future delete({required String sharedById, required String sharedWithId}) async { + await _partnerApiRepository.delete(sharedWithId); + await _partnerRepository.delete(sharedById: sharedById, sharedWithId: sharedWithId); } } diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index 35a8f899a8..3c8999decd 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -8,6 +8,7 @@ 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:openapi/api.dart' show Optional; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:logging/logging.dart'; @@ -137,7 +138,7 @@ class RemoteAlbumService { Future updateAlbum( String albumId, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, diff --git a/mobile/lib/domain/services/store.service.dart b/mobile/lib/domain/services/store.service.dart index 16ed64e6d3..758622a43b 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -54,7 +54,13 @@ 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` diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index 3bc76083b8..ddcd6721d7 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -1,3 +1,5 @@ +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'; @@ -5,6 +7,7 @@ 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/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; @@ -16,6 +19,7 @@ final syncLinkedAlbumServiceProvider = Provider( ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider), ref.watch(storeServiceProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -24,13 +28,15 @@ class SyncLinkedAlbumService { final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; final StoreService _storeService; + final Completer? _cancellation; SyncLinkedAlbumService( this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository, - this._storeService, - ); + this._storeService, { + this._cancellation, + }); final _log = Logger("SyncLinkedAlbumService"); @@ -55,7 +61,11 @@ 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); + final album = await _albumApiRepository.addAssets( + remoteAlbum.id, + assetIds, + abortTrigger: _cancellation?.future, + ); await _remoteAlbumRepository.addAssets(remoteAlbum.id, album.added); } }), diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 200dca2418..08109b25d3 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -38,7 +38,7 @@ class SyncStreamService { final IPermissionRepository _permissionRepository; final SyncMigrationRepository _syncMigrationRepository; final ApiService _api; - final bool Function()? _cancelChecker; + final Completer? _cancellation; SyncStreamService({ required this._syncApiRepository, @@ -49,10 +49,10 @@ class SyncStreamService { required this._permissionRepository, required this._syncMigrationRepository, required this._api, - this._cancelChecker, + this._cancellation, }); - bool get isCancelled => _cancelChecker?.call() ?? false; + bool get isCancelled => _cancellation?.isCompleted ?? false; Future sync() async { _logger.info("Remote sync request for user"); @@ -80,10 +80,15 @@ 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); + await _syncApiRepository.streamChanges( + _handleEvents, + serverVersion: serverSemVer, + abortSignal: _cancellation?.future, + ); } previousLength = migrations.length; @@ -318,7 +323,7 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV1Batch(List batchData) async { - if (batchData.isEmpty) { + if (batchData.isEmpty || isCancelled) { return; } @@ -361,7 +366,7 @@ class SyncStreamService { } Future handleWsAssetUploadReadyV2Batch(List batchData) async { - if (batchData.isEmpty) { + if (batchData.isEmpty || isCancelled) { return; } @@ -404,6 +409,9 @@ class SyncStreamService { } Future handleWsAssetEditReadyV1(dynamic data) async { + if (isCancelled) { + return; + } _logger.info('Processing AssetEditReadyV1 event'); try { @@ -444,6 +452,9 @@ class SyncStreamService { } Future handleWsAssetEditReadyV2(dynamic data) async { + if (isCancelled) { + return; + } _logger.info('Processing AssetEditReadyV2 event'); try { diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 030e77cd54..82f397d9b6 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -50,53 +50,27 @@ class BackgroundSyncManager { }); Future cancel() async { - final futures = []; - - if (_syncTask != null) { - futures.add(_syncTask!.future); + final tasks = [ + _syncTask, + _syncWebsocketTask, + _cloudIdSyncTask, + _linkedAlbumSyncTask, + _deviceAlbumSyncTask, + _hashTask, + ]; + final futures = [ + for (final task in tasks) + if (task != null) task.future, + ]; + for (final task in tasks) { + task?.cancel(); } - _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; + _hashTask = null; try { await Future.wait(futures); diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index 32188b4838..efef6e8327 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; @@ -9,6 +11,7 @@ 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'; @@ -51,9 +54,10 @@ 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); + await _processCloudIdMappingsInBatches(db, currentUser.id, assetApi, canBulkUpdateMetadata, logger, cancellation); } Future _processCloudIdMappingsInBatches( @@ -62,12 +66,17 @@ 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; @@ -98,9 +107,9 @@ Future _processCloudIdMappingsInBatches( if (items.isNotEmpty) { if (canBulkUpdate) { - await _bulkUpdateCloudIds(assetsApi, items); + await _bulkUpdateCloudIds(assetsApi, items, cancellation.future); } else { - await _sequentialUpdateCloudIds(assetsApi, items); + await _sequentialUpdateCloudIds(assetsApi, items, cancellation); } } @@ -111,20 +120,35 @@ Future _processCloudIdMappingsInBatches( } } -Future _sequentialUpdateCloudIds(AssetsApi assetsApi, List items) async { +Future _sequentialUpdateCloudIds( + AssetsApi assetsApi, + List items, + Completer cancellation, +) 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])); + await assetsApi.updateAssetMetadata( + item.assetId, + AssetMetadataUpsertDto(items: [upsertItem]), + abortTrigger: cancellation.future, + ); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to update metadata for asset ${item.assetId}', error, stack); } } } -Future _bulkUpdateCloudIds(AssetsApi assetsApi, List items) async { +Future _bulkUpdateCloudIds( + AssetsApi assetsApi, + List items, + Future abortTrigger, +) async { try { - await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items)); + await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items), abortTrigger: abortTrigger); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to bulk update metadata', error, stack); } diff --git a/mobile/lib/extensions/asset_extensions.dart b/mobile/lib/extensions/asset_extensions.dart index 7e1bef1a1c..445af99e79 100644 --- a/mobile/lib/extensions/asset_extensions.dart +++ b/mobile/lib/extensions/asset_extensions.dart @@ -18,11 +18,11 @@ extension DTOToAsset on api.AssetResponseDto { height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId, + livePhotoVideoId: livePhotoVideoId.orElse(null), thumbHash: thumbhash, localId: null, type: type.toAssetType(), - stackId: stack?.id, + stackId: stack.orElse(null)?.id, isEdited: isEdited, ); } @@ -41,13 +41,13 @@ extension DTOToAsset on api.AssetResponseDto { height: height?.toInt(), width: width?.toInt(), isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId, + livePhotoVideoId: livePhotoVideoId.orElse(null), thumbHash: thumbhash, localId: null, type: type.toAssetType(), - stackId: stack?.id, + stackId: stack.orElse(null)?.id, isEdited: isEdited, - exifInfo: exifInfo != null ? ExifDtoConverter.fromDto(exifInfo!) : const ExifInfo(), + exifInfo: exifInfo.orElse(null) != null ? ExifDtoConverter.fromDto(exifInfo.orElse(null)!) : const ExifInfo(), ); } } diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.dart b/mobile/lib/infrastructure/entities/local_asset.entity.dart index 5a14a44fb7..a19c1aa540 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.dart @@ -6,6 +6,7 @@ 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(); diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart index e01e6ce745..fe03f9b208 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart @@ -1348,3 +1348,7 @@ 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)', ); +i0.Index get idxLocalAssetCreatedAt => i0.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', +); diff --git a/mobile/lib/infrastructure/mapper.dart b/mobile/lib/infrastructure/mapper.dart new file mode 100644 index 0000000000..a53aa2419a --- /dev/null +++ b/mobile/lib/infrastructure/mapper.dart @@ -0,0 +1,15 @@ +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/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 6bb2e946f1..2499a08a36 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -1,7 +1,8 @@ import 'dart:async'; +import 'dart:io'; import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; +import 'package:drift_sqlite_async/drift_sqlite_async.dart'; import 'package:flutter/foundation.dart'; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'; @@ -13,7 +14,6 @@ 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/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; import 'package:immich_mobile/infrastructure/entities/person.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'; @@ -22,6 +22,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d 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'; @@ -31,6 +32,11 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart' import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.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:sqlite_async/native.dart'; +import 'package:sqlite_async/sqlite_async.dart'; @DriftDatabase( tables: [ @@ -60,8 +66,9 @@ import 'package:logging/logging.dart'; include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, ) class Drift extends $Drift { - Drift([QueryExecutor? executor]) - : super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true))); + Drift(super.executor); + + Drift.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db)); Future reset() async { // https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94 @@ -98,7 +105,7 @@ class Drift extends $Drift { } @override - int get schemaVersion => 27; + int get schemaVersion => 28; @override MigrationStrategy get migration => MigrationStrategy( @@ -279,6 +286,9 @@ class Drift extends $Drift { from26To27: (m, v27) async { await customStatement('ALTER TABLE metadata RENAME TO settings'); }, + from27To28: (m, v28) async { + await m.createIndex(v28.idxLocalAssetCreatedAt); + }, ), ); @@ -308,3 +318,41 @@ class DriftDatabaseRepository { Future transaction(Future Function() callback) => _db.transaction(callback); } + +Future openSqliteConnection({required String name}) async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, '$name.sqlite')); + 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 + ), + ), + ); +} + +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; +} diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart index 692523219b..3a913dda97 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.drift.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -112,6 +112,7 @@ abstract class $Drift extends i0.GeneratedDatabase { i7.idxLocalAlbumAssetAlbumAsset, i4.idxLocalAssetChecksum, i4.idxLocalAssetCloudId, + i4.idxLocalAssetCreatedAt, i3.idxStackPrimaryAssetId, i2.uQRemoteAssetsOwnerChecksum, i2.uQRemoteAssetsOwnerLibraryChecksum, diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart index a51174d980..9f3498df49 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.steps.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -14083,6 +14083,554 @@ final class Schema27 extends i0.VersionedSchema { ); } +final class Schema28 extends i0.VersionedSchema { + Schema28({required super.database}) : super(version: 28); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape33 userEntity = Shape33( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_110, + _column_111, + _column_112, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape50 remoteAssetEntity = Shape50( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_119, + _column_120, + _column_121, + _column_122, + _column_123, + _column_124, + _column_212, + _column_125, + _column_126, + _column_127, + _column_128, + _column_129, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape35 stackEntity = Shape35( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_130, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape36 localAssetEntity = Shape36( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_131, + _column_120, + _column_132, + _column_133, + _column_134, + _column_135, + _column_136, + _column_137, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape48 remoteAlbumEntity = Shape48( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_138, + _column_114, + _column_115, + _column_139, + _column_140, + _column_141, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape38 localAlbumEntity = Shape38( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_115, + _column_142, + _column_143, + _column_144, + _column_145, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape39 localAlbumAssetEntity = Shape39( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_146, _column_147, _column_145], + 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 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 idxLocalAssetCreatedAt = i1.Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + 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 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 idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + late final Shape40 authUserEntity = Shape40( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_108, + _column_109, + _column_148, + _column_110, + _column_111, + _column_149, + _column_150, + _column_151, + _column_152, + ], + 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_153, _column_154, _column_155], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape41 partnerEntity = Shape41( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_156, _column_157, _column_158], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape42 remoteExifEntity = Shape42( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_160, + _column_161, + _column_162, + _column_163, + _column_164, + _column_117, + _column_116, + _column_165, + _column_166, + _column_167, + _column_168, + _column_135, + _column_136, + _column_169, + _column_170, + _column_171, + _column_172, + _column_173, + _column_174, + _column_175, + _column_176, + ], + 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_159, _column_177], + 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_177, _column_153, _column_178], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape43 remoteAssetCloudIdEntity = Shape43( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_159, + _column_179, + _column_180, + _column_134, + _column_135, + _column_136, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape44 memoryEntity = Shape44( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_124, + _column_121, + _column_113, + _column_181, + _column_182, + _column_183, + _column_184, + _column_185, + _column_186, + ], + 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_159, _column_187], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape45 personEntity = Shape45( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_114, + _column_115, + _column_121, + _column_108, + _column_188, + _column_189, + _column_190, + _column_191, + _column_192, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape46 assetFaceEntity = Shape46( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_107, + _column_159, + _column_193, + _column_194, + _column_195, + _column_196, + _column_197, + _column_198, + _column_199, + _column_200, + _column_201, + _column_124, + ], + 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_202, _column_203, _column_204], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape47 trashedLocalAssetEntity = Shape47( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_108, + _column_113, + _column_114, + _column_115, + _column_116, + _column_117, + _column_118, + _column_107, + _column_205, + _column_131, + _column_120, + _column_132, + _column_206, + _column_137, + ], + 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_107, + _column_159, + _column_207, + _column_208, + _column_209, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape49 settings = Shape49( + source: i0.VersionedTable( + entityName: 'settings', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY("key")'], + columns: [_column_210, _column_211, _column_115], + 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 idxRemoteExifCity = i1.Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + 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 idxAssetFaceVisiblePerson = i1.Index( + 'idx_asset_face_visible_person', + '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', + ); + 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)', + ); +} + i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -14110,6 +14658,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema25 schema) from24To25, required Future Function(i1.Migrator m, Schema26 schema) from25To26, required Future Function(i1.Migrator m, Schema27 schema) from26To27, + required Future Function(i1.Migrator m, Schema28 schema) from27To28, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -14243,6 +14792,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from26To27(migrator, schema); return 27; + case 27: + final schema = Schema28(database: database); + final migrator = i1.Migrator(database, schema); + await from27To28(migrator, schema); + return 28; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -14276,6 +14830,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema25 schema) from24To25, required Future Function(i1.Migrator m, Schema26 schema) from25To26, required Future Function(i1.Migrator m, Schema27 schema) from26To27, + required Future Function(i1.Migrator m, Schema28 schema) from27To28, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, @@ -14304,5 +14859,6 @@ i1.OnUpgrade stepByStep({ from24To25: from24To25, from25To26: from25To26, from26To27: from26To27, + from27To28: from27To28, ), ); diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index 9b355334d4..2c80385c34 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -241,7 +241,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository { innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), ]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull()) - ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]); + ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]); return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); } diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.dart index d11174356d..32af4af748 100644 --- a/mobile/lib/infrastructure/repositories/logger_db.repository.dart +++ b/mobile/lib/infrastructure/repositories/logger_db.repository.dart @@ -1,14 +1,14 @@ import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; +import 'package:drift_sqlite_async/drift_sqlite_async.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.drift.dart'; +import 'package:sqlite_async/sqlite_async.dart'; @DriftDatabase(tables: [LogMessageEntity]) class DriftLogger extends $DriftLogger { - DriftLogger([QueryExecutor? executor]) - : super( - executor ?? driftDatabase(name: 'immich_logs', native: const DriftNativeOptions(shareAcrossIsolates: true)), - ); + DriftLogger.fromExecutor(super.executor); + + DriftLogger.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db)); @override int get schemaVersion => 1; @@ -19,7 +19,8 @@ class DriftLogger extends $DriftLogger { await customStatement('PRAGMA foreign_keys = ON'); await customStatement('PRAGMA synchronous = NORMAL'); await customStatement('PRAGMA journal_mode = WAL'); - await customStatement('PRAGMA busy_timeout = 500'); + await customStatement('PRAGMA busy_timeout = 30000'); // 30s + await customStatement('PRAGMA cache_size = -32000'); // 32MB await customStatement('PRAGMA temp_store = MEMORY'); }, ); diff --git a/mobile/lib/infrastructure/repositories/partner.repository.dart b/mobile/lib/infrastructure/repositories/partner.repository.dart index b12061ad24..ee18c84b4e 100644 --- a/mobile/lib/infrastructure/repositories/partner.repository.dart +++ b/mobile/lib/infrastructure/repositories/partner.repository.dart @@ -1,106 +1,62 @@ 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'; -class DriftPartnerRepository extends DriftDatabaseRepository { +class PartnerRepository { final Drift _db; - const DriftPartnerRepository(this._db) : super(_db); + const PartnerRepository(this._db); - 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 update({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 delete({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(); - } - - // 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)); + Partner _resultToPartner(TypedResult result) { + final user = result.readTable(_db.userEntity); + final partner = result.readTable(_db.partnerEntity); + return mapToPartner(user, partner); } } diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index 7d4e23c22b..2e4a239a0b 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -267,7 +267,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository { ); } - 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)), ); diff --git a/mobile/lib/infrastructure/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart index bcfddfce6e..395d4045cf 100644 --- a/mobile/lib/infrastructure/repositories/search_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart @@ -1,6 +1,7 @@ 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 { @@ -20,50 +21,64 @@ class SearchApiRepository extends ApiRepository { (filter.assetId != null && filter.assetId!.isNotEmpty)) { return _api.searchSmart( SmartSearchDto( - 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, + 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), ), ); } return _api.searchAssets( MetadataSearchDto( - 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, + 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), ), ); } diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index d9d262e64f..65214f3846 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -20,7 +20,7 @@ class SyncApiRepository { } Future deleteSyncAck(List types) { - return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: types)); + return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: Optional.present(types))); } Future streamChanges( @@ -29,6 +29,7 @@ class SyncApiRepository { Function()? onReset, int batchSize = kSyncEventBatchSize, http.Client? httpClient, + Future? abortSignal, }) async { final stopwatch = Stopwatch()..start(); final client = httpClient ?? NetworkRepository.client; @@ -36,7 +37,7 @@ class SyncApiRepository { final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'}; - final request = http.Request('POST', Uri.parse(endpoint)); + final request = http.AbortableRequest('POST', Uri.parse(endpoint), abortTrigger: abortSignal); request.headers.addAll(headers); request.body = jsonEncode( SyncStreamDto( diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index b7593c3202..bd672171bc 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -91,7 +91,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), isAdmin: Value(user.isAdmin), pinCode: Value(user.pinCode), quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0), @@ -133,7 +133,7 @@ class SyncStreamRepository extends DriftDatabaseRepository { email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), ); batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion)); diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart index afcf2271dd..6df7344991 100644 --- a/mobile/lib/infrastructure/repositories/user.repository.dart +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -2,9 +2,17 @@ import 'package:drift/drift.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/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart'; +class UserRepository { + final Drift _db; + const UserRepository(this._db); + + Stream> getAll() => _db.select(_db.userEntity).map(mapToUser).watch(); +} + class DriftAuthUserRepository extends DriftDatabaseRepository { final Drift _db; const DriftAuthUserRepository(super.db) : _db = db; diff --git a/mobile/lib/infrastructure/utils/exif.converter.dart b/mobile/lib/infrastructure/utils/exif.converter.dart index 50639e8e42..9f9b6f9324 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, - 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), + 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)?.toInt(), + exposureSeconds: exposureTimeToSeconds(dto.exposureTime.orElse(null)), ); } diff --git a/mobile/lib/infrastructure/utils/user.converter.dart b/mobile/lib/infrastructure/utils/user.converter.dart index 826649b247..7e8a2c6fcc 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 ?? false, + inTimeline: dto.inTimeline.orElse(null) ?? false, isPartnerSharedBy: false, isPartnerSharedWith: false, profileChangedAt: dto.profileChangedAt, diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index cc5f131572..75f1c2221a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -24,6 +24,7 @@ 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/view_intent/view_intent_handler.provider.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/platform.provider.dart'; @@ -128,6 +129,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve case AppLifecycleState.resumed: dPrint(() => "[APP STATE] resumed"); ref.read(appStateProvider.notifier).handleAppResume(); + unawaited(ref.read(viewIntentHandlerProvider).onAppResumed()); break; case AppLifecycleState.inactive: dPrint(() => "[APP STATE] inactive"); @@ -233,6 +235,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve } }); + ref.read(viewIntentHandlerProvider).init(); ref.read(shareIntentUploadProvider.notifier).init(); } diff --git a/mobile/lib/models/search/search_filter.model.dart b/mobile/lib/models/search/search_filter.model.dart index cf1a1dcdaf..825acb32c6 100644 --- a/mobile/lib/models/search/search_filter.model.dart +++ b/mobile/lib/models/search/search_filter.model.dart @@ -3,6 +3,7 @@ import 'dart:convert'; 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'; class SearchLocationFilter { String? country; @@ -133,19 +134,26 @@ class SearchDateFilter { } class SearchRatingFilter { - int? rating; - SearchRatingFilter({this.rating}); + /// none = no filter; some(null) = filter for unrated; some(1-5) = filter for that rating + Option rating; + SearchRatingFilter({this.rating = const Option.none()}); - SearchRatingFilter copyWith({int? rating}) { + SearchRatingFilter copyWith({Option? rating}) { return SearchRatingFilter(rating: rating ?? this.rating); } Map toMap() { - return {'rating': rating}; + if (rating.isNone) { + return {'active': false}; + } + return {'active': true, 'value': rating.unwrapOrNull}; } factory SearchRatingFilter.fromMap(Map map) { - return SearchRatingFilter(rating: map['rating'] != null ? map['rating'] as int : null); + if (!(map['active'] as bool? ?? false)) { + return SearchRatingFilter(); + } + return SearchRatingFilter(rating: Option.some(map['value'] as int?)); } String toJson() => json.encode(toMap()); @@ -270,7 +278,7 @@ class SearchFilter { display.isNotInAlbum == false && display.isArchive == false && display.isFavorite == false && - rating.rating == null && + rating.rating.isNone && mediaType == AssetType.other; } diff --git a/mobile/lib/models/shared_link/shared_link.model.dart b/mobile/lib/models/shared_link/shared_link.model.dart index 4315cf616a..e7b65a96ef 100644 --- a/mobile/lib/models/shared_link/shared_link.model.dart +++ b/mobile/lib/models/shared_link/shared_link.model.dart @@ -73,10 +73,10 @@ class SharedLink { slug = dto.slug, type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual, title = dto.type == SharedLinkType.ALBUM - ? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE" + ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" : "INDIVIDUAL SHARE", thumbAssetId = dto.type == SharedLinkType.ALBUM - ? dto.album?.albumThumbnailAssetId + ? dto.album.orElse(null)?.albumThumbnailAssetId : dto.assets.isNotEmpty ? dto.assets[0].id : null; diff --git a/mobile/lib/models/view_intent/view_intent_payload.extension.dart b/mobile/lib/models/view_intent/view_intent_payload.extension.dart new file mode 100644 index 0000000000..ca66e6a163 --- /dev/null +++ b/mobile/lib/models/view_intent/view_intent_payload.extension.dart @@ -0,0 +1,35 @@ +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/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index 2e18c3edc6..9e78fb4795 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -8,6 +8,7 @@ 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'; @@ -15,11 +16,16 @@ import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.w 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() @@ -162,11 +168,7 @@ class _DriftBackupPageState extends ConsumerState { ), ), }, - TextButton.icon( - icon: const Icon(Icons.info_outline_rounded), - onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), - label: Text("view_details".t(context: context)), - ), + const _BackupFooter(), ], ], ), @@ -177,6 +179,137 @@ 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()); + } + } + + void showPermissionsDialog() { + 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(); + openAppSettings(); + }, + ), + ], + ), + ); + } + + void showBatteryOptimizationInfo() { + 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: () => 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: () { + ref.read(notificationPermissionProvider.notifier).requestNotificationPermission().then((p) { + if (p == PermissionStatus.permanentlyDenied) { + 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: 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(); diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index aaa9fffc05..de6fda5773 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -17,6 +17,7 @@ import 'package:immich_mobile/providers/auth.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/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'; @@ -314,6 +315,7 @@ 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( @@ -328,6 +330,8 @@ class SplashScreenPageState extends ConsumerState { backgroundManager.syncRemote().then((success) => syncSuccess = success), ]); + await viewIntentHandler.flushDeferredViewIntent(); + if (syncSuccess) { await Future.wait([ backgroundManager.hashAssets().then((_) { diff --git a/mobile/lib/pages/library/partner/drift_partner.page.dart b/mobile/lib/pages/library/partner/drift_partner.page.dart deleted file mode 100644 index a24323c02a..0000000000 --- a/mobile/lib/pages/library/partner/drift_partner.page.dart +++ /dev/null @@ -1,139 +0,0 @@ -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/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 new file mode 100644 index 0000000000..0d9e8f95bd --- /dev/null +++ b/mobile/lib/pages/library/partner/partner.page.dart @@ -0,0 +1,200 @@ +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/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/widgets/common/confirm_dialog.dart'; + +@visibleForTesting +final candidatesStateProvider = 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).getCandidates(currentUser.id); +}); + +@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); +}); + +Future _addPartner(BuildContext context, WidgetRef ref) async { + final selected = await showDialog(context: context, builder: (_) => const PartnerSelectionDialog()); + final currentUser = ref.read(currentUserProvider); + if (selected != null && currentUser != null) { + await ref.read(partnerServiceProvider).create(sharedById: currentUser.id, sharedWithId: selected.id); + } +} + +Future _removePartner(BuildContext context, WidgetRef ref, Partner partner) => showDialog( + context: context, + builder: (_) => ConfirmDialog( + title: "stop_photo_sharing", + content: context.t.partner_page_stop_sharing_content(partner: partner.name), + onOk: () { + final currentUser = ref.read(currentUserProvider); + if (currentUser != null) { + ref.read(partnerServiceProvider).delete(sharedById: currentUser.id, sharedWithId: partner.id); + } + }, + ), +); + +@RoutePage() +class PartnerPage extends ConsumerWidget { + const PartnerPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sharedByAsync = ref.watch(partnersStateProvider); + + return Scaffold( + appBar: AppBar( + title: Text(context.t.partners), + elevation: 0, + centerTitle: false, + actions: [ + IconButton( + onPressed: () => _addPartner(context, ref), + icon: const Icon(Icons.person_add), + tooltip: context.t.add_partner, + ), + ], + ), + body: sharedByAsync.when( + data: (partners) => PartnerSharedByList( + partners: partners.toList(growable: false), + onAdd: () => _addPartner(context, ref), + onRemove: (partner) => _removePartner(context, ref, partner), + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, _) => Center(child: Text(context.t.error_loading_partners(error: error))), + ), + ); + } +} + +class _EmptyPartners extends StatelessWidget { + const _EmptyPartners({required this.onAdd}); + + final VoidCallback onAdd; + + @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)), + ), + Align( + alignment: .center, + child: ElevatedButton.icon( + onPressed: onAdd, + icon: const Icon(Icons.person_add), + label: Text(context.t.add_partner), + ), + ), + ], + ), + ); + } +} + +@visibleForTesting +class PartnerSharedByList extends StatelessWidget { + const PartnerSharedByList({super.key, required this.partners, required this.onAdd, required this.onRemove}); + + final List partners; + final VoidCallback onAdd; + final ValueChanged onRemove; + + @override + Widget build(BuildContext context) { + if (partners.isEmpty) { + return _EmptyPartners(onAdd: onAdd); + } + + 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: IconButton(icon: const Icon(Icons.person_remove), onPressed: () => onRemove(partner)), + ); + }, + ); + } +} + +@visibleForTesting +class PartnerSelectionDialog extends ConsumerWidget { + const PartnerSelectionDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final candidatesAsync = ref.watch(candidatesStateProvider); + + return SimpleDialog( + title: const Text("partner_page_select_partner").tr(), + children: candidatesAsync.when( + data: (candidates) { + final users = candidates.toList(); + if (users.isEmpty) { + return [ + Padding( + padding: const .symmetric(horizontal: 24, vertical: 8), + child: const Text("partner_page_no_more_users").tr(), + ), + ]; + } + 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/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 41486d7c98..65973918e4 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 @@ -11,6 +11,7 @@ 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:openapi/api.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'; @@ -365,11 +366,10 @@ class SharedLinkEditPage extends HookConsumerWidget { bool? download; bool? upload; bool? meta; - String? desc; - String? password; + var password = const Optional.absent(); + var description = const Optional.absent(); String? slug; - DateTime? expiry; - bool? changeExpiry; + var expiry = const Optional.absent(); if (allowDownload.value != existingLink!.allowDownload) { download = allowDownload.value; @@ -383,12 +383,16 @@ class SharedLinkEditPage extends HookConsumerWidget { meta = showMetadata.value; } - if (descriptionController.text != existingLink!.description) { - desc = descriptionController.text; + if (descriptionController.text != (existingLink!.description ?? '')) { + description = descriptionController.text.isEmpty + ? const Optional.present(null) + : Optional.present(descriptionController.text); } - if (passwordController.text != existingLink!.password) { - password = passwordController.text; + if (passwordController.text != (existingLink!.password ?? '')) { + password = passwordController.text.isEmpty + ? const Optional.present(null) + : Optional.present(passwordController.text); } if (slugController.text != (existingLink!.slug ?? "")) { @@ -399,8 +403,7 @@ class SharedLinkEditPage extends HookConsumerWidget { final newExpiry = expiryAfter.value; if (newExpiry?.toUtc() != existingLink!.expiresAt?.toUtc()) { - expiry = newExpiry; - changeExpiry = true; + expiry = newExpiry == null ? const Optional.present(null) : Optional.present(newExpiry.toUtc()); } await ref @@ -410,11 +413,10 @@ class SharedLinkEditPage extends HookConsumerWidget { showMeta: meta, allowDownload: download, allowUpload: upload, - description: desc, + description: description, password: password, slug: slug, - expiresAt: expiry?.toUtc(), - changeExpiry: changeExpiry, + expiresAt: expiry, ); if (!context.mounted) { return; diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart index ff6ca7bf9d..bd979af87b 100644 --- a/mobile/lib/platform/native_sync_api.g.dart +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -635,6 +635,20 @@ class NativeSyncApi { _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); } + Future cancelSync() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelSync$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow(pigeonVar_replyList, pigeonVar_channelName, isNullValid: true); + } + Future>> getTrashedAssets() async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/platform/permission_api.g.dart b/mobile/lib/platform/permission_api.g.dart index d2646e482f..7b85d611d2 100644 --- a/mobile/lib/platform/permission_api.g.dart +++ b/mobile/lib/platform/permission_api.g.dart @@ -26,6 +26,8 @@ Object? _extractReplyValueOrThrow(List? replyList, String channelName, return replyList.firstOrNull; } +enum PermissionStatus { granted, denied, permanentlyDenied } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -33,6 +35,9 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); + } else if (value is PermissionStatus) { + buffer.putUint8(129); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -41,6 +46,9 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { + case 129: + final value = readValue(buffer) as int?; + return value == null ? null : PermissionStatus.values[value]; default: return super.readValueOfType(type, buffer); } @@ -60,6 +68,25 @@ class PermissionApi { final String pigeonVar_messageChannelSuffix; + Future isIgnoringBatteryOptimizations() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as PermissionStatus; + } + Future hasManageMediaPermission() async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/platform/view_intent_api.g.dart b/mobile/lib/platform/view_intent_api.g.dart new file mode 100644 index 0000000000..d457c249de --- /dev/null +++ b/mobile/lib/platform/view_intent_api.g.dart @@ -0,0 +1,191 @@ +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow(List? replyList, String channelName, {required bool isNullValid}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; +} + +bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == 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) { + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; + } + return a == b; +} + +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + +class ViewIntentPayload { + ViewIntentPayload({this.path, required this.mimeType, this.localAssetId}); + + String? path; + + String mimeType; + + String? localAssetId; + + List _toList() { + return [path, mimeType, localAssetId]; + } + + Object encode() { + return _toList(); + } + + static ViewIntentPayload decode(Object result) { + result as List; + return ViewIntentPayload( + path: result[0] as String?, + mimeType: result[1]! as String, + localAssetId: result[2] as String?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! ViewIntentPayload || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(path, other.path) && + _deepEquals(mimeType, other.mimeType) && + _deepEquals(localAssetId, other.localAssetId); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._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 ViewIntentPayload) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return ViewIntentPayload.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ViewIntentHostApi { + /// Constructor for [ViewIntentHostApi]. 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. + ViewIntentHostApi({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 consumeViewIntent() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ViewIntentHostApi.consumeViewIntent$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as ViewIntentPayload?; + } +} diff --git a/mobile/lib/presentation/pages/drift_library.page.dart b/mobile/lib/presentation/pages/drift_library.page.dart index d78567066c..c715e9341a 100644 --- a/mobile/lib/presentation/pages/drift_library.page.dart +++ b/mobile/lib/presentation/pages/drift_library.page.dart @@ -8,13 +8,15 @@ import 'package:immich_mobile/extensions/build_context_extensions.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/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/memory.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/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/providers/user.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'; @@ -404,12 +406,23 @@ class _MemoriesCollectionCard 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(driftSharedWithPartnerProvider); + final partnerSharedWithAsync = ref.watch(sharedWithPartnerProvider); final partners = partnerSharedWithAsync.valueOrNull ?? []; return SliverPadding( @@ -464,9 +477,9 @@ class _QuickAccessButtonList extends ConsumerWidget { 'partners'.t(context: context), style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), ), - onTap: () => context.pushRoute(const DriftPartnerRoute()), + onTap: () => context.pushRoute(const PartnerRoute()), ), - _PartnerList(partners: partners), + _PartnerList(partners: partners.toList()), ], ), ), @@ -478,7 +491,7 @@ class _QuickAccessButtonList extends ConsumerWidget { class _PartnerList extends StatelessWidget { const _PartnerList({required this.partners}); - final List partners; + final List partners; @override Widget build(BuildContext context) { @@ -498,7 +511,7 @@ class _PartnerList extends StatelessWidget { ), ), contentPadding: const EdgeInsets.only(left: 12.0, right: 18.0), - leading: PartnerUserAvatar(partner: partner), + leading: PartnerUserAvatar(userId: partner.id, name: partner.name), title: const Text( "partner_list_user_photos", style: TextStyle(fontWeight: FontWeight.w500), diff --git a/mobile/lib/presentation/pages/drift_partner_detail.page.dart b/mobile/lib/presentation/pages/drift_partner_detail.page.dart index f8a19b6b70..7df96cf78e 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 PartnerUserDto partner; + final Partner partner; const DriftPartnerDetailPage({super.key, required this.partner}); @@ -39,7 +39,7 @@ class DriftPartnerDetailPage extends StatelessWidget { } class _InfoBox extends ConsumerStatefulWidget { - final PartnerUserDto partner; + final Partner partner; const _InfoBox({required this.partner}); @@ -63,7 +63,9 @@ class _InfoBoxState extends ConsumerState<_InfoBox> { } try { - await ref.read(partnerUsersProvider.notifier).toggleShowInTimeline(widget.partner.id, user.id); + await ref + .read(partnerServiceProvider) + .update(sharedById: widget.partner.id, sharedWithId: user.id, inTimeline: !_inTimeline); setState(() { _inTimeline = !_inTimeline; diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index ccbddb99f3..5a5b49a2df 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -20,6 +20,7 @@ import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/remote_album_sliver_app_bar.dart'; +import 'package:openapi/api.dart' show Optional; @RoutePage() class RemoteAlbumPage extends ConsumerStatefulWidget { @@ -247,10 +248,13 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { try { final newTitle = titleController.text.trim(); final newDescription = descriptionController.text.trim(); + final description = newDescription.isEmpty + ? const Optional.present(null) + : Optional.present(newDescription); await ref .read(remoteAlbumProvider.notifier) - .updateAlbum(widget.album.id, name: newTitle, description: newDescription); + .updateAlbum(widget.album.id, name: newTitle, description: description); if (mounted) { Navigator.of( diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 2a40556641..2ae6371f6a 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -404,12 +404,15 @@ class DriftSearchPage extends HookConsumerWidget { handleClear() { ratingCurrentFilterWidget.value = null; - search(filter.value.copyWith(rating: SearchRatingFilter(rating: null))); + search(filter.value.copyWith(rating: SearchRatingFilter())); } handleApply() { - ratingCurrentFilterWidget.value = rating.rating != null - ? Text('rating_count'.t(args: {'count': rating.rating!}), style: context.textTheme.labelLarge) + ratingCurrentFilterWidget.value = rating.rating.isSome + ? Text( + 'rating_count'.t(args: {'count': rating.rating.unwrapOrNull ?? 0}), + style: context.textTheme.labelLarge, + ) : null; search(filter.value.copyWith(rating: rating)); } 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 index 98eb09a4aa..599e11d467 100644 --- a/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -10,6 +11,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu 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/providers/view_intent/view_intent_file_path.provider.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/services/view_intent.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_ui/immich_ui.dart'; @@ -26,7 +30,11 @@ class UploadActionButton extends ConsumerWidget { } final isTimeline = source == ActionSource.timeline; + final viewerIntentFilePath = source == ActionSource.viewer ? ref.read(viewIntentFilePathProvider) : null; List? assets; + var isUploadDialogOpen = false; + var wasUploadCancelled = false; + Future? uploadDialogFuture; if (source == ActionSource.timeline) { assets = ref.read(multiSelectProvider).selectedAssets.whereType().toList(); @@ -35,22 +43,50 @@ class UploadActionButton extends ConsumerWidget { } ref.read(multiSelectProvider.notifier).reset(); } else { - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) => const _UploadProgressDialog(), - ), - ); + isUploadDialogOpen = true; + uploadDialogFuture = + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => _UploadProgressDialog( + onCancel: () { + wasUploadCancelled = true; + }, + ), + ).whenComplete(() { + isUploadDialogOpen = false; + }); + unawaited(uploadDialogFuture); } - final result = await ref.read(actionProvider.notifier).upload(source, assets: assets); + var success = false; + if (!isTimeline && viewerIntentFilePath != null) { + final viewIntentService = ref.read(viewIntentServiceProvider); + viewIntentService.markUploadActive(viewerIntentFilePath); + var hasError = false; + try { + await ref + .read(foregroundUploadServiceProvider) + .uploadShareIntent( + [File(viewerIntentFilePath)], + onError: (_, _) { + hasError = true; + }, + ); + } finally { + await viewIntentService.markUploadInactive(viewerIntentFilePath); + } + success = !hasError; + } else { + final result = await ref.read(actionProvider.notifier).upload(source, assets: assets); + success = result.success; + } - if (!isTimeline && context.mounted) { + if (!isTimeline && context.mounted && isUploadDialogOpen) { Navigator.of(context, rootNavigator: true).pop(); } - if (context.mounted && !result.success) { + if (context.mounted && !success && !wasUploadCancelled) { ImmichToast.show( context: context, msg: 'scaffold_body_error_occurred'.t(context: context), @@ -73,7 +109,9 @@ class UploadActionButton extends ConsumerWidget { } class _UploadProgressDialog extends ConsumerWidget { - const _UploadProgressDialog(); + final VoidCallback onCancel; + + const _UploadProgressDialog({required this.onCancel}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -103,7 +141,8 @@ class _UploadProgressDialog extends ConsumerWidget { onPressed: () { ref.read(manualUploadCancelTokenProvider)?.complete(); ref.read(manualUploadCancelTokenProvider.notifier).state = null; - Navigator.of(context).pop(); + onCancel(); + Navigator.of(context, rootNavigator: true).pop(); }, labelText: 'cancel'.t(context: context), ), 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 1056626119..e501c2ee3e 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 @@ -44,7 +44,7 @@ class RatingDetails extends ConsumerWidget { await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round()); }, onClearRating: () async { - await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, 0); + await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, null); }, ), ], 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 84edc4df65..fdedd15a0f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -21,6 +21,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.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/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'; @@ -323,14 +324,16 @@ class _AssetPageState extends ConsumerState { required PhotoViewHeroAttributes? heroAttributes, required bool isCurrent, required bool isPlayingMotionVideo, + required String? localFilePath, }) { final size = context.sizeData; + final imageProvider = getFullImageProvider(asset, size: size, localFilePath: localFilePath); if (asset.isImage && !isPlayingMotionVideo) { return PhotoView( key: Key(asset.heroTag), index: widget.index, - imageProvider: getFullImageProvider(asset, size: size), + imageProvider: imageProvider, heroAttributes: heroAttributes, loadingBuilder: (context, progress, index) => const Center(child: ImmichLoadingIndicator()), gaplessPlayback: true, @@ -377,12 +380,9 @@ class _AssetPageState extends ConsumerState { child: NativeVideoViewer( key: _NativeVideoViewerKey(asset.heroTag), asset: asset, + localFilePath: localFilePath, isCurrent: isCurrent, - image: Image( - image: getFullImageProvider(asset, size: size), - fit: BoxFit.contain, - alignment: Alignment.center, - ), + image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), ), ); } @@ -393,6 +393,7 @@ class _AssetPageState extends ConsumerState { _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.read(timelineServiceProvider).origin; final asset = _asset; if (asset == null) { @@ -421,6 +422,8 @@ class _AssetPageState extends ConsumerState { _scrollController.snapPosition.snapOffset = _snapOffset; } + final viewIntentFilePath = timelineOrigin == TimelineOrigin.deepLink ? ref.watch(viewIntentFilePathProvider) : null; + return Stack( children: [ SingleChildScrollView( @@ -440,6 +443,7 @@ class _AssetPageState extends ConsumerState { : null, isCurrent: isCurrent, isPlayingMotionVideo: isPlayingMotionVideo, + localFilePath: viewIntentFilePath, ), ), IgnorePointer( 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 bd4935e41f..b956ef103c 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart @@ -77,7 +77,11 @@ class _RatingBarState extends State { setState(() { _currentRating = newRating; }); - widget.onRatingUpdate?.call(newRating.round()); + if (newRating == 0) { + widget.onClearRating?.call(); + } else { + widget.onRatingUpdate?.call(newRating.round()); + } } } 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 c1e6fe10e6..2be7bb91e8 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -19,6 +20,7 @@ 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; @@ -26,6 +28,7 @@ class NativeVideoViewer extends ConsumerStatefulWidget { const NativeVideoViewer({ super.key, required this.asset, + this.localFilePath, required this.image, this.isCurrent = false, this.showControls = true, @@ -106,6 +109,19 @@ class _NativeVideoViewerState extends ConsumerState with Widg } try { + final localFilePath = widget.localFilePath; + if (localFilePath != null) { + final file = File(localFilePath); + 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, + ); + } + if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) { final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!; final file = await StorageRepository().getFileForAsset(id); diff --git a/mobile/lib/presentation/widgets/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index 36d9678277..b3c58314db 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:ui' as ui; import 'package:async/async.dart'; @@ -146,10 +147,17 @@ mixin CancellableImageProviderMixin on CancellableImageProvide } } -ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080, 1920), bool edited = true}) { +ImageProvider getFullImageProvider( + BaseAsset asset, { + Size size = const Size(1080, 1920), + bool edited = true, + String? localFilePath, +}) { // Create new provider and cache it final ImageProvider provider; - if (_shouldUseLocalAsset(asset)) { + if (localFilePath != null) { + provider = FileImage(File(localFilePath)); + } else 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); } else { 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 8b391d50c6..8618d78362 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.partner}); + const PartnerUserAvatar({super.key, required this.userId, required this.name}); - final PartnerUserDto partner; + final String userId; + final String name; @override Widget build(BuildContext context) { - final url = "${Store.get(StoreKey.serverEndpoint)}/users/${partner.id}/profile-image"; - final nameFirstLetter = partner.name.isNotEmpty ? partner.name[0] : ""; + final url = "${Store.get(StoreKey.serverEndpoint)}/users/$userId/profile-image"; + final nameFirstLetter = name.isNotEmpty ? name[0] : ""; return CircleAvatar( radius: 16, backgroundColor: context.primaryColor.withAlpha(50), diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 5cd294d781..bb1877cbd7 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.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/notification_permission.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:logging/logging.dart'; 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 66a8deb466..8bd0581061 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -1,101 +1,101 @@ -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, - ]; - } -} +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); + 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/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index aa734f56b8..426c028822 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -36,11 +36,12 @@ class ActionResult { final int count; final bool success; final String? error; + final List remoteAssetIds; - const ActionResult({required this.count, required this.success, this.error}); + const ActionResult({required this.count, required this.success, this.error, this.remoteAssetIds = const []}); @override - String toString() => 'ActionResult(count: $count, success: $success, error: $error)'; + String toString() => 'ActionResult(count: $count, success: $success, error: $error, remoteAssetIds: $remoteAssetIds)'; } class ActionNotifier extends Notifier { @@ -465,7 +466,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'); @@ -554,10 +555,14 @@ class ActionNotifier extends Notifier { final uploadedAssetIds = {}; final failedAssetIds = {}; final postUploadTasks = >[]; + if (assetsToUpload.isEmpty) { + return const ActionResult(count: 0, success: false, error: 'No assets to upload'); + } 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) { @@ -574,6 +579,7 @@ class ActionNotifier extends Notifier { progressNotifier.setProgress(localAssetId, progress); }, onSuccess: (localAssetId, remoteAssetId) { + remoteAssetIds.add(remoteAssetId); progressNotifier.remove(localAssetId); uploadedAssetIds.add(localAssetId); final asset = assetById[localAssetId]; diff --git a/mobile/lib/providers/infrastructure/cancel.provider.dart b/mobile/lib/providers/infrastructure/cancel.provider.dart index 6851861e1a..9d4a6790f2 100644 --- a/mobile/lib/providers/infrastructure/cancel.provider.dart +++ b/mobile/lib/providers/infrastructure/cancel.provider.dart @@ -1,8 +1,9 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; -/// 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( +/// Holds the isolate's cancellation signal. +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/partner.provider.dart b/mobile/lib/providers/infrastructure/partner.provider.dart deleted file mode 100644 index ac3d74d85b..0000000000 --- a/mobile/lib/providers/infrastructure/partner.provider.dart +++ /dev/null @@ -1,86 +0,0 @@ -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/providers/infrastructure/user.provider.dart'; -import 'package:immich_mobile/providers/user.provider.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/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index a4bbbae818..b7d9b7cfd9 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -8,6 +8,7 @@ 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:openapi/api.dart' show Optional; 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'; @@ -153,7 +154,7 @@ class RemoteAlbumNotifier extends Notifier { Future updateAlbum( String albumId, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 75c8e09326..700b51f12d 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -26,7 +26,7 @@ final syncStreamServiceProvider = Provider( permissionRepository: ref.watch(permissionRepositoryProvider), syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider), api: ref.watch(apiServiceProvider), - cancelChecker: ref.watch(cancellationProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -42,6 +42,7 @@ final localSyncServiceProvider = Provider( assetMediaRepository: ref.watch(assetMediaRepositoryProvider), permissionRepository: ref.watch(permissionRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), + cancellation: ref.watch(cancellationProvider), ), ); @@ -51,5 +52,6 @@ final hashServiceProvider = Provider( localAssetRepository: ref.watch(localAssetRepository), nativeSyncApi: ref.watch(nativeSyncApiProvider), trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + cancellation: ref.watch(cancellationProvider), ), ); diff --git a/mobile/lib/providers/infrastructure/user.provider.dart b/mobile/lib/providers/infrastructure/user.provider.dart index d8e7029f8c..09f74db37d 100644 --- a/mobile/lib/providers/infrastructure/user.provider.dart +++ b/mobile/lib/providers/infrastructure/user.provider.dart @@ -1,15 +1,16 @@ 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'; +final userRepositoryProvider = Provider((ref) => UserRepository(ref.watch(driftProvider))); + final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi)); final userServiceProvider = Provider( @@ -19,13 +20,12 @@ final userServiceProvider = Provider( ), ); -/// Drifts -final driftPartnerRepositoryProvider = Provider( - (ref) => DriftPartnerRepository(ref.watch(driftProvider)), -); +final partnerRepositoryProvider = Provider((ref) => PartnerRepository(ref.watch(driftProvider))); -final driftPartnerServiceProvider = Provider( - (ref) => DriftPartnerService(ref.watch(driftPartnerRepositoryProvider), ref.watch(partnerApiRepositoryProvider)), +final partnerServiceProvider = Provider( + (ref) => PartnerService( + ref.watch(userRepositoryProvider), + ref.watch(partnerRepositoryProvider), + ref.watch(partnerApiRepositoryProvider), + ), ); - -final partnerUsersProvider = NotifierProvider>(PartnerNotifier.new); diff --git a/mobile/lib/providers/notification_permission.provider.dart b/mobile/lib/providers/permission.provider.dart similarity index 57% rename from mobile/lib/providers/notification_permission.provider.dart rename to mobile/lib/providers/permission.provider.dart index da0badd4ec..b7011e1357 100644 --- a/mobile/lib/providers/notification_permission.provider.dart +++ b/mobile/lib/providers/permission.provider.dart @@ -1,6 +1,9 @@ +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 { @@ -39,3 +42,26 @@ 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/search/search_page_state.provider.dart b/mobile/lib/providers/search/search_page_state.provider.dart index 23d5606922..c6a72c922e 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!.city!, id: data.id)) + .map((data) => SearchCuratedContent(label: data.exifInfo.orElse(null)!.city.orElse(null)!, id: data.id)) .toList(); return curatedContent; 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 new file mode 100644 index 0000000000..75cd304a3a --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000000..b266887cab --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler.provider.dart @@ -0,0 +1,23 @@ +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 new file mode 100644 index 0000000000..c00ff38648 --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler_android.dart @@ -0,0 +1,103 @@ +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/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(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 new file mode 100644 index 0000000000..ebc6d7425b --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_handler_stub.dart @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000000..c3f68eff79 --- /dev/null +++ b/mobile/lib/providers/view_intent/view_intent_pending.provider.dart @@ -0,0 +1,39 @@ +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/repositories/activity_api.repository.dart b/mobile/lib/repositories/activity_api.repository.dart index e8f9abc8c8..9ae83b0e7b 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, - comment: comment, + assetId: assetId == null ? const Optional.absent() : Optional.present(assetId), + comment: comment == null ? const Optional.absent() : Optional.present(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, + comment: dto.comment.orElse(null), ); } diff --git a/mobile/lib/repositories/asset_api.repository.dart b/mobile/lib/repositories/asset_api.repository.dart index fdb4e3323b..0fdfb7e21b 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -24,7 +24,7 @@ class AssetApiRepository extends ApiRepository { AssetApiRepository(this._api, this._stacksApi, this._trashApi); Future delete(List ids, bool force) async { - return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: force)); + return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: Optional.present(force))); } Future restoreTrash(List ids) async { @@ -42,19 +42,27 @@ class AssetApiRepository extends ApiRepository { } Future updateVisibility(List ids, AssetVisibilityEnum visibility) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility))); + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: Optional.present(_mapVisibility(visibility)))); } Future updateFavorite(List ids, bool isFavorite) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: isFavorite)); + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: Optional.present(isFavorite))); } Future updateLocation(List ids, LatLng location) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, latitude: location.latitude, longitude: location.longitude)); + return _api.updateAssets( + AssetBulkUpdateDto( + ids: ids, + latitude: Optional.present(location.latitude), + longitude: Optional.present(location.longitude), + ), + ); } Future updateDateTime(List ids, DateTime dateTime) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: dateTime.toIso8601String())); + return _api.updateAssets( + AssetBulkUpdateDto(ids: ids, dateTimeOriginal: Optional.present(dateTime.toIso8601String())), + ); } Future stack(List ids) async { @@ -82,15 +90,15 @@ class AssetApiRepository extends ApiRepository { 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; + return response.originalMimeType.orElse(null); } Future updateDescription(String assetId, String description) { - return _api.updateAsset(assetId, UpdateAssetDto(description: description)); + return _api.updateAsset(assetId, UpdateAssetDto(description: Optional.present(description))); } - Future updateRating(String assetId, int rating) { - return _api.updateAsset(assetId, UpdateAssetDto(rating: rating)); + Future updateRating(String assetId, int? rating) { + return _api.updateAsset(assetId, UpdateAssetDto(rating: Optional.present(rating))); } Future editAsset(String assetId, List edits) { diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index 446aba68b3..05dc7f103a 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: newPassword)); + await _apiService.usersApi.updateMyUser(UserUpdateMeDto(password: Optional.present(newPassword))); } Future login(String email, String password) async { @@ -46,7 +46,7 @@ class AuthApiRepository extends ApiRepository { Future unlockPinCode(String pinCode) async { try { - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: pinCode)); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(pinCode))); return true; } catch (_) { return false; diff --git a/mobile/lib/repositories/drift_album_api_repository.dart b/mobile/lib/repositories/drift_album_api_repository.dart index a0c7a3732a..c43e1fd4a0 100644 --- a/mobile/lib/repositories/drift_album_api_repository.dart +++ b/mobile/lib/repositories/drift_album_api_repository.dart @@ -22,7 +22,13 @@ class DriftAlbumApiRepository extends ApiRepository { String? description, }) async { final responseDto = await checkNull( - _api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())), + _api.createAlbum( + CreateAlbumDto( + albumName: name, + description: description == null ? const Optional.absent() : Optional.present(description), + assetIds: Optional.present(assetIds.toList()), + ), + ), ); return responseDto.toRemoteAlbum(owner); @@ -41,8 +47,14 @@ class DriftAlbumApiRepository extends ApiRepository { return (removed: removed, failed: failed); } - Future<({List added, List failed})> addAssets(String albumId, Iterable assetIds) async { - final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); + 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 = [], failed = []; for (final dto in response) { if (dto.success) { @@ -59,7 +71,7 @@ class DriftAlbumApiRepository extends ApiRepository { String albumId, UserDto owner, { String? name, - String? description, + Optional description = const Optional.absent(), String? thumbnailAssetId, bool? isActivityEnabled, AlbumAssetOrder? order, @@ -73,11 +85,13 @@ class DriftAlbumApiRepository extends ApiRepository { _api.updateAlbumInfo( albumId, UpdateAlbumDto( - albumName: name, + albumName: name == null ? const Optional.absent() : Optional.present(name), description: description, - albumThumbnailAssetId: thumbnailAssetId, - isActivityEnabled: isActivityEnabled, - order: apiOrder, + 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), ), ), ); @@ -99,7 +113,9 @@ class DriftAlbumApiRepository extends ApiRepository { } Future setActivityStatus(String albumId, bool isEnabled) async { - final response = await checkNull(_api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: isEnabled))); + final response = await checkNull( + _api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: Optional.present(isEnabled))), + ); return response.isActivityEnabled; } } @@ -116,7 +132,7 @@ extension on AlbumResponseDto { updatedAt: updatedAt, thumbnailAssetId: albumThumbnailAssetId, isActivityEnabled: isActivityEnabled, - order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, + order: order.orElse(null) == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, assetCount: assetCount, isShared: albumUsers.length > 2, ); diff --git a/mobile/lib/repositories/partner_api.repository.dart b/mobile/lib/repositories/partner_api.repository.dart index 69b6740cbe..eaca839bfd 100644 --- a/mobile/lib/repositories/partner_api.repository.dart +++ b/mobile/lib/repositories/partner_api.repository.dart @@ -16,7 +16,7 @@ class PartnerApiRepository extends ApiRepository { Future> getAll(Direction direction) async { final response = await checkNull( - _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.by : PartnerDirection.with_), + _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.sharedBy : PartnerDirection.sharedWith), ); return response.map(UserConverter.fromPartnerDto).toList(); } diff --git a/mobile/lib/repositories/person_api.repository.dart b/mobile/lib/repositories/person_api.repository.dart index bbf55e674a..26662601b7 100644 --- a/mobile/lib/repositories/person_api.repository.dart +++ b/mobile/lib/repositories/person_api.repository.dart @@ -18,7 +18,10 @@ class PersonApiRepository extends ApiRepository { 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, birthDate: birthdayUtc); + final dto = PersonUpdateDto( + name: name == null ? const Optional.absent() : Optional.present(name), + birthDate: birthdayUtc == null ? const Optional.absent() : Optional.present(birthdayUtc), + ); final response = await checkNull(_api.updatePerson(id, dto)); return _toPerson(response); } diff --git a/mobile/lib/repositories/sessions_api.repository.dart b/mobile/lib/repositories/sessions_api.repository.dart index f25e724f19..b2ed54c26c 100644 --- a/mobile/lib/repositories/sessions_api.repository.dart +++ b/mobile/lib/repositories/sessions_api.repository.dart @@ -15,7 +15,13 @@ class SessionsAPIRepository extends ApiRepository { Future createSession(String deviceType, String deviceOS, {int? duration}) async { final dto = await checkNull( - _api.createSession(SessionCreateDto(deviceType: deviceType, deviceOS: deviceOS, duration: duration)), + _api.createSession( + SessionCreateDto( + deviceType: Optional.present(deviceType), + deviceOS: Optional.present(deviceOS), + duration: duration == null ? const Optional.absent() : Optional.present(duration), + ), + ), ); return SessionCreateResponse( @@ -23,7 +29,7 @@ class SessionsAPIRepository extends ApiRepository { current: dto.current, deviceType: deviceType, deviceOS: deviceOS, - expiresAt: dto.expiresAt, + expiresAt: dto.expiresAt.orElse(null), createdAt: dto.createdAt, updatedAt: dto.updatedAt, token: dto.token, diff --git a/mobile/lib/routing/locked_guard.dart b/mobile/lib/routing/locked_guard.dart index ddb6a7e694..38484538e0 100644 --- a/mobile/lib/routing/locked_guard.dart +++ b/mobile/lib/routing/locked_guard.dart @@ -55,7 +55,7 @@ class LockedGuard extends AutoRouteGuard { return; } - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: securePinCode)); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(securePinCode))); resolver.next(true); } on PlatformException catch (error) { diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index e9d3f3c250..fda397a8bd 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -27,7 +27,7 @@ import 'package:immich_mobile/pages/common/splash_screen.page.dart'; import 'package:immich_mobile/pages/common/tab_shell.page.dart'; import 'package:immich_mobile/pages/library/folder/folder.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/shared_link/shared_link.page.dart'; import 'package:immich_mobile/pages/library/shared_link/shared_link_edit.page.dart'; import 'package:immich_mobile/pages/login/change_password.page.dart'; @@ -58,8 +58,8 @@ 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_taken.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'; @@ -177,7 +177,7 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftPlaceRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPlaceDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftUserSelectionRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: DriftPartnerRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: PartnerRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftUploadDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: SyncStatusRoute.page, guards: [_duplicateGuard]), AutoRoute(page: DriftPeopleCollectionRoute.page, guards: [_authGuard, _duplicateGuard]), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index 8aebf11d4c..29fc694812 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -843,7 +843,7 @@ class DriftPartnerDetailRoute extends PageRouteInfo { DriftPartnerDetailRoute({ Key? key, - required PartnerUserDto partner, + required Partner partner, List? children, }) : super( DriftPartnerDetailRoute.name, @@ -867,7 +867,7 @@ class DriftPartnerDetailRouteArgs { final Key? key; - final PartnerUserDto partner; + final Partner partner; @override String toString() { @@ -885,22 +885,6 @@ class DriftPartnerDetailRouteArgs { int get hashCode => key.hashCode ^ partner.hashCode; } -/// 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 { @@ -1472,6 +1456,22 @@ class MapLocationPickerRouteArgs { int get hashCode => key.hashCode ^ initialLatLng.hashCode; } +/// 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 /// [PinAuthPage] class PinAuthRoute extends PageRouteInfo { diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index b22c6680a4..88f33395f2 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -231,7 +231,7 @@ 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); diff --git a/mobile/lib/services/foreground_upload.service.dart b/mobile/lib/services/foreground_upload.service.dart index ef7f32d168..aea187dc9f 100644 --- a/mobile/lib/services/foreground_upload.service.dart +++ b/mobile/lib/services/foreground_upload.service.dart @@ -151,7 +151,7 @@ class ForegroundUploadService { List files, { Completer? cancelToken, void Function(String fileId, int bytes, int totalBytes)? onProgress, - void Function(String fileId)? onSuccess, + void Function(String fileId, String remoteAssetId)? onSuccess, void Function(String fileId, String errorMessage)? onError, }) async { if (files.isEmpty) { @@ -171,7 +171,7 @@ class ForegroundUploadService { ); if (result.isSuccess) { - onSuccess?.call(fileId); + onSuccess?.call(fileId, result.remoteAssetId!); } else if (!result.isCancelled && result.errorMessage != null) { onError?.call(fileId, result.errorMessage!); } diff --git a/mobile/lib/services/oauth.service.dart b/mobile/lib/services/oauth.service.dart index 99ceca3229..d8b1604e00 100644 --- a/mobile/lib/services/oauth.service.dart +++ b/mobile/lib/services/oauth.service.dart @@ -18,7 +18,11 @@ class OAuthService { log.info("Starting OAuth flow with redirect URI: $redirectUri"); final dto = await _apiService.oAuthApi.startOAuth( - OAuthConfigDto(redirectUri: redirectUri, state: state, codeChallenge: codeChallenge), + OAuthConfigDto( + redirectUri: redirectUri, + state: Optional.present(state), + codeChallenge: Optional.present(codeChallenge), + ), ); final authUrl = dto?.url; @@ -37,7 +41,7 @@ class OAuthService { } return await _apiService.oAuthApi.finishOAuth( - OAuthCallbackDto(url: result, state: state, codeVerifier: codeVerifier), + OAuthCallbackDto(url: result, state: Optional.present(state), codeVerifier: Optional.present(codeVerifier)), ); } } diff --git a/mobile/lib/services/shared_link.service.dart b/mobile/lib/services/shared_link.service.dart index 46e83f0fc4..975070f2a0 100644 --- a/mobile/lib/services/shared_link.service.dart +++ b/mobile/lib/services/shared_link.service.dart @@ -48,26 +48,26 @@ class SharedLinkService { if (type == SharedLinkType.ALBUM) { dto = SharedLinkCreateDto( type: type, - albumId: albumId, - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, - password: password, - slug: slug, + 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), ); } else if (assetIds != null) { dto = SharedLinkCreateDto( type: type, - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, - password: password, - slug: slug, - assetIds: assetIds, + 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), ); } @@ -88,24 +88,22 @@ class SharedLinkService { required bool? showMeta, required bool? allowDownload, required bool? allowUpload, - bool? changeExpiry = false, - String? description, - String? password, + Optional password = const Optional.absent(), + Optional description = const Optional.absent(), String? slug, - DateTime? expiresAt, + Optional expiresAt = const Optional.absent(), }) async { try { final responseDto = await _apiService.sharedLinksApi.updateSharedLink( id, SharedLinkEditDto( - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, + 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, - slug: slug, - changeExpiryTime: changeExpiry, + description: description, + expiresAt: expiresAt, + slug: slug == null ? const Optional.absent() : Optional.present(slug), ), ); if (responseDto != null) { diff --git a/mobile/lib/services/view_intent.service.dart b/mobile/lib/services/view_intent.service.dart new file mode 100644 index 0000000000..22a3407e5a --- /dev/null +++ b/mobile/lib/services/view_intent.service.dart @@ -0,0 +1,108 @@ +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); + 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 new file mode 100644 index 0000000000..7bda1bdc13 --- /dev/null +++ b/mobile/lib/services/view_intent_asset_resolver.service.dart @@ -0,0 +1,65 @@ +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/asset.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(localAssetRepository), + timelineFactory: ref.read(timelineFactoryProvider), + ), +); + +class ViewIntentAssetResolver { + final DriftLocalAssetRepository _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/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 9bd652381a..1c27d7ea93 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -43,8 +43,9 @@ void configureFileDownloaderNotifications() { abstract final class Bootstrap { static Future<(Drift, DriftLogger)> initDomain({bool listenStoreUpdates = true, bool shouldBufferLogs = true}) async { - final drift = Drift(); - final logDb = DriftLogger(); + await configureSqliteCache(); + final drift = Drift.sqlite(await openSqliteConnection(name: 'immich')); + final logDb = DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs')); final DriftStoreRepository storeRepo = DriftStoreRepository(drift); await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index 20b56d4875..ab3b19b78f 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -8,10 +8,9 @@ import 'package:immich_mobile/entities/store.entity.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'; +import 'package:worker_manager/worker_manager.dart' show Cancelable; class InvalidIsolateUsageException implements Exception { const InvalidIsolateUsageException(); @@ -30,50 +29,27 @@ Cancelable runInIsolateGentle({ throw const InvalidIsolateUsageException(); } - return workerManagerPatch.executeGentle((cancelledChecker) async { - T? result; - await runZonedGuarded( - () async { - BackgroundIsolateBinaryMessenger.ensureInitialized(token); - DartPluginRegistrant.ensureInitialized(); + return workerManagerPatch.executeGentle((onCancel) async { + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + DartPluginRegistrant.ensureInitialized(); - final (drift, logDb) = await Bootstrap.initDomain(shouldBufferLogs: false, listenStoreUpdates: false); - final ref = ProviderContainer( - overrides: [ - 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(); - } 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"); - }, + 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))], ); - return result; + + 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(); + } }); } diff --git a/mobile/lib/utils/isolate_worker.dart b/mobile/lib/utils/isolate_worker.dart new file mode 100644 index 0000000000..60048c2c81 --- /dev/null +++ b/mobile/lib/utils/isolate_worker.dart @@ -0,0 +1,163 @@ +// 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/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index cbd6c2bcce..eca190ce8b 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -1,69 +1,58 @@ +import 'package:flutter/foundation.dart'; import 'package:openapi/api.dart'; -dynamic upgradeDto(dynamic value, String targetType) { - switch (targetType) { - case 'UserPreferencesResponseDto': - if (value is Map) { - addDefault(value, 'download.includeEmbeddedVideos', false); - addDefault(value, 'folders', FoldersResponse(enabled: false, sidebarWeb: false).toJson()); - addDefault(value, 'memories', MemoriesResponse(enabled: true, duration: 5).toJson()); - addDefault(value, 'ratings', RatingsResponse(enabled: false).toJson()); - addDefault(value, 'people', PeopleResponse(enabled: true, sidebarWeb: false).toJson()); - addDefault(value, 'tags', TagsResponse(enabled: false, sidebarWeb: false).toJson()); - addDefault(value, 'sharedLinks', SharedLinksResponse(enabled: true, sidebarWeb: false).toJson()); - addDefault(value, 'cast', CastResponse(gCastEnabled: false).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'); - addDefault(value, 'minFaces', 3); - } - 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); - addDefault(value, 'realtimeTranscoding', false); - } - break; - case 'MemoriesResponse': - if (value is Map) { - addDefault(value, 'duration', 5); - } - break; +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'}, + }, + '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}, + 'LoginResponseDto': {'isOnboarded': false}, + 'SyncUserV1': {'profileChangedAt': _now, 'hasProfileImage': false}, + 'SyncAssetV1': {'isEdited': false}, + 'ServerFeaturesDto': {'ocr': false, 'realtimeTranscoding': false}, + 'MemoriesResponse': {'duration': 5}, +}; + +void upgradeDto(dynamic value, String targetType) { + if (value is! Map) { + return; } + final fields = openApiPatches[targetType]; + if (fields == null) { + return; + } + fields.forEach((key, defaultValue) { + addDefault(value, key, defaultValue is _Dynamic ? defaultValue.resolve() : defaultValue); + }); } addDefault(dynamic value, String keys, dynamic defaultValue) { diff --git a/mobile/lib/utils/option.dart b/mobile/lib/utils/option.dart index 3470e8489e..1d73ddfbcc 100644 --- a/mobile/lib/utils/option.dart +++ b/mobile/lib/utils/option.dart @@ -1,3 +1,5 @@ +import 'package:openapi/api.dart' show Optional; + sealed class Option { const Option(); @@ -56,3 +58,10 @@ final class None extends Option { extension ObjectOptionExtension on T? { Option toOption() => Option.fromNullable(this); } + +extension OptionToOptional on Option { + Optional toOptional() => switch (this) { + None() => const Optional.absent(), + Some(:final value) => Optional.present(value), + }; +} diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 090c9bb2b8..7615218159 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -21,6 +21,7 @@ import 'package:immich_mobile/providers/background_sync.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/routing/router.dart'; @@ -182,9 +183,11 @@ 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) { @@ -259,7 +262,7 @@ class LoginForm extends HookConsumerWidget { } unawaited(handleSyncFlow()); ref.read(websocketProvider.notifier).connect(); - unawaited(context.replaceRoute(const TabShellRoute())); + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error) { @@ -346,7 +349,7 @@ class LoginForm extends HookConsumerWidget { await getManageMediaPermission(); } unawaited(handleSyncFlow()); - unawaited(context.replaceRoute(const TabShellRoute())); + unawaited(context.router.replaceAll([const TabShellRoute()])); return; } } catch (error, stack) { 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 917d56e802..32d1ab5bd4 100644 --- a/mobile/lib/widgets/search/search_filter/star_rating_picker.dart +++ b/mobile/lib/widgets/search/search_filter/star_rating_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.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}); @@ -13,12 +14,12 @@ class StarRatingPicker extends HookWidget { final selectedRating = useState(filter); return RadioGroup( - groupValue: selectedRating.value?.rating, + groupValue: selectedRating.value?.rating.fold((v) => v ?? 0, () => null), onChanged: (int? newValue) { if (newValue == null) { return; } - final newFilter = SearchRatingFilter(rating: newValue); + final newFilter = SearchRatingFilter(rating: Option.some(newValue == 0 ? null : newValue)); selectedRating.value = newFilter; onSelect(newFilter); }, diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index 46120bb218..cbef5ea109 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -2,7 +2,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/providers/notification_permission.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.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:permission_handler/permission_handler.dart'; diff --git a/mobile/lib/wm_executor.dart b/mobile/lib/wm_executor.dart index 2eb31fe300..e873c5f76d 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,6 +16,13 @@ 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; } @@ -51,13 +58,13 @@ mixin _ExecutorLogger on Mixinable<_Executor> { 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); + UnmodifiableListView get pool => UnmodifiableListView(_pool); @override Future init({int? isolatesCount, bool? dynamicSpawning}) async { @@ -80,117 +87,37 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { @override Future dispose() async { _queue.clear(); - for (final worker in _pool) { - if (worker.initialized || worker.initializing) { - worker.kill(); - } - } + final shutdown = _pool.map((worker) => worker.shutdown()).toList(growable: false); _pool.clear(); + await Future.wait(shutdown); super.dispose(); } - 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); - } - } - - 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(Worker()); - } - } - - Future _initializeWorkers() async { - await Future.wait(_pool.map((e) => e.initialize())); - } - - Cancelable _createCancelable({ - required Function execution, - WorkPriority priority = WorkPriority.immediately, - void Function(Object value)? onMessage, - }) { + /// 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; } final id = _nextTaskId.toString(); _nextTaskId++; - late final Task task; - final completer = Completer(); - 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!, - ); - } else if (execution is Execute) { - task = TaskRegular(id: id, workPriority: priority, execution: execution, completer: completer); - } + final task = _GentleTask(id: id, workPriority: priority, execution: execution, completer: Completer()); _queue.add(task); _schedule(); logTaskAdded(task.id); return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); } + void _createWorkers() { + for (var i = 0; i < _isolatesCount; i++) { + _pool.add(IsolateWorker()); + } + } + + Future _initializeWorkers() async { + await Future.wait(_pool.map((e) => e.initialize())); + } + Future _ensureWorkersInitialized() async { if (_pool.isEmpty) { _createWorkers(); @@ -240,7 +167,9 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { ) .whenComplete(() { if (_dynamicSpawning && _queue.isEmpty) { - availableWorker.kill(); + // 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(); }); @@ -250,15 +179,8 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { void _cancel(Task task) { task.cancel(); _queue.remove(task); - final targetWorker = _pool.firstWhereOrNull((worker) => worker.taskId == task.id); - if (task is Gentle) { - targetWorker?.cancelGentle(); - } else { - targetWorker?.kill(); - if (!_dynamicSpawning) { - targetWorker?.initialize(); - } - } + // All tasks are gentle: signal cancellation; the worker unwinds on its own. + _pool.firstWhereOrNull((worker) => worker.taskId == task.id)?.cancelGentle(); super._cancel(task); } } diff --git a/mobile/mise.lock b/mobile/mise.lock new file mode 100644 index 0000000000..e95323e4d7 --- /dev/null +++ b/mobile/mise.lock @@ -0,0 +1,89 @@ +# @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.1" +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.1-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.1-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.1-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.1-stable.tar.xz" + +[tools."aqua:flutter/flutter"."platforms.macos-arm64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.1-stable.zip" + +[tools."aqua:flutter/flutter"."platforms.macos-x64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.1-stable.zip" + +[tools."aqua:flutter/flutter"."platforms.windows-x64"] +url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.1-stable.zip" + +[[tools."github:CQLabs/homebrew-dcm"]] +version = "1.37.0" +backend = "github:CQLabs/homebrew-dcm" + +[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"."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"."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"."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"."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."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" diff --git a/mobile/mise.toml b/mobile/mise.toml index a0f25718ac..002e349d4e 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -1,3 +1,19 @@ +[tools] +"aqua:flutter/flutter" = "3.44.1" +java = "21.0.2" + +[tools."github:CQLabs/homebrew-dcm"] +version = "1.37.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:dart"] alias = "codegen" description = "Execute build_runner to auto-generate dart code" @@ -22,14 +38,8 @@ run = "dart run build_runner watch --delete-conflicting-outputs" alias = "pigeon" description = "Generate pigeon platform code" run = [ - "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 lib/platform/local_image_api.g.dart lib/platform/remote_image_api.g.dart lib/platform/background_worker_api.g.dart lib/platform/background_worker_lock_api.g.dart lib/platform/connectivity_api.g.dart lib/platform/network_api.g.dart", + "ls pigeon/*.dart | xargs -n1 -P4 -I{} dart run pigeon --input {}", + "dart format lib/platform/", ] [tasks."codegen:translation"] @@ -132,10 +142,10 @@ run = "dcm fix lib" [tasks.checklist] run = [ - {task = "codegen:pigeon" }, - {task = "codegen:dart" }, - {task = "codegen:translation" }, - {task = "analyze" }, - {task = "format" }, - {task = "test" }, + { task = "codegen:pigeon" }, + { task = "codegen:dart" }, + { task = "codegen:translation" }, + { task = "analyze" }, + { task = "format" }, + { task = "test" }, ] diff --git a/mobile/openapi/.openapi-generator/VERSION b/mobile/openapi/.openapi-generator/VERSION index 09a6d30847..696eaac5ce 100644 --- a/mobile/openapi/.openapi-generator/VERSION +++ b/mobile/openapi/.openapi-generator/VERSION @@ -1 +1 @@ -7.8.0 +7.22.0 diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 70a6fa8063..47fa5dfeaa 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -4,7 +4,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: 3.0.0 -- Generator version: 7.8.0 +- Generator version: 7.22.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 550089b09d..3c2ba70b79 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -29,6 +29,7 @@ part 'auth/api_key_auth.dart'; part 'auth/oauth.dart'; part 'auth/http_basic_auth.dart'; part 'auth/http_bearer_auth.dart'; +part 'optional.dart'; part 'api/api_keys_api.dart'; part 'api/activities_api.dart'; diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 6cf11022c3..6c824d4a86 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -226,6 +226,9 @@ Future _decodeBodyBytes(Response response) async { /// 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; + if (T == double && value is int) { + return value.toDouble() as T; + } return value is T ? value : null; } diff --git a/mobile/openapi/lib/model/activity_create_dto.dart b/mobile/openapi/lib/model/activity_create_dto.dart index bc220e64ce..7dbea342c9 100644 --- a/mobile/openapi/lib/model/activity_create_dto.dart +++ b/mobile/openapi/lib/model/activity_create_dto.dart @@ -14,8 +14,8 @@ class ActivityCreateDto { /// Returns a new [ActivityCreateDto] instance. ActivityCreateDto({ required this.albumId, - this.assetId, - this.comment, + this.assetId = const Optional.absent(), + this.comment = const Optional.absent(), required this.type, }); @@ -29,7 +29,7 @@ class ActivityCreateDto { /// 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; + Optional assetId; /// Comment text (required if type is comment) /// @@ -38,7 +38,7 @@ class ActivityCreateDto { /// 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; + Optional comment; ReactionType type; @@ -63,15 +63,13 @@ class ActivityCreateDto { 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.assetId.isPresent) { + final value = this.assetId.value; + json[r'assetId'] = value; } - if (this.comment != null) { - json[r'comment'] = this.comment; - } else { - // json[r'comment'] = null; + if (this.comment.isPresent) { + final value = this.comment.value; + json[r'comment'] = value; } json[r'type'] = this.type; return json; @@ -87,8 +85,8 @@ class ActivityCreateDto { return ActivityCreateDto( albumId: mapValueOfType(json, r'albumId')!, - assetId: mapValueOfType(json, r'assetId'), - comment: mapValueOfType(json, r'comment'), + assetId: json.containsKey(r'assetId') ? Optional.present(mapValueOfType(json, r'assetId')) : const Optional.absent(), + comment: json.containsKey(r'comment') ? Optional.present(mapValueOfType(json, r'comment')) : const Optional.absent(), type: ReactionType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/activity_response_dto.dart b/mobile/openapi/lib/model/activity_response_dto.dart index 1b0e279ab7..8a8f807260 100644 --- a/mobile/openapi/lib/model/activity_response_dto.dart +++ b/mobile/openapi/lib/model/activity_response_dto.dart @@ -14,7 +14,7 @@ class ActivityResponseDto { /// Returns a new [ActivityResponseDto] instance. ActivityResponseDto({ required this.assetId, - this.comment, + this.comment = const Optional.absent(), required this.createdAt, required this.id, required this.type, @@ -25,7 +25,7 @@ class ActivityResponseDto { String? assetId; /// Comment text (for comment activities) - String? comment; + Optional comment; /// Creation date DateTime createdAt; @@ -64,12 +64,11 @@ class ActivityResponseDto { if (this.assetId != null) { json[r'assetId'] = this.assetId; } else { - // json[r'assetId'] = null; + json[r'assetId'] = null; } - if (this.comment != null) { - json[r'comment'] = this.comment; - } else { - // json[r'comment'] = null; + if (this.comment.isPresent) { + final value = this.comment.value; + json[r'comment'] = value; } json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch @@ -90,7 +89,7 @@ class ActivityResponseDto { return ActivityResponseDto( assetId: mapValueOfType(json, r'assetId'), - comment: mapValueOfType(json, r'comment'), + comment: json.containsKey(r'comment') ? Optional.present(mapValueOfType(json, r'comment')) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, id: mapValueOfType(json, r'id')!, type: ReactionType.fromJson(json[r'type'])!, diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index fd90f23e3a..a7e350fd53 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -17,17 +17,17 @@ class AlbumResponseDto { required this.albumThumbnailAssetId, this.albumUsers = const [], required this.assetCount, - this.contributorCounts = const [], + this.contributorCounts = const Optional.present(const []), required this.createdAt, required this.description, - this.endDate, + this.endDate = const Optional.absent(), required this.hasSharedLink, required this.id, required this.isActivityEnabled, - this.lastModifiedAssetTimestamp, - this.order, + this.lastModifiedAssetTimestamp = const Optional.absent(), + this.order = const Optional.absent(), required this.shared, - this.startDate, + this.startDate = const Optional.absent(), required this.updatedAt, }); @@ -46,7 +46,7 @@ class AlbumResponseDto { /// Maximum value: 9007199254740991 int assetCount; - List contributorCounts; + Optional?> contributorCounts; /// Creation date DateTime createdAt; @@ -61,7 +61,7 @@ class AlbumResponseDto { /// 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; + Optional endDate; /// Has shared link bool hasSharedLink; @@ -79,7 +79,7 @@ class AlbumResponseDto { /// 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; + Optional lastModifiedAssetTimestamp; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -87,7 +87,7 @@ class AlbumResponseDto { /// 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; + Optional order; /// Is shared album bool shared; @@ -99,7 +99,7 @@ class AlbumResponseDto { /// 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; + Optional startDate; /// Last update date DateTime updatedAt; @@ -152,36 +152,35 @@ class AlbumResponseDto { if (this.albumThumbnailAssetId != null) { json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; } else { - // json[r'albumThumbnailAssetId'] = null; + json[r'albumThumbnailAssetId'] = null; } json[r'albumUsers'] = this.albumUsers; json[r'assetCount'] = this.assetCount; - json[r'contributorCounts'] = this.contributorCounts; + if (this.contributorCounts.isPresent) { + final value = this.contributorCounts.value; + json[r'contributorCounts'] = value; + } 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; + if (this.endDate.isPresent) { + final value = this.endDate.value; + json[r'endDate'] = value == null ? null : value.toUtc().toIso8601String(); } 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.lastModifiedAssetTimestamp.isPresent) { + final value = this.lastModifiedAssetTimestamp.value; + json[r'lastModifiedAssetTimestamp'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } json[r'shared'] = this.shared; - if (this.startDate != null) { - json[r'startDate'] = this.startDate!.toUtc().toIso8601String(); - } else { - // json[r'startDate'] = null; + if (this.startDate.isPresent) { + final value = this.startDate.value; + json[r'startDate'] = value == null ? null : value.toUtc().toIso8601String(); } json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); return json; @@ -200,17 +199,17 @@ class AlbumResponseDto { albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']), assetCount: mapValueOfType(json, r'assetCount')!, - contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']), + contributorCounts: json.containsKey(r'contributorCounts') ? Optional.present(ContributorCountResponseDto.listFromJson(json[r'contributorCounts'])) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'')!, description: mapValueOfType(json, r'description')!, - endDate: mapDateTime(json, r'endDate', r''), + endDate: json.containsKey(r'endDate') ? Optional.present(mapDateTime(json, r'endDate', r'')) : const Optional.absent(), 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']), + lastModifiedAssetTimestamp: json.containsKey(r'lastModifiedAssetTimestamp') ? Optional.present(mapDateTime(json, r'lastModifiedAssetTimestamp', r'')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), shared: mapValueOfType(json, r'shared')!, - startDate: mapDateTime(json, r'startDate', r''), + startDate: json.containsKey(r'startDate') ? Optional.present(mapDateTime(json, r'startDate', r'')) : const Optional.absent(), updatedAt: mapDateTime(json, r'updatedAt', r'')!, ); } diff --git a/mobile/openapi/lib/model/album_user_add_dto.dart b/mobile/openapi/lib/model/album_user_add_dto.dart index ee457905bd..e47ffc421c 100644 --- a/mobile/openapi/lib/model/album_user_add_dto.dart +++ b/mobile/openapi/lib/model/album_user_add_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumUserAddDto { /// Returns a new [AlbumUserAddDto] instance. AlbumUserAddDto({ - this.role, + this.role = const Optional.absent(), required this.userId, }); @@ -23,7 +23,7 @@ class AlbumUserAddDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AlbumUserRole? role; + Optional role; /// User ID String userId; @@ -44,10 +44,9 @@ class AlbumUserAddDto { Map toJson() { final json = {}; - if (this.role != null) { - json[r'role'] = this.role; - } else { - // json[r'role'] = null; + if (this.role.isPresent) { + final value = this.role.value; + json[r'role'] = value; } json[r'userId'] = this.userId; return json; @@ -62,7 +61,7 @@ class AlbumUserAddDto { final json = value.cast(); return AlbumUserAddDto( - role: AlbumUserRole.fromJson(json[r'role']), + role: json.containsKey(r'role') ? Optional.present(AlbumUserRole.fromJson(json[r'role'])) : const Optional.absent(), userId: mapValueOfType(json, r'userId')!, ); } diff --git a/mobile/openapi/lib/model/albums_add_assets_response_dto.dart b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart index 99e679222e..943bd30bc5 100644 --- a/mobile/openapi/lib/model/albums_add_assets_response_dto.dart +++ b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumsAddAssetsResponseDto { /// Returns a new [AlbumsAddAssetsResponseDto] instance. AlbumsAddAssetsResponseDto({ - this.error, + this.error = const Optional.absent(), required this.success, }); @@ -23,7 +23,7 @@ class AlbumsAddAssetsResponseDto { /// 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; + Optional error; /// Operation success bool success; @@ -44,10 +44,9 @@ class AlbumsAddAssetsResponseDto { Map toJson() { final json = {}; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } json[r'success'] = this.success; return json; @@ -62,7 +61,7 @@ class AlbumsAddAssetsResponseDto { final json = value.cast(); return AlbumsAddAssetsResponseDto( - error: BulkIdErrorReason.fromJson(json[r'error']), + error: json.containsKey(r'error') ? Optional.present(BulkIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), success: mapValueOfType(json, r'success')!, ); } diff --git a/mobile/openapi/lib/model/albums_update.dart b/mobile/openapi/lib/model/albums_update.dart index d61b5c1398..46bb3ef66d 100644 --- a/mobile/openapi/lib/model/albums_update.dart +++ b/mobile/openapi/lib/model/albums_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class AlbumsUpdate { /// Returns a new [AlbumsUpdate] instance. AlbumsUpdate({ - this.defaultAssetOrder, + this.defaultAssetOrder = const Optional.absent(), }); /// @@ -22,7 +22,7 @@ class AlbumsUpdate { /// 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; + Optional defaultAssetOrder; @override bool operator ==(Object other) => identical(this, other) || other is AlbumsUpdate && @@ -38,10 +38,9 @@ class AlbumsUpdate { Map toJson() { final json = {}; - if (this.defaultAssetOrder != null) { - json[r'defaultAssetOrder'] = this.defaultAssetOrder; - } else { - // json[r'defaultAssetOrder'] = null; + if (this.defaultAssetOrder.isPresent) { + final value = this.defaultAssetOrder.value; + json[r'defaultAssetOrder'] = value; } return json; } @@ -55,7 +54,7 @@ class AlbumsUpdate { final json = value.cast(); return AlbumsUpdate( - defaultAssetOrder: AssetOrder.fromJson(json[r'defaultAssetOrder']), + defaultAssetOrder: json.containsKey(r'defaultAssetOrder') ? Optional.present(AssetOrder.fromJson(json[r'defaultAssetOrder'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/api_key_create_dto.dart b/mobile/openapi/lib/model/api_key_create_dto.dart index 6d3ffc1eb1..e1a50fecd5 100644 --- a/mobile/openapi/lib/model/api_key_create_dto.dart +++ b/mobile/openapi/lib/model/api_key_create_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class ApiKeyCreateDto { /// Returns a new [ApiKeyCreateDto] instance. ApiKeyCreateDto({ - this.name, + this.name = const Optional.absent(), this.permissions = const [], }); @@ -24,7 +24,7 @@ class ApiKeyCreateDto { /// 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; + Optional name; /// List of permissions List permissions; @@ -45,10 +45,9 @@ class ApiKeyCreateDto { Map toJson() { final json = {}; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } json[r'permissions'] = this.permissions; return json; @@ -63,7 +62,7 @@ class ApiKeyCreateDto { final json = value.cast(); return ApiKeyCreateDto( - name: mapValueOfType(json, r'name'), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), permissions: Permission.listFromJson(json[r'permissions']), ); } diff --git a/mobile/openapi/lib/model/api_key_update_dto.dart b/mobile/openapi/lib/model/api_key_update_dto.dart index c8df4be654..c6b0b5ed3c 100644 --- a/mobile/openapi/lib/model/api_key_update_dto.dart +++ b/mobile/openapi/lib/model/api_key_update_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class ApiKeyUpdateDto { /// Returns a new [ApiKeyUpdateDto] instance. ApiKeyUpdateDto({ - this.name, - this.permissions = const [], + this.name = const Optional.absent(), + this.permissions = const Optional.present(const []), }); /// API key name @@ -24,10 +24,10 @@ class ApiKeyUpdateDto { /// 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; + Optional name; /// List of permissions - List permissions; + Optional?> permissions; @override bool operator ==(Object other) => identical(this, other) || other is ApiKeyUpdateDto && @@ -45,12 +45,14 @@ class ApiKeyUpdateDto { Map toJson() { final json = {}; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; + } + if (this.permissions.isPresent) { + final value = this.permissions.value; + json[r'permissions'] = value; } - json[r'permissions'] = this.permissions; return json; } @@ -63,8 +65,8 @@ class ApiKeyUpdateDto { final json = value.cast(); return ApiKeyUpdateDto( - name: mapValueOfType(json, r'name'), - permissions: Permission.listFromJson(json[r'permissions']), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + permissions: json.containsKey(r'permissions') ? Optional.present(Permission.listFromJson(json[r'permissions'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_bulk_delete_dto.dart b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart index 055ef16015..bfe51e1779 100644 --- a/mobile/openapi/lib/model/asset_bulk_delete_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class AssetBulkDeleteDto { /// Returns a new [AssetBulkDeleteDto] instance. AssetBulkDeleteDto({ - this.force, + this.force = const Optional.absent(), this.ids = const [], }); @@ -24,7 +24,7 @@ class AssetBulkDeleteDto { /// 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; + Optional force; /// IDs to process List ids; @@ -45,10 +45,9 @@ class AssetBulkDeleteDto { Map toJson() { final json = {}; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } json[r'ids'] = this.ids; return json; @@ -63,7 +62,7 @@ class AssetBulkDeleteDto { final json = value.cast(); return AssetBulkDeleteDto( - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart index f85026f054..f5e679274f 100644 --- a/mobile/openapi/lib/model/asset_bulk_update_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -13,17 +13,17 @@ part of openapi.api; class AssetBulkUpdateDto { /// Returns a new [AssetBulkUpdateDto] instance. AssetBulkUpdateDto({ - this.dateTimeOriginal, - this.dateTimeRelative, - this.description, - this.duplicateId, + this.dateTimeOriginal = const Optional.absent(), + this.dateTimeRelative = const Optional.absent(), + this.description = const Optional.absent(), + this.duplicateId = const Optional.absent(), this.ids = const [], - this.isFavorite, - this.latitude, - this.longitude, - this.rating, - this.timeZone, - this.visibility, + this.isFavorite = const Optional.absent(), + this.latitude = const Optional.absent(), + this.longitude = const Optional.absent(), + this.rating = const Optional.absent(), + this.timeZone = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Original date and time @@ -33,7 +33,7 @@ class AssetBulkUpdateDto { /// 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; + Optional dateTimeOriginal; /// Relative time offset in seconds /// @@ -45,7 +45,7 @@ class AssetBulkUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? dateTimeRelative; + Optional dateTimeRelative; /// Asset description /// @@ -54,10 +54,10 @@ class AssetBulkUpdateDto { /// 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; + Optional description; /// Duplicate ID - String? duplicateId; + Optional duplicateId; /// Asset IDs to update List ids; @@ -69,7 +69,7 @@ class AssetBulkUpdateDto { /// 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; + Optional isFavorite; /// Latitude coordinate /// @@ -81,7 +81,7 @@ class AssetBulkUpdateDto { /// 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; + Optional latitude; /// Longitude coordinate /// @@ -93,13 +93,13 @@ class AssetBulkUpdateDto { /// 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; + Optional longitude; /// Rating in range [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// Time zone (IANA timezone) /// @@ -108,7 +108,7 @@ class AssetBulkUpdateDto { /// 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; + Optional timeZone; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -116,7 +116,7 @@ class AssetBulkUpdateDto { /// 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; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && @@ -152,56 +152,46 @@ class AssetBulkUpdateDto { Map toJson() { final json = {}; - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal; - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value; } - if (this.dateTimeRelative != null) { - json[r'dateTimeRelative'] = this.dateTimeRelative; - } else { - // json[r'dateTimeRelative'] = null; + if (this.dateTimeRelative.isPresent) { + final value = this.dateTimeRelative.value; + json[r'dateTimeRelative'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.duplicateId != null) { - json[r'duplicateId'] = this.duplicateId; - } else { - // json[r'duplicateId'] = null; + if (this.duplicateId.isPresent) { + final value = this.duplicateId.value; + json[r'duplicateId'] = value; } json[r'ids'] = this.ids; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.timeZone != null) { - json[r'timeZone'] = this.timeZone; - } else { - // json[r'timeZone'] = null; + if (this.timeZone.isPresent) { + final value = this.timeZone.value; + json[r'timeZone'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -215,19 +205,19 @@ class AssetBulkUpdateDto { final json = value.cast(); return AssetBulkUpdateDto( - dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), - dateTimeRelative: mapValueOfType(json, r'dateTimeRelative'), - description: mapValueOfType(json, r'description'), - duplicateId: mapValueOfType(json, r'duplicateId'), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapValueOfType(json, r'dateTimeOriginal')) : const Optional.absent(), + dateTimeRelative: json.containsKey(r'dateTimeRelative') ? Optional.present(json[r'dateTimeRelative'] == null ? null : int.parse('${json[r'dateTimeRelative']}')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + duplicateId: json.containsKey(r'duplicateId') ? Optional.present(mapValueOfType(json, r'duplicateId')) : const Optional.absent(), 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: mapValueOfType(json, r'rating'), - timeZone: mapValueOfType(json, r'timeZone'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + timeZone: json.containsKey(r'timeZone') ? Optional.present(mapValueOfType(json, r'timeZone')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart index bf3ee8e244..ed7508b18e 100644 --- a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart @@ -14,10 +14,10 @@ class AssetBulkUploadCheckResult { /// Returns a new [AssetBulkUploadCheckResult] instance. AssetBulkUploadCheckResult({ required this.action, - this.assetId, + this.assetId = const Optional.absent(), required this.id, - this.isTrashed, - this.reason, + this.isTrashed = const Optional.absent(), + this.reason = const Optional.absent(), }); AssetUploadAction action; @@ -29,7 +29,7 @@ class AssetBulkUploadCheckResult { /// 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; + Optional assetId; /// Asset ID String id; @@ -41,7 +41,7 @@ class AssetBulkUploadCheckResult { /// 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; + Optional isTrashed; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -49,7 +49,7 @@ class AssetBulkUploadCheckResult { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetRejectReason? reason; + Optional reason; @override bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResult && @@ -74,21 +74,18 @@ class AssetBulkUploadCheckResult { Map toJson() { final json = {}; json[r'action'] = this.action; - if (this.assetId != null) { - json[r'assetId'] = this.assetId; - } else { - // json[r'assetId'] = null; + if (this.assetId.isPresent) { + final value = this.assetId.value; + json[r'assetId'] = value; } json[r'id'] = this.id; - if (this.isTrashed != null) { - json[r'isTrashed'] = this.isTrashed; - } else { - // json[r'isTrashed'] = null; + if (this.isTrashed.isPresent) { + final value = this.isTrashed.value; + json[r'isTrashed'] = value; } - if (this.reason != null) { - json[r'reason'] = this.reason; - } else { - // json[r'reason'] = null; + if (this.reason.isPresent) { + final value = this.reason.value; + json[r'reason'] = value; } return json; } @@ -103,10 +100,10 @@ class AssetBulkUploadCheckResult { return AssetBulkUploadCheckResult( action: AssetUploadAction.fromJson(json[r'action'])!, - assetId: mapValueOfType(json, r'assetId'), + assetId: json.containsKey(r'assetId') ? Optional.present(mapValueOfType(json, r'assetId')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isTrashed: mapValueOfType(json, r'isTrashed'), - reason: AssetRejectReason.fromJson(json[r'reason']), + isTrashed: json.containsKey(r'isTrashed') ? Optional.present(mapValueOfType(json, r'isTrashed')) : const Optional.absent(), + reason: json.containsKey(r'reason') ? Optional.present(AssetRejectReason.fromJson(json[r'reason'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_copy_dto.dart b/mobile/openapi/lib/model/asset_copy_dto.dart index 2e68c5c113..577ba9ffa6 100644 --- a/mobile/openapi/lib/model/asset_copy_dto.dart +++ b/mobile/openapi/lib/model/asset_copy_dto.dart @@ -13,32 +13,32 @@ part of openapi.api; class AssetCopyDto { /// Returns a new [AssetCopyDto] instance. AssetCopyDto({ - this.albums = true, - this.favorite = true, - this.sharedLinks = true, - this.sidecar = true, + this.albums = const Optional.present(true), + this.favorite = const Optional.present(true), + this.sharedLinks = const Optional.present(true), + this.sidecar = const Optional.present(true), required this.sourceId, - this.stack = true, + this.stack = const Optional.present(true), required this.targetId, }); /// Copy album associations - bool albums; + Optional albums; /// Copy favorite status - bool favorite; + Optional favorite; /// Copy shared links - bool sharedLinks; + Optional sharedLinks; /// Copy sidecar file - bool sidecar; + Optional sidecar; /// Source asset ID String sourceId; /// Copy stack association - bool stack; + Optional stack; /// Target asset ID String targetId; @@ -69,12 +69,27 @@ class AssetCopyDto { Map toJson() { final json = {}; - json[r'albums'] = this.albums; - json[r'favorite'] = this.favorite; - json[r'sharedLinks'] = this.sharedLinks; - json[r'sidecar'] = this.sidecar; + if (this.albums.isPresent) { + final value = this.albums.value; + json[r'albums'] = value; + } + if (this.favorite.isPresent) { + final value = this.favorite.value; + json[r'favorite'] = value; + } + if (this.sharedLinks.isPresent) { + final value = this.sharedLinks.value; + json[r'sharedLinks'] = value; + } + if (this.sidecar.isPresent) { + final value = this.sidecar.value; + json[r'sidecar'] = value; + } json[r'sourceId'] = this.sourceId; - json[r'stack'] = this.stack; + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } json[r'targetId'] = this.targetId; return json; } @@ -88,12 +103,12 @@ class AssetCopyDto { 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, + albums: json.containsKey(r'albums') ? Optional.present(mapValueOfType(json, r'albums')) : const Optional.absent(), + favorite: json.containsKey(r'favorite') ? Optional.present(mapValueOfType(json, r'favorite')) : const Optional.absent(), + sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(mapValueOfType(json, r'sharedLinks')) : const Optional.absent(), + sidecar: json.containsKey(r'sidecar') ? Optional.present(mapValueOfType(json, r'sidecar')) : const Optional.absent(), sourceId: mapValueOfType(json, r'sourceId')!, - stack: mapValueOfType(json, r'stack') ?? true, + stack: json.containsKey(r'stack') ? Optional.present(mapValueOfType(json, r'stack')) : const Optional.absent(), targetId: mapValueOfType(json, r'targetId')!, ); } diff --git a/mobile/openapi/lib/model/asset_face_response_dto.dart b/mobile/openapi/lib/model/asset_face_response_dto.dart index 21b86dfe4e..aa7b8b65f0 100644 --- a/mobile/openapi/lib/model/asset_face_response_dto.dart +++ b/mobile/openapi/lib/model/asset_face_response_dto.dart @@ -21,7 +21,7 @@ class AssetFaceResponseDto { required this.imageHeight, required this.imageWidth, required this.person, - this.sourceType, + this.sourceType = const Optional.absent(), }); /// Bounding box X1 coordinate @@ -71,7 +71,7 @@ class AssetFaceResponseDto { /// 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; + Optional sourceType; @override bool operator ==(Object other) => identical(this, other) || other is AssetFaceResponseDto && @@ -113,12 +113,11 @@ class AssetFaceResponseDto { if (this.person != null) { json[r'person'] = this.person; } else { - // json[r'person'] = null; + json[r'person'] = null; } - if (this.sourceType != null) { - json[r'sourceType'] = this.sourceType; - } else { - // json[r'sourceType'] = null; + if (this.sourceType.isPresent) { + final value = this.sourceType.value; + json[r'sourceType'] = value; } return json; } @@ -140,7 +139,7 @@ class AssetFaceResponseDto { imageHeight: mapValueOfType(json, r'imageHeight')!, imageWidth: mapValueOfType(json, r'imageWidth')!, person: PersonResponseDto.fromJson(json[r'person']), - sourceType: SourceType.fromJson(json[r'sourceType']), + sourceType: json.containsKey(r'sourceType') ? Optional.present(SourceType.fromJson(json[r'sourceType'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/asset_ids_response_dto.dart b/mobile/openapi/lib/model/asset_ids_response_dto.dart index cafe1b21b9..6d8076952e 100644 --- a/mobile/openapi/lib/model/asset_ids_response_dto.dart +++ b/mobile/openapi/lib/model/asset_ids_response_dto.dart @@ -14,7 +14,7 @@ class AssetIdsResponseDto { /// Returns a new [AssetIdsResponseDto] instance. AssetIdsResponseDto({ required this.assetId, - this.error, + this.error = const Optional.absent(), required this.success, }); @@ -27,7 +27,7 @@ class AssetIdsResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - AssetIdErrorReason? error; + Optional error; /// Whether operation succeeded bool success; @@ -51,10 +51,9 @@ class AssetIdsResponseDto { Map toJson() { final json = {}; json[r'assetId'] = this.assetId; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } json[r'success'] = this.success; return json; @@ -70,7 +69,7 @@ class AssetIdsResponseDto { return AssetIdsResponseDto( assetId: mapValueOfType(json, r'assetId')!, - error: AssetIdErrorReason.fromJson(json[r'error']), + error: json.containsKey(r'error') ? Optional.present(AssetIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), success: mapValueOfType(json, r'success')!, ); } diff --git a/mobile/openapi/lib/model/asset_ocr_response_dto.dart b/mobile/openapi/lib/model/asset_ocr_response_dto.dart index c7937c6eb2..23c51f054c 100644 --- a/mobile/openapi/lib/model/asset_ocr_response_dto.dart +++ b/mobile/openapi/lib/model/asset_ocr_response_dto.dart @@ -129,18 +129,18 @@ class AssetOcrResponseDto { return AssetOcrResponseDto( assetId: mapValueOfType(json, r'assetId')!, - boxScore: (mapValueOfType(json, r'boxScore')!).toDouble(), + boxScore: mapValueOfType(json, r'boxScore')!, 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(), + textScore: mapValueOfType(json, r'textScore')!, + x1: mapValueOfType(json, r'x1')!, + x2: mapValueOfType(json, r'x2')!, + x3: mapValueOfType(json, r'x3')!, + x4: mapValueOfType(json, r'x4')!, + y1: mapValueOfType(json, r'y1')!, + y2: mapValueOfType(json, r'y2')!, + y3: mapValueOfType(json, r'y3')!, + y4: mapValueOfType(json, r'y4')!, ); } return null; diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index eca87789ce..3c09de3f15 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -15,9 +15,9 @@ class AssetResponseDto { AssetResponseDto({ required this.checksum, required this.createdAt, - this.duplicateId, + this.duplicateId = const Optional.absent(), required this.duration, - this.exifInfo, + this.exifInfo = const Optional.absent(), required this.fileCreatedAt, required this.fileModifiedAt, required this.hasMetadata, @@ -28,18 +28,18 @@ class AssetResponseDto { required this.isFavorite, required this.isOffline, required this.isTrashed, - this.libraryId, - this.livePhotoVideoId, + this.libraryId = const Optional.absent(), + this.livePhotoVideoId = const Optional.absent(), required this.localDateTime, required this.originalFileName, - this.originalMimeType, + this.originalMimeType = const Optional.absent(), required this.originalPath, - this.owner, + this.owner = const Optional.absent(), required this.ownerId, - this.people = const [], - this.resized, - this.stack, - this.tags = const [], + this.people = const Optional.present(const []), + this.resized = const Optional.absent(), + this.stack = const Optional.absent(), + this.tags = const Optional.present(const []), required this.thumbhash, required this.type, required this.updatedAt, @@ -54,7 +54,7 @@ class AssetResponseDto { DateTime createdAt; /// Duplicate group ID - String? duplicateId; + Optional duplicateId; /// Video/gif duration in milliseconds (null for static images) /// @@ -68,7 +68,7 @@ class AssetResponseDto { /// 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; + Optional 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; @@ -104,10 +104,10 @@ class AssetResponseDto { bool isTrashed; /// Library ID - String? libraryId; + Optional libraryId; /// Live photo video ID - String? livePhotoVideoId; + Optional 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; @@ -122,7 +122,7 @@ class AssetResponseDto { /// 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; + Optional originalMimeType; /// Original file path String originalPath; @@ -133,12 +133,12 @@ class AssetResponseDto { /// 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; + Optional owner; /// Owner user ID String ownerId; - List people; + Optional?> people; /// Is resized /// @@ -147,11 +147,11 @@ class AssetResponseDto { /// 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; + Optional resized; - AssetStackResponseDto? stack; + Optional stack; - List tags; + Optional?> tags; /// Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. String? thumbhash; @@ -247,20 +247,18 @@ class AssetResponseDto { final json = {}; json[r'checksum'] = this.checksum; json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); - if (this.duplicateId != null) { - json[r'duplicateId'] = this.duplicateId; - } else { - // json[r'duplicateId'] = null; + if (this.duplicateId.isPresent) { + final value = this.duplicateId.value; + json[r'duplicateId'] = value; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } - if (this.exifInfo != null) { - json[r'exifInfo'] = this.exifInfo; - } else { - // json[r'exifInfo'] = null; + if (this.exifInfo.isPresent) { + final value = this.exifInfo.value; + json[r'exifInfo'] = value; } json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String(); json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String(); @@ -268,7 +266,7 @@ class AssetResponseDto { if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isArchived'] = this.isArchived; @@ -276,46 +274,46 @@ class AssetResponseDto { 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.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.livePhotoVideoId != null) { - json[r'livePhotoVideoId'] = this.livePhotoVideoId; - } else { - // json[r'livePhotoVideoId'] = null; + if (this.livePhotoVideoId.isPresent) { + final value = this.livePhotoVideoId.value; + json[r'livePhotoVideoId'] = value; } 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; + if (this.originalMimeType.isPresent) { + final value = this.originalMimeType.value; + json[r'originalMimeType'] = value; } json[r'originalPath'] = this.originalPath; - if (this.owner != null) { - json[r'owner'] = this.owner; - } else { - // json[r'owner'] = null; + if (this.owner.isPresent) { + final value = this.owner.value; + json[r'owner'] = value; } 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.people.isPresent) { + final value = this.people.value; + json[r'people'] = value; } - if (this.stack != null) { - json[r'stack'] = this.stack; - } else { - // json[r'stack'] = null; + if (this.resized.isPresent) { + final value = this.resized.value; + json[r'resized'] = value; + } + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } + if (this.tags.isPresent) { + final value = this.tags.value; + json[r'tags'] = value; } - json[r'tags'] = this.tags; if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + json[r'thumbhash'] = null; } json[r'type'] = this.type; json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); @@ -323,7 +321,7 @@ class AssetResponseDto { if (this.width != null) { json[r'width'] = this.width; } else { - // json[r'width'] = null; + json[r'width'] = null; } return json; } @@ -339,9 +337,9 @@ class AssetResponseDto { return AssetResponseDto( checksum: mapValueOfType(json, r'checksum')!, createdAt: mapDateTime(json, r'createdAt', r'')!, - duplicateId: mapValueOfType(json, r'duplicateId'), + duplicateId: json.containsKey(r'duplicateId') ? Optional.present(mapValueOfType(json, r'duplicateId')) : const Optional.absent(), duration: mapValueOfType(json, r'duration'), - exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), + exifInfo: json.containsKey(r'exifInfo') ? Optional.present(ExifResponseDto.fromJson(json[r'exifInfo'])) : const Optional.absent(), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!, fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!, hasMetadata: mapValueOfType(json, r'hasMetadata')!, @@ -352,18 +350,18 @@ class AssetResponseDto { 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'), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + livePhotoVideoId: json.containsKey(r'livePhotoVideoId') ? Optional.present(mapValueOfType(json, r'livePhotoVideoId')) : const Optional.absent(), localDateTime: mapDateTime(json, r'localDateTime', r'')!, originalFileName: mapValueOfType(json, r'originalFileName')!, - originalMimeType: mapValueOfType(json, r'originalMimeType'), + originalMimeType: json.containsKey(r'originalMimeType') ? Optional.present(mapValueOfType(json, r'originalMimeType')) : const Optional.absent(), originalPath: mapValueOfType(json, r'originalPath')!, - owner: UserResponseDto.fromJson(json[r'owner']), + owner: json.containsKey(r'owner') ? Optional.present(UserResponseDto.fromJson(json[r'owner'])) : const Optional.absent(), ownerId: mapValueOfType(json, r'ownerId')!, - people: PersonResponseDto.listFromJson(json[r'people']), - resized: mapValueOfType(json, r'resized'), - stack: AssetStackResponseDto.fromJson(json[r'stack']), - tags: TagResponseDto.listFromJson(json[r'tags']), + people: json.containsKey(r'people') ? Optional.present(PersonResponseDto.listFromJson(json[r'people'])) : const Optional.absent(), + resized: json.containsKey(r'resized') ? Optional.present(mapValueOfType(json, r'resized')) : const Optional.absent(), + stack: json.containsKey(r'stack') ? Optional.present(AssetStackResponseDto.fromJson(json[r'stack'])) : const Optional.absent(), + tags: json.containsKey(r'tags') ? Optional.present(TagResponseDto.listFromJson(json[r'tags'])) : const Optional.absent(), thumbhash: mapValueOfType(json, r'thumbhash'), type: AssetTypeEnum.fromJson(json[r'type'])!, updatedAt: mapDateTime(json, r'updatedAt', r'')!, diff --git a/mobile/openapi/lib/model/auth_status_response_dto.dart b/mobile/openapi/lib/model/auth_status_response_dto.dart index 23b9d40525..f0dc61215e 100644 --- a/mobile/openapi/lib/model/auth_status_response_dto.dart +++ b/mobile/openapi/lib/model/auth_status_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class AuthStatusResponseDto { /// Returns a new [AuthStatusResponseDto] instance. AuthStatusResponseDto({ - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.isElevated, required this.password, required this.pinCode, - this.pinExpiresAt, + this.pinExpiresAt = const Optional.absent(), }); /// Session expiration date @@ -27,7 +27,7 @@ class AuthStatusResponseDto { /// 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; + Optional expiresAt; /// Is elevated session bool isElevated; @@ -45,7 +45,7 @@ class AuthStatusResponseDto { /// 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; + Optional pinExpiresAt; @override bool operator ==(Object other) => identical(this, other) || other is AuthStatusResponseDto && @@ -69,18 +69,16 @@ class AuthStatusResponseDto { Map toJson() { final json = {}; - if (this.expiresAt != null) { - json[r'expiresAt'] = this.expiresAt; - } else { - // json[r'expiresAt'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } 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; + if (this.pinExpiresAt.isPresent) { + final value = this.pinExpiresAt.value; + json[r'pinExpiresAt'] = value; } return json; } @@ -94,11 +92,11 @@ class AuthStatusResponseDto { final json = value.cast(); return AuthStatusResponseDto( - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), isElevated: mapValueOfType(json, r'isElevated')!, password: mapValueOfType(json, r'password')!, pinCode: mapValueOfType(json, r'pinCode')!, - pinExpiresAt: mapValueOfType(json, r'pinExpiresAt'), + pinExpiresAt: json.containsKey(r'pinExpiresAt') ? Optional.present(mapValueOfType(json, r'pinExpiresAt')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/avatar_update.dart b/mobile/openapi/lib/model/avatar_update.dart index 875eb138a8..1075f0df46 100644 --- a/mobile/openapi/lib/model/avatar_update.dart +++ b/mobile/openapi/lib/model/avatar_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class AvatarUpdate { /// Returns a new [AvatarUpdate] instance. AvatarUpdate({ - this.color, + this.color = const Optional.absent(), }); /// @@ -22,7 +22,7 @@ class AvatarUpdate { /// 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; + Optional color; @override bool operator ==(Object other) => identical(this, other) || other is AvatarUpdate && @@ -38,10 +38,9 @@ class AvatarUpdate { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } return json; } @@ -55,7 +54,7 @@ class AvatarUpdate { final json = value.cast(); return AvatarUpdate( - color: UserAvatarColor.fromJson(json[r'color']), + color: json.containsKey(r'color') ? Optional.present(UserAvatarColor.fromJson(json[r'color'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/bulk_id_response_dto.dart b/mobile/openapi/lib/model/bulk_id_response_dto.dart index bb3f1d8856..301400fa5e 100644 --- a/mobile/openapi/lib/model/bulk_id_response_dto.dart +++ b/mobile/openapi/lib/model/bulk_id_response_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class BulkIdResponseDto { /// Returns a new [BulkIdResponseDto] instance. BulkIdResponseDto({ - this.error, - this.errorMessage, + this.error = const Optional.absent(), + this.errorMessage = const Optional.absent(), required this.id, required this.success, }); @@ -25,7 +25,7 @@ class BulkIdResponseDto { /// 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; + Optional error; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -33,7 +33,7 @@ class BulkIdResponseDto { /// 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; + Optional errorMessage; /// ID String id; @@ -61,15 +61,13 @@ class BulkIdResponseDto { Map toJson() { final json = {}; - if (this.error != null) { - json[r'error'] = this.error; - } else { - // json[r'error'] = null; + if (this.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } - if (this.errorMessage != null) { - json[r'errorMessage'] = this.errorMessage; - } else { - // json[r'errorMessage'] = null; + if (this.errorMessage.isPresent) { + final value = this.errorMessage.value; + json[r'errorMessage'] = value; } json[r'id'] = this.id; json[r'success'] = this.success; @@ -85,8 +83,8 @@ class BulkIdResponseDto { final json = value.cast(); return BulkIdResponseDto( - error: BulkIdErrorReason.fromJson(json[r'error']), - errorMessage: mapValueOfType(json, r'errorMessage'), + error: json.containsKey(r'error') ? Optional.present(BulkIdErrorReason.fromJson(json[r'error'])) : const Optional.absent(), + errorMessage: json.containsKey(r'errorMessage') ? Optional.present(mapValueOfType(json, r'errorMessage')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, success: mapValueOfType(json, r'success')!, ); diff --git a/mobile/openapi/lib/model/cast_update.dart b/mobile/openapi/lib/model/cast_update.dart index 8dbf80f171..f9eb5be382 100644 --- a/mobile/openapi/lib/model/cast_update.dart +++ b/mobile/openapi/lib/model/cast_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class CastUpdate { /// Returns a new [CastUpdate] instance. CastUpdate({ - this.gCastEnabled, + this.gCastEnabled = const Optional.absent(), }); /// Whether Google Cast is enabled @@ -23,7 +23,7 @@ class CastUpdate { /// 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; + Optional gCastEnabled; @override bool operator ==(Object other) => identical(this, other) || other is CastUpdate && @@ -39,10 +39,9 @@ class CastUpdate { Map toJson() { final json = {}; - if (this.gCastEnabled != null) { - json[r'gCastEnabled'] = this.gCastEnabled; - } else { - // json[r'gCastEnabled'] = null; + if (this.gCastEnabled.isPresent) { + final value = this.gCastEnabled.value; + json[r'gCastEnabled'] = value; } return json; } @@ -56,7 +55,7 @@ class CastUpdate { final json = value.cast(); return CastUpdate( - gCastEnabled: mapValueOfType(json, r'gCastEnabled'), + gCastEnabled: json.containsKey(r'gCastEnabled') ? Optional.present(mapValueOfType(json, r'gCastEnabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/change_password_dto.dart b/mobile/openapi/lib/model/change_password_dto.dart index 3dd6e437da..369e960dac 100644 --- a/mobile/openapi/lib/model/change_password_dto.dart +++ b/mobile/openapi/lib/model/change_password_dto.dart @@ -13,13 +13,13 @@ part of openapi.api; class ChangePasswordDto { /// Returns a new [ChangePasswordDto] instance. ChangePasswordDto({ - this.invalidateSessions = false, + this.invalidateSessions = const Optional.present(false), required this.newPassword, required this.password, }); /// Invalidate all other sessions - bool invalidateSessions; + Optional invalidateSessions; /// New password (min 8 characters) String newPassword; @@ -45,7 +45,10 @@ class ChangePasswordDto { Map toJson() { final json = {}; - json[r'invalidateSessions'] = this.invalidateSessions; + if (this.invalidateSessions.isPresent) { + final value = this.invalidateSessions.value; + json[r'invalidateSessions'] = value; + } json[r'newPassword'] = this.newPassword; json[r'password'] = this.password; return json; @@ -60,7 +63,7 @@ class ChangePasswordDto { final json = value.cast(); return ChangePasswordDto( - invalidateSessions: mapValueOfType(json, r'invalidateSessions') ?? false, + invalidateSessions: json.containsKey(r'invalidateSessions') ? Optional.present(mapValueOfType(json, r'invalidateSessions')) : const Optional.absent(), newPassword: mapValueOfType(json, r'newPassword')!, password: mapValueOfType(json, r'password')!, ); diff --git a/mobile/openapi/lib/model/create_album_dto.dart b/mobile/openapi/lib/model/create_album_dto.dart index 183a41c772..a028146964 100644 --- a/mobile/openapi/lib/model/create_album_dto.dart +++ b/mobile/openapi/lib/model/create_album_dto.dart @@ -14,19 +14,19 @@ class CreateAlbumDto { /// Returns a new [CreateAlbumDto] instance. CreateAlbumDto({ required this.albumName, - this.albumUsers = const [], - this.assetIds = const [], - this.description, + this.albumUsers = const Optional.present(const []), + this.assetIds = const Optional.present(const []), + this.description = const Optional.absent(), }); /// Album name String albumName; /// Album users - List albumUsers; + Optional?> albumUsers; /// Initial asset IDs - List assetIds; + Optional?> assetIds; /// Album description /// @@ -35,7 +35,7 @@ class CreateAlbumDto { /// 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; + Optional description; @override bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto && @@ -58,12 +58,17 @@ class CreateAlbumDto { 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; + if (this.albumUsers.isPresent) { + final value = this.albumUsers.value; + json[r'albumUsers'] = value; + } + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; + } + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } return json; } @@ -78,11 +83,11 @@ class CreateAlbumDto { return CreateAlbumDto( albumName: mapValueOfType(json, r'albumName')!, - albumUsers: AlbumUserCreateDto.listFromJson(json[r'albumUsers']), - assetIds: json[r'assetIds'] is Iterable + albumUsers: json.containsKey(r'albumUsers') ? Optional.present(AlbumUserCreateDto.listFromJson(json[r'albumUsers'])) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], - description: mapValueOfType(json, r'description'), + : const []) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/create_library_dto.dart b/mobile/openapi/lib/model/create_library_dto.dart index ba12c62d76..61eb9867b8 100644 --- a/mobile/openapi/lib/model/create_library_dto.dart +++ b/mobile/openapi/lib/model/create_library_dto.dart @@ -13,17 +13,17 @@ part of openapi.api; class CreateLibraryDto { /// Returns a new [CreateLibraryDto] instance. CreateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], - this.name, + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), + this.name = const Optional.absent(), required this.ownerId, }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths (max 128) - List importPaths; + Optional?> importPaths; /// Library name /// @@ -32,7 +32,7 @@ class CreateLibraryDto { /// 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; + Optional name; /// Owner user ID String ownerId; @@ -57,12 +57,17 @@ class CreateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } json[r'ownerId'] = this.ownerId; return json; @@ -77,13 +82,13 @@ class CreateLibraryDto { final json = value.cast(); return CreateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], - name: mapValueOfType(json, r'name'), + : const []) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ownerId: mapValueOfType(json, r'ownerId')!, ); } diff --git a/mobile/openapi/lib/model/download_archive_dto.dart b/mobile/openapi/lib/model/download_archive_dto.dart index 20e8527f18..f89ac8c867 100644 --- a/mobile/openapi/lib/model/download_archive_dto.dart +++ b/mobile/openapi/lib/model/download_archive_dto.dart @@ -14,7 +14,7 @@ class DownloadArchiveDto { /// Returns a new [DownloadArchiveDto] instance. DownloadArchiveDto({ this.assetIds = const [], - this.edited, + this.edited = const Optional.absent(), }); /// Asset IDs @@ -27,7 +27,7 @@ class DownloadArchiveDto { /// 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; + Optional edited; @override bool operator ==(Object other) => identical(this, other) || other is DownloadArchiveDto && @@ -46,10 +46,9 @@ class DownloadArchiveDto { Map toJson() { final json = {}; json[r'assetIds'] = this.assetIds; - if (this.edited != null) { - json[r'edited'] = this.edited; - } else { - // json[r'edited'] = null; + if (this.edited.isPresent) { + final value = this.edited.value; + json[r'edited'] = value; } return json; } @@ -66,7 +65,7 @@ class DownloadArchiveDto { assetIds: json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) : const [], - edited: mapValueOfType(json, r'edited'), + edited: json.containsKey(r'edited') ? Optional.present(mapValueOfType(json, r'edited')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/download_info_dto.dart b/mobile/openapi/lib/model/download_info_dto.dart index 8a0cebd945..47e09de05a 100644 --- a/mobile/openapi/lib/model/download_info_dto.dart +++ b/mobile/openapi/lib/model/download_info_dto.dart @@ -13,10 +13,10 @@ part of openapi.api; class DownloadInfoDto { /// Returns a new [DownloadInfoDto] instance. DownloadInfoDto({ - this.albumId, - this.archiveSize, - this.assetIds = const [], - this.userId, + this.albumId = const Optional.absent(), + this.archiveSize = const Optional.absent(), + this.assetIds = const Optional.present(const []), + this.userId = const Optional.absent(), }); /// Album ID to download @@ -26,7 +26,7 @@ class DownloadInfoDto { /// 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; + Optional albumId; /// Archive size limit in bytes /// @@ -38,10 +38,10 @@ class DownloadInfoDto { /// 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; + Optional archiveSize; /// Asset IDs to download - List assetIds; + Optional?> assetIds; /// User ID to download assets from /// @@ -50,7 +50,7 @@ class DownloadInfoDto { /// 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; + Optional userId; @override bool operator ==(Object other) => identical(this, other) || other is DownloadInfoDto && @@ -72,21 +72,21 @@ class DownloadInfoDto { Map toJson() { final json = {}; - if (this.albumId != null) { - json[r'albumId'] = this.albumId; - } else { - // json[r'albumId'] = null; + if (this.albumId.isPresent) { + final value = this.albumId.value; + json[r'albumId'] = value; } - if (this.archiveSize != null) { - json[r'archiveSize'] = this.archiveSize; - } else { - // json[r'archiveSize'] = null; + if (this.archiveSize.isPresent) { + final value = this.archiveSize.value; + json[r'archiveSize'] = value; } - json[r'assetIds'] = this.assetIds; - if (this.userId != null) { - json[r'userId'] = this.userId; - } else { - // json[r'userId'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; + } + if (this.userId.isPresent) { + final value = this.userId.value; + json[r'userId'] = value; } return json; } @@ -100,12 +100,12 @@ class DownloadInfoDto { final json = value.cast(); return DownloadInfoDto( - albumId: mapValueOfType(json, r'albumId'), - archiveSize: mapValueOfType(json, r'archiveSize'), - assetIds: json[r'assetIds'] is Iterable + albumId: json.containsKey(r'albumId') ? Optional.present(mapValueOfType(json, r'albumId')) : const Optional.absent(), + archiveSize: json.containsKey(r'archiveSize') ? Optional.present(json[r'archiveSize'] == null ? null : int.parse('${json[r'archiveSize']}')) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], - userId: mapValueOfType(json, r'userId'), + : const []) : const Optional.absent(), + userId: json.containsKey(r'userId') ? Optional.present(mapValueOfType(json, r'userId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/download_update.dart b/mobile/openapi/lib/model/download_update.dart index c5feb9df43..08369ef0fb 100644 --- a/mobile/openapi/lib/model/download_update.dart +++ b/mobile/openapi/lib/model/download_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class DownloadUpdate { /// Returns a new [DownloadUpdate] instance. DownloadUpdate({ - this.archiveSize, - this.includeEmbeddedVideos, + this.archiveSize = const Optional.absent(), + this.includeEmbeddedVideos = const Optional.absent(), }); /// Maximum archive size in bytes @@ -27,7 +27,7 @@ class DownloadUpdate { /// 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; + Optional archiveSize; /// Whether to include embedded videos in downloads /// @@ -36,7 +36,7 @@ class DownloadUpdate { /// 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; + Optional includeEmbeddedVideos; @override bool operator ==(Object other) => identical(this, other) || other is DownloadUpdate && @@ -54,15 +54,13 @@ class DownloadUpdate { Map toJson() { final json = {}; - if (this.archiveSize != null) { - json[r'archiveSize'] = this.archiveSize; - } else { - // json[r'archiveSize'] = null; + if (this.archiveSize.isPresent) { + final value = this.archiveSize.value; + json[r'archiveSize'] = value; } - if (this.includeEmbeddedVideos != null) { - json[r'includeEmbeddedVideos'] = this.includeEmbeddedVideos; - } else { - // json[r'includeEmbeddedVideos'] = null; + if (this.includeEmbeddedVideos.isPresent) { + final value = this.includeEmbeddedVideos.value; + json[r'includeEmbeddedVideos'] = value; } return json; } @@ -76,8 +74,8 @@ class DownloadUpdate { final json = value.cast(); return DownloadUpdate( - archiveSize: mapValueOfType(json, r'archiveSize'), - includeEmbeddedVideos: mapValueOfType(json, r'includeEmbeddedVideos'), + archiveSize: json.containsKey(r'archiveSize') ? Optional.present(json[r'archiveSize'] == null ? null : int.parse('${json[r'archiveSize']}')) : const Optional.absent(), + includeEmbeddedVideos: json.containsKey(r'includeEmbeddedVideos') ? Optional.present(mapValueOfType(json, r'includeEmbeddedVideos')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/duplicate_detection_config.dart b/mobile/openapi/lib/model/duplicate_detection_config.dart index 43233826ef..d0f016a4f3 100644 --- a/mobile/openapi/lib/model/duplicate_detection_config.dart +++ b/mobile/openapi/lib/model/duplicate_detection_config.dart @@ -57,7 +57,7 @@ class DuplicateDetectionConfig { return DuplicateDetectionConfig( enabled: mapValueOfType(json, r'enabled')!, - maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + maxDistance: mapValueOfType(json, r'maxDistance')!, ); } return null; diff --git a/mobile/openapi/lib/model/email_notifications_update.dart b/mobile/openapi/lib/model/email_notifications_update.dart index e158e45598..89724e0552 100644 --- a/mobile/openapi/lib/model/email_notifications_update.dart +++ b/mobile/openapi/lib/model/email_notifications_update.dart @@ -13,9 +13,9 @@ part of openapi.api; class EmailNotificationsUpdate { /// Returns a new [EmailNotificationsUpdate] instance. EmailNotificationsUpdate({ - this.albumInvite, - this.albumUpdate, - this.enabled, + this.albumInvite = const Optional.absent(), + this.albumUpdate = const Optional.absent(), + this.enabled = const Optional.absent(), }); /// Whether to receive email notifications for album invites @@ -25,7 +25,7 @@ class EmailNotificationsUpdate { /// 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; + Optional albumInvite; /// Whether to receive email notifications for album updates /// @@ -34,7 +34,7 @@ class EmailNotificationsUpdate { /// 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; + Optional albumUpdate; /// Whether email notifications are enabled /// @@ -43,7 +43,7 @@ class EmailNotificationsUpdate { /// 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; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is EmailNotificationsUpdate && @@ -63,20 +63,17 @@ class EmailNotificationsUpdate { Map toJson() { final json = {}; - if (this.albumInvite != null) { - json[r'albumInvite'] = this.albumInvite; - } else { - // json[r'albumInvite'] = null; + if (this.albumInvite.isPresent) { + final value = this.albumInvite.value; + json[r'albumInvite'] = value; } - if (this.albumUpdate != null) { - json[r'albumUpdate'] = this.albumUpdate; - } else { - // json[r'albumUpdate'] = null; + if (this.albumUpdate.isPresent) { + final value = this.albumUpdate.value; + json[r'albumUpdate'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -90,9 +87,9 @@ class EmailNotificationsUpdate { final json = value.cast(); return EmailNotificationsUpdate( - albumInvite: mapValueOfType(json, r'albumInvite'), - albumUpdate: mapValueOfType(json, r'albumUpdate'), - enabled: mapValueOfType(json, r'enabled'), + albumInvite: json.containsKey(r'albumInvite') ? Optional.present(mapValueOfType(json, r'albumInvite')) : const Optional.absent(), + albumUpdate: json.containsKey(r'albumUpdate') ? Optional.present(mapValueOfType(json, r'albumUpdate')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart index ed5ffd2958..2cec6f5161 100644 --- a/mobile/openapi/lib/model/exif_response_dto.dart +++ b/mobile/openapi/lib/model/exif_response_dto.dart @@ -13,110 +13,110 @@ 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, + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.dateTimeOriginal = const Optional.absent(), + this.description = const Optional.absent(), + this.exifImageHeight = const Optional.absent(), + this.exifImageWidth = const Optional.absent(), + this.exposureTime = const Optional.absent(), + this.fNumber = const Optional.absent(), + this.fileSizeInByte = const Optional.absent(), + this.focalLength = const Optional.absent(), + this.iso = const Optional.absent(), + this.latitude = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.longitude = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.modifyDate = const Optional.absent(), + this.orientation = const Optional.absent(), + this.projectionType = const Optional.absent(), + this.rating = const Optional.absent(), + this.state = const Optional.absent(), + this.timeZone = const Optional.absent(), }); /// City name - String? city; + Optional city; /// Country name - String? country; + Optional country; /// Original date/time - DateTime? dateTimeOriginal; + Optional dateTimeOriginal; /// Image description - String? description; + Optional description; /// Image height in pixels /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? exifImageHeight; + Optional exifImageHeight; /// Image width in pixels /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? exifImageWidth; + Optional exifImageWidth; /// Exposure time - String? exposureTime; + Optional exposureTime; /// F-number (aperture) - num? fNumber; + Optional fNumber; /// File size in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? fileSizeInByte; + Optional fileSizeInByte; /// Focal length in mm - num? focalLength; + Optional focalLength; /// ISO sensitivity /// /// Minimum value: -9007199254740991 /// Maximum value: 9007199254740991 - int? iso; + Optional iso; /// GPS latitude - num? latitude; + Optional latitude; /// Lens model - String? lensModel; + Optional lensModel; /// GPS longitude - num? longitude; + Optional longitude; /// Camera make - String? make; + Optional make; /// Camera model - String? model; + Optional model; /// Modification date/time - DateTime? modifyDate; + Optional modifyDate; /// Image orientation - String? orientation; + Optional orientation; /// Projection type - String? projectionType; + Optional projectionType; /// Rating /// - /// Minimum value: -9007199254740991 - /// Maximum value: 9007199254740991 - int? rating; + /// Minimum value: 1 + /// Maximum value: 5 + Optional rating; /// State/province name - String? state; + Optional state; /// Time zone - String? timeZone; + Optional timeZone; @override bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && @@ -174,115 +174,93 @@ class ExifResponseDto { Map toJson() { final json = {}; - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal!.toUtc().toIso8601String(); - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.exifImageHeight != null) { - json[r'exifImageHeight'] = this.exifImageHeight; - } else { - // json[r'exifImageHeight'] = null; + if (this.exifImageHeight.isPresent) { + final value = this.exifImageHeight.value; + json[r'exifImageHeight'] = value; } - if (this.exifImageWidth != null) { - json[r'exifImageWidth'] = this.exifImageWidth; - } else { - // json[r'exifImageWidth'] = null; + if (this.exifImageWidth.isPresent) { + final value = this.exifImageWidth.value; + json[r'exifImageWidth'] = value; } - if (this.exposureTime != null) { - json[r'exposureTime'] = this.exposureTime; - } else { - // json[r'exposureTime'] = null; + if (this.exposureTime.isPresent) { + final value = this.exposureTime.value; + json[r'exposureTime'] = value; } - if (this.fNumber != null) { - json[r'fNumber'] = this.fNumber; - } else { - // json[r'fNumber'] = null; + if (this.fNumber.isPresent) { + final value = this.fNumber.value; + json[r'fNumber'] = value; } - if (this.fileSizeInByte != null) { - json[r'fileSizeInByte'] = this.fileSizeInByte; - } else { - // json[r'fileSizeInByte'] = null; + if (this.fileSizeInByte.isPresent) { + final value = this.fileSizeInByte.value; + json[r'fileSizeInByte'] = value; } - if (this.focalLength != null) { - json[r'focalLength'] = this.focalLength; - } else { - // json[r'focalLength'] = null; + if (this.focalLength.isPresent) { + final value = this.focalLength.value; + json[r'focalLength'] = value; } - if (this.iso != null) { - json[r'iso'] = this.iso; - } else { - // json[r'iso'] = null; + if (this.iso.isPresent) { + final value = this.iso.value; + json[r'iso'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.modifyDate != null) { - json[r'modifyDate'] = this.modifyDate!.toUtc().toIso8601String(); - } else { - // json[r'modifyDate'] = null; + if (this.modifyDate.isPresent) { + final value = this.modifyDate.value; + json[r'modifyDate'] = value == null ? null : value.toUtc().toIso8601String(); } - if (this.orientation != null) { - json[r'orientation'] = this.orientation; - } else { - // json[r'orientation'] = null; + if (this.orientation.isPresent) { + final value = this.orientation.value; + json[r'orientation'] = value; } - if (this.projectionType != null) { - json[r'projectionType'] = this.projectionType; - } else { - // json[r'projectionType'] = null; + if (this.projectionType.isPresent) { + final value = this.projectionType.value; + json[r'projectionType'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.timeZone != null) { - json[r'timeZone'] = this.timeZone; - } else { - // json[r'timeZone'] = null; + if (this.timeZone.isPresent) { + final value = this.timeZone.value; + json[r'timeZone'] = value; } return json; } @@ -296,36 +274,28 @@ class ExifResponseDto { 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: mapValueOfType(json, r'exifImageHeight'), - exifImageWidth: mapValueOfType(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: mapValueOfType(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: mapValueOfType(json, r'rating'), - state: mapValueOfType(json, r'state'), - timeZone: mapValueOfType(json, r'timeZone'), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapDateTime(json, r'dateTimeOriginal', r'')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + exifImageHeight: json.containsKey(r'exifImageHeight') ? Optional.present(json[r'exifImageHeight'] == null ? null : int.parse('${json[r'exifImageHeight']}')) : const Optional.absent(), + exifImageWidth: json.containsKey(r'exifImageWidth') ? Optional.present(json[r'exifImageWidth'] == null ? null : int.parse('${json[r'exifImageWidth']}')) : const Optional.absent(), + exposureTime: json.containsKey(r'exposureTime') ? Optional.present(mapValueOfType(json, r'exposureTime')) : const Optional.absent(), + fNumber: json.containsKey(r'fNumber') ? Optional.present(json[r'fNumber'] == null ? null : num.parse('${json[r'fNumber']}')) : const Optional.absent(), + fileSizeInByte: json.containsKey(r'fileSizeInByte') ? Optional.present(json[r'fileSizeInByte'] == null ? null : int.parse('${json[r'fileSizeInByte']}')) : const Optional.absent(), + focalLength: json.containsKey(r'focalLength') ? Optional.present(json[r'focalLength'] == null ? null : num.parse('${json[r'focalLength']}')) : const Optional.absent(), + iso: json.containsKey(r'iso') ? Optional.present(json[r'iso'] == null ? null : int.parse('${json[r'iso']}')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + modifyDate: json.containsKey(r'modifyDate') ? Optional.present(mapDateTime(json, r'modifyDate', r'')) : const Optional.absent(), + orientation: json.containsKey(r'orientation') ? Optional.present(mapValueOfType(json, r'orientation')) : const Optional.absent(), + projectionType: json.containsKey(r'projectionType') ? Optional.present(mapValueOfType(json, r'projectionType')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + timeZone: json.containsKey(r'timeZone') ? Optional.present(mapValueOfType(json, r'timeZone')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/facial_recognition_config.dart b/mobile/openapi/lib/model/facial_recognition_config.dart index 66cb542ccf..c5f477e1d5 100644 --- a/mobile/openapi/lib/model/facial_recognition_config.dart +++ b/mobile/openapi/lib/model/facial_recognition_config.dart @@ -84,9 +84,9 @@ class FacialRecognitionConfig { return FacialRecognitionConfig( enabled: mapValueOfType(json, r'enabled')!, - maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + maxDistance: mapValueOfType(json, r'maxDistance')!, minFaces: mapValueOfType(json, r'minFaces')!, - minScore: (mapValueOfType(json, r'minScore')!).toDouble(), + minScore: mapValueOfType(json, r'minScore')!, modelName: mapValueOfType(json, r'modelName')!, ); } diff --git a/mobile/openapi/lib/model/folders_update.dart b/mobile/openapi/lib/model/folders_update.dart index edd58014d4..2ce0cde807 100644 --- a/mobile/openapi/lib/model/folders_update.dart +++ b/mobile/openapi/lib/model/folders_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class FoldersUpdate { /// Returns a new [FoldersUpdate] instance. FoldersUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether folders are enabled @@ -24,7 +24,7 @@ class FoldersUpdate { /// 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; + Optional enabled; /// Whether folders appear in web sidebar /// @@ -33,7 +33,7 @@ class FoldersUpdate { /// 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; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is FoldersUpdate && @@ -51,15 +51,13 @@ class FoldersUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class FoldersUpdate { final json = value.cast(); return FoldersUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/library_response_dto.dart b/mobile/openapi/lib/model/library_response_dto.dart index 88ebceae24..e52a752656 100644 --- a/mobile/openapi/lib/model/library_response_dto.dart +++ b/mobile/openapi/lib/model/library_response_dto.dart @@ -98,7 +98,7 @@ class LibraryResponseDto { ? this.refreshedAt!.millisecondsSinceEpoch : this.refreshedAt!.toUtc().toIso8601String(); } else { - // json[r'refreshedAt'] = null; + json[r'refreshedAt'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/maintenance_login_dto.dart b/mobile/openapi/lib/model/maintenance_login_dto.dart index 64cf6b234b..eaa91ae738 100644 --- a/mobile/openapi/lib/model/maintenance_login_dto.dart +++ b/mobile/openapi/lib/model/maintenance_login_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class MaintenanceLoginDto { /// Returns a new [MaintenanceLoginDto] instance. MaintenanceLoginDto({ - this.token, + this.token = const Optional.absent(), }); /// Maintenance token @@ -23,7 +23,7 @@ class MaintenanceLoginDto { /// 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; + Optional token; @override bool operator ==(Object other) => identical(this, other) || other is MaintenanceLoginDto && @@ -39,10 +39,9 @@ class MaintenanceLoginDto { Map toJson() { final json = {}; - if (this.token != null) { - json[r'token'] = this.token; - } else { - // json[r'token'] = null; + if (this.token.isPresent) { + final value = this.token.value; + json[r'token'] = value; } return json; } @@ -56,7 +55,7 @@ class MaintenanceLoginDto { final json = value.cast(); return MaintenanceLoginDto( - token: mapValueOfType(json, r'token'), + token: json.containsKey(r'token') ? Optional.present(mapValueOfType(json, r'token')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/maintenance_status_response_dto.dart b/mobile/openapi/lib/model/maintenance_status_response_dto.dart index c1c94acd91..82ad12d340 100644 --- a/mobile/openapi/lib/model/maintenance_status_response_dto.dart +++ b/mobile/openapi/lib/model/maintenance_status_response_dto.dart @@ -15,9 +15,9 @@ class MaintenanceStatusResponseDto { MaintenanceStatusResponseDto({ required this.action, required this.active, - this.error, - this.progress, - this.task, + this.error = const Optional.absent(), + this.progress = const Optional.absent(), + this.task = const Optional.absent(), }); MaintenanceAction action; @@ -30,7 +30,7 @@ class MaintenanceStatusResponseDto { /// 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; + Optional error; /// Minimum value: -9007199254740991 /// Maximum value: 9007199254740991 @@ -40,7 +40,7 @@ class MaintenanceStatusResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? progress; + Optional progress; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -48,7 +48,7 @@ class MaintenanceStatusResponseDto { /// 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; + Optional task; @override bool operator ==(Object other) => identical(this, other) || other is MaintenanceStatusResponseDto && @@ -74,20 +74,17 @@ class MaintenanceStatusResponseDto { 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.error.isPresent) { + final value = this.error.value; + json[r'error'] = value; } - if (this.progress != null) { - json[r'progress'] = this.progress; - } else { - // json[r'progress'] = null; + if (this.progress.isPresent) { + final value = this.progress.value; + json[r'progress'] = value; } - if (this.task != null) { - json[r'task'] = this.task; - } else { - // json[r'task'] = null; + if (this.task.isPresent) { + final value = this.task.value; + json[r'task'] = value; } return json; } @@ -103,9 +100,9 @@ class MaintenanceStatusResponseDto { return MaintenanceStatusResponseDto( action: MaintenanceAction.fromJson(json[r'action'])!, active: mapValueOfType(json, r'active')!, - error: mapValueOfType(json, r'error'), - progress: mapValueOfType(json, r'progress'), - task: mapValueOfType(json, r'task'), + error: json.containsKey(r'error') ? Optional.present(mapValueOfType(json, r'error')) : const Optional.absent(), + progress: json.containsKey(r'progress') ? Optional.present(json[r'progress'] == null ? null : int.parse('${json[r'progress']}')) : const Optional.absent(), + task: json.containsKey(r'task') ? Optional.present(mapValueOfType(json, r'task')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/map_marker_response_dto.dart b/mobile/openapi/lib/model/map_marker_response_dto.dart index c0a47a5458..3f19c21b6b 100644 --- a/mobile/openapi/lib/model/map_marker_response_dto.dart +++ b/mobile/openapi/lib/model/map_marker_response_dto.dart @@ -66,12 +66,12 @@ class MapMarkerResponseDto { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } json[r'id'] = this.id; json[r'lat'] = this.lat; @@ -79,7 +79,7 @@ class MapMarkerResponseDto { if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } return json; } @@ -96,8 +96,8 @@ class 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(), + lat: mapValueOfType(json, r'lat')!, + lon: mapValueOfType(json, r'lon')!, state: mapValueOfType(json, r'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 index 85435485e6..0fc30f2b88 100644 --- a/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart +++ b/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart @@ -48,17 +48,17 @@ class MapReverseGeocodeResponseDto { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } return json; } diff --git a/mobile/openapi/lib/model/memories_update.dart b/mobile/openapi/lib/model/memories_update.dart index ede9910d74..350cf19182 100644 --- a/mobile/openapi/lib/model/memories_update.dart +++ b/mobile/openapi/lib/model/memories_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class MemoriesUpdate { /// Returns a new [MemoriesUpdate] instance. MemoriesUpdate({ - this.duration, - this.enabled, + this.duration = const Optional.absent(), + this.enabled = const Optional.absent(), }); /// Memory duration in seconds @@ -27,7 +27,7 @@ class MemoriesUpdate { /// 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; + Optional duration; /// Whether memories are enabled /// @@ -36,7 +36,7 @@ class MemoriesUpdate { /// 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; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate && @@ -54,15 +54,13 @@ class MemoriesUpdate { Map toJson() { final json = {}; - if (this.duration != null) { - json[r'duration'] = this.duration; - } else { - // json[r'duration'] = null; + if (this.duration.isPresent) { + final value = this.duration.value; + json[r'duration'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -76,8 +74,8 @@ class MemoriesUpdate { final json = value.cast(); return MemoriesUpdate( - duration: mapValueOfType(json, r'duration'), - enabled: mapValueOfType(json, r'enabled'), + duration: json.containsKey(r'duration') ? Optional.present(json[r'duration'] == null ? null : int.parse('${json[r'duration']}')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart index b906f6dd1d..a7be9bf70d 100644 --- a/mobile/openapi/lib/model/memory_create_dto.dart +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class MemoryCreateDto { /// Returns a new [MemoryCreateDto] instance. MemoryCreateDto({ - this.assetIds = const [], + this.assetIds = const Optional.present(const []), required this.data, - this.hideAt, - this.isSaved, + this.hideAt = const Optional.absent(), + this.isSaved = const Optional.absent(), required this.memoryAt, - this.seenAt, - this.showAt, + this.seenAt = const Optional.absent(), + this.showAt = const Optional.absent(), required this.type, }); /// Asset IDs to associate with memory - List assetIds; + Optional?> assetIds; OnThisDayDto data; @@ -35,7 +35,7 @@ class MemoryCreateDto { /// 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; + Optional hideAt; /// Is memory saved /// @@ -44,7 +44,7 @@ class MemoryCreateDto { /// 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; + Optional isSaved; /// Memory date DateTime memoryAt; @@ -56,7 +56,7 @@ class MemoryCreateDto { /// 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; + Optional seenAt; /// Date when memory should be shown /// @@ -65,7 +65,7 @@ class MemoryCreateDto { /// 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; + Optional showAt; MemoryType type; @@ -97,36 +97,35 @@ class MemoryCreateDto { Map toJson() { final json = {}; - json[r'assetIds'] = this.assetIds; - json[r'data'] = this.data; - if (this.hideAt != null) { - json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.hideAt!.millisecondsSinceEpoch - : this.hideAt!.toUtc().toIso8601String(); - } else { - // json[r'hideAt'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; } - if (this.isSaved != null) { - json[r'isSaved'] = this.isSaved; - } else { - // json[r'isSaved'] = null; + json[r'data'] = this.data; + if (this.hideAt.isPresent) { + final value = this.hideAt.value; + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.isSaved.isPresent) { + final value = this.isSaved.value; + json[r'isSaved'] = value; } json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.showAt != null) { - json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.showAt!.millisecondsSinceEpoch - : this.showAt!.toUtc().toIso8601String(); - } else { - // json[r'showAt'] = null; + if (this.showAt.isPresent) { + final value = this.showAt.value; + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'type'] = this.type; return json; @@ -141,15 +140,15 @@ class MemoryCreateDto { final json = value.cast(); return MemoryCreateDto( - assetIds: json[r'assetIds'] is Iterable + assetIds: json.containsKey(r'assetIds') ? Optional.present(json[r'assetIds'] is Iterable ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), data: OnThisDayDto.fromJson(json[r'data'])!, - hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - isSaved: mapValueOfType(json, r'isSaved'), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart index e736667d57..799c692955 100644 --- a/mobile/openapi/lib/model/memory_response_dto.dart +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -16,14 +16,14 @@ class MemoryResponseDto { this.assets = const [], required this.createdAt, required this.data, - this.deletedAt, - this.hideAt, + this.deletedAt = const Optional.absent(), + this.hideAt = const Optional.absent(), required this.id, required this.isSaved, required this.memoryAt, required this.ownerId, - this.seenAt, - this.showAt, + this.seenAt = const Optional.absent(), + this.showAt = const Optional.absent(), required this.type, required this.updatedAt, }); @@ -42,7 +42,7 @@ class MemoryResponseDto { /// 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; + Optional deletedAt; /// Date when memory should be hidden /// @@ -51,7 +51,7 @@ class MemoryResponseDto { /// 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; + Optional hideAt; /// Memory ID String id; @@ -72,7 +72,7 @@ class MemoryResponseDto { /// 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; + Optional seenAt; /// Date when memory should be shown /// @@ -81,7 +81,7 @@ class MemoryResponseDto { /// 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; + Optional showAt; MemoryType type; @@ -131,19 +131,17 @@ class MemoryResponseDto { ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'data'] = this.data; - if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.deletedAt!.millisecondsSinceEpoch - : this.deletedAt!.toUtc().toIso8601String(); - } else { - // json[r'deletedAt'] = null; + if (this.deletedAt.isPresent) { + final value = this.deletedAt.value; + json[r'deletedAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.hideAt != null) { - json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.hideAt!.millisecondsSinceEpoch - : this.hideAt!.toUtc().toIso8601String(); - } else { - // json[r'hideAt'] = null; + if (this.hideAt.isPresent) { + final value = this.hideAt.value; + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; @@ -151,19 +149,17 @@ class MemoryResponseDto { ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); json[r'ownerId'] = this.ownerId; - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.showAt != null) { - json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.showAt!.millisecondsSinceEpoch - : this.showAt!.toUtc().toIso8601String(); - } else { - // json[r'showAt'] = null; + if (this.showAt.isPresent) { + final value = this.showAt.value; + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'type'] = this.type; json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') @@ -184,14 +180,14 @@ class MemoryResponseDto { assets: AssetResponseDto.listFromJson(json[r'assets']), createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, data: OnThisDayDto.fromJson(json[r'data'])!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: json.containsKey(r'deletedAt') ? Optional.present(mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isSaved: mapValueOfType(json, r'isSaved')!, memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, ownerId: mapValueOfType(json, r'ownerId')!, - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, ); diff --git a/mobile/openapi/lib/model/memory_update_dto.dart b/mobile/openapi/lib/model/memory_update_dto.dart index d8d7e9643b..e39506b78e 100644 --- a/mobile/openapi/lib/model/memory_update_dto.dart +++ b/mobile/openapi/lib/model/memory_update_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class MemoryUpdateDto { /// Returns a new [MemoryUpdateDto] instance. MemoryUpdateDto({ - this.isSaved, - this.memoryAt, - this.seenAt, + this.isSaved = const Optional.absent(), + this.memoryAt = const Optional.absent(), + this.seenAt = const Optional.absent(), }); /// Is memory saved @@ -25,7 +25,7 @@ class MemoryUpdateDto { /// 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; + Optional isSaved; /// Memory date /// @@ -34,7 +34,7 @@ class MemoryUpdateDto { /// 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; + Optional memoryAt; /// Date when memory was seen /// @@ -43,7 +43,7 @@ class MemoryUpdateDto { /// 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; + Optional seenAt; @override bool operator ==(Object other) => identical(this, other) || other is MemoryUpdateDto && @@ -63,24 +63,21 @@ class MemoryUpdateDto { Map toJson() { final json = {}; - if (this.isSaved != null) { - json[r'isSaved'] = this.isSaved; - } else { - // json[r'isSaved'] = null; + if (this.isSaved.isPresent) { + final value = this.isSaved.value; + json[r'isSaved'] = value; } - if (this.memoryAt != null) { - json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.memoryAt!.millisecondsSinceEpoch - : this.memoryAt!.toUtc().toIso8601String(); - } else { - // json[r'memoryAt'] = null; + if (this.memoryAt.isPresent) { + final value = this.memoryAt.value; + json[r'memoryAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.seenAt!.millisecondsSinceEpoch - : this.seenAt!.toUtc().toIso8601String(); - } else { - // json[r'seenAt'] = null; + if (this.seenAt.isPresent) { + final value = this.seenAt.value; + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -94,9 +91,9 @@ class MemoryUpdateDto { final json = value.cast(); return MemoryUpdateDto( - isSaved: mapValueOfType(json, r'isSaved'), - memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), + memoryAt: json.containsKey(r'memoryAt') ? Optional.present(mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 29b1d5b68d..2fd717b4f0 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -13,52 +13,52 @@ 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.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, - 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, + this.albumIds = const Optional.present(const []), + this.checksum = const Optional.absent(), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.description = const Optional.absent(), + this.encodedVideoPath = const Optional.absent(), + this.id = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.order = const Optional.absent(), + this.originalFileName = const Optional.absent(), + this.originalPath = const Optional.absent(), + this.page = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.previewPath = const Optional.absent(), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.thumbnailPath = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), + this.withPeople = const Optional.absent(), + this.withStacked = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by file checksum /// @@ -67,13 +67,13 @@ class MetadataSearchDto { /// 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; + Optional checksum; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -82,7 +82,7 @@ class MetadataSearchDto { /// 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; + Optional createdAfter; /// Filter by creation date (before) /// @@ -91,7 +91,7 @@ class MetadataSearchDto { /// 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; + Optional createdBefore; /// Filter by description text /// @@ -100,7 +100,7 @@ class MetadataSearchDto { /// 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; + Optional description; /// Filter by encoded video file path /// @@ -109,7 +109,7 @@ class MetadataSearchDto { /// 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; + Optional encodedVideoPath; /// Filter by asset ID /// @@ -118,7 +118,7 @@ class MetadataSearchDto { /// 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; + Optional id; /// Filter by encoded status /// @@ -127,7 +127,7 @@ class MetadataSearchDto { /// 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; + Optional isEncoded; /// Filter by favorite status /// @@ -136,7 +136,7 @@ class MetadataSearchDto { /// 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; + Optional isFavorite; /// Filter by motion photo status /// @@ -145,7 +145,7 @@ class MetadataSearchDto { /// 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; + Optional isMotion; /// Filter assets not in any album /// @@ -154,7 +154,7 @@ class MetadataSearchDto { /// 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; + Optional isNotInAlbum; /// Filter by offline status /// @@ -163,19 +163,19 @@ class MetadataSearchDto { /// 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; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -184,7 +184,7 @@ class MetadataSearchDto { /// 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; + Optional ocr; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -192,7 +192,7 @@ class MetadataSearchDto { /// 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; + Optional order; /// Filter by original file name /// @@ -201,7 +201,7 @@ class MetadataSearchDto { /// 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; + Optional originalFileName; /// Filter by original file path /// @@ -210,7 +210,7 @@ class MetadataSearchDto { /// 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; + Optional originalPath; /// Page number /// @@ -222,10 +222,10 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? page; + Optional page; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by preview file path /// @@ -234,13 +234,13 @@ class MetadataSearchDto { /// 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; + Optional previewPath; /// Filter by rating [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -252,13 +252,13 @@ class MetadataSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -267,7 +267,7 @@ class MetadataSearchDto { /// 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; + Optional takenAfter; /// Filter by taken date (before) /// @@ -276,7 +276,7 @@ class MetadataSearchDto { /// 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; + Optional takenBefore; /// Filter by thumbnail file path /// @@ -285,7 +285,7 @@ class MetadataSearchDto { /// 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; + Optional thumbnailPath; /// Filter by trash date (after) /// @@ -294,7 +294,7 @@ class MetadataSearchDto { /// 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; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -303,7 +303,7 @@ class MetadataSearchDto { /// 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; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -311,7 +311,7 @@ class MetadataSearchDto { /// 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; + Optional type; /// Filter by update date (after) /// @@ -320,7 +320,7 @@ class MetadataSearchDto { /// 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; + Optional updatedAfter; /// Filter by update date (before) /// @@ -329,7 +329,7 @@ class MetadataSearchDto { /// 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; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -337,7 +337,7 @@ class MetadataSearchDto { /// 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; + Optional visibility; /// Include deleted assets /// @@ -346,7 +346,7 @@ class MetadataSearchDto { /// 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; + Optional withDeleted; /// Include EXIF data in response /// @@ -355,7 +355,7 @@ class MetadataSearchDto { /// 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; + Optional withExif; /// Include people data in response /// @@ -364,7 +364,7 @@ class MetadataSearchDto { /// 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; + Optional withPeople; /// Include stacked assets /// @@ -373,7 +373,7 @@ class MetadataSearchDto { /// 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; + Optional withStacked; @override bool operator ==(Object other) => identical(this, other) || other is MetadataSearchDto && @@ -471,223 +471,189 @@ class MetadataSearchDto { 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.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.city != null) { - json[r'city'] = this.city; - } else { - // json[r'city'] = null; + if (this.checksum.isPresent) { + final value = this.checksum.value; + json[r'checksum'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.encodedVideoPath != null) { - json[r'encodedVideoPath'] = this.encodedVideoPath; - } else { - // json[r'encodedVideoPath'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.id != null) { - json[r'id'] = this.id; - } else { - // json[r'id'] = null; + if (this.encodedVideoPath.isPresent) { + final value = this.encodedVideoPath.value; + json[r'encodedVideoPath'] = value; } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.id.isPresent) { + final value = this.id.value; + json[r'id'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.originalFileName != null) { - json[r'originalFileName'] = this.originalFileName; - } else { - // json[r'originalFileName'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } - if (this.originalPath != null) { - json[r'originalPath'] = this.originalPath; - } else { - // json[r'originalPath'] = null; + if (this.originalFileName.isPresent) { + final value = this.originalFileName.value; + json[r'originalFileName'] = value; } - if (this.page != null) { - json[r'page'] = this.page; - } else { - // json[r'page'] = null; + if (this.originalPath.isPresent) { + final value = this.originalPath.value; + json[r'originalPath'] = value; } - json[r'personIds'] = this.personIds; - if (this.previewPath != null) { - json[r'previewPath'] = this.previewPath; - } else { - // json[r'previewPath'] = null; + if (this.page.isPresent) { + final value = this.page.value; + json[r'page'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.previewPath.isPresent) { + final value = this.previewPath.value; + json[r'previewPath'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.thumbnailPath != null) { - json[r'thumbnailPath'] = this.thumbnailPath; - } else { - // json[r'thumbnailPath'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.thumbnailPath.isPresent) { + final value = this.thumbnailPath.value; + json[r'thumbnailPath'] = value; } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } - if (this.withPeople != null) { - json[r'withPeople'] = this.withPeople; - } else { - // json[r'withPeople'] = null; + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; } - if (this.withStacked != null) { - json[r'withStacked'] = this.withStacked; - } else { - // json[r'withStacked'] = null; + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; + } + if (this.withPeople.isPresent) { + final value = this.withPeople.value; + json[r'withPeople'] = value; + } + if (this.withStacked.isPresent) { + final value = this.withStacked.value; + json[r'withStacked'] = value; } return json; } @@ -701,54 +667,54 @@ class MetadataSearchDto { final json = value.cast(); return MetadataSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - description: mapValueOfType(json, r'description'), - 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']), - originalFileName: mapValueOfType(json, r'originalFileName'), - originalPath: mapValueOfType(json, r'originalPath'), - page: mapValueOfType(json, r'page'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + checksum: json.containsKey(r'checksum') ? Optional.present(mapValueOfType(json, r'checksum')) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(mapValueOfType(json, r'encodedVideoPath')) : const Optional.absent(), + id: json.containsKey(r'id') ? Optional.present(mapValueOfType(json, r'id')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), + originalFileName: json.containsKey(r'originalFileName') ? Optional.present(mapValueOfType(json, r'originalFileName')) : const Optional.absent(), + originalPath: json.containsKey(r'originalPath') ? Optional.present(mapValueOfType(json, r'originalPath')) : const Optional.absent(), + page: json.containsKey(r'page') ? Optional.present(json[r'page'] == null ? null : int.parse('${json[r'page']}')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - previewPath: mapValueOfType(json, r'previewPath'), - rating: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + previewPath: json.containsKey(r'previewPath') ? Optional.present(mapValueOfType(json, r'previewPath')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - thumbnailPath: mapValueOfType(json, r'thumbnailPath'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - 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'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + thumbnailPath: json.containsKey(r'thumbnailPath') ? Optional.present(mapValueOfType(json, r'thumbnailPath')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), + withPeople: json.containsKey(r'withPeople') ? Optional.present(mapValueOfType(json, r'withPeople')) : const Optional.absent(), + withStacked: json.containsKey(r'withStacked') ? Optional.present(mapValueOfType(json, r'withStacked')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/notification_create_dto.dart b/mobile/openapi/lib/model/notification_create_dto.dart index f9771246f9..c6e7c02231 100644 --- a/mobile/openapi/lib/model/notification_create_dto.dart +++ b/mobile/openapi/lib/model/notification_create_dto.dart @@ -13,20 +13,20 @@ part of openapi.api; class NotificationCreateDto { /// Returns a new [NotificationCreateDto] instance. NotificationCreateDto({ - this.data = const {}, - this.description, - this.level, - this.readAt, + this.data = const Optional.present(const {}), + this.description = const Optional.absent(), + this.level = const Optional.absent(), + this.readAt = const Optional.absent(), required this.title, - this.type, + this.type = const Optional.absent(), required this.userId, }); /// Additional notification data - Map data; + Optional?> data; /// Notification description - String? description; + Optional description; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -34,10 +34,10 @@ class NotificationCreateDto { /// 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; + Optional level; /// Date when notification was read - DateTime? readAt; + Optional readAt; /// Notification title String title; @@ -48,7 +48,7 @@ class NotificationCreateDto { /// 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; + Optional type; /// User ID to send notification to String userId; @@ -79,29 +79,28 @@ class NotificationCreateDto { Map toJson() { final json = {}; - json[r'data'] = this.data; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.data.isPresent) { + final value = this.data.value; + json[r'data'] = value; } - if (this.level != null) { - json[r'level'] = this.level; - } else { - // json[r'level'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.level.isPresent) { + final value = this.level.value; + json[r'level'] = value; + } + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'title'] = this.title; - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } json[r'userId'] = this.userId; return json; @@ -116,12 +115,12 @@ class NotificationCreateDto { final json = value.cast(); return NotificationCreateDto( - data: mapCastOfType(json, r'data') ?? const {}, - description: mapValueOfType(json, r'description'), - level: NotificationLevel.fromJson(json[r'level']), - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + level: json.containsKey(r'level') ? Optional.present(NotificationLevel.fromJson(json[r'level'])) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, - type: NotificationType.fromJson(json[r'type']), + type: json.containsKey(r'type') ? Optional.present(NotificationType.fromJson(json[r'type'])) : const Optional.absent(), userId: mapValueOfType(json, r'userId')!, ); } diff --git a/mobile/openapi/lib/model/notification_dto.dart b/mobile/openapi/lib/model/notification_dto.dart index ad0e79cb27..4e4fab8ee6 100644 --- a/mobile/openapi/lib/model/notification_dto.dart +++ b/mobile/openapi/lib/model/notification_dto.dart @@ -14,11 +14,11 @@ class NotificationDto { /// Returns a new [NotificationDto] instance. NotificationDto({ required this.createdAt, - this.data = const {}, - this.description, + this.data = const Optional.present(const {}), + this.description = const Optional.absent(), required this.id, required this.level, - this.readAt, + this.readAt = const Optional.absent(), required this.title, required this.type, }); @@ -27,7 +27,7 @@ class NotificationDto { DateTime createdAt; /// Additional notification data - Map data; + Optional?> data; /// Notification description /// @@ -36,7 +36,7 @@ class NotificationDto { /// 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; + Optional description; /// Notification ID String id; @@ -50,7 +50,7 @@ class NotificationDto { /// 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; + Optional readAt; /// Notification title String title; @@ -88,20 +88,21 @@ class NotificationDto { json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); - json[r'data'] = this.data; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.data.isPresent) { + final value = this.data.value; + json[r'data'] = value; + } + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } json[r'id'] = this.id; json[r'level'] = this.level; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } json[r'title'] = this.title; json[r'type'] = this.type; @@ -118,11 +119,11 @@ class NotificationDto { return NotificationDto( createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - data: mapCastOfType(json, r'data') ?? const {}, - description: mapValueOfType(json, r'description'), + data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, level: NotificationLevel.fromJson(json[r'level'])!, - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, type: NotificationType.fromJson(json[r'type'])!, ); diff --git a/mobile/openapi/lib/model/notification_update_all_dto.dart b/mobile/openapi/lib/model/notification_update_all_dto.dart index 5ac61ededc..1a95e2647a 100644 --- a/mobile/openapi/lib/model/notification_update_all_dto.dart +++ b/mobile/openapi/lib/model/notification_update_all_dto.dart @@ -14,14 +14,14 @@ class NotificationUpdateAllDto { /// Returns a new [NotificationUpdateAllDto] instance. NotificationUpdateAllDto({ this.ids = const [], - this.readAt, + this.readAt = const Optional.absent(), }); /// Notification IDs to update List ids; /// Date when notifications were read - DateTime? readAt; + Optional readAt; @override bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateAllDto && @@ -40,12 +40,11 @@ class NotificationUpdateAllDto { Map toJson() { final json = {}; json[r'ids'] = this.ids; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -62,7 +61,7 @@ class NotificationUpdateAllDto { ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/notification_update_dto.dart b/mobile/openapi/lib/model/notification_update_dto.dart index c5d949d7b2..4fa0cd5e97 100644 --- a/mobile/openapi/lib/model/notification_update_dto.dart +++ b/mobile/openapi/lib/model/notification_update_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class NotificationUpdateDto { /// Returns a new [NotificationUpdateDto] instance. NotificationUpdateDto({ - this.readAt, + this.readAt = const Optional.absent(), }); /// Date when notification was read - DateTime? readAt; + Optional readAt; @override bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateDto && @@ -33,12 +33,11 @@ class NotificationUpdateDto { Map toJson() { final json = {}; - if (this.readAt != null) { - json[r'readAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.readAt!.millisecondsSinceEpoch - : this.readAt!.toUtc().toIso8601String(); - } else { - // json[r'readAt'] = null; + if (this.readAt.isPresent) { + final value = this.readAt.value; + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } return json; } @@ -52,7 +51,7 @@ class NotificationUpdateDto { final json = value.cast(); return NotificationUpdateDto( - readAt: mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/o_auth_callback_dto.dart b/mobile/openapi/lib/model/o_auth_callback_dto.dart index d94374935a..61de33e1a6 100644 --- a/mobile/openapi/lib/model/o_auth_callback_dto.dart +++ b/mobile/openapi/lib/model/o_auth_callback_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class OAuthCallbackDto { /// Returns a new [OAuthCallbackDto] instance. OAuthCallbackDto({ - this.codeVerifier, - this.state, + this.codeVerifier = const Optional.absent(), + this.state = const Optional.absent(), required this.url, }); @@ -25,7 +25,7 @@ class OAuthCallbackDto { /// 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; + Optional codeVerifier; /// OAuth state parameter /// @@ -34,7 +34,7 @@ class OAuthCallbackDto { /// 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; + Optional state; /// OAuth callback URL String url; @@ -57,15 +57,13 @@ class OAuthCallbackDto { Map toJson() { final json = {}; - if (this.codeVerifier != null) { - json[r'codeVerifier'] = this.codeVerifier; - } else { - // json[r'codeVerifier'] = null; + if (this.codeVerifier.isPresent) { + final value = this.codeVerifier.value; + json[r'codeVerifier'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } json[r'url'] = this.url; return json; @@ -80,8 +78,8 @@ class OAuthCallbackDto { final json = value.cast(); return OAuthCallbackDto( - codeVerifier: mapValueOfType(json, r'codeVerifier'), - state: mapValueOfType(json, r'state'), + codeVerifier: json.containsKey(r'codeVerifier') ? Optional.present(mapValueOfType(json, r'codeVerifier')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), url: mapValueOfType(json, r'url')!, ); } diff --git a/mobile/openapi/lib/model/o_auth_config_dto.dart b/mobile/openapi/lib/model/o_auth_config_dto.dart index 1c9ce8d5b8..fb9f95dd92 100644 --- a/mobile/openapi/lib/model/o_auth_config_dto.dart +++ b/mobile/openapi/lib/model/o_auth_config_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class OAuthConfigDto { /// Returns a new [OAuthConfigDto] instance. OAuthConfigDto({ - this.codeChallenge, + this.codeChallenge = const Optional.absent(), required this.redirectUri, - this.state, + this.state = const Optional.absent(), }); /// OAuth code challenge (PKCE) @@ -25,7 +25,7 @@ class OAuthConfigDto { /// 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; + Optional codeChallenge; /// OAuth redirect URI String redirectUri; @@ -37,7 +37,7 @@ class OAuthConfigDto { /// 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; + Optional state; @override bool operator ==(Object other) => identical(this, other) || other is OAuthConfigDto && @@ -57,16 +57,14 @@ class OAuthConfigDto { Map toJson() { final json = {}; - if (this.codeChallenge != null) { - json[r'codeChallenge'] = this.codeChallenge; - } else { - // json[r'codeChallenge'] = null; + if (this.codeChallenge.isPresent) { + final value = this.codeChallenge.value; + json[r'codeChallenge'] = value; } json[r'redirectUri'] = this.redirectUri; - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } return json; } @@ -80,9 +78,9 @@ class OAuthConfigDto { final json = value.cast(); return OAuthConfigDto( - codeChallenge: mapValueOfType(json, r'codeChallenge'), + codeChallenge: json.containsKey(r'codeChallenge') ? Optional.present(mapValueOfType(json, r'codeChallenge')) : const Optional.absent(), redirectUri: mapValueOfType(json, r'redirectUri')!, - state: mapValueOfType(json, r'state'), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), ); } return null; 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 index b63f027af7..414108068a 100644 --- a/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart +++ b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart @@ -23,13 +23,13 @@ class OAuthTokenEndpointAuthMethod { String toJson() => value; - static const post = OAuthTokenEndpointAuthMethod._(r'client_secret_post'); - static const basic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic'); + static const clientSecretPost = OAuthTokenEndpointAuthMethod._(r'client_secret_post'); + static const clientSecretBasic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic'); /// List of all possible values in this [enum][OAuthTokenEndpointAuthMethod]. static const values = [ - post, - basic, + clientSecretPost, + clientSecretBasic, ]; static OAuthTokenEndpointAuthMethod? fromJson(dynamic value) => OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); @@ -68,8 +68,8 @@ class OAuthTokenEndpointAuthMethodTypeTransformer { 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; + case r'client_secret_post': return OAuthTokenEndpointAuthMethod.clientSecretPost; + case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.clientSecretBasic; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/ocr_config.dart b/mobile/openapi/lib/model/ocr_config.dart index 2ce5646731..d58c8af3ee 100644 --- a/mobile/openapi/lib/model/ocr_config.dart +++ b/mobile/openapi/lib/model/ocr_config.dart @@ -85,8 +85,8 @@ class OcrConfig { return OcrConfig( enabled: mapValueOfType(json, r'enabled')!, maxResolution: mapValueOfType(json, r'maxResolution')!, - minDetectionScore: (mapValueOfType(json, r'minDetectionScore')!).toDouble(), - minRecognitionScore: (mapValueOfType(json, r'minRecognitionScore')!).toDouble(), + minDetectionScore: mapValueOfType(json, r'minDetectionScore')!, + minRecognitionScore: mapValueOfType(json, r'minRecognitionScore')!, modelName: mapValueOfType(json, r'modelName')!, ); } diff --git a/mobile/openapi/lib/model/partner_direction.dart b/mobile/openapi/lib/model/partner_direction.dart index c5e3b308ac..c1e38b8dfe 100644 --- a/mobile/openapi/lib/model/partner_direction.dart +++ b/mobile/openapi/lib/model/partner_direction.dart @@ -23,13 +23,13 @@ class PartnerDirection { String toJson() => value; - static const by = PartnerDirection._(r'shared-by'); - static const with_ = PartnerDirection._(r'shared-with'); + static const sharedBy = PartnerDirection._(r'shared-by'); + static const sharedWith = PartnerDirection._(r'shared-with'); /// List of all possible values in this [enum][PartnerDirection]. static const values = [ - by, - with_, + sharedBy, + sharedWith, ]; static PartnerDirection? fromJson(dynamic value) => PartnerDirectionTypeTransformer().decode(value); @@ -68,8 +68,8 @@ class PartnerDirectionTypeTransformer { 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_; + case r'shared-by': return PartnerDirection.sharedBy; + case r'shared-with': return PartnerDirection.sharedWith; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart index f4612cc98a..967c5b930b 100644 --- a/mobile/openapi/lib/model/partner_response_dto.dart +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -16,7 +16,7 @@ class PartnerResponseDto { required this.avatarColor, required this.email, required this.id, - this.inTimeline, + this.inTimeline = const Optional.absent(), required this.name, required this.profileChangedAt, required this.profileImagePath, @@ -37,7 +37,7 @@ class PartnerResponseDto { /// 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; + Optional inTimeline; /// User name String name; @@ -77,10 +77,9 @@ class PartnerResponseDto { 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; + if (this.inTimeline.isPresent) { + final value = this.inTimeline.value; + json[r'inTimeline'] = value; } json[r'name'] = this.name; json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); @@ -100,7 +99,7 @@ class PartnerResponseDto { avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, email: mapValueOfType(json, r'email')!, id: mapValueOfType(json, r'id')!, - inTimeline: mapValueOfType(json, r'inTimeline'), + inTimeline: json.containsKey(r'inTimeline') ? Optional.present(mapValueOfType(json, r'inTimeline')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, diff --git a/mobile/openapi/lib/model/people_response.dart b/mobile/openapi/lib/model/people_response.dart index ba7128d932..838d1e2324 100644 --- a/mobile/openapi/lib/model/people_response.dart +++ b/mobile/openapi/lib/model/people_response.dart @@ -14,7 +14,7 @@ class PeopleResponse { /// Returns a new [PeopleResponse] instance. PeopleResponse({ required this.enabled, - this.minimumFaces, + this.minimumFaces = const Optional.absent(), required this.sidebarWeb, }); @@ -31,7 +31,7 @@ class PeopleResponse { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? minimumFaces; + Optional minimumFaces; /// Whether people appear in web sidebar bool sidebarWeb; @@ -55,10 +55,9 @@ class PeopleResponse { Map toJson() { final json = {}; json[r'enabled'] = this.enabled; - if (this.minimumFaces != null) { - json[r'minimumFaces'] = this.minimumFaces; - } else { - // json[r'minimumFaces'] = null; + if (this.minimumFaces.isPresent) { + final value = this.minimumFaces.value; + json[r'minimumFaces'] = value; } json[r'sidebarWeb'] = this.sidebarWeb; return json; @@ -74,7 +73,7 @@ class PeopleResponse { return PeopleResponse( enabled: mapValueOfType(json, r'enabled')!, - minimumFaces: mapValueOfType(json, r'minimumFaces'), + minimumFaces: json.containsKey(r'minimumFaces') ? Optional.present(json[r'minimumFaces'] == null ? null : int.parse('${json[r'minimumFaces']}')) : const Optional.absent(), sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, ); } diff --git a/mobile/openapi/lib/model/people_response_dto.dart b/mobile/openapi/lib/model/people_response_dto.dart index 87edc6b4a7..f9fc157239 100644 --- a/mobile/openapi/lib/model/people_response_dto.dart +++ b/mobile/openapi/lib/model/people_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class PeopleResponseDto { /// Returns a new [PeopleResponseDto] instance. PeopleResponseDto({ - this.hasNextPage, + this.hasNextPage = const Optional.absent(), required this.hidden, this.people = const [], required this.total, @@ -26,7 +26,7 @@ class PeopleResponseDto { /// 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; + Optional hasNextPage; /// Number of hidden people /// @@ -62,10 +62,9 @@ class PeopleResponseDto { Map toJson() { final json = {}; - if (this.hasNextPage != null) { - json[r'hasNextPage'] = this.hasNextPage; - } else { - // json[r'hasNextPage'] = null; + if (this.hasNextPage.isPresent) { + final value = this.hasNextPage.value; + json[r'hasNextPage'] = value; } json[r'hidden'] = this.hidden; json[r'people'] = this.people; @@ -82,7 +81,7 @@ class PeopleResponseDto { final json = value.cast(); return PeopleResponseDto( - hasNextPage: mapValueOfType(json, r'hasNextPage'), + hasNextPage: json.containsKey(r'hasNextPage') ? Optional.present(mapValueOfType(json, r'hasNextPage')) : const Optional.absent(), hidden: mapValueOfType(json, r'hidden')!, people: PersonResponseDto.listFromJson(json[r'people']), total: mapValueOfType(json, r'total')!, diff --git a/mobile/openapi/lib/model/people_update.dart b/mobile/openapi/lib/model/people_update.dart index 05459f19f3..ea8ae73138 100644 --- a/mobile/openapi/lib/model/people_update.dart +++ b/mobile/openapi/lib/model/people_update.dart @@ -13,9 +13,9 @@ part of openapi.api; class PeopleUpdate { /// Returns a new [PeopleUpdate] instance. PeopleUpdate({ - this.enabled, - this.minimumFaces, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.minimumFaces = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether people are enabled @@ -25,7 +25,7 @@ class PeopleUpdate { /// 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; + Optional enabled; /// People face threshold /// @@ -37,7 +37,7 @@ class PeopleUpdate { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? minimumFaces; + Optional minimumFaces; /// Whether people appear in web sidebar /// @@ -46,7 +46,7 @@ class PeopleUpdate { /// 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; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is PeopleUpdate && @@ -66,20 +66,17 @@ class PeopleUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.minimumFaces != null) { - json[r'minimumFaces'] = this.minimumFaces; - } else { - // json[r'minimumFaces'] = null; + if (this.minimumFaces.isPresent) { + final value = this.minimumFaces.value; + json[r'minimumFaces'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -93,9 +90,9 @@ class PeopleUpdate { final json = value.cast(); return PeopleUpdate( - enabled: mapValueOfType(json, r'enabled'), - minimumFaces: mapValueOfType(json, r'minimumFaces'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + minimumFaces: json.containsKey(r'minimumFaces') ? Optional.present(json[r'minimumFaces'] == null ? null : int.parse('${json[r'minimumFaces']}')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/people_update_item.dart b/mobile/openapi/lib/model/people_update_item.dart index 5e20aeb464..9ebbd93e28 100644 --- a/mobile/openapi/lib/model/people_update_item.dart +++ b/mobile/openapi/lib/model/people_update_item.dart @@ -13,20 +13,20 @@ part of openapi.api; class PeopleUpdateItem { /// Returns a new [PeopleUpdateItem] instance. PeopleUpdateItem({ - this.birthDate, - this.color, - this.featureFaceAssetId, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.featureFaceAssetId = const Optional.absent(), required this.id, - this.isFavorite, - this.isHidden, - this.name, + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Asset ID used for feature face thumbnail /// @@ -35,7 +35,7 @@ class PeopleUpdateItem { /// 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; + Optional featureFaceAssetId; /// Person ID String id; @@ -47,7 +47,7 @@ class PeopleUpdateItem { /// 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; + Optional isFavorite; /// Person visibility (hidden) /// @@ -56,7 +56,7 @@ class PeopleUpdateItem { /// 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; + Optional isHidden; /// Person name /// @@ -65,7 +65,7 @@ class PeopleUpdateItem { /// 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; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && @@ -93,36 +93,30 @@ class PeopleUpdateItem { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.featureFaceAssetId != null) { - json[r'featureFaceAssetId'] = this.featureFaceAssetId; - } else { - // json[r'featureFaceAssetId'] = null; + if (this.featureFaceAssetId.isPresent) { + final value = this.featureFaceAssetId.value; + json[r'featureFaceAssetId'] = value; } json[r'id'] = this.id; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -136,13 +130,13 @@ class PeopleUpdateItem { final json = value.cast(); return PeopleUpdateItem( - birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), - featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + featureFaceAssetId: json.containsKey(r'featureFaceAssetId') ? Optional.present(mapValueOfType(json, r'featureFaceAssetId')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isFavorite: mapValueOfType(json, r'isFavorite'), - isHidden: mapValueOfType(json, r'isHidden'), - name: mapValueOfType(json, r'name'), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_create_dto.dart b/mobile/openapi/lib/model/person_create_dto.dart index f2ba702c2f..22c9d2fb4c 100644 --- a/mobile/openapi/lib/model/person_create_dto.dart +++ b/mobile/openapi/lib/model/person_create_dto.dart @@ -13,18 +13,18 @@ part of openapi.api; class PersonCreateDto { /// Returns a new [PersonCreateDto] instance. PersonCreateDto({ - this.birthDate, - this.color, - this.isFavorite, - this.isHidden, - this.name, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Mark as favorite /// @@ -33,7 +33,7 @@ class PersonCreateDto { /// 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; + Optional isFavorite; /// Person visibility (hidden) /// @@ -42,7 +42,7 @@ class PersonCreateDto { /// 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; + Optional isHidden; /// Person name /// @@ -51,7 +51,7 @@ class PersonCreateDto { /// 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; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PersonCreateDto && @@ -75,30 +75,25 @@ class PersonCreateDto { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -112,11 +107,11 @@ class PersonCreateDto { 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'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index 455dfb98d6..a99f465236 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -14,13 +14,13 @@ class PersonResponseDto { /// Returns a new [PersonResponseDto] instance. PersonResponseDto({ required this.birthDate, - this.color, + this.color = const Optional.absent(), required this.id, - this.isFavorite, + this.isFavorite = const Optional.absent(), required this.isHidden, required this.name, required this.thumbnailPath, - this.updatedAt, + this.updatedAt = const Optional.absent(), }); /// Person date of birth @@ -33,7 +33,7 @@ class PersonResponseDto { /// 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; + Optional color; /// Person ID String id; @@ -45,7 +45,7 @@ class PersonResponseDto { /// 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; + Optional isFavorite; /// Is hidden bool isHidden; @@ -63,7 +63,7 @@ class PersonResponseDto { /// 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; + Optional updatedAt; @override bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && @@ -96,26 +96,23 @@ class PersonResponseDto { if (this.birthDate != null) { json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); } else { - // json[r'birthDate'] = null; + json[r'birthDate'] = null; } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } json[r'id'] = this.id; - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } 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; + if (this.updatedAt.isPresent) { + final value = this.updatedAt.value; + json[r'updatedAt'] = value == null ? null : value.toUtc().toIso8601String(); } return json; } @@ -130,13 +127,13 @@ class PersonResponseDto { return PersonResponseDto( birthDate: mapDateTime(json, r'birthDate', r''), - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, - isFavorite: mapValueOfType(json, r'isFavorite'), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), isHidden: mapValueOfType(json, r'isHidden')!, name: mapValueOfType(json, r'name')!, thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, - updatedAt: mapDateTime(json, r'updatedAt', r''), + updatedAt: json.containsKey(r'updatedAt') ? Optional.present(mapDateTime(json, r'updatedAt', r'')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/person_update_dto.dart b/mobile/openapi/lib/model/person_update_dto.dart index b56940e51d..56b99606ee 100644 --- a/mobile/openapi/lib/model/person_update_dto.dart +++ b/mobile/openapi/lib/model/person_update_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class PersonUpdateDto { /// Returns a new [PersonUpdateDto] instance. PersonUpdateDto({ - this.birthDate, - this.color, - this.featureFaceAssetId, - this.isFavorite, - this.isHidden, - this.name, + this.birthDate = const Optional.absent(), + this.color = const Optional.absent(), + this.featureFaceAssetId = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isHidden = const Optional.absent(), + this.name = const Optional.absent(), }); /// Person date of birth - DateTime? birthDate; + Optional birthDate; /// Person color (hex) - String? color; + Optional color; /// Asset ID used for feature face thumbnail /// @@ -34,7 +34,7 @@ class PersonUpdateDto { /// 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; + Optional featureFaceAssetId; /// Mark as favorite /// @@ -43,7 +43,7 @@ class PersonUpdateDto { /// 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; + Optional isFavorite; /// Person visibility (hidden) /// @@ -52,7 +52,7 @@ class PersonUpdateDto { /// 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; + Optional isHidden; /// Person name /// @@ -61,7 +61,7 @@ class PersonUpdateDto { /// 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; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && @@ -87,35 +87,29 @@ class PersonUpdateDto { Map toJson() { final json = {}; - if (this.birthDate != null) { - json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); - } else { - // json[r'birthDate'] = null; + if (this.birthDate.isPresent) { + final value = this.birthDate.value; + json[r'birthDate'] = value == null ? null : _dateFormatter.format(value.toUtc()); } - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } - if (this.featureFaceAssetId != null) { - json[r'featureFaceAssetId'] = this.featureFaceAssetId; - } else { - // json[r'featureFaceAssetId'] = null; + if (this.featureFaceAssetId.isPresent) { + final value = this.featureFaceAssetId.value; + json[r'featureFaceAssetId'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isHidden != null) { - json[r'isHidden'] = this.isHidden; - } else { - // json[r'isHidden'] = null; + if (this.isHidden.isPresent) { + final value = this.isHidden.value; + json[r'isHidden'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -129,12 +123,12 @@ class PersonUpdateDto { 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'), + birthDate: json.containsKey(r'birthDate') ? Optional.present(mapDateTime(json, r'birthDate', r'')) : const Optional.absent(), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), + featureFaceAssetId: json.containsKey(r'featureFaceAssetId') ? Optional.present(mapValueOfType(json, r'featureFaceAssetId')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isHidden: json.containsKey(r'isHidden') ? Optional.present(mapValueOfType(json, r'isHidden')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/pin_code_change_dto.dart b/mobile/openapi/lib/model/pin_code_change_dto.dart index 068cc9e91b..42c244933f 100644 --- a/mobile/openapi/lib/model/pin_code_change_dto.dart +++ b/mobile/openapi/lib/model/pin_code_change_dto.dart @@ -14,8 +14,8 @@ class PinCodeChangeDto { /// Returns a new [PinCodeChangeDto] instance. PinCodeChangeDto({ required this.newPinCode, - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// New PIN code (4-6 digits) @@ -28,7 +28,7 @@ class PinCodeChangeDto { /// 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; + Optional password; /// New PIN code (4-6 digits) /// @@ -37,7 +37,7 @@ class PinCodeChangeDto { /// 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; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is PinCodeChangeDto && @@ -58,15 +58,13 @@ class PinCodeChangeDto { 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.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -81,8 +79,8 @@ class PinCodeChangeDto { return PinCodeChangeDto( newPinCode: mapValueOfType(json, r'newPinCode')!, - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/pin_code_reset_dto.dart b/mobile/openapi/lib/model/pin_code_reset_dto.dart index c37be76f18..04ad61eeeb 100644 --- a/mobile/openapi/lib/model/pin_code_reset_dto.dart +++ b/mobile/openapi/lib/model/pin_code_reset_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class PinCodeResetDto { /// Returns a new [PinCodeResetDto] instance. PinCodeResetDto({ - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// User password (required if PIN code is not provided) @@ -24,7 +24,7 @@ class PinCodeResetDto { /// 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; + Optional password; /// New PIN code (4-6 digits) /// @@ -33,7 +33,7 @@ class PinCodeResetDto { /// 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; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is PinCodeResetDto && @@ -51,15 +51,13 @@ class PinCodeResetDto { Map toJson() { final json = {}; - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -73,8 +71,8 @@ class PinCodeResetDto { final json = value.cast(); return PinCodeResetDto( - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/places_response_dto.dart b/mobile/openapi/lib/model/places_response_dto.dart index 94aa58eba4..f222c33dba 100644 --- a/mobile/openapi/lib/model/places_response_dto.dart +++ b/mobile/openapi/lib/model/places_response_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class PlacesResponseDto { /// Returns a new [PlacesResponseDto] instance. PlacesResponseDto({ - this.admin1name, - this.admin2name, + this.admin1name = const Optional.absent(), + this.admin2name = const Optional.absent(), required this.latitude, required this.longitude, required this.name, @@ -27,7 +27,7 @@ class PlacesResponseDto { /// 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; + Optional admin1name; /// Administrative level 2 name (county/district) /// @@ -36,7 +36,7 @@ class PlacesResponseDto { /// 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; + Optional admin2name; /// Latitude coordinate num latitude; @@ -69,15 +69,13 @@ class PlacesResponseDto { Map toJson() { final json = {}; - if (this.admin1name != null) { - json[r'admin1name'] = this.admin1name; - } else { - // json[r'admin1name'] = null; + if (this.admin1name.isPresent) { + final value = this.admin1name.value; + json[r'admin1name'] = value; } - if (this.admin2name != null) { - json[r'admin2name'] = this.admin2name; - } else { - // json[r'admin2name'] = null; + if (this.admin2name.isPresent) { + final value = this.admin2name.value; + json[r'admin2name'] = value; } json[r'latitude'] = this.latitude; json[r'longitude'] = this.longitude; @@ -94,8 +92,8 @@ class PlacesResponseDto { final json = value.cast(); return PlacesResponseDto( - admin1name: mapValueOfType(json, r'admin1name'), - admin2name: mapValueOfType(json, r'admin2name'), + admin1name: json.containsKey(r'admin1name') ? Optional.present(mapValueOfType(json, r'admin1name')) : const Optional.absent(), + admin2name: json.containsKey(r'admin2name') ? Optional.present(mapValueOfType(json, r'admin2name')) : const Optional.absent(), latitude: num.parse('${json[r'latitude']}'), longitude: num.parse('${json[r'longitude']}'), name: mapValueOfType(json, r'name')!, diff --git a/mobile/openapi/lib/model/plugin_method_response_dto.dart b/mobile/openapi/lib/model/plugin_method_response_dto.dart index 2887f4cc16..1d6f9c1331 100644 --- a/mobile/openapi/lib/model/plugin_method_response_dto.dart +++ b/mobile/openapi/lib/model/plugin_method_response_dto.dart @@ -17,7 +17,7 @@ class PluginMethodResponseDto { required this.hostFunctions, required this.key, required this.name, - this.schema, + this.schema = const Optional.absent(), required this.title, this.types = const [], this.uiHints = const [], @@ -40,7 +40,7 @@ class PluginMethodResponseDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - Object? schema; + Optional schema; /// Title String title; @@ -83,10 +83,9 @@ class PluginMethodResponseDto { json[r'hostFunctions'] = this.hostFunctions; json[r'key'] = this.key; json[r'name'] = this.name; - if (this.schema != null) { - json[r'schema'] = this.schema; - } else { - // json[r'schema'] = null; + if (this.schema.isPresent) { + final value = this.schema.value; + json[r'schema'] = value; } json[r'title'] = this.title; json[r'types'] = this.types; @@ -107,7 +106,7 @@ class PluginMethodResponseDto { hostFunctions: mapValueOfType(json, r'hostFunctions')!, key: mapValueOfType(json, r'key')!, name: mapValueOfType(json, r'name')!, - schema: mapValueOfType(json, r'schema'), + schema: json.containsKey(r'schema') ? Optional.present(mapValueOfType(json, r'schema')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, types: WorkflowType.listFromJson(json[r'types']), uiHints: json[r'uiHints'] is Iterable diff --git a/mobile/openapi/lib/model/plugin_template_step_response_dto.dart b/mobile/openapi/lib/model/plugin_template_step_response_dto.dart index b58884d1cd..3e82c029d2 100644 --- a/mobile/openapi/lib/model/plugin_template_step_response_dto.dart +++ b/mobile/openapi/lib/model/plugin_template_step_response_dto.dart @@ -14,7 +14,7 @@ class PluginTemplateStepResponseDto { /// Returns a new [PluginTemplateStepResponseDto] instance. PluginTemplateStepResponseDto({ this.config = const {}, - this.enabled, + this.enabled = const Optional.absent(), required this.method, }); @@ -28,7 +28,7 @@ class PluginTemplateStepResponseDto { /// 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; + Optional enabled; /// Step plugin method String method; @@ -54,12 +54,11 @@ class PluginTemplateStepResponseDto { if (this.config != null) { json[r'config'] = this.config; } else { - // json[r'config'] = null; + json[r'config'] = null; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } json[r'method'] = this.method; return json; @@ -75,7 +74,7 @@ class PluginTemplateStepResponseDto { return PluginTemplateStepResponseDto( config: mapCastOfType(json, r'config'), - enabled: mapValueOfType(json, r'enabled'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), method: mapValueOfType(json, r'method')!, ); } diff --git a/mobile/openapi/lib/model/purchase_update.dart b/mobile/openapi/lib/model/purchase_update.dart index 913faf9bc4..8a0fb0d1b6 100644 --- a/mobile/openapi/lib/model/purchase_update.dart +++ b/mobile/openapi/lib/model/purchase_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class PurchaseUpdate { /// Returns a new [PurchaseUpdate] instance. PurchaseUpdate({ - this.hideBuyButtonUntil, - this.showSupportBadge, + this.hideBuyButtonUntil = const Optional.absent(), + this.showSupportBadge = const Optional.absent(), }); /// Date until which to hide buy button @@ -24,7 +24,7 @@ class PurchaseUpdate { /// 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; + Optional hideBuyButtonUntil; /// Whether to show support badge /// @@ -33,7 +33,7 @@ class PurchaseUpdate { /// 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; + Optional showSupportBadge; @override bool operator ==(Object other) => identical(this, other) || other is PurchaseUpdate && @@ -51,15 +51,13 @@ class PurchaseUpdate { Map toJson() { final json = {}; - if (this.hideBuyButtonUntil != null) { - json[r'hideBuyButtonUntil'] = this.hideBuyButtonUntil; - } else { - // json[r'hideBuyButtonUntil'] = null; + if (this.hideBuyButtonUntil.isPresent) { + final value = this.hideBuyButtonUntil.value; + json[r'hideBuyButtonUntil'] = value; } - if (this.showSupportBadge != null) { - json[r'showSupportBadge'] = this.showSupportBadge; - } else { - // json[r'showSupportBadge'] = null; + if (this.showSupportBadge.isPresent) { + final value = this.showSupportBadge.value; + json[r'showSupportBadge'] = value; } return json; } @@ -73,8 +71,8 @@ class PurchaseUpdate { final json = value.cast(); return PurchaseUpdate( - hideBuyButtonUntil: mapValueOfType(json, r'hideBuyButtonUntil'), - showSupportBadge: mapValueOfType(json, r'showSupportBadge'), + hideBuyButtonUntil: json.containsKey(r'hideBuyButtonUntil') ? Optional.present(mapValueOfType(json, r'hideBuyButtonUntil')) : const Optional.absent(), + showSupportBadge: json.containsKey(r'showSupportBadge') ? Optional.present(mapValueOfType(json, r'showSupportBadge')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_command_dto.dart b/mobile/openapi/lib/model/queue_command_dto.dart index fb68d85583..e8a600923f 100644 --- a/mobile/openapi/lib/model/queue_command_dto.dart +++ b/mobile/openapi/lib/model/queue_command_dto.dart @@ -14,7 +14,7 @@ class QueueCommandDto { /// Returns a new [QueueCommandDto] instance. QueueCommandDto({ required this.command, - this.force, + this.force = const Optional.absent(), }); QueueCommand command; @@ -26,7 +26,7 @@ class QueueCommandDto { /// 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; + Optional force; @override bool operator ==(Object other) => identical(this, other) || other is QueueCommandDto && @@ -45,10 +45,9 @@ class QueueCommandDto { Map toJson() { final json = {}; json[r'command'] = this.command; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } return json; } @@ -63,7 +62,7 @@ class QueueCommandDto { return QueueCommandDto( command: QueueCommand.fromJson(json[r'command'])!, - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_delete_dto.dart b/mobile/openapi/lib/model/queue_delete_dto.dart index d319238f92..9511313f01 100644 --- a/mobile/openapi/lib/model/queue_delete_dto.dart +++ b/mobile/openapi/lib/model/queue_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class QueueDeleteDto { /// Returns a new [QueueDeleteDto] instance. QueueDeleteDto({ - this.failed, + this.failed = const Optional.absent(), }); /// If true, will also remove failed jobs from the queue. @@ -23,7 +23,7 @@ class QueueDeleteDto { /// 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; + Optional failed; @override bool operator ==(Object other) => identical(this, other) || other is QueueDeleteDto && @@ -39,10 +39,9 @@ class QueueDeleteDto { Map toJson() { final json = {}; - if (this.failed != null) { - json[r'failed'] = this.failed; - } else { - // json[r'failed'] = null; + if (this.failed.isPresent) { + final value = this.failed.value; + json[r'failed'] = value; } return json; } @@ -56,7 +55,7 @@ class QueueDeleteDto { final json = value.cast(); return QueueDeleteDto( - failed: mapValueOfType(json, r'failed'), + failed: json.containsKey(r'failed') ? Optional.present(mapValueOfType(json, r'failed')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/queue_job_response_dto.dart b/mobile/openapi/lib/model/queue_job_response_dto.dart index 06d433edad..ca26361a3e 100644 --- a/mobile/openapi/lib/model/queue_job_response_dto.dart +++ b/mobile/openapi/lib/model/queue_job_response_dto.dart @@ -14,7 +14,7 @@ class QueueJobResponseDto { /// Returns a new [QueueJobResponseDto] instance. QueueJobResponseDto({ this.data = const {}, - this.id, + this.id = const Optional.absent(), required this.name, required this.timestamp, }); @@ -29,7 +29,7 @@ class QueueJobResponseDto { /// 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; + Optional id; JobName name; @@ -60,10 +60,9 @@ class QueueJobResponseDto { Map toJson() { final json = {}; json[r'data'] = this.data; - if (this.id != null) { - json[r'id'] = this.id; - } else { - // json[r'id'] = null; + if (this.id.isPresent) { + final value = this.id.value; + json[r'id'] = value; } json[r'name'] = this.name; json[r'timestamp'] = this.timestamp; @@ -80,7 +79,7 @@ class QueueJobResponseDto { return QueueJobResponseDto( data: mapCastOfType(json, r'data')!, - id: mapValueOfType(json, r'id'), + id: json.containsKey(r'id') ? Optional.present(mapValueOfType(json, r'id')) : const Optional.absent(), name: JobName.fromJson(json[r'name'])!, timestamp: mapValueOfType(json, r'timestamp')!, ); diff --git a/mobile/openapi/lib/model/queue_update_dto.dart b/mobile/openapi/lib/model/queue_update_dto.dart index 28aafe95f7..189fc219f1 100644 --- a/mobile/openapi/lib/model/queue_update_dto.dart +++ b/mobile/openapi/lib/model/queue_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class QueueUpdateDto { /// Returns a new [QueueUpdateDto] instance. QueueUpdateDto({ - this.isPaused, + this.isPaused = const Optional.absent(), }); /// Whether to pause the queue @@ -23,7 +23,7 @@ class QueueUpdateDto { /// 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; + Optional isPaused; @override bool operator ==(Object other) => identical(this, other) || other is QueueUpdateDto && @@ -39,10 +39,9 @@ class QueueUpdateDto { Map toJson() { final json = {}; - if (this.isPaused != null) { - json[r'isPaused'] = this.isPaused; - } else { - // json[r'isPaused'] = null; + if (this.isPaused.isPresent) { + final value = this.isPaused.value; + json[r'isPaused'] = value; } return json; } @@ -56,7 +55,7 @@ class QueueUpdateDto { final json = value.cast(); return QueueUpdateDto( - isPaused: mapValueOfType(json, r'isPaused'), + isPaused: json.containsKey(r'isPaused') ? Optional.present(mapValueOfType(json, r'isPaused')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index 728072639c..d8a2a7635a 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -13,48 +13,48 @@ part of openapi.api; class RandomSearchDto { /// Returns a new [RandomSearchDto] instance. RandomSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - 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, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), + this.withPeople = const Optional.absent(), + this.withStacked = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -63,7 +63,7 @@ class RandomSearchDto { /// 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; + Optional createdAfter; /// Filter by creation date (before) /// @@ -72,7 +72,7 @@ class RandomSearchDto { /// 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; + Optional createdBefore; /// Filter by encoded status /// @@ -81,7 +81,7 @@ class RandomSearchDto { /// 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; + Optional isEncoded; /// Filter by favorite status /// @@ -90,7 +90,7 @@ class RandomSearchDto { /// 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; + Optional isFavorite; /// Filter by motion photo status /// @@ -99,7 +99,7 @@ class RandomSearchDto { /// 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; + Optional isMotion; /// Filter assets not in any album /// @@ -108,7 +108,7 @@ class RandomSearchDto { /// 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; + Optional isNotInAlbum; /// Filter by offline status /// @@ -117,19 +117,19 @@ class RandomSearchDto { /// 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; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -138,16 +138,16 @@ class RandomSearchDto { /// 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; + Optional ocr; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by rating [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -159,13 +159,13 @@ class RandomSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -174,7 +174,7 @@ class RandomSearchDto { /// 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; + Optional takenAfter; /// Filter by taken date (before) /// @@ -183,7 +183,7 @@ class RandomSearchDto { /// 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; + Optional takenBefore; /// Filter by trash date (after) /// @@ -192,7 +192,7 @@ class RandomSearchDto { /// 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; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -201,7 +201,7 @@ class RandomSearchDto { /// 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; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -209,7 +209,7 @@ class RandomSearchDto { /// 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; + Optional type; /// Filter by update date (after) /// @@ -218,7 +218,7 @@ class RandomSearchDto { /// 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; + Optional updatedAfter; /// Filter by update date (before) /// @@ -227,7 +227,7 @@ class RandomSearchDto { /// 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; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -235,7 +235,7 @@ class RandomSearchDto { /// 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; + Optional visibility; /// Include deleted assets /// @@ -244,7 +244,7 @@ class RandomSearchDto { /// 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; + Optional withDeleted; /// Include EXIF data in response /// @@ -253,7 +253,7 @@ class RandomSearchDto { /// 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; + Optional withExif; /// Include people data in response /// @@ -262,7 +262,7 @@ class RandomSearchDto { /// 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; + Optional withPeople; /// Include stacked assets /// @@ -271,7 +271,7 @@ class RandomSearchDto { /// 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; + Optional withStacked; @override bool operator ==(Object other) => identical(this, other) || other is RandomSearchDto && @@ -349,173 +349,149 @@ class RandomSearchDto { 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.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - json[r'personIds'] = this.personIds; - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } - if (this.withPeople != null) { - json[r'withPeople'] = this.withPeople; - } else { - // json[r'withPeople'] = null; + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; } - if (this.withStacked != null) { - json[r'withStacked'] = this.withStacked; - } else { - // json[r'withStacked'] = null; + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; + } + if (this.withPeople.isPresent) { + final value = this.withPeople.value; + json[r'withPeople'] = value; + } + if (this.withStacked.isPresent) { + final value = this.withStacked.value; + json[r'withStacked'] = value; } return json; } @@ -529,44 +505,44 @@ class RandomSearchDto { final json = value.cast(); return RandomSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - 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 + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - rating: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - 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'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), + withPeople: json.containsKey(r'withPeople') ? Optional.present(mapValueOfType(json, r'withPeople')) : const Optional.absent(), + withStacked: json.containsKey(r'withStacked') ? Optional.present(mapValueOfType(json, r'withStacked')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/ratings_update.dart b/mobile/openapi/lib/model/ratings_update.dart index 8079172e21..085efd97e6 100644 --- a/mobile/openapi/lib/model/ratings_update.dart +++ b/mobile/openapi/lib/model/ratings_update.dart @@ -13,7 +13,7 @@ part of openapi.api; class RatingsUpdate { /// Returns a new [RatingsUpdate] instance. RatingsUpdate({ - this.enabled, + this.enabled = const Optional.absent(), }); /// Whether ratings are enabled @@ -23,7 +23,7 @@ class RatingsUpdate { /// 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; + Optional enabled; @override bool operator ==(Object other) => identical(this, other) || other is RatingsUpdate && @@ -39,10 +39,9 @@ class RatingsUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } return json; } @@ -56,7 +55,7 @@ class RatingsUpdate { final json = value.cast(); return RatingsUpdate( - enabled: mapValueOfType(json, r'enabled'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart index 6ad8c1a7b9..07bb226ac0 100644 --- a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart +++ b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart @@ -42,12 +42,12 @@ class ReverseGeocodingStateResponseDto { if (this.lastImportFileName != null) { json[r'lastImportFileName'] = this.lastImportFileName; } else { - // json[r'lastImportFileName'] = null; + json[r'lastImportFileName'] = null; } if (this.lastUpdate != null) { json[r'lastUpdate'] = this.lastUpdate; } else { - // json[r'lastUpdate'] = null; + json[r'lastUpdate'] = null; } return json; } diff --git a/mobile/openapi/lib/model/search_asset_response_dto.dart b/mobile/openapi/lib/model/search_asset_response_dto.dart index f4ffade26b..82971c3c49 100644 --- a/mobile/openapi/lib/model/search_asset_response_dto.dart +++ b/mobile/openapi/lib/model/search_asset_response_dto.dart @@ -67,7 +67,7 @@ class SearchAssetResponseDto { if (this.nextPage != null) { json[r'nextPage'] = this.nextPage; } else { - // json[r'nextPage'] = null; + json[r'nextPage'] = null; } json[r'total'] = this.total; return json; diff --git a/mobile/openapi/lib/model/server_about_response_dto.dart b/mobile/openapi/lib/model/server_about_response_dto.dart index 1ae53763fe..dcfb279204 100644 --- a/mobile/openapi/lib/model/server_about_response_dto.dart +++ b/mobile/openapi/lib/model/server_about_response_dto.dart @@ -13,25 +13,25 @@ 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, + this.build = const Optional.absent(), + this.buildImage = const Optional.absent(), + this.buildImageUrl = const Optional.absent(), + this.buildUrl = const Optional.absent(), + this.exiftool = const Optional.absent(), + this.ffmpeg = const Optional.absent(), + this.imagemagick = const Optional.absent(), + this.libvips = const Optional.absent(), required this.licensed, - this.nodejs, - this.repository, - this.repositoryUrl, - this.sourceCommit, - this.sourceRef, - this.sourceUrl, - this.thirdPartyBugFeatureUrl, - this.thirdPartyDocumentationUrl, - this.thirdPartySourceUrl, - this.thirdPartySupportUrl, + this.nodejs = const Optional.absent(), + this.repository = const Optional.absent(), + this.repositoryUrl = const Optional.absent(), + this.sourceCommit = const Optional.absent(), + this.sourceRef = const Optional.absent(), + this.sourceUrl = const Optional.absent(), + this.thirdPartyBugFeatureUrl = const Optional.absent(), + this.thirdPartyDocumentationUrl = const Optional.absent(), + this.thirdPartySourceUrl = const Optional.absent(), + this.thirdPartySupportUrl = const Optional.absent(), required this.version, required this.versionUrl, }); @@ -43,7 +43,7 @@ class ServerAboutResponseDto { /// 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; + Optional build; /// Build image name /// @@ -52,7 +52,7 @@ class ServerAboutResponseDto { /// 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; + Optional buildImage; /// Build image URL /// @@ -61,7 +61,7 @@ class ServerAboutResponseDto { /// 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; + Optional buildImageUrl; /// Build URL /// @@ -70,7 +70,7 @@ class ServerAboutResponseDto { /// 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; + Optional buildUrl; /// ExifTool version /// @@ -79,7 +79,7 @@ class ServerAboutResponseDto { /// 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; + Optional exiftool; /// FFmpeg version /// @@ -88,7 +88,7 @@ class ServerAboutResponseDto { /// 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; + Optional ffmpeg; /// ImageMagick version /// @@ -97,7 +97,7 @@ class ServerAboutResponseDto { /// 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; + Optional imagemagick; /// libvips version /// @@ -106,7 +106,7 @@ class ServerAboutResponseDto { /// 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; + Optional libvips; /// Whether the server is licensed bool licensed; @@ -118,7 +118,7 @@ class ServerAboutResponseDto { /// 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; + Optional nodejs; /// Repository name /// @@ -127,7 +127,7 @@ class ServerAboutResponseDto { /// 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; + Optional repository; /// Repository URL /// @@ -136,7 +136,7 @@ class ServerAboutResponseDto { /// 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; + Optional repositoryUrl; /// Source commit hash /// @@ -145,7 +145,7 @@ class ServerAboutResponseDto { /// 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; + Optional sourceCommit; /// Source reference (branch/tag) /// @@ -154,7 +154,7 @@ class ServerAboutResponseDto { /// 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; + Optional sourceRef; /// Source URL /// @@ -163,7 +163,7 @@ class ServerAboutResponseDto { /// 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; + Optional sourceUrl; /// Third-party bug/feature URL /// @@ -172,7 +172,7 @@ class ServerAboutResponseDto { /// 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; + Optional thirdPartyBugFeatureUrl; /// Third-party documentation URL /// @@ -181,7 +181,7 @@ class ServerAboutResponseDto { /// 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; + Optional thirdPartyDocumentationUrl; /// Third-party source URL /// @@ -190,7 +190,7 @@ class ServerAboutResponseDto { /// 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; + Optional thirdPartySourceUrl; /// Third-party support URL /// @@ -199,7 +199,7 @@ class ServerAboutResponseDto { /// 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; + Optional thirdPartySupportUrl; /// Server version String version; @@ -261,96 +261,78 @@ class ServerAboutResponseDto { Map toJson() { final json = {}; - if (this.build != null) { - json[r'build'] = this.build; - } else { - // json[r'build'] = null; + if (this.build.isPresent) { + final value = this.build.value; + json[r'build'] = value; } - if (this.buildImage != null) { - json[r'buildImage'] = this.buildImage; - } else { - // json[r'buildImage'] = null; + if (this.buildImage.isPresent) { + final value = this.buildImage.value; + json[r'buildImage'] = value; } - if (this.buildImageUrl != null) { - json[r'buildImageUrl'] = this.buildImageUrl; - } else { - // json[r'buildImageUrl'] = null; + if (this.buildImageUrl.isPresent) { + final value = this.buildImageUrl.value; + json[r'buildImageUrl'] = value; } - if (this.buildUrl != null) { - json[r'buildUrl'] = this.buildUrl; - } else { - // json[r'buildUrl'] = null; + if (this.buildUrl.isPresent) { + final value = this.buildUrl.value; + json[r'buildUrl'] = value; } - if (this.exiftool != null) { - json[r'exiftool'] = this.exiftool; - } else { - // json[r'exiftool'] = null; + if (this.exiftool.isPresent) { + final value = this.exiftool.value; + json[r'exiftool'] = value; } - if (this.ffmpeg != null) { - json[r'ffmpeg'] = this.ffmpeg; - } else { - // json[r'ffmpeg'] = null; + if (this.ffmpeg.isPresent) { + final value = this.ffmpeg.value; + json[r'ffmpeg'] = value; } - if (this.imagemagick != null) { - json[r'imagemagick'] = this.imagemagick; - } else { - // json[r'imagemagick'] = null; + if (this.imagemagick.isPresent) { + final value = this.imagemagick.value; + json[r'imagemagick'] = value; } - if (this.libvips != null) { - json[r'libvips'] = this.libvips; - } else { - // json[r'libvips'] = null; + if (this.libvips.isPresent) { + final value = this.libvips.value; + json[r'libvips'] = value; } json[r'licensed'] = this.licensed; - if (this.nodejs != null) { - json[r'nodejs'] = this.nodejs; - } else { - // json[r'nodejs'] = null; + if (this.nodejs.isPresent) { + final value = this.nodejs.value; + json[r'nodejs'] = value; } - if (this.repository != null) { - json[r'repository'] = this.repository; - } else { - // json[r'repository'] = null; + if (this.repository.isPresent) { + final value = this.repository.value; + json[r'repository'] = value; } - if (this.repositoryUrl != null) { - json[r'repositoryUrl'] = this.repositoryUrl; - } else { - // json[r'repositoryUrl'] = null; + if (this.repositoryUrl.isPresent) { + final value = this.repositoryUrl.value; + json[r'repositoryUrl'] = value; } - if (this.sourceCommit != null) { - json[r'sourceCommit'] = this.sourceCommit; - } else { - // json[r'sourceCommit'] = null; + if (this.sourceCommit.isPresent) { + final value = this.sourceCommit.value; + json[r'sourceCommit'] = value; } - if (this.sourceRef != null) { - json[r'sourceRef'] = this.sourceRef; - } else { - // json[r'sourceRef'] = null; + if (this.sourceRef.isPresent) { + final value = this.sourceRef.value; + json[r'sourceRef'] = value; } - if (this.sourceUrl != null) { - json[r'sourceUrl'] = this.sourceUrl; - } else { - // json[r'sourceUrl'] = null; + if (this.sourceUrl.isPresent) { + final value = this.sourceUrl.value; + json[r'sourceUrl'] = value; } - if (this.thirdPartyBugFeatureUrl != null) { - json[r'thirdPartyBugFeatureUrl'] = this.thirdPartyBugFeatureUrl; - } else { - // json[r'thirdPartyBugFeatureUrl'] = null; + if (this.thirdPartyBugFeatureUrl.isPresent) { + final value = this.thirdPartyBugFeatureUrl.value; + json[r'thirdPartyBugFeatureUrl'] = value; } - if (this.thirdPartyDocumentationUrl != null) { - json[r'thirdPartyDocumentationUrl'] = this.thirdPartyDocumentationUrl; - } else { - // json[r'thirdPartyDocumentationUrl'] = null; + if (this.thirdPartyDocumentationUrl.isPresent) { + final value = this.thirdPartyDocumentationUrl.value; + json[r'thirdPartyDocumentationUrl'] = value; } - if (this.thirdPartySourceUrl != null) { - json[r'thirdPartySourceUrl'] = this.thirdPartySourceUrl; - } else { - // json[r'thirdPartySourceUrl'] = null; + if (this.thirdPartySourceUrl.isPresent) { + final value = this.thirdPartySourceUrl.value; + json[r'thirdPartySourceUrl'] = value; } - if (this.thirdPartySupportUrl != null) { - json[r'thirdPartySupportUrl'] = this.thirdPartySupportUrl; - } else { - // json[r'thirdPartySupportUrl'] = null; + if (this.thirdPartySupportUrl.isPresent) { + final value = this.thirdPartySupportUrl.value; + json[r'thirdPartySupportUrl'] = value; } json[r'version'] = this.version; json[r'versionUrl'] = this.versionUrl; @@ -366,25 +348,25 @@ class ServerAboutResponseDto { 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'), + build: json.containsKey(r'build') ? Optional.present(mapValueOfType(json, r'build')) : const Optional.absent(), + buildImage: json.containsKey(r'buildImage') ? Optional.present(mapValueOfType(json, r'buildImage')) : const Optional.absent(), + buildImageUrl: json.containsKey(r'buildImageUrl') ? Optional.present(mapValueOfType(json, r'buildImageUrl')) : const Optional.absent(), + buildUrl: json.containsKey(r'buildUrl') ? Optional.present(mapValueOfType(json, r'buildUrl')) : const Optional.absent(), + exiftool: json.containsKey(r'exiftool') ? Optional.present(mapValueOfType(json, r'exiftool')) : const Optional.absent(), + ffmpeg: json.containsKey(r'ffmpeg') ? Optional.present(mapValueOfType(json, r'ffmpeg')) : const Optional.absent(), + imagemagick: json.containsKey(r'imagemagick') ? Optional.present(mapValueOfType(json, r'imagemagick')) : const Optional.absent(), + libvips: json.containsKey(r'libvips') ? Optional.present(mapValueOfType(json, r'libvips')) : const Optional.absent(), 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'), + nodejs: json.containsKey(r'nodejs') ? Optional.present(mapValueOfType(json, r'nodejs')) : const Optional.absent(), + repository: json.containsKey(r'repository') ? Optional.present(mapValueOfType(json, r'repository')) : const Optional.absent(), + repositoryUrl: json.containsKey(r'repositoryUrl') ? Optional.present(mapValueOfType(json, r'repositoryUrl')) : const Optional.absent(), + sourceCommit: json.containsKey(r'sourceCommit') ? Optional.present(mapValueOfType(json, r'sourceCommit')) : const Optional.absent(), + sourceRef: json.containsKey(r'sourceRef') ? Optional.present(mapValueOfType(json, r'sourceRef')) : const Optional.absent(), + sourceUrl: json.containsKey(r'sourceUrl') ? Optional.present(mapValueOfType(json, r'sourceUrl')) : const Optional.absent(), + thirdPartyBugFeatureUrl: json.containsKey(r'thirdPartyBugFeatureUrl') ? Optional.present(mapValueOfType(json, r'thirdPartyBugFeatureUrl')) : const Optional.absent(), + thirdPartyDocumentationUrl: json.containsKey(r'thirdPartyDocumentationUrl') ? Optional.present(mapValueOfType(json, r'thirdPartyDocumentationUrl')) : const Optional.absent(), + thirdPartySourceUrl: json.containsKey(r'thirdPartySourceUrl') ? Optional.present(mapValueOfType(json, r'thirdPartySourceUrl')) : const Optional.absent(), + thirdPartySupportUrl: json.containsKey(r'thirdPartySupportUrl') ? Optional.present(mapValueOfType(json, r'thirdPartySupportUrl')) : const Optional.absent(), version: mapValueOfType(json, r'version')!, versionUrl: mapValueOfType(json, r'versionUrl')!, ); diff --git a/mobile/openapi/lib/model/server_storage_response_dto.dart b/mobile/openapi/lib/model/server_storage_response_dto.dart index 4a66d54e37..f4f77c7f9b 100644 --- a/mobile/openapi/lib/model/server_storage_response_dto.dart +++ b/mobile/openapi/lib/model/server_storage_response_dto.dart @@ -101,7 +101,7 @@ class ServerStorageResponseDto { diskAvailableRaw: mapValueOfType(json, r'diskAvailableRaw')!, diskSize: mapValueOfType(json, r'diskSize')!, diskSizeRaw: mapValueOfType(json, r'diskSizeRaw')!, - diskUsagePercentage: (mapValueOfType(json, r'diskUsagePercentage')!).toDouble(), + diskUsagePercentage: mapValueOfType(json, r'diskUsagePercentage')!, diskUse: mapValueOfType(json, r'diskUse')!, diskUseRaw: mapValueOfType(json, r'diskUseRaw')!, ); diff --git a/mobile/openapi/lib/model/server_version_response_dto.dart b/mobile/openapi/lib/model/server_version_response_dto.dart index eae574f335..8f4a192920 100644 --- a/mobile/openapi/lib/model/server_version_response_dto.dart +++ b/mobile/openapi/lib/model/server_version_response_dto.dart @@ -69,7 +69,7 @@ class ServerVersionResponseDto { if (this.prerelease != null) { json[r'prerelease'] = this.prerelease; } else { - // json[r'prerelease'] = null; + json[r'prerelease'] = null; } return json; } diff --git a/mobile/openapi/lib/model/session_create_dto.dart b/mobile/openapi/lib/model/session_create_dto.dart index 37c07955cd..8033bb7f71 100644 --- a/mobile/openapi/lib/model/session_create_dto.dart +++ b/mobile/openapi/lib/model/session_create_dto.dart @@ -13,9 +13,9 @@ part of openapi.api; class SessionCreateDto { /// Returns a new [SessionCreateDto] instance. SessionCreateDto({ - this.deviceOS, - this.deviceType, - this.duration, + this.deviceOS = const Optional.absent(), + this.deviceType = const Optional.absent(), + this.duration = const Optional.absent(), }); /// Device OS @@ -25,7 +25,7 @@ class SessionCreateDto { /// 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; + Optional deviceOS; /// Device type /// @@ -34,7 +34,7 @@ class SessionCreateDto { /// 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; + Optional deviceType; /// Session duration in seconds /// @@ -46,7 +46,7 @@ class SessionCreateDto { /// 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; + Optional duration; @override bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto && @@ -66,20 +66,17 @@ class SessionCreateDto { Map toJson() { final json = {}; - if (this.deviceOS != null) { - json[r'deviceOS'] = this.deviceOS; - } else { - // json[r'deviceOS'] = null; + if (this.deviceOS.isPresent) { + final value = this.deviceOS.value; + json[r'deviceOS'] = value; } - if (this.deviceType != null) { - json[r'deviceType'] = this.deviceType; - } else { - // json[r'deviceType'] = null; + if (this.deviceType.isPresent) { + final value = this.deviceType.value; + json[r'deviceType'] = value; } - if (this.duration != null) { - json[r'duration'] = this.duration; - } else { - // json[r'duration'] = null; + if (this.duration.isPresent) { + final value = this.duration.value; + json[r'duration'] = value; } return json; } @@ -93,9 +90,9 @@ class SessionCreateDto { final json = value.cast(); return SessionCreateDto( - deviceOS: mapValueOfType(json, r'deviceOS'), - deviceType: mapValueOfType(json, r'deviceType'), - duration: mapValueOfType(json, r'duration'), + deviceOS: json.containsKey(r'deviceOS') ? Optional.present(mapValueOfType(json, r'deviceOS')) : const Optional.absent(), + deviceType: json.containsKey(r'deviceType') ? Optional.present(mapValueOfType(json, r'deviceType')) : const Optional.absent(), + duration: json.containsKey(r'duration') ? Optional.present(json[r'duration'] == null ? null : int.parse('${json[r'duration']}')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/session_create_response_dto.dart b/mobile/openapi/lib/model/session_create_response_dto.dart index f35232b0e8..497da9afe8 100644 --- a/mobile/openapi/lib/model/session_create_response_dto.dart +++ b/mobile/openapi/lib/model/session_create_response_dto.dart @@ -18,7 +18,7 @@ class SessionCreateResponseDto { required this.current, required this.deviceOS, required this.deviceType, - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.id, required this.isPendingSyncReset, required this.token, @@ -47,7 +47,7 @@ class SessionCreateResponseDto { /// 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; + Optional expiresAt; /// Session ID String id; @@ -96,16 +96,15 @@ class SessionCreateResponseDto { if (this.appVersion != null) { json[r'appVersion'] = this.appVersion; } else { - // json[r'appVersion'] = null; + 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; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } json[r'id'] = this.id; json[r'isPendingSyncReset'] = this.isPendingSyncReset; @@ -128,7 +127,7 @@ class SessionCreateResponseDto { current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, deviceType: mapValueOfType(json, r'deviceType')!, - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, token: mapValueOfType(json, r'token')!, diff --git a/mobile/openapi/lib/model/session_response_dto.dart b/mobile/openapi/lib/model/session_response_dto.dart index ed84160827..e1e20619cb 100644 --- a/mobile/openapi/lib/model/session_response_dto.dart +++ b/mobile/openapi/lib/model/session_response_dto.dart @@ -18,7 +18,7 @@ class SessionResponseDto { required this.current, required this.deviceOS, required this.deviceType, - this.expiresAt, + this.expiresAt = const Optional.absent(), required this.id, required this.isPendingSyncReset, required this.updatedAt, @@ -46,7 +46,7 @@ class SessionResponseDto { /// 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; + Optional expiresAt; /// Session ID String id; @@ -90,16 +90,15 @@ class SessionResponseDto { if (this.appVersion != null) { json[r'appVersion'] = this.appVersion; } else { - // json[r'appVersion'] = null; + 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; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value; } json[r'id'] = this.id; json[r'isPendingSyncReset'] = this.isPendingSyncReset; @@ -121,7 +120,7 @@ class SessionResponseDto { current: mapValueOfType(json, r'current')!, deviceOS: mapValueOfType(json, r'deviceOS')!, deviceType: mapValueOfType(json, r'deviceType')!, - expiresAt: mapValueOfType(json, r'expiresAt'), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapValueOfType(json, r'expiresAt')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, updatedAt: mapValueOfType(json, r'updatedAt')!, diff --git a/mobile/openapi/lib/model/session_unlock_dto.dart b/mobile/openapi/lib/model/session_unlock_dto.dart index 48ee75fb05..960b58acf0 100644 --- a/mobile/openapi/lib/model/session_unlock_dto.dart +++ b/mobile/openapi/lib/model/session_unlock_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class SessionUnlockDto { /// Returns a new [SessionUnlockDto] instance. SessionUnlockDto({ - this.password, - this.pinCode, + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), }); /// User password (required if PIN code is not provided) @@ -24,7 +24,7 @@ class SessionUnlockDto { /// 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; + Optional password; /// New PIN code (4-6 digits) /// @@ -33,7 +33,7 @@ class SessionUnlockDto { /// 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; + Optional pinCode; @override bool operator ==(Object other) => identical(this, other) || other is SessionUnlockDto && @@ -51,15 +51,13 @@ class SessionUnlockDto { Map toJson() { final json = {}; - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } return json; } @@ -73,8 +71,8 @@ class SessionUnlockDto { final json = value.cast(); return SessionUnlockDto( - password: mapValueOfType(json, r'password'), - pinCode: mapValueOfType(json, r'pinCode'), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/session_update_dto.dart b/mobile/openapi/lib/model/session_update_dto.dart index 3ab430deaa..90cbaffaf4 100644 --- a/mobile/openapi/lib/model/session_update_dto.dart +++ b/mobile/openapi/lib/model/session_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SessionUpdateDto { /// Returns a new [SessionUpdateDto] instance. SessionUpdateDto({ - this.isPendingSyncReset, + this.isPendingSyncReset = const Optional.absent(), }); /// Reset pending sync state @@ -23,7 +23,7 @@ class SessionUpdateDto { /// 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; + Optional isPendingSyncReset; @override bool operator ==(Object other) => identical(this, other) || other is SessionUpdateDto && @@ -39,10 +39,9 @@ class SessionUpdateDto { Map toJson() { final json = {}; - if (this.isPendingSyncReset != null) { - json[r'isPendingSyncReset'] = this.isPendingSyncReset; - } else { - // json[r'isPendingSyncReset'] = null; + if (this.isPendingSyncReset.isPresent) { + final value = this.isPendingSyncReset.value; + json[r'isPendingSyncReset'] = value; } return json; } @@ -56,7 +55,7 @@ class SessionUpdateDto { final json = value.cast(); return SessionUpdateDto( - isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset'), + isPendingSyncReset: json.containsKey(r'isPendingSyncReset') ? Optional.present(mapValueOfType(json, r'isPendingSyncReset')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/set_maintenance_mode_dto.dart b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart index e7c9dc0d63..21f123bb84 100644 --- a/mobile/openapi/lib/model/set_maintenance_mode_dto.dart +++ b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart @@ -14,7 +14,7 @@ class SetMaintenanceModeDto { /// Returns a new [SetMaintenanceModeDto] instance. SetMaintenanceModeDto({ required this.action, - this.restoreBackupFilename, + this.restoreBackupFilename = const Optional.absent(), }); MaintenanceAction action; @@ -26,7 +26,7 @@ class SetMaintenanceModeDto { /// 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; + Optional restoreBackupFilename; @override bool operator ==(Object other) => identical(this, other) || other is SetMaintenanceModeDto && @@ -45,10 +45,9 @@ class SetMaintenanceModeDto { Map toJson() { final json = {}; json[r'action'] = this.action; - if (this.restoreBackupFilename != null) { - json[r'restoreBackupFilename'] = this.restoreBackupFilename; - } else { - // json[r'restoreBackupFilename'] = null; + if (this.restoreBackupFilename.isPresent) { + final value = this.restoreBackupFilename.value; + json[r'restoreBackupFilename'] = value; } return json; } @@ -63,7 +62,7 @@ class SetMaintenanceModeDto { return SetMaintenanceModeDto( action: MaintenanceAction.fromJson(json[r'action'])!, - restoreBackupFilename: mapValueOfType(json, r'restoreBackupFilename'), + restoreBackupFilename: json.containsKey(r'restoreBackupFilename') ? Optional.present(mapValueOfType(json, r'restoreBackupFilename')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/shared_link_create_dto.dart b/mobile/openapi/lib/model/shared_link_create_dto.dart index a32714d556..239db7f811 100644 --- a/mobile/openapi/lib/model/shared_link_create_dto.dart +++ b/mobile/openapi/lib/model/shared_link_create_dto.dart @@ -13,15 +13,15 @@ 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, + this.albumId = const Optional.absent(), + this.allowDownload = const Optional.present(true), + this.allowUpload = const Optional.absent(), + this.assetIds = const Optional.present(const []), + this.description = const Optional.absent(), + this.expiresAt = const Optional.absent(), + this.password = const Optional.absent(), + this.showMetadata = const Optional.present(true), + this.slug = const Optional.absent(), required this.type, }); @@ -32,10 +32,10 @@ class SharedLinkCreateDto { /// 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; + Optional albumId; /// Allow downloads - bool allowDownload; + Optional allowDownload; /// Allow uploads /// @@ -44,25 +44,25 @@ class SharedLinkCreateDto { /// 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; + Optional allowUpload; /// Asset IDs (for individual assets) - List assetIds; + Optional?> assetIds; /// Link description - String? description; + Optional description; /// Expiration date - DateTime? expiresAt; + Optional expiresAt; /// Link password - String? password; + Optional password; /// Show metadata - bool showMetadata; + Optional showMetadata; /// Custom URL slug - String? slug; + Optional slug; SharedLinkType type; @@ -98,40 +98,43 @@ class SharedLinkCreateDto { Map toJson() { final json = {}; - if (this.albumId != null) { - json[r'albumId'] = this.albumId; - } else { - // json[r'albumId'] = null; + if (this.albumId.isPresent) { + final value = this.albumId.value; + json[r'albumId'] = value; } - json[r'allowDownload'] = this.allowDownload; - if (this.allowUpload != null) { - json[r'allowUpload'] = this.allowUpload; - } else { - // json[r'allowUpload'] = null; + if (this.allowDownload.isPresent) { + final value = this.allowDownload.value; + json[r'allowDownload'] = value; } - json[r'assetIds'] = this.assetIds; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.allowUpload.isPresent) { + final value = this.allowUpload.value; + json[r'allowUpload'] = value; } - if (this.expiresAt != null) { - json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.expiresAt!.millisecondsSinceEpoch - : this.expiresAt!.toUtc().toIso8601String(); - } else { - // json[r'expiresAt'] = null; + if (this.assetIds.isPresent) { + final value = this.assetIds.value; + json[r'assetIds'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - json[r'showMetadata'] = this.showMetadata; - if (this.slug != null) { - json[r'slug'] = this.slug; - } else { - // json[r'slug'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; + } + if (this.showMetadata.isPresent) { + final value = this.showMetadata.value; + json[r'showMetadata'] = value; + } + if (this.slug.isPresent) { + final value = this.slug.value; + json[r'slug'] = value; } json[r'type'] = this.type; return json; @@ -146,17 +149,17 @@ class SharedLinkCreateDto { 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 + albumId: json.containsKey(r'albumId') ? Optional.present(mapValueOfType(json, r'albumId')) : const Optional.absent(), + allowDownload: json.containsKey(r'allowDownload') ? Optional.present(mapValueOfType(json, r'allowDownload')) : const Optional.absent(), + allowUpload: json.containsKey(r'allowUpload') ? Optional.present(mapValueOfType(json, r'allowUpload')) : const Optional.absent(), + assetIds: json.containsKey(r'assetIds') ? Optional.present(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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - password: mapValueOfType(json, r'password'), - showMetadata: mapValueOfType(json, r'showMetadata') ?? true, - slug: mapValueOfType(json, r'slug'), + : const []) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), + slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), type: SharedLinkType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/shared_link_edit_dto.dart b/mobile/openapi/lib/model/shared_link_edit_dto.dart index 11d6cdd52e..a38d28b79b 100644 --- a/mobile/openapi/lib/model/shared_link_edit_dto.dart +++ b/mobile/openapi/lib/model/shared_link_edit_dto.dart @@ -13,14 +13,13 @@ 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, + this.allowDownload = const Optional.absent(), + this.allowUpload = const Optional.absent(), + this.description = const Optional.absent(), + this.expiresAt = const Optional.absent(), + this.password = const Optional.absent(), + this.showMetadata = const Optional.absent(), + this.slug = const Optional.absent(), }); /// Allow downloads @@ -30,7 +29,7 @@ class SharedLinkEditDto { /// 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; + Optional allowDownload; /// Allow uploads /// @@ -39,25 +38,16 @@ class SharedLinkEditDto { /// 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; + Optional allowUpload; /// Link description - String? description; + Optional description; /// Expiration date - DateTime? expiresAt; + Optional expiresAt; /// Link password - String? password; + Optional password; /// Show metadata /// @@ -66,16 +56,15 @@ class SharedLinkEditDto { /// 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; + Optional showMetadata; /// Custom URL slug - String? slug; + Optional 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 && @@ -87,7 +76,6 @@ class SharedLinkEditDto { // 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) + @@ -95,51 +83,39 @@ class SharedLinkEditDto { (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]'; + String toString() => 'SharedLinkEditDto[allowDownload=$allowDownload, allowUpload=$allowUpload, 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.allowDownload.isPresent) { + final value = this.allowDownload.value; + json[r'allowDownload'] = value; } - if (this.allowUpload != null) { - json[r'allowUpload'] = this.allowUpload; - } else { - // json[r'allowUpload'] = null; + if (this.allowUpload.isPresent) { + final value = this.allowUpload.value; + json[r'allowUpload'] = value; } - if (this.changeExpiryTime != null) { - json[r'changeExpiryTime'] = this.changeExpiryTime; - } else { - // json[r'changeExpiryTime'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.expiresAt.isPresent) { + final value = this.expiresAt.value; + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.expiresAt != null) { - json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.expiresAt!.millisecondsSinceEpoch - : this.expiresAt!.toUtc().toIso8601String(); - } else { - // json[r'expiresAt'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.showMetadata.isPresent) { + final value = this.showMetadata.value; + json[r'showMetadata'] = value; } - 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; + if (this.slug.isPresent) { + final value = this.slug.value; + json[r'slug'] = value; } return json; } @@ -153,14 +129,13 @@ class SharedLinkEditDto { 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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - password: mapValueOfType(json, r'password'), - showMetadata: mapValueOfType(json, r'showMetadata'), - slug: mapValueOfType(json, r'slug'), + allowDownload: json.containsKey(r'allowDownload') ? Optional.present(mapValueOfType(json, r'allowDownload')) : const Optional.absent(), + allowUpload: json.containsKey(r'allowUpload') ? Optional.present(mapValueOfType(json, r'allowUpload')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), + slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/shared_link_response_dto.dart b/mobile/openapi/lib/model/shared_link_response_dto.dart index bad0966ca2..8d7cb74eca 100644 --- a/mobile/openapi/lib/model/shared_link_response_dto.dart +++ b/mobile/openapi/lib/model/shared_link_response_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SharedLinkResponseDto { /// Returns a new [SharedLinkResponseDto] instance. SharedLinkResponseDto({ - this.album, + this.album = const Optional.absent(), required this.allowDownload, required this.allowUpload, this.assets = const [], @@ -35,7 +35,7 @@ class SharedLinkResponseDto { /// 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; + Optional album; /// Allow downloads bool allowDownload; @@ -114,10 +114,9 @@ class SharedLinkResponseDto { Map toJson() { final json = {}; - if (this.album != null) { - json[r'album'] = this.album; - } else { - // json[r'album'] = null; + if (this.album.isPresent) { + final value = this.album.value; + json[r'album'] = value; } json[r'allowDownload'] = this.allowDownload; json[r'allowUpload'] = this.allowUpload; @@ -128,27 +127,27 @@ class SharedLinkResponseDto { if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } if (this.expiresAt != null) { json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.expiresAt!.millisecondsSinceEpoch : this.expiresAt!.toUtc().toIso8601String(); } else { - // json[r'expiresAt'] = null; + 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'password'] = null; } json[r'showMetadata'] = this.showMetadata; if (this.slug != null) { json[r'slug'] = this.slug; } else { - // json[r'slug'] = null; + json[r'slug'] = null; } json[r'type'] = this.type; json[r'userId'] = this.userId; @@ -164,7 +163,7 @@ class SharedLinkResponseDto { final json = value.cast(); return SharedLinkResponseDto( - album: AlbumResponseDto.fromJson(json[r'album']), + album: json.containsKey(r'album') ? Optional.present(AlbumResponseDto.fromJson(json[r'album'])) : const Optional.absent(), allowDownload: mapValueOfType(json, r'allowDownload')!, allowUpload: mapValueOfType(json, r'allowUpload')!, assets: AssetResponseDto.listFromJson(json[r'assets']), diff --git a/mobile/openapi/lib/model/shared_links_update.dart b/mobile/openapi/lib/model/shared_links_update.dart index 8e792b4f49..7c5761e343 100644 --- a/mobile/openapi/lib/model/shared_links_update.dart +++ b/mobile/openapi/lib/model/shared_links_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class SharedLinksUpdate { /// Returns a new [SharedLinksUpdate] instance. SharedLinksUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether shared links are enabled @@ -24,7 +24,7 @@ class SharedLinksUpdate { /// 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; + Optional enabled; /// Whether shared links appear in web sidebar /// @@ -33,7 +33,7 @@ class SharedLinksUpdate { /// 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; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is SharedLinksUpdate && @@ -51,15 +51,13 @@ class SharedLinksUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class SharedLinksUpdate { final json = value.cast(); return SharedLinksUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 9bbb4a25f0..dfd4d60ffd 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -13,50 +13,50 @@ part of openapi.api; class SmartSearchDto { /// Returns a new [SmartSearchDto] instance. SmartSearchDto({ - this.albumIds = const [], - this.city, - this.country, - this.createdAfter, - this.createdBefore, - 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, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.language = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.page = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.query = const Optional.absent(), + this.queryAssetId = const Optional.absent(), + this.rating = const Optional.absent(), + this.size = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), + this.withDeleted = const Optional.absent(), + this.withExif = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -65,7 +65,7 @@ class SmartSearchDto { /// 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; + Optional createdAfter; /// Filter by creation date (before) /// @@ -74,7 +74,7 @@ class SmartSearchDto { /// 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; + Optional createdBefore; /// Filter by encoded status /// @@ -83,7 +83,7 @@ class SmartSearchDto { /// 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; + Optional isEncoded; /// Filter by favorite status /// @@ -92,7 +92,7 @@ class SmartSearchDto { /// 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; + Optional isFavorite; /// Filter by motion photo status /// @@ -101,7 +101,7 @@ class SmartSearchDto { /// 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; + Optional isMotion; /// Filter assets not in any album /// @@ -110,7 +110,7 @@ class SmartSearchDto { /// 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; + Optional isNotInAlbum; /// Filter by offline status /// @@ -119,7 +119,7 @@ class SmartSearchDto { /// 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; + Optional isOffline; /// Search language code /// @@ -128,19 +128,19 @@ class SmartSearchDto { /// 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; + Optional language; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -149,7 +149,7 @@ class SmartSearchDto { /// 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; + Optional ocr; /// Page number /// @@ -161,10 +161,10 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? page; + Optional page; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Natural language search query /// @@ -173,7 +173,7 @@ class SmartSearchDto { /// 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; + Optional query; /// Asset ID to use as search reference /// @@ -182,13 +182,13 @@ class SmartSearchDto { /// 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; + Optional queryAssetId; /// Filter by rating [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// Number of results to return /// @@ -200,13 +200,13 @@ class SmartSearchDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - int? size; + Optional size; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -215,7 +215,7 @@ class SmartSearchDto { /// 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; + Optional takenAfter; /// Filter by taken date (before) /// @@ -224,7 +224,7 @@ class SmartSearchDto { /// 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; + Optional takenBefore; /// Filter by trash date (after) /// @@ -233,7 +233,7 @@ class SmartSearchDto { /// 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; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -242,7 +242,7 @@ class SmartSearchDto { /// 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; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -250,7 +250,7 @@ class SmartSearchDto { /// 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; + Optional type; /// Filter by update date (after) /// @@ -259,7 +259,7 @@ class SmartSearchDto { /// 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; + Optional updatedAfter; /// Filter by update date (before) /// @@ -268,7 +268,7 @@ class SmartSearchDto { /// 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; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -276,7 +276,7 @@ class SmartSearchDto { /// 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; + Optional visibility; /// Include deleted assets /// @@ -285,7 +285,7 @@ class SmartSearchDto { /// 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; + Optional withDeleted; /// Include EXIF data in response /// @@ -294,7 +294,7 @@ class SmartSearchDto { /// 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; + Optional withExif; @override bool operator ==(Object other) => identical(this, other) || other is SmartSearchDto && @@ -376,183 +376,157 @@ class SmartSearchDto { 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.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.language != null) { - json[r'language'] = this.language; - } else { - // json[r'language'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.language.isPresent) { + final value = this.language.value; + json[r'language'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - if (this.page != null) { - json[r'page'] = this.page; - } else { - // json[r'page'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - json[r'personIds'] = this.personIds; - if (this.query != null) { - json[r'query'] = this.query; - } else { - // json[r'query'] = null; + if (this.page.isPresent) { + final value = this.page.value; + json[r'page'] = value; } - if (this.queryAssetId != null) { - json[r'queryAssetId'] = this.queryAssetId; - } else { - // json[r'queryAssetId'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.query.isPresent) { + final value = this.query.value; + json[r'query'] = value; } - if (this.size != null) { - json[r'size'] = this.size; - } else { - // json[r'size'] = null; + if (this.queryAssetId.isPresent) { + final value = this.queryAssetId.value; + json[r'queryAssetId'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.size.isPresent) { + final value = this.size.value; + json[r'size'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withDeleted != null) { - json[r'withDeleted'] = this.withDeleted; - } else { - // json[r'withDeleted'] = null; + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.withExif != null) { - json[r'withExif'] = this.withExif; - } else { - // json[r'withExif'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; + } + if (this.withDeleted.isPresent) { + final value = this.withDeleted.value; + json[r'withDeleted'] = value; + } + if (this.withExif.isPresent) { + final value = this.withExif.value; + json[r'withExif'] = value; } return json; } @@ -566,46 +540,46 @@ class SmartSearchDto { final json = value.cast(); return SmartSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - 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: mapValueOfType(json, r'page'), - personIds: json[r'personIds'] is Iterable + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + language: json.containsKey(r'language') ? Optional.present(mapValueOfType(json, r'language')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + page: json.containsKey(r'page') ? Optional.present(json[r'page'] == null ? null : int.parse('${json[r'page']}')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(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: mapValueOfType(json, r'rating'), - size: mapValueOfType(json, r'size'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + query: json.containsKey(r'query') ? Optional.present(mapValueOfType(json, r'query')) : const Optional.absent(), + queryAssetId: json.containsKey(r'queryAssetId') ? Optional.present(mapValueOfType(json, r'queryAssetId')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + size: json.containsKey(r'size') ? Optional.present(json[r'size'] == null ? null : int.parse('${json[r'size']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), - withDeleted: mapValueOfType(json, r'withDeleted'), - withExif: mapValueOfType(json, r'withExif'), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), + withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), + withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/stack_update_dto.dart b/mobile/openapi/lib/model/stack_update_dto.dart index e81c204f97..98787f3a43 100644 --- a/mobile/openapi/lib/model/stack_update_dto.dart +++ b/mobile/openapi/lib/model/stack_update_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class StackUpdateDto { /// Returns a new [StackUpdateDto] instance. StackUpdateDto({ - this.primaryAssetId, + this.primaryAssetId = const Optional.absent(), }); /// Primary asset ID @@ -23,7 +23,7 @@ class StackUpdateDto { /// 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; + Optional primaryAssetId; @override bool operator ==(Object other) => identical(this, other) || other is StackUpdateDto && @@ -39,10 +39,9 @@ class StackUpdateDto { Map toJson() { final json = {}; - if (this.primaryAssetId != null) { - json[r'primaryAssetId'] = this.primaryAssetId; - } else { - // json[r'primaryAssetId'] = null; + if (this.primaryAssetId.isPresent) { + final value = this.primaryAssetId.value; + json[r'primaryAssetId'] = value; } return json; } @@ -56,7 +55,7 @@ class StackUpdateDto { final json = value.cast(); return StackUpdateDto( - primaryAssetId: mapValueOfType(json, r'primaryAssetId'), + primaryAssetId: json.containsKey(r'primaryAssetId') ? Optional.present(mapValueOfType(json, r'primaryAssetId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index f276e3717b..9410d6ab63 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -13,44 +13,44 @@ 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.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, + this.albumIds = const Optional.present(const []), + this.city = const Optional.absent(), + this.country = const Optional.absent(), + this.createdAfter = const Optional.absent(), + this.createdBefore = const Optional.absent(), + this.description = const Optional.absent(), + this.isEncoded = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.isMotion = const Optional.absent(), + this.isNotInAlbum = const Optional.absent(), + this.isOffline = const Optional.absent(), + this.lensModel = const Optional.absent(), + this.libraryId = const Optional.absent(), + this.make = const Optional.absent(), + this.model = const Optional.absent(), + this.ocr = const Optional.absent(), + this.personIds = const Optional.present(const []), + this.rating = const Optional.absent(), + this.state = const Optional.absent(), + this.tagIds = const Optional.present(const []), + this.takenAfter = const Optional.absent(), + this.takenBefore = const Optional.absent(), + this.trashedAfter = const Optional.absent(), + this.trashedBefore = const Optional.absent(), + this.type = const Optional.absent(), + this.updatedAfter = const Optional.absent(), + this.updatedBefore = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Filter by album IDs - List albumIds; + Optional?> albumIds; /// Filter by city name - String? city; + Optional city; /// Filter by country name - String? country; + Optional country; /// Filter by creation date (after) /// @@ -59,7 +59,7 @@ class StatisticsSearchDto { /// 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; + Optional createdAfter; /// Filter by creation date (before) /// @@ -68,7 +68,7 @@ class StatisticsSearchDto { /// 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; + Optional createdBefore; /// Filter by description text /// @@ -77,7 +77,7 @@ class StatisticsSearchDto { /// 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; + Optional description; /// Filter by encoded status /// @@ -86,7 +86,7 @@ class StatisticsSearchDto { /// 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; + Optional isEncoded; /// Filter by favorite status /// @@ -95,7 +95,7 @@ class StatisticsSearchDto { /// 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; + Optional isFavorite; /// Filter by motion photo status /// @@ -104,7 +104,7 @@ class StatisticsSearchDto { /// 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; + Optional isMotion; /// Filter assets not in any album /// @@ -113,7 +113,7 @@ class StatisticsSearchDto { /// 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; + Optional isNotInAlbum; /// Filter by offline status /// @@ -122,19 +122,19 @@ class StatisticsSearchDto { /// 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; + Optional isOffline; /// Filter by lens model - String? lensModel; + Optional lensModel; /// Library ID to filter by - String? libraryId; + Optional libraryId; /// Filter by camera make - String? make; + Optional make; /// Filter by camera model - String? model; + Optional model; /// Filter by OCR text content /// @@ -143,22 +143,22 @@ class StatisticsSearchDto { /// 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; + Optional ocr; /// Filter by person IDs - List personIds; + Optional?> personIds; /// Filter by rating [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// Filter by state/province name - String? state; + Optional state; /// Filter by tag IDs - List? tagIds; + Optional?> tagIds; /// Filter by taken date (after) /// @@ -167,7 +167,7 @@ class StatisticsSearchDto { /// 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; + Optional takenAfter; /// Filter by taken date (before) /// @@ -176,7 +176,7 @@ class StatisticsSearchDto { /// 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; + Optional takenBefore; /// Filter by trash date (after) /// @@ -185,7 +185,7 @@ class StatisticsSearchDto { /// 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; + Optional trashedAfter; /// Filter by trash date (before) /// @@ -194,7 +194,7 @@ class StatisticsSearchDto { /// 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; + Optional trashedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -202,7 +202,7 @@ class StatisticsSearchDto { /// 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; + Optional type; /// Filter by update date (after) /// @@ -211,7 +211,7 @@ class StatisticsSearchDto { /// 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; + Optional updatedAfter; /// Filter by update date (before) /// @@ -220,7 +220,7 @@ class StatisticsSearchDto { /// 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; + Optional updatedBefore; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -228,7 +228,7 @@ class StatisticsSearchDto { /// 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; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is StatisticsSearchDto && @@ -298,153 +298,133 @@ class StatisticsSearchDto { 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.albumIds.isPresent) { + final value = this.albumIds.value; + json[r'albumIds'] = value; } - if (this.country != null) { - json[r'country'] = this.country; - } else { - // json[r'country'] = null; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; } - if (this.createdAfter != null) { - json[r'createdAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdAfter!.millisecondsSinceEpoch - : this.createdAfter!.toUtc().toIso8601String(); - } else { - // json[r'createdAfter'] = null; + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; } - if (this.createdBefore != null) { - json[r'createdBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.createdBefore!.millisecondsSinceEpoch - : this.createdBefore!.toUtc().toIso8601String(); - } else { - // json[r'createdBefore'] = null; + if (this.createdAfter.isPresent) { + final value = this.createdAfter.value; + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.createdBefore.isPresent) { + final value = this.createdBefore.value; + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.isEncoded != null) { - json[r'isEncoded'] = this.isEncoded; - } else { - // json[r'isEncoded'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isEncoded.isPresent) { + final value = this.isEncoded.value; + json[r'isEncoded'] = value; } - if (this.isMotion != null) { - json[r'isMotion'] = this.isMotion; - } else { - // json[r'isMotion'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.isNotInAlbum != null) { - json[r'isNotInAlbum'] = this.isNotInAlbum; - } else { - // json[r'isNotInAlbum'] = null; + if (this.isMotion.isPresent) { + final value = this.isMotion.value; + json[r'isMotion'] = value; } - if (this.isOffline != null) { - json[r'isOffline'] = this.isOffline; - } else { - // json[r'isOffline'] = null; + if (this.isNotInAlbum.isPresent) { + final value = this.isNotInAlbum.value; + json[r'isNotInAlbum'] = value; } - if (this.lensModel != null) { - json[r'lensModel'] = this.lensModel; - } else { - // json[r'lensModel'] = null; + if (this.isOffline.isPresent) { + final value = this.isOffline.value; + json[r'isOffline'] = value; } - if (this.libraryId != null) { - json[r'libraryId'] = this.libraryId; - } else { - // json[r'libraryId'] = null; + if (this.lensModel.isPresent) { + final value = this.lensModel.value; + json[r'lensModel'] = value; } - if (this.make != null) { - json[r'make'] = this.make; - } else { - // json[r'make'] = null; + if (this.libraryId.isPresent) { + final value = this.libraryId.value; + json[r'libraryId'] = value; } - if (this.model != null) { - json[r'model'] = this.model; - } else { - // json[r'model'] = null; + if (this.make.isPresent) { + final value = this.make.value; + json[r'make'] = value; } - if (this.ocr != null) { - json[r'ocr'] = this.ocr; - } else { - // json[r'ocr'] = null; + if (this.model.isPresent) { + final value = this.model.value; + json[r'model'] = value; } - json[r'personIds'] = this.personIds; - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.ocr.isPresent) { + final value = this.ocr.value; + json[r'ocr'] = value; } - if (this.state != null) { - json[r'state'] = this.state; - } else { - // json[r'state'] = null; + if (this.personIds.isPresent) { + final value = this.personIds.value; + json[r'personIds'] = value; } - if (this.tagIds != null) { - json[r'tagIds'] = this.tagIds; - } else { - // json[r'tagIds'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.takenAfter != null) { - json[r'takenAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenAfter!.millisecondsSinceEpoch - : this.takenAfter!.toUtc().toIso8601String(); - } else { - // json[r'takenAfter'] = null; + if (this.state.isPresent) { + final value = this.state.value; + json[r'state'] = value; } - if (this.takenBefore != null) { - json[r'takenBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.takenBefore!.millisecondsSinceEpoch - : this.takenBefore!.toUtc().toIso8601String(); - } else { - // json[r'takenBefore'] = null; + if (this.tagIds.isPresent) { + final value = this.tagIds.value; + json[r'tagIds'] = value; } - if (this.trashedAfter != null) { - json[r'trashedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedAfter!.millisecondsSinceEpoch - : this.trashedAfter!.toUtc().toIso8601String(); - } else { - // json[r'trashedAfter'] = null; + if (this.takenAfter.isPresent) { + final value = this.takenAfter.value; + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.trashedBefore != null) { - json[r'trashedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.trashedBefore!.millisecondsSinceEpoch - : this.trashedBefore!.toUtc().toIso8601String(); - } else { - // json[r'trashedBefore'] = null; + if (this.takenBefore.isPresent) { + final value = this.takenBefore.value; + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.type != null) { - json[r'type'] = this.type; - } else { - // json[r'type'] = null; + if (this.trashedAfter.isPresent) { + final value = this.trashedAfter.value; + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedAfter != null) { - json[r'updatedAfter'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedAfter!.millisecondsSinceEpoch - : this.updatedAfter!.toUtc().toIso8601String(); - } else { - // json[r'updatedAfter'] = null; + if (this.trashedBefore.isPresent) { + final value = this.trashedBefore.value; + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); } - if (this.updatedBefore != null) { - json[r'updatedBefore'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') - ? this.updatedBefore!.millisecondsSinceEpoch - : this.updatedBefore!.toUtc().toIso8601String(); - } else { - // json[r'updatedBefore'] = null; + if (this.type.isPresent) { + final value = this.type.value; + json[r'type'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.updatedAfter.isPresent) { + final value = this.updatedAfter.value; + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.updatedBefore.isPresent) { + final value = this.updatedBefore.value; + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + } + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -458,40 +438,40 @@ class StatisticsSearchDto { final json = value.cast(); return StatisticsSearchDto( - albumIds: json[r'albumIds'] is Iterable + albumIds: json.containsKey(r'albumIds') ? Optional.present(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'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - createdBefore: mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - description: mapValueOfType(json, r'description'), - 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 + : const []) : const Optional.absent(), + city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), + isNotInAlbum: json.containsKey(r'isNotInAlbum') ? Optional.present(mapValueOfType(json, r'isNotInAlbum')) : const Optional.absent(), + isOffline: json.containsKey(r'isOffline') ? Optional.present(mapValueOfType(json, r'isOffline')) : const Optional.absent(), + lensModel: json.containsKey(r'lensModel') ? Optional.present(mapValueOfType(json, r'lensModel')) : const Optional.absent(), + libraryId: json.containsKey(r'libraryId') ? Optional.present(mapValueOfType(json, r'libraryId')) : const Optional.absent(), + make: json.containsKey(r'make') ? Optional.present(mapValueOfType(json, r'make')) : const Optional.absent(), + model: json.containsKey(r'model') ? Optional.present(mapValueOfType(json, r'model')) : const Optional.absent(), + ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType(json, r'ocr')) : const Optional.absent(), + personIds: json.containsKey(r'personIds') ? Optional.present(json[r'personIds'] is Iterable ? (json[r'personIds'] as Iterable).cast().toList(growable: false) - : const [], - rating: mapValueOfType(json, r'rating'), - state: mapValueOfType(json, r'state'), - tagIds: json[r'tagIds'] is Iterable + : const []) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + state: json.containsKey(r'state') ? Optional.present(mapValueOfType(json, r'state')) : const Optional.absent(), + tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) - : const [], - takenAfter: mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - takenBefore: mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedAfter: mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - trashedBefore: mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - type: AssetTypeEnum.fromJson(json[r'type']), - updatedAfter: mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedBefore: mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + : const []) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/sync_ack_delete_dto.dart b/mobile/openapi/lib/model/sync_ack_delete_dto.dart index b72ae8c5a6..76e2b780a7 100644 --- a/mobile/openapi/lib/model/sync_ack_delete_dto.dart +++ b/mobile/openapi/lib/model/sync_ack_delete_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class SyncAckDeleteDto { /// Returns a new [SyncAckDeleteDto] instance. SyncAckDeleteDto({ - this.types = const [], + this.types = const Optional.present(const []), }); /// Sync entity types to delete acks for - List types; + Optional?> types; @override bool operator ==(Object other) => identical(this, other) || other is SyncAckDeleteDto && @@ -33,7 +33,10 @@ class SyncAckDeleteDto { Map toJson() { final json = {}; - json[r'types'] = this.types; + if (this.types.isPresent) { + final value = this.types.value; + json[r'types'] = value; + } return json; } @@ -46,7 +49,7 @@ class SyncAckDeleteDto { final json = value.cast(); return SyncAckDeleteDto( - types: SyncEntityType.listFromJson(json[r'types']), + types: json.containsKey(r'types') ? Optional.present(SyncEntityType.listFromJson(json[r'types'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/sync_album_v1.dart b/mobile/openapi/lib/model/sync_album_v1.dart index 17b2bda02b..0a216cd93d 100644 --- a/mobile/openapi/lib/model/sync_album_v1.dart +++ b/mobile/openapi/lib/model/sync_album_v1.dart @@ -92,7 +92,7 @@ class SyncAlbumV1 { if (this.thumbnailAssetId != null) { json[r'thumbnailAssetId'] = this.thumbnailAssetId; } else { - // json[r'thumbnailAssetId'] = null; + json[r'thumbnailAssetId'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/sync_album_v2.dart b/mobile/openapi/lib/model/sync_album_v2.dart index 67c65a190b..c0d0cf339b 100644 --- a/mobile/openapi/lib/model/sync_album_v2.dart +++ b/mobile/openapi/lib/model/sync_album_v2.dart @@ -85,7 +85,7 @@ class SyncAlbumV2 { if (this.thumbnailAssetId != null) { json[r'thumbnailAssetId'] = this.thumbnailAssetId; } else { - // json[r'thumbnailAssetId'] = null; + json[r'thumbnailAssetId'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/sync_asset_exif_v1.dart b/mobile/openapi/lib/model/sync_asset_exif_v1.dart index caaeed7fb3..fb806322c9 100644 --- a/mobile/openapi/lib/model/sync_asset_exif_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_exif_v1.dart @@ -196,126 +196,126 @@ class SyncAssetExifV1 { if (this.city != null) { json[r'city'] = this.city; } else { - // json[r'city'] = null; + json[r'city'] = null; } if (this.country != null) { json[r'country'] = this.country; } else { - // json[r'country'] = null; + json[r'country'] = null; } if (this.dateTimeOriginal != null) { json[r'dateTimeOriginal'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.dateTimeOriginal!.millisecondsSinceEpoch : this.dateTimeOriginal!.toUtc().toIso8601String(); } else { - // json[r'dateTimeOriginal'] = null; + json[r'dateTimeOriginal'] = null; } if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } if (this.exifImageHeight != null) { json[r'exifImageHeight'] = this.exifImageHeight; } else { - // json[r'exifImageHeight'] = null; + json[r'exifImageHeight'] = null; } if (this.exifImageWidth != null) { json[r'exifImageWidth'] = this.exifImageWidth; } else { - // json[r'exifImageWidth'] = null; + json[r'exifImageWidth'] = null; } if (this.exposureTime != null) { json[r'exposureTime'] = this.exposureTime; } else { - // json[r'exposureTime'] = null; + json[r'exposureTime'] = null; } if (this.fNumber != null) { json[r'fNumber'] = this.fNumber; } else { - // json[r'fNumber'] = null; + json[r'fNumber'] = null; } if (this.fileSizeInByte != null) { json[r'fileSizeInByte'] = this.fileSizeInByte; } else { - // json[r'fileSizeInByte'] = null; + json[r'fileSizeInByte'] = null; } if (this.focalLength != null) { json[r'focalLength'] = this.focalLength; } else { - // json[r'focalLength'] = null; + json[r'focalLength'] = null; } if (this.fps != null) { json[r'fps'] = this.fps; } else { - // json[r'fps'] = null; + json[r'fps'] = null; } if (this.iso != null) { json[r'iso'] = this.iso; } else { - // json[r'iso'] = null; + json[r'iso'] = null; } if (this.latitude != null) { json[r'latitude'] = this.latitude; } else { - // json[r'latitude'] = null; + json[r'latitude'] = null; } if (this.lensModel != null) { json[r'lensModel'] = this.lensModel; } else { - // json[r'lensModel'] = null; + json[r'lensModel'] = null; } if (this.longitude != null) { json[r'longitude'] = this.longitude; } else { - // json[r'longitude'] = null; + json[r'longitude'] = null; } if (this.make != null) { json[r'make'] = this.make; } else { - // json[r'make'] = null; + json[r'make'] = null; } if (this.model != null) { json[r'model'] = this.model; } else { - // json[r'model'] = null; + json[r'model'] = null; } if (this.modifyDate != null) { json[r'modifyDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.modifyDate!.millisecondsSinceEpoch : this.modifyDate!.toUtc().toIso8601String(); } else { - // json[r'modifyDate'] = null; + json[r'modifyDate'] = null; } if (this.orientation != null) { json[r'orientation'] = this.orientation; } else { - // json[r'orientation'] = null; + json[r'orientation'] = null; } if (this.profileDescription != null) { json[r'profileDescription'] = this.profileDescription; } else { - // json[r'profileDescription'] = null; + json[r'profileDescription'] = null; } if (this.projectionType != null) { json[r'projectionType'] = this.projectionType; } else { - // json[r'projectionType'] = null; + json[r'projectionType'] = null; } if (this.rating != null) { json[r'rating'] = this.rating; } else { - // json[r'rating'] = null; + json[r'rating'] = null; } if (this.state != null) { json[r'state'] = this.state; } else { - // json[r'state'] = null; + json[r'state'] = null; } if (this.timeZone != null) { json[r'timeZone'] = this.timeZone; } else { - // json[r'timeZone'] = null; + json[r'timeZone'] = null; } return json; } @@ -337,14 +337,14 @@ class SyncAssetExifV1 { exifImageHeight: mapValueOfType(json, r'exifImageHeight'), exifImageWidth: mapValueOfType(json, r'exifImageWidth'), exposureTime: mapValueOfType(json, r'exposureTime'), - fNumber: (mapValueOfType(json, r'fNumber'))?.toDouble(), + fNumber: mapValueOfType(json, r'fNumber'), fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), - focalLength: (mapValueOfType(json, r'focalLength'))?.toDouble(), - fps: (mapValueOfType(json, r'fps'))?.toDouble(), + focalLength: mapValueOfType(json, r'focalLength'), + fps: mapValueOfType(json, r'fps'), iso: mapValueOfType(json, r'iso'), - latitude: (mapValueOfType(json, r'latitude'))?.toDouble(), + latitude: mapValueOfType(json, r'latitude'), lensModel: mapValueOfType(json, r'lensModel'), - longitude: (mapValueOfType(json, r'longitude'))?.toDouble(), + longitude: mapValueOfType(json, r'longitude'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), modifyDate: mapDateTime(json, r'modifyDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), diff --git a/mobile/openapi/lib/model/sync_asset_face_v1.dart b/mobile/openapi/lib/model/sync_asset_face_v1.dart index c3f74ff2cd..7ccc455f47 100644 --- a/mobile/openapi/lib/model/sync_asset_face_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_face_v1.dart @@ -116,7 +116,7 @@ class SyncAssetFaceV1 { if (this.personId != null) { json[r'personId'] = this.personId; } else { - // json[r'personId'] = null; + json[r'personId'] = null; } json[r'sourceType'] = this.sourceType; return json; diff --git a/mobile/openapi/lib/model/sync_asset_face_v2.dart b/mobile/openapi/lib/model/sync_asset_face_v2.dart index aeefc2ece9..aa5f016176 100644 --- a/mobile/openapi/lib/model/sync_asset_face_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_face_v2.dart @@ -127,7 +127,7 @@ class SyncAssetFaceV2 { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'id'] = this.id; json[r'imageHeight'] = this.imageHeight; @@ -136,7 +136,7 @@ class SyncAssetFaceV2 { if (this.personId != null) { json[r'personId'] = this.personId; } else { - // json[r'personId'] = null; + json[r'personId'] = null; } json[r'sourceType'] = this.sourceType; return json; diff --git a/mobile/openapi/lib/model/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart index 9a7a3a1f16..a9b8ca30cb 100644 --- a/mobile/openapi/lib/model/sync_asset_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_v1.dart @@ -157,38 +157,38 @@ class SyncAssetV1 { ? this.createdAt!.millisecondsSinceEpoch : this.createdAt!.toUtc().toIso8601String(); } else { - // json[r'createdAt'] = null; + json[r'createdAt'] = null; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } if (this.fileCreatedAt != null) { json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { - // json[r'fileCreatedAt'] = null; + json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { - // json[r'fileModifiedAt'] = null; + json[r'fileModifiedAt'] = null; } if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isEdited'] = this.isEdited; @@ -196,38 +196,38 @@ class SyncAssetV1 { if (this.libraryId != null) { json[r'libraryId'] = this.libraryId; } else { - // json[r'libraryId'] = null; + json[r'libraryId'] = null; } if (this.livePhotoVideoId != null) { json[r'livePhotoVideoId'] = this.livePhotoVideoId; } else { - // json[r'livePhotoVideoId'] = null; + json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { - // json[r'localDateTime'] = null; + 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; + json[r'stackId'] = null; } if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + 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; + json[r'width'] = null; } return json; } diff --git a/mobile/openapi/lib/model/sync_asset_v2.dart b/mobile/openapi/lib/model/sync_asset_v2.dart index 7d1dfa298e..987f8ded93 100644 --- a/mobile/openapi/lib/model/sync_asset_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_v2.dart @@ -160,38 +160,38 @@ class SyncAssetV2 { ? this.createdAt!.millisecondsSinceEpoch : this.createdAt!.toUtc().toIso8601String(); } else { - // json[r'createdAt'] = null; + json[r'createdAt'] = null; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.duration != null) { json[r'duration'] = this.duration; } else { - // json[r'duration'] = null; + json[r'duration'] = null; } if (this.fileCreatedAt != null) { json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { - // json[r'fileCreatedAt'] = null; + json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { - // json[r'fileModifiedAt'] = null; + json[r'fileModifiedAt'] = null; } if (this.height != null) { json[r'height'] = this.height; } else { - // json[r'height'] = null; + json[r'height'] = null; } json[r'id'] = this.id; json[r'isEdited'] = this.isEdited; @@ -199,38 +199,38 @@ class SyncAssetV2 { if (this.libraryId != null) { json[r'libraryId'] = this.libraryId; } else { - // json[r'libraryId'] = null; + json[r'libraryId'] = null; } if (this.livePhotoVideoId != null) { json[r'livePhotoVideoId'] = this.livePhotoVideoId; } else { - // json[r'livePhotoVideoId'] = null; + json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { - // json[r'localDateTime'] = null; + 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; + json[r'stackId'] = null; } if (this.thumbhash != null) { json[r'thumbhash'] = this.thumbhash; } else { - // json[r'thumbhash'] = null; + 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; + json[r'width'] = null; } return json; } diff --git a/mobile/openapi/lib/model/sync_auth_user_v1.dart b/mobile/openapi/lib/model/sync_auth_user_v1.dart index c64d82bfbd..0eac7cf9b4 100644 --- a/mobile/openapi/lib/model/sync_auth_user_v1.dart +++ b/mobile/openapi/lib/model/sync_auth_user_v1.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncAuthUserV1 { /// Returns a new [SyncAuthUserV1] instance. SyncAuthUserV1({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.deletedAt, required this.email, required this.hasProfileImage, @@ -28,7 +28,7 @@ class SyncAuthUserV1 { required this.storageLabel, }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User deleted at DateTime? deletedAt; @@ -110,17 +110,16 @@ class SyncAuthUserV1 { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'hasProfileImage'] = this.hasProfileImage; @@ -131,7 +130,7 @@ class SyncAuthUserV1 { if (this.pinCode != null) { json[r'pinCode'] = this.pinCode; } else { - // json[r'pinCode'] = null; + json[r'pinCode'] = null; } json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.profileChangedAt.millisecondsSinceEpoch @@ -139,13 +138,13 @@ class SyncAuthUserV1 { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; if (this.storageLabel != null) { json[r'storageLabel'] = this.storageLabel; } else { - // json[r'storageLabel'] = null; + json[r'storageLabel'] = null; } return json; } @@ -159,7 +158,7 @@ class SyncAuthUserV1 { final json = value.cast(); return SyncAuthUserV1( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, diff --git a/mobile/openapi/lib/model/sync_memory_v1.dart b/mobile/openapi/lib/model/sync_memory_v1.dart index 855340f4d7..f78ad0e7ba 100644 --- a/mobile/openapi/lib/model/sync_memory_v1.dart +++ b/mobile/openapi/lib/model/sync_memory_v1.dart @@ -107,14 +107,14 @@ class SyncMemoryV1 { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } if (this.hideAt != null) { json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.hideAt!.millisecondsSinceEpoch : this.hideAt!.toUtc().toIso8601String(); } else { - // json[r'hideAt'] = null; + json[r'hideAt'] = null; } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; @@ -127,14 +127,14 @@ class SyncMemoryV1 { ? this.seenAt!.millisecondsSinceEpoch : this.seenAt!.toUtc().toIso8601String(); } else { - // json[r'seenAt'] = null; + json[r'seenAt'] = null; } if (this.showAt != null) { json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.showAt!.millisecondsSinceEpoch : this.showAt!.toUtc().toIso8601String(); } else { - // json[r'showAt'] = null; + json[r'showAt'] = null; } json[r'type'] = this.type; json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') diff --git a/mobile/openapi/lib/model/sync_person_v1.dart b/mobile/openapi/lib/model/sync_person_v1.dart index 1bd6f4a160..e79b7cfe16 100644 --- a/mobile/openapi/lib/model/sync_person_v1.dart +++ b/mobile/openapi/lib/model/sync_person_v1.dart @@ -92,12 +92,12 @@ class SyncPersonV1 { ? this.birthDate!.millisecondsSinceEpoch : this.birthDate!.toUtc().toIso8601String(); } else { - // json[r'birthDate'] = null; + json[r'birthDate'] = null; } if (this.color != null) { json[r'color'] = this.color; } else { - // json[r'color'] = null; + json[r'color'] = null; } json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.createdAt.millisecondsSinceEpoch @@ -105,7 +105,7 @@ class SyncPersonV1 { if (this.faceAssetId != null) { json[r'faceAssetId'] = this.faceAssetId; } else { - // json[r'faceAssetId'] = null; + json[r'faceAssetId'] = null; } json[r'id'] = this.id; json[r'isFavorite'] = this.isFavorite; diff --git a/mobile/openapi/lib/model/sync_stream_dto.dart b/mobile/openapi/lib/model/sync_stream_dto.dart index 932477cb15..12dcfb4b84 100644 --- a/mobile/openapi/lib/model/sync_stream_dto.dart +++ b/mobile/openapi/lib/model/sync_stream_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncStreamDto { /// Returns a new [SyncStreamDto] instance. SyncStreamDto({ - this.reset, + this.reset = const Optional.absent(), this.types = const [], }); @@ -24,7 +24,7 @@ class SyncStreamDto { /// 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; + Optional reset; /// Sync request types List types; @@ -45,10 +45,9 @@ class SyncStreamDto { Map toJson() { final json = {}; - if (this.reset != null) { - json[r'reset'] = this.reset; - } else { - // json[r'reset'] = null; + if (this.reset.isPresent) { + final value = this.reset.value; + json[r'reset'] = value; } json[r'types'] = this.types; return json; @@ -63,7 +62,7 @@ class SyncStreamDto { final json = value.cast(); return SyncStreamDto( - reset: mapValueOfType(json, r'reset'), + reset: json.containsKey(r'reset') ? Optional.present(mapValueOfType(json, r'reset')) : const Optional.absent(), types: SyncRequestType.listFromJson(json[r'types']), ); } diff --git a/mobile/openapi/lib/model/sync_user_v1.dart b/mobile/openapi/lib/model/sync_user_v1.dart index 0a81593547..2fda2fbefa 100644 --- a/mobile/openapi/lib/model/sync_user_v1.dart +++ b/mobile/openapi/lib/model/sync_user_v1.dart @@ -13,7 +13,7 @@ part of openapi.api; class SyncUserV1 { /// Returns a new [SyncUserV1] instance. SyncUserV1({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.deletedAt, required this.email, required this.hasProfileImage, @@ -22,7 +22,7 @@ class SyncUserV1 { required this.profileChangedAt, }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User deleted at DateTime? deletedAt; @@ -68,17 +68,16 @@ class SyncUserV1 { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } if (this.deletedAt != null) { json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'hasProfileImage'] = this.hasProfileImage; @@ -99,7 +98,7 @@ class SyncUserV1 { final json = value.cast(); return SyncUserV1( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, 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 index d78f8fadd5..f0d27ffa85 100644 --- a/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart +++ b/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart @@ -15,7 +15,7 @@ class SystemConfigGeneratedFullsizeImageDto { SystemConfigGeneratedFullsizeImageDto({ required this.enabled, required this.format, - this.progressive, + this.progressive = const Optional.absent(), required this.quality, }); @@ -31,7 +31,7 @@ class SystemConfigGeneratedFullsizeImageDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? progressive; + Optional progressive; /// Quality /// @@ -61,10 +61,9 @@ class SystemConfigGeneratedFullsizeImageDto { final json = {}; json[r'enabled'] = this.enabled; json[r'format'] = this.format; - if (this.progressive != null) { - json[r'progressive'] = this.progressive; - } else { - // json[r'progressive'] = null; + if (this.progressive.isPresent) { + final value = this.progressive.value; + json[r'progressive'] = value; } json[r'quality'] = this.quality; return json; @@ -81,7 +80,7 @@ class SystemConfigGeneratedFullsizeImageDto { return SystemConfigGeneratedFullsizeImageDto( enabled: mapValueOfType(json, r'enabled')!, format: ImageFormat.fromJson(json[r'format'])!, - progressive: mapValueOfType(json, r'progressive'), + progressive: json.containsKey(r'progressive') ? Optional.present(mapValueOfType(json, r'progressive')) : const Optional.absent(), quality: mapValueOfType(json, r'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 index 2571c0cab0..6aff16322c 100644 --- a/mobile/openapi/lib/model/system_config_generated_image_dto.dart +++ b/mobile/openapi/lib/model/system_config_generated_image_dto.dart @@ -14,7 +14,7 @@ class SystemConfigGeneratedImageDto { /// Returns a new [SystemConfigGeneratedImageDto] instance. SystemConfigGeneratedImageDto({ required this.format, - this.progressive, + this.progressive = const Optional.absent(), required this.quality, required this.size, }); @@ -28,7 +28,7 @@ class SystemConfigGeneratedImageDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - bool? progressive; + Optional progressive; /// Quality /// @@ -63,10 +63,9 @@ class SystemConfigGeneratedImageDto { Map toJson() { final json = {}; json[r'format'] = this.format; - if (this.progressive != null) { - json[r'progressive'] = this.progressive; - } else { - // json[r'progressive'] = null; + if (this.progressive.isPresent) { + final value = this.progressive.value; + json[r'progressive'] = value; } json[r'quality'] = this.quality; json[r'size'] = this.size; @@ -83,7 +82,7 @@ class SystemConfigGeneratedImageDto { return SystemConfigGeneratedImageDto( format: ImageFormat.fromJson(json[r'format'])!, - progressive: mapValueOfType(json, r'progressive'), + progressive: json.containsKey(r'progressive') ? Optional.present(mapValueOfType(json, r'progressive')) : const Optional.absent(), quality: mapValueOfType(json, r'quality')!, size: mapValueOfType(json, r'size')!, ); diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart index c65de03391..44eefe605c 100644 --- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart +++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart @@ -167,7 +167,7 @@ class SystemConfigOAuthDto { if (this.defaultStorageQuota != null) { json[r'defaultStorageQuota'] = this.defaultStorageQuota; } else { - // json[r'defaultStorageQuota'] = null; + json[r'defaultStorageQuota'] = null; } json[r'enabled'] = this.enabled; json[r'endSessionEndpoint'] = this.endSessionEndpoint; diff --git a/mobile/openapi/lib/model/tag_create_dto.dart b/mobile/openapi/lib/model/tag_create_dto.dart index e05b29f1ed..e46f3fc8b6 100644 --- a/mobile/openapi/lib/model/tag_create_dto.dart +++ b/mobile/openapi/lib/model/tag_create_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class TagCreateDto { /// Returns a new [TagCreateDto] instance. TagCreateDto({ - this.color, + this.color = const Optional.absent(), required this.name, - this.parentId, + this.parentId = const Optional.absent(), }); /// Tag color (hex) - String? color; + Optional color; /// Tag name String name; /// Parent tag ID - String? parentId; + Optional parentId; @override bool operator ==(Object other) => identical(this, other) || other is TagCreateDto && @@ -45,16 +45,14 @@ class TagCreateDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } json[r'name'] = this.name; - if (this.parentId != null) { - json[r'parentId'] = this.parentId; - } else { - // json[r'parentId'] = null; + if (this.parentId.isPresent) { + final value = this.parentId.value; + json[r'parentId'] = value; } return json; } @@ -68,9 +66,9 @@ class TagCreateDto { final json = value.cast(); return TagCreateDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, - parentId: mapValueOfType(json, r'parentId'), + parentId: json.containsKey(r'parentId') ? Optional.present(mapValueOfType(json, r'parentId')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart index 9a71912153..79a89f6d33 100644 --- a/mobile/openapi/lib/model/tag_response_dto.dart +++ b/mobile/openapi/lib/model/tag_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class TagResponseDto { /// Returns a new [TagResponseDto] instance. TagResponseDto({ - this.color, + this.color = const Optional.absent(), required this.createdAt, required this.id, required this.name, - this.parentId, + this.parentId = const Optional.absent(), required this.updatedAt, required this.value, }); @@ -29,7 +29,7 @@ class TagResponseDto { /// 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; + Optional color; /// Creation date DateTime createdAt; @@ -47,7 +47,7 @@ class TagResponseDto { /// 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; + Optional parentId; /// Last update date DateTime updatedAt; @@ -81,18 +81,16 @@ class TagResponseDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } 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; + if (this.parentId.isPresent) { + final value = this.parentId.value; + json[r'parentId'] = value; } json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); json[r'value'] = this.value; @@ -108,11 +106,11 @@ class TagResponseDto { final json = value.cast(); return TagResponseDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), createdAt: mapDateTime(json, r'createdAt', r'')!, id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, - parentId: mapValueOfType(json, r'parentId'), + parentId: json.containsKey(r'parentId') ? Optional.present(mapValueOfType(json, r'parentId')) : const Optional.absent(), updatedAt: mapDateTime(json, r'updatedAt', r'')!, value: mapValueOfType(json, r'value')!, ); diff --git a/mobile/openapi/lib/model/tag_update_dto.dart b/mobile/openapi/lib/model/tag_update_dto.dart index 98cb6af523..d66bb9097e 100644 --- a/mobile/openapi/lib/model/tag_update_dto.dart +++ b/mobile/openapi/lib/model/tag_update_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class TagUpdateDto { /// Returns a new [TagUpdateDto] instance. TagUpdateDto({ - this.color, + this.color = const Optional.absent(), }); /// Tag color (hex) - String? color; + Optional color; @override bool operator ==(Object other) => identical(this, other) || other is TagUpdateDto && @@ -33,10 +33,9 @@ class TagUpdateDto { Map toJson() { final json = {}; - if (this.color != null) { - json[r'color'] = this.color; - } else { - // json[r'color'] = null; + if (this.color.isPresent) { + final value = this.color.value; + json[r'color'] = value; } return json; } @@ -50,7 +49,7 @@ class TagUpdateDto { final json = value.cast(); return TagUpdateDto( - color: mapValueOfType(json, r'color'), + color: json.containsKey(r'color') ? Optional.present(mapValueOfType(json, r'color')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/tags_update.dart b/mobile/openapi/lib/model/tags_update.dart index e42357e3d4..9a9e78f1d3 100644 --- a/mobile/openapi/lib/model/tags_update.dart +++ b/mobile/openapi/lib/model/tags_update.dart @@ -13,8 +13,8 @@ part of openapi.api; class TagsUpdate { /// Returns a new [TagsUpdate] instance. TagsUpdate({ - this.enabled, - this.sidebarWeb, + this.enabled = const Optional.absent(), + this.sidebarWeb = const Optional.absent(), }); /// Whether tags are enabled @@ -24,7 +24,7 @@ class TagsUpdate { /// 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; + Optional enabled; /// Whether tags appear in web sidebar /// @@ -33,7 +33,7 @@ class TagsUpdate { /// 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; + Optional sidebarWeb; @override bool operator ==(Object other) => identical(this, other) || other is TagsUpdate && @@ -51,15 +51,13 @@ class TagsUpdate { Map toJson() { final json = {}; - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.sidebarWeb != null) { - json[r'sidebarWeb'] = this.sidebarWeb; - } else { - // json[r'sidebarWeb'] = null; + if (this.sidebarWeb.isPresent) { + final value = this.sidebarWeb.value; + json[r'sidebarWeb'] = value; } return json; } @@ -73,8 +71,8 @@ class TagsUpdate { final json = value.cast(); return TagsUpdate( - enabled: mapValueOfType(json, r'enabled'), - sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType(json, r'sidebarWeb')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart index 32e08a9ea0..154ca97504 100644 --- a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart +++ b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart @@ -13,8 +13,8 @@ part of openapi.api; class TimeBucketAssetResponseDto { /// Returns a new [TimeBucketAssetResponseDto] instance. TimeBucketAssetResponseDto({ - this.city = const [], - this.country = const [], + this.city = const Optional.present(const []), + this.country = const Optional.present(const []), this.createdAt = const [], this.duration = const [], this.fileCreatedAt = const [], @@ -22,29 +22,29 @@ class TimeBucketAssetResponseDto { this.isFavorite = const [], this.isImage = const [], this.isTrashed = const [], - this.latitude = const [], + this.latitude = const Optional.present(const []), this.livePhotoVideoId = const [], this.localOffsetHours = const [], - this.longitude = const [], + this.longitude = const Optional.present(const []), this.ownerId = const [], this.projectionType = const [], this.ratio = const [], - this.stack = const [], + this.stack = const Optional.present(const []), this.thumbhash = const [], this.visibility = const [], }); /// Array of city names extracted from EXIF GPS data - List city; + Optional?> city; /// Array of country names extracted from EXIF GPS data - List country; + Optional?> country; /// Array of UTC timestamps when each asset was originally uploaded to Immich List createdAt; /// Array of video/gif durations in milliseconds (null for static images) - List duration; + List duration; /// Array of file creation timestamps in UTC List fileCreatedAt; @@ -62,31 +62,31 @@ class TimeBucketAssetResponseDto { List isTrashed; /// Array of latitude coordinates extracted from EXIF GPS data - List latitude; + Optional?> latitude; /// Array of live photo video asset IDs (null for non-live photos) - List livePhotoVideoId; + 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; + Optional?> longitude; /// Array of owner IDs for each asset List ownerId; /// Array of projection types for 360° content (e.g., \"EQUIRECTANGULAR\", \"CUBEFACE\", \"CYLINDRICAL\") - List projectionType; + 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; + Optional?>?> stack; /// Array of BlurHash strings for generating asset previews (base64 encoded) - List thumbhash; + List thumbhash; /// Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED) List visibility; @@ -141,8 +141,14 @@ class TimeBucketAssetResponseDto { Map toJson() { final json = {}; - json[r'city'] = this.city; - json[r'country'] = this.country; + if (this.city.isPresent) { + final value = this.city.value; + json[r'city'] = value; + } + if (this.country.isPresent) { + final value = this.country.value; + json[r'country'] = value; + } json[r'createdAt'] = this.createdAt; json[r'duration'] = this.duration; json[r'fileCreatedAt'] = this.fileCreatedAt; @@ -150,14 +156,23 @@ class TimeBucketAssetResponseDto { json[r'isFavorite'] = this.isFavorite; json[r'isImage'] = this.isImage; json[r'isTrashed'] = this.isTrashed; - json[r'latitude'] = this.latitude; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; + } json[r'livePhotoVideoId'] = this.livePhotoVideoId; json[r'localOffsetHours'] = this.localOffsetHours; - json[r'longitude'] = this.longitude; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; + } json[r'ownerId'] = this.ownerId; json[r'projectionType'] = this.projectionType; json[r'ratio'] = this.ratio; - json[r'stack'] = this.stack; + if (this.stack.isPresent) { + final value = this.stack.value; + json[r'stack'] = value; + } json[r'thumbhash'] = this.thumbhash; json[r'visibility'] = this.visibility; return json; @@ -172,12 +187,12 @@ class TimeBucketAssetResponseDto { final json = value.cast(); return TimeBucketAssetResponseDto( - city: json[r'city'] is Iterable + city: json.containsKey(r'city') ? Optional.present(json[r'city'] is Iterable ? (json[r'city'] as Iterable).cast().toList(growable: false) - : const [], - country: json[r'country'] is Iterable + : const []) : const Optional.absent(), + country: json.containsKey(r'country') ? Optional.present(json[r'country'] is Iterable ? (json[r'country'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), createdAt: json[r'createdAt'] is Iterable ? (json[r'createdAt'] as Iterable).cast().toList(growable: false) : const [], @@ -199,18 +214,18 @@ class TimeBucketAssetResponseDto { isTrashed: json[r'isTrashed'] is Iterable ? (json[r'isTrashed'] as Iterable).cast().toList(growable: false) : const [], - latitude: json[r'latitude'] is Iterable + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] is Iterable ? (json[r'latitude'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), 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 + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] is Iterable ? (json[r'longitude'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), ownerId: json[r'ownerId'] is Iterable ? (json[r'ownerId'] as Iterable).cast().toList(growable: false) : const [], @@ -220,11 +235,11 @@ class TimeBucketAssetResponseDto { ratio: json[r'ratio'] is Iterable ? (json[r'ratio'] as Iterable).cast().toList(growable: false) : const [], - stack: json[r'stack'] is List + stack: json.containsKey(r'stack') ? Optional.present(json[r'stack'] is List ? (json[r'stack'] as List).map((e) => - e == null ? null : (e as List).cast() + e == null ? null : (e as List).map((value) => value as String).toList(growable: false) ).toList() - : const [], + : const []) : const Optional.absent(), thumbhash: json[r'thumbhash'] is Iterable ? (json[r'thumbhash'] as Iterable).cast().toList(growable: false) : const [], diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart index ae4a5c1f87..8995a69656 100644 --- a/mobile/openapi/lib/model/update_album_dto.dart +++ b/mobile/openapi/lib/model/update_album_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class UpdateAlbumDto { /// Returns a new [UpdateAlbumDto] instance. UpdateAlbumDto({ - this.albumName, - this.albumThumbnailAssetId, - this.description, - this.isActivityEnabled, - this.order, + this.albumName = const Optional.absent(), + this.albumThumbnailAssetId = const Optional.absent(), + this.description = const Optional.absent(), + this.isActivityEnabled = const Optional.absent(), + this.order = const Optional.absent(), }); /// Album name @@ -27,7 +27,7 @@ class UpdateAlbumDto { /// 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; + Optional albumName; /// Album thumbnail asset ID /// @@ -36,7 +36,7 @@ class UpdateAlbumDto { /// 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; + Optional albumThumbnailAssetId; /// Album description /// @@ -45,7 +45,7 @@ class UpdateAlbumDto { /// 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; + Optional description; /// Enable activity feed /// @@ -54,7 +54,7 @@ class UpdateAlbumDto { /// 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; + Optional isActivityEnabled; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -62,7 +62,7 @@ class UpdateAlbumDto { /// 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; + Optional order; @override bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto && @@ -86,30 +86,25 @@ class UpdateAlbumDto { Map toJson() { final json = {}; - if (this.albumName != null) { - json[r'albumName'] = this.albumName; - } else { - // json[r'albumName'] = null; + if (this.albumName.isPresent) { + final value = this.albumName.value; + json[r'albumName'] = value; } - if (this.albumThumbnailAssetId != null) { - json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; - } else { - // json[r'albumThumbnailAssetId'] = null; + if (this.albumThumbnailAssetId.isPresent) { + final value = this.albumThumbnailAssetId.value; + json[r'albumThumbnailAssetId'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isActivityEnabled != null) { - json[r'isActivityEnabled'] = this.isActivityEnabled; - } else { - // json[r'isActivityEnabled'] = null; + if (this.isActivityEnabled.isPresent) { + final value = this.isActivityEnabled.value; + json[r'isActivityEnabled'] = value; } - if (this.order != null) { - json[r'order'] = this.order; - } else { - // json[r'order'] = null; + if (this.order.isPresent) { + final value = this.order.value; + json[r'order'] = value; } return json; } @@ -123,11 +118,11 @@ class UpdateAlbumDto { 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']), + albumName: json.containsKey(r'albumName') ? Optional.present(mapValueOfType(json, r'albumName')) : const Optional.absent(), + albumThumbnailAssetId: json.containsKey(r'albumThumbnailAssetId') ? Optional.present(mapValueOfType(json, r'albumThumbnailAssetId')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isActivityEnabled: json.containsKey(r'isActivityEnabled') ? Optional.present(mapValueOfType(json, r'isActivityEnabled')) : const Optional.absent(), + order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index 2c4c3352ea..73a8498494 100644 --- a/mobile/openapi/lib/model/update_asset_dto.dart +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -13,14 +13,14 @@ 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, + this.dateTimeOriginal = const Optional.absent(), + this.description = const Optional.absent(), + this.isFavorite = const Optional.absent(), + this.latitude = const Optional.absent(), + this.livePhotoVideoId = const Optional.absent(), + this.longitude = const Optional.absent(), + this.rating = const Optional.absent(), + this.visibility = const Optional.absent(), }); /// Original date and time @@ -30,7 +30,7 @@ class UpdateAssetDto { /// 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; + Optional dateTimeOriginal; /// Asset description /// @@ -39,7 +39,7 @@ class UpdateAssetDto { /// 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; + Optional description; /// Mark as favorite /// @@ -48,7 +48,7 @@ class UpdateAssetDto { /// 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; + Optional isFavorite; /// Latitude coordinate /// @@ -60,10 +60,10 @@ class UpdateAssetDto { /// 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; + Optional latitude; /// Live photo video ID - String? livePhotoVideoId; + Optional livePhotoVideoId; /// Longitude coordinate /// @@ -75,13 +75,13 @@ class UpdateAssetDto { /// 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; + Optional longitude; /// Rating in range [1-5], or null for unrated /// - /// Minimum value: -1 + /// Minimum value: 1 /// Maximum value: 5 - int? rating; + Optional rating; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -89,7 +89,7 @@ class UpdateAssetDto { /// 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; + Optional visibility; @override bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && @@ -119,45 +119,37 @@ class UpdateAssetDto { Map toJson() { final json = {}; - if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = this.dateTimeOriginal; - } else { - // json[r'dateTimeOriginal'] = null; + if (this.dateTimeOriginal.isPresent) { + final value = this.dateTimeOriginal.value; + json[r'dateTimeOriginal'] = value; } - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.isFavorite != null) { - json[r'isFavorite'] = this.isFavorite; - } else { - // json[r'isFavorite'] = null; + if (this.isFavorite.isPresent) { + final value = this.isFavorite.value; + json[r'isFavorite'] = value; } - if (this.latitude != null) { - json[r'latitude'] = this.latitude; - } else { - // json[r'latitude'] = null; + if (this.latitude.isPresent) { + final value = this.latitude.value; + json[r'latitude'] = value; } - if (this.livePhotoVideoId != null) { - json[r'livePhotoVideoId'] = this.livePhotoVideoId; - } else { - // json[r'livePhotoVideoId'] = null; + if (this.livePhotoVideoId.isPresent) { + final value = this.livePhotoVideoId.value; + json[r'livePhotoVideoId'] = value; } - if (this.longitude != null) { - json[r'longitude'] = this.longitude; - } else { - // json[r'longitude'] = null; + if (this.longitude.isPresent) { + final value = this.longitude.value; + json[r'longitude'] = value; } - if (this.rating != null) { - json[r'rating'] = this.rating; - } else { - // json[r'rating'] = null; + if (this.rating.isPresent) { + final value = this.rating.value; + json[r'rating'] = value; } - if (this.visibility != null) { - json[r'visibility'] = this.visibility; - } else { - // json[r'visibility'] = null; + if (this.visibility.isPresent) { + final value = this.visibility.value; + json[r'visibility'] = value; } return json; } @@ -171,14 +163,14 @@ class UpdateAssetDto { 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: mapValueOfType(json, r'rating'), - visibility: AssetVisibility.fromJson(json[r'visibility']), + dateTimeOriginal: json.containsKey(r'dateTimeOriginal') ? Optional.present(mapValueOfType(json, r'dateTimeOriginal')) : const Optional.absent(), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), + latitude: json.containsKey(r'latitude') ? Optional.present(json[r'latitude'] == null ? null : num.parse('${json[r'latitude']}')) : const Optional.absent(), + livePhotoVideoId: json.containsKey(r'livePhotoVideoId') ? Optional.present(mapValueOfType(json, r'livePhotoVideoId')) : const Optional.absent(), + longitude: json.containsKey(r'longitude') ? Optional.present(json[r'longitude'] == null ? null : num.parse('${json[r'longitude']}')) : const Optional.absent(), + rating: json.containsKey(r'rating') ? Optional.present(json[r'rating'] == null ? null : int.parse('${json[r'rating']}')) : const Optional.absent(), + visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/update_library_dto.dart b/mobile/openapi/lib/model/update_library_dto.dart index 276d43ecd9..44aa042f35 100644 --- a/mobile/openapi/lib/model/update_library_dto.dart +++ b/mobile/openapi/lib/model/update_library_dto.dart @@ -13,16 +13,16 @@ part of openapi.api; class UpdateLibraryDto { /// Returns a new [UpdateLibraryDto] instance. UpdateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], - this.name, + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), + this.name = const Optional.absent(), }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths (max 128) - List importPaths; + Optional?> importPaths; /// Library name /// @@ -31,7 +31,7 @@ class UpdateLibraryDto { /// 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; + Optional name; @override bool operator ==(Object other) => identical(this, other) || other is UpdateLibraryDto && @@ -51,12 +51,17 @@ class UpdateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } return json; } @@ -70,13 +75,13 @@ class UpdateLibraryDto { final json = value.cast(); return UpdateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], - name: mapValueOfType(json, r'name'), + : const []) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart index 462b82c3e0..fbf2cc02e4 100644 --- a/mobile/openapi/lib/model/usage_by_user_dto.dart +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -97,7 +97,7 @@ class UsageByUserDto { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } json[r'usage'] = this.usage; json[r'usagePhotos'] = this.usagePhotos; diff --git a/mobile/openapi/lib/model/user_admin_create_dto.dart b/mobile/openapi/lib/model/user_admin_create_dto.dart index 54da0b0566..8ed867c2cf 100644 --- a/mobile/openapi/lib/model/user_admin_create_dto.dart +++ b/mobile/openapi/lib/model/user_admin_create_dto.dart @@ -13,19 +13,19 @@ part of openapi.api; class UserAdminCreateDto { /// Returns a new [UserAdminCreateDto] instance. UserAdminCreateDto({ - this.avatarColor, + this.avatarColor = const Optional.absent(), required this.email, - this.isAdmin, + this.isAdmin = const Optional.absent(), required this.name, - this.notify, + this.notify = const Optional.absent(), required this.password, - this.pinCode, - this.quotaSizeInBytes, - this.shouldChangePassword, - this.storageLabel, + this.pinCode = const Optional.absent(), + this.quotaSizeInBytes = const Optional.absent(), + this.shouldChangePassword = const Optional.absent(), + this.storageLabel = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email String email; @@ -37,7 +37,7 @@ class UserAdminCreateDto { /// 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; + Optional isAdmin; /// User name String name; @@ -49,19 +49,19 @@ class UserAdminCreateDto { /// 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; + Optional notify; /// User password String password; /// PIN code - String? pinCode; + Optional pinCode; /// Storage quota in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? quotaSizeInBytes; + Optional quotaSizeInBytes; /// Require password change on next login /// @@ -70,10 +70,10 @@ class UserAdminCreateDto { /// 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; + Optional shouldChangePassword; /// Storage label - String? storageLabel; + Optional storageLabel; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto && @@ -107,43 +107,36 @@ class UserAdminCreateDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } json[r'email'] = this.email; - if (this.isAdmin != null) { - json[r'isAdmin'] = this.isAdmin; - } else { - // json[r'isAdmin'] = null; + if (this.isAdmin.isPresent) { + final value = this.isAdmin.value; + json[r'isAdmin'] = value; } json[r'name'] = this.name; - if (this.notify != null) { - json[r'notify'] = this.notify; - } else { - // json[r'notify'] = null; + if (this.notify.isPresent) { + final value = this.notify.value; + json[r'notify'] = value; } json[r'password'] = this.password; - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } - if (this.quotaSizeInBytes != null) { - json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; - } else { - // json[r'quotaSizeInBytes'] = null; + if (this.quotaSizeInBytes.isPresent) { + final value = this.quotaSizeInBytes.value; + json[r'quotaSizeInBytes'] = value; } - if (this.shouldChangePassword != null) { - json[r'shouldChangePassword'] = this.shouldChangePassword; - } else { - // json[r'shouldChangePassword'] = null; + if (this.shouldChangePassword.isPresent) { + final value = this.shouldChangePassword.value; + json[r'shouldChangePassword'] = value; } - if (this.storageLabel != null) { - json[r'storageLabel'] = this.storageLabel; - } else { - // json[r'storageLabel'] = null; + if (this.storageLabel.isPresent) { + final value = this.storageLabel.value; + json[r'storageLabel'] = value; } return json; } @@ -157,16 +150,16 @@ class UserAdminCreateDto { final json = value.cast(); return UserAdminCreateDto( - avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), email: mapValueOfType(json, r'email')!, - isAdmin: mapValueOfType(json, r'isAdmin'), + isAdmin: json.containsKey(r'isAdmin') ? Optional.present(mapValueOfType(json, r'isAdmin')) : const Optional.absent(), name: mapValueOfType(json, r'name')!, - notify: mapValueOfType(json, r'notify'), + notify: json.containsKey(r'notify') ? Optional.present(mapValueOfType(json, r'notify')) : const Optional.absent(), 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'), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), + quotaSizeInBytes: json.containsKey(r'quotaSizeInBytes') ? Optional.present(json[r'quotaSizeInBytes'] == null ? null : int.parse('${json[r'quotaSizeInBytes']}')) : const Optional.absent(), + shouldChangePassword: json.containsKey(r'shouldChangePassword') ? Optional.present(mapValueOfType(json, r'shouldChangePassword')) : const Optional.absent(), + storageLabel: json.containsKey(r'storageLabel') ? Optional.present(mapValueOfType(json, r'storageLabel')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_admin_delete_dto.dart b/mobile/openapi/lib/model/user_admin_delete_dto.dart index 6be70f37b7..8d7ab73076 100644 --- a/mobile/openapi/lib/model/user_admin_delete_dto.dart +++ b/mobile/openapi/lib/model/user_admin_delete_dto.dart @@ -13,7 +13,7 @@ part of openapi.api; class UserAdminDeleteDto { /// Returns a new [UserAdminDeleteDto] instance. UserAdminDeleteDto({ - this.force, + this.force = const Optional.absent(), }); /// Force delete even if user has assets @@ -23,7 +23,7 @@ class UserAdminDeleteDto { /// 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; + Optional force; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminDeleteDto && @@ -39,10 +39,9 @@ class UserAdminDeleteDto { Map toJson() { final json = {}; - if (this.force != null) { - json[r'force'] = this.force; - } else { - // json[r'force'] = null; + if (this.force.isPresent) { + final value = this.force.value; + json[r'force'] = value; } return json; } @@ -56,7 +55,7 @@ class UserAdminDeleteDto { final json = value.cast(); return UserAdminDeleteDto( - force: mapValueOfType(json, r'force'), + force: json.containsKey(r'force') ? Optional.present(mapValueOfType(json, r'force')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_admin_response_dto.dart b/mobile/openapi/lib/model/user_admin_response_dto.dart index 09f8cedce4..733f217b68 100644 --- a/mobile/openapi/lib/model/user_admin_response_dto.dart +++ b/mobile/openapi/lib/model/user_admin_response_dto.dart @@ -141,7 +141,7 @@ class UserAdminResponseDto { ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { - // json[r'deletedAt'] = null; + json[r'deletedAt'] = null; } json[r'email'] = this.email; json[r'id'] = this.id; @@ -149,7 +149,7 @@ class UserAdminResponseDto { if (this.license != null) { json[r'license'] = this.license; } else { - // json[r'license'] = null; + json[r'license'] = null; } json[r'name'] = this.name; json[r'oauthId'] = this.oauthId; @@ -158,19 +158,19 @@ class UserAdminResponseDto { if (this.quotaSizeInBytes != null) { json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; } else { - // json[r'quotaSizeInBytes'] = null; + json[r'quotaSizeInBytes'] = null; } if (this.quotaUsageInBytes != null) { json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; } else { - // json[r'quotaUsageInBytes'] = null; + 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'storageLabel'] = null; } json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') ? this.updatedAt.millisecondsSinceEpoch diff --git a/mobile/openapi/lib/model/user_admin_update_dto.dart b/mobile/openapi/lib/model/user_admin_update_dto.dart index 0c33a46139..f1b91d8e61 100644 --- a/mobile/openapi/lib/model/user_admin_update_dto.dart +++ b/mobile/openapi/lib/model/user_admin_update_dto.dart @@ -13,18 +13,18 @@ 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, + this.avatarColor = const Optional.absent(), + this.email = const Optional.absent(), + this.isAdmin = const Optional.absent(), + this.name = const Optional.absent(), + this.password = const Optional.absent(), + this.pinCode = const Optional.absent(), + this.quotaSizeInBytes = const Optional.absent(), + this.shouldChangePassword = const Optional.absent(), + this.storageLabel = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email /// @@ -33,7 +33,7 @@ class UserAdminUpdateDto { /// 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; + Optional email; /// Grant admin privileges /// @@ -42,7 +42,7 @@ class UserAdminUpdateDto { /// 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; + Optional isAdmin; /// User name /// @@ -51,7 +51,7 @@ class UserAdminUpdateDto { /// 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; + Optional name; /// User password /// @@ -60,16 +60,16 @@ class UserAdminUpdateDto { /// 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; + Optional password; /// PIN code - String? pinCode; + Optional pinCode; /// Storage quota in bytes /// /// Minimum value: 0 /// Maximum value: 9007199254740991 - int? quotaSizeInBytes; + Optional quotaSizeInBytes; /// Require password change on next login /// @@ -78,10 +78,10 @@ class UserAdminUpdateDto { /// 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; + Optional shouldChangePassword; /// Storage label - String? storageLabel; + Optional storageLabel; @override bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto && @@ -113,50 +113,41 @@ class UserAdminUpdateDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } - if (this.email != null) { - json[r'email'] = this.email; - } else { - // json[r'email'] = null; + if (this.email.isPresent) { + final value = this.email.value; + json[r'email'] = value; } - if (this.isAdmin != null) { - json[r'isAdmin'] = this.isAdmin; - } else { - // json[r'isAdmin'] = null; + if (this.isAdmin.isPresent) { + final value = this.isAdmin.value; + json[r'isAdmin'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } - if (this.pinCode != null) { - json[r'pinCode'] = this.pinCode; - } else { - // json[r'pinCode'] = null; + if (this.pinCode.isPresent) { + final value = this.pinCode.value; + json[r'pinCode'] = value; } - if (this.quotaSizeInBytes != null) { - json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; - } else { - // json[r'quotaSizeInBytes'] = null; + if (this.quotaSizeInBytes.isPresent) { + final value = this.quotaSizeInBytes.value; + json[r'quotaSizeInBytes'] = value; } - if (this.shouldChangePassword != null) { - json[r'shouldChangePassword'] = this.shouldChangePassword; - } else { - // json[r'shouldChangePassword'] = null; + if (this.shouldChangePassword.isPresent) { + final value = this.shouldChangePassword.value; + json[r'shouldChangePassword'] = value; } - if (this.storageLabel != null) { - json[r'storageLabel'] = this.storageLabel; - } else { - // json[r'storageLabel'] = null; + if (this.storageLabel.isPresent) { + final value = this.storageLabel.value; + json[r'storageLabel'] = value; } return json; } @@ -170,15 +161,15 @@ class UserAdminUpdateDto { 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'), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), + email: json.containsKey(r'email') ? Optional.present(mapValueOfType(json, r'email')) : const Optional.absent(), + isAdmin: json.containsKey(r'isAdmin') ? Optional.present(mapValueOfType(json, r'isAdmin')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), + pinCode: json.containsKey(r'pinCode') ? Optional.present(mapValueOfType(json, r'pinCode')) : const Optional.absent(), + quotaSizeInBytes: json.containsKey(r'quotaSizeInBytes') ? Optional.present(json[r'quotaSizeInBytes'] == null ? null : int.parse('${json[r'quotaSizeInBytes']}')) : const Optional.absent(), + shouldChangePassword: json.containsKey(r'shouldChangePassword') ? Optional.present(mapValueOfType(json, r'shouldChangePassword')) : const Optional.absent(), + storageLabel: json.containsKey(r'storageLabel') ? Optional.present(mapValueOfType(json, r'storageLabel')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_preferences_update_dto.dart b/mobile/openapi/lib/model/user_preferences_update_dto.dart index 3b9b178b55..f3f2d85bdf 100644 --- a/mobile/openapi/lib/model/user_preferences_update_dto.dart +++ b/mobile/openapi/lib/model/user_preferences_update_dto.dart @@ -13,18 +13,18 @@ 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, + this.albums = const Optional.absent(), + this.avatar = const Optional.absent(), + this.cast = const Optional.absent(), + this.download = const Optional.absent(), + this.emailNotifications = const Optional.absent(), + this.folders = const Optional.absent(), + this.memories = const Optional.absent(), + this.people = const Optional.absent(), + this.purchase = const Optional.absent(), + this.ratings = const Optional.absent(), + this.sharedLinks = const Optional.absent(), + this.tags = const Optional.absent(), }); /// @@ -33,7 +33,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional albums; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -41,7 +41,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional avatar; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -49,7 +49,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional cast; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -57,7 +57,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional download; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -65,7 +65,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional emailNotifications; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -73,7 +73,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional folders; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -81,7 +81,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional memories; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -89,7 +89,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional people; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -97,7 +97,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional purchase; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -105,7 +105,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional ratings; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -113,7 +113,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional sharedLinks; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -121,7 +121,7 @@ class UserPreferencesUpdateDto { /// 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; + Optional tags; @override bool operator ==(Object other) => identical(this, other) || other is UserPreferencesUpdateDto && @@ -159,65 +159,53 @@ class UserPreferencesUpdateDto { Map toJson() { final json = {}; - if (this.albums != null) { - json[r'albums'] = this.albums; - } else { - // json[r'albums'] = null; + if (this.albums.isPresent) { + final value = this.albums.value; + json[r'albums'] = value; } - if (this.avatar != null) { - json[r'avatar'] = this.avatar; - } else { - // json[r'avatar'] = null; + if (this.avatar.isPresent) { + final value = this.avatar.value; + json[r'avatar'] = value; } - if (this.cast != null) { - json[r'cast'] = this.cast; - } else { - // json[r'cast'] = null; + if (this.cast.isPresent) { + final value = this.cast.value; + json[r'cast'] = value; } - if (this.download != null) { - json[r'download'] = this.download; - } else { - // json[r'download'] = null; + if (this.download.isPresent) { + final value = this.download.value; + json[r'download'] = value; } - if (this.emailNotifications != null) { - json[r'emailNotifications'] = this.emailNotifications; - } else { - // json[r'emailNotifications'] = null; + if (this.emailNotifications.isPresent) { + final value = this.emailNotifications.value; + json[r'emailNotifications'] = value; } - if (this.folders != null) { - json[r'folders'] = this.folders; - } else { - // json[r'folders'] = null; + if (this.folders.isPresent) { + final value = this.folders.value; + json[r'folders'] = value; } - if (this.memories != null) { - json[r'memories'] = this.memories; - } else { - // json[r'memories'] = null; + if (this.memories.isPresent) { + final value = this.memories.value; + json[r'memories'] = value; } - if (this.people != null) { - json[r'people'] = this.people; - } else { - // json[r'people'] = null; + if (this.people.isPresent) { + final value = this.people.value; + json[r'people'] = value; } - if (this.purchase != null) { - json[r'purchase'] = this.purchase; - } else { - // json[r'purchase'] = null; + if (this.purchase.isPresent) { + final value = this.purchase.value; + json[r'purchase'] = value; } - if (this.ratings != null) { - json[r'ratings'] = this.ratings; - } else { - // json[r'ratings'] = null; + if (this.ratings.isPresent) { + final value = this.ratings.value; + json[r'ratings'] = value; } - if (this.sharedLinks != null) { - json[r'sharedLinks'] = this.sharedLinks; - } else { - // json[r'sharedLinks'] = null; + if (this.sharedLinks.isPresent) { + final value = this.sharedLinks.value; + json[r'sharedLinks'] = value; } - if (this.tags != null) { - json[r'tags'] = this.tags; - } else { - // json[r'tags'] = null; + if (this.tags.isPresent) { + final value = this.tags.value; + json[r'tags'] = value; } return json; } @@ -231,18 +219,18 @@ class UserPreferencesUpdateDto { 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']), + albums: json.containsKey(r'albums') ? Optional.present(AlbumsUpdate.fromJson(json[r'albums'])) : const Optional.absent(), + avatar: json.containsKey(r'avatar') ? Optional.present(AvatarUpdate.fromJson(json[r'avatar'])) : const Optional.absent(), + cast: json.containsKey(r'cast') ? Optional.present(CastUpdate.fromJson(json[r'cast'])) : const Optional.absent(), + download: json.containsKey(r'download') ? Optional.present(DownloadUpdate.fromJson(json[r'download'])) : const Optional.absent(), + emailNotifications: json.containsKey(r'emailNotifications') ? Optional.present(EmailNotificationsUpdate.fromJson(json[r'emailNotifications'])) : const Optional.absent(), + folders: json.containsKey(r'folders') ? Optional.present(FoldersUpdate.fromJson(json[r'folders'])) : const Optional.absent(), + memories: json.containsKey(r'memories') ? Optional.present(MemoriesUpdate.fromJson(json[r'memories'])) : const Optional.absent(), + people: json.containsKey(r'people') ? Optional.present(PeopleUpdate.fromJson(json[r'people'])) : const Optional.absent(), + purchase: json.containsKey(r'purchase') ? Optional.present(PurchaseUpdate.fromJson(json[r'purchase'])) : const Optional.absent(), + ratings: json.containsKey(r'ratings') ? Optional.present(RatingsUpdate.fromJson(json[r'ratings'])) : const Optional.absent(), + sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(SharedLinksUpdate.fromJson(json[r'sharedLinks'])) : const Optional.absent(), + tags: json.containsKey(r'tags') ? Optional.present(TagsUpdate.fromJson(json[r'tags'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/user_update_me_dto.dart b/mobile/openapi/lib/model/user_update_me_dto.dart index 0751d4096b..c4859747a6 100644 --- a/mobile/openapi/lib/model/user_update_me_dto.dart +++ b/mobile/openapi/lib/model/user_update_me_dto.dart @@ -13,13 +13,13 @@ part of openapi.api; class UserUpdateMeDto { /// Returns a new [UserUpdateMeDto] instance. UserUpdateMeDto({ - this.avatarColor, - this.email, - this.name, - this.password, + this.avatarColor = const Optional.absent(), + this.email = const Optional.absent(), + this.name = const Optional.absent(), + this.password = const Optional.absent(), }); - UserAvatarColor? avatarColor; + Optional avatarColor; /// User email /// @@ -28,7 +28,7 @@ class UserUpdateMeDto { /// 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; + Optional email; /// User name /// @@ -37,7 +37,7 @@ class UserUpdateMeDto { /// 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; + Optional name; /// User password (deprecated, use change password endpoint) /// @@ -46,7 +46,7 @@ class UserUpdateMeDto { /// 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; + Optional password; @override bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto && @@ -68,25 +68,21 @@ class UserUpdateMeDto { Map toJson() { final json = {}; - if (this.avatarColor != null) { - json[r'avatarColor'] = this.avatarColor; - } else { - // json[r'avatarColor'] = null; + if (this.avatarColor.isPresent) { + final value = this.avatarColor.value; + json[r'avatarColor'] = value; } - if (this.email != null) { - json[r'email'] = this.email; - } else { - // json[r'email'] = null; + if (this.email.isPresent) { + final value = this.email.value; + json[r'email'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - if (this.password != null) { - json[r'password'] = this.password; - } else { - // json[r'password'] = null; + if (this.password.isPresent) { + final value = this.password.value; + json[r'password'] = value; } return json; } @@ -100,10 +96,10 @@ class UserUpdateMeDto { 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'), + avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), + email: json.containsKey(r'email') ? Optional.present(mapValueOfType(json, r'email')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/validate_library_dto.dart b/mobile/openapi/lib/model/validate_library_dto.dart index 68fb0e9fe2..6d85712191 100644 --- a/mobile/openapi/lib/model/validate_library_dto.dart +++ b/mobile/openapi/lib/model/validate_library_dto.dart @@ -13,15 +13,15 @@ part of openapi.api; class ValidateLibraryDto { /// Returns a new [ValidateLibraryDto] instance. ValidateLibraryDto({ - this.exclusionPatterns = const [], - this.importPaths = const [], + this.exclusionPatterns = const Optional.present(const []), + this.importPaths = const Optional.present(const []), }); /// Exclusion patterns (max 128) - List exclusionPatterns; + Optional?> exclusionPatterns; /// Import paths to validate (max 128) - List importPaths; + Optional?> importPaths; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryDto && @@ -39,8 +39,14 @@ class ValidateLibraryDto { Map toJson() { final json = {}; - json[r'exclusionPatterns'] = this.exclusionPatterns; - json[r'importPaths'] = this.importPaths; + if (this.exclusionPatterns.isPresent) { + final value = this.exclusionPatterns.value; + json[r'exclusionPatterns'] = value; + } + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } return json; } @@ -53,12 +59,12 @@ class ValidateLibraryDto { final json = value.cast(); return ValidateLibraryDto( - exclusionPatterns: json[r'exclusionPatterns'] is Iterable + exclusionPatterns: json.containsKey(r'exclusionPatterns') ? Optional.present(json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) - : const [], - importPaths: json[r'importPaths'] is Iterable + : const []) : const Optional.absent(), + importPaths: json.containsKey(r'importPaths') ? Optional.present(json[r'importPaths'] is Iterable ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) - : const [], + : const []) : const Optional.absent(), ); } return null; 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 index ebcb881935..3c8d7c58cd 100644 --- a/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart +++ b/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart @@ -15,7 +15,7 @@ class ValidateLibraryImportPathResponseDto { ValidateLibraryImportPathResponseDto({ required this.importPath, required this.isValid, - this.message, + this.message = const Optional.absent(), }); /// Import path @@ -31,7 +31,7 @@ class ValidateLibraryImportPathResponseDto { /// 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; + Optional message; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryImportPathResponseDto && @@ -53,10 +53,9 @@ class ValidateLibraryImportPathResponseDto { 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; + if (this.message.isPresent) { + final value = this.message.value; + json[r'message'] = value; } return json; } @@ -72,7 +71,7 @@ class ValidateLibraryImportPathResponseDto { return ValidateLibraryImportPathResponseDto( importPath: mapValueOfType(json, r'importPath')!, isValid: mapValueOfType(json, r'isValid')!, - message: mapValueOfType(json, r'message'), + message: json.containsKey(r'message') ? Optional.present(mapValueOfType(json, r'message')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/validate_library_response_dto.dart b/mobile/openapi/lib/model/validate_library_response_dto.dart index 37f6ad07d1..5106b93ca8 100644 --- a/mobile/openapi/lib/model/validate_library_response_dto.dart +++ b/mobile/openapi/lib/model/validate_library_response_dto.dart @@ -13,11 +13,11 @@ part of openapi.api; class ValidateLibraryResponseDto { /// Returns a new [ValidateLibraryResponseDto] instance. ValidateLibraryResponseDto({ - this.importPaths = const [], + this.importPaths = const Optional.present(const []), }); /// Validation results for import paths - List importPaths; + Optional?> importPaths; @override bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryResponseDto && @@ -33,7 +33,10 @@ class ValidateLibraryResponseDto { Map toJson() { final json = {}; - json[r'importPaths'] = this.importPaths; + if (this.importPaths.isPresent) { + final value = this.importPaths.value; + json[r'importPaths'] = value; + } return json; } @@ -46,7 +49,7 @@ class ValidateLibraryResponseDto { final json = value.cast(); return ValidateLibraryResponseDto( - importPaths: ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths']), + importPaths: json.containsKey(r'importPaths') ? Optional.present(ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/version_check_state_response_dto.dart b/mobile/openapi/lib/model/version_check_state_response_dto.dart index 71075a681c..4ad9458a1b 100644 --- a/mobile/openapi/lib/model/version_check_state_response_dto.dart +++ b/mobile/openapi/lib/model/version_check_state_response_dto.dart @@ -42,12 +42,12 @@ class VersionCheckStateResponseDto { if (this.checkedAt != null) { json[r'checkedAt'] = this.checkedAt; } else { - // json[r'checkedAt'] = null; + json[r'checkedAt'] = null; } if (this.releaseVersion != null) { json[r'releaseVersion'] = this.releaseVersion; } else { - // json[r'releaseVersion'] = null; + json[r'releaseVersion'] = null; } return json; } diff --git a/mobile/openapi/lib/model/workflow_create_dto.dart b/mobile/openapi/lib/model/workflow_create_dto.dart index dfd2d51290..e84554e8c9 100644 --- a/mobile/openapi/lib/model/workflow_create_dto.dart +++ b/mobile/openapi/lib/model/workflow_create_dto.dart @@ -13,15 +13,15 @@ part of openapi.api; class WorkflowCreateDto { /// Returns a new [WorkflowCreateDto] instance. WorkflowCreateDto({ - this.description, - this.enabled, - this.name, - this.steps = const [], + this.description = const Optional.absent(), + this.enabled = const Optional.absent(), + this.name = const Optional.absent(), + this.steps = const Optional.present(const []), required this.trigger, }); /// Workflow description - String? description; + Optional description; /// Workflow enabled /// @@ -30,12 +30,12 @@ class WorkflowCreateDto { /// 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; + Optional enabled; /// Workflow name - String? name; + Optional name; - List steps; + Optional?> steps; WorkflowTrigger trigger; @@ -61,22 +61,22 @@ class WorkflowCreateDto { Map toJson() { final json = {}; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; + } + if (this.steps.isPresent) { + final value = this.steps.value; + json[r'steps'] = value; } - json[r'steps'] = this.steps; json[r'trigger'] = this.trigger; return json; } @@ -90,10 +90,10 @@ class WorkflowCreateDto { final json = value.cast(); return WorkflowCreateDto( - description: mapValueOfType(json, r'description'), - enabled: mapValueOfType(json, r'enabled'), - name: mapValueOfType(json, r'name'), - steps: WorkflowStepDto.listFromJson(json[r'steps']), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + steps: json.containsKey(r'steps') ? Optional.present(WorkflowStepDto.listFromJson(json[r'steps'])) : const Optional.absent(), trigger: WorkflowTrigger.fromJson(json[r'trigger'])!, ); } diff --git a/mobile/openapi/lib/model/workflow_response_dto.dart b/mobile/openapi/lib/model/workflow_response_dto.dart index f44506d69d..b2b5586601 100644 --- a/mobile/openapi/lib/model/workflow_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_response_dto.dart @@ -78,14 +78,14 @@ class WorkflowResponseDto { if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } json[r'enabled'] = this.enabled; json[r'id'] = this.id; if (this.name != null) { json[r'name'] = this.name; } else { - // json[r'name'] = null; + json[r'name'] = null; } json[r'steps'] = this.steps; json[r'trigger'] = this.trigger; diff --git a/mobile/openapi/lib/model/workflow_share_response_dto.dart b/mobile/openapi/lib/model/workflow_share_response_dto.dart index 336e8503c5..d7e90085c2 100644 --- a/mobile/openapi/lib/model/workflow_share_response_dto.dart +++ b/mobile/openapi/lib/model/workflow_share_response_dto.dart @@ -53,12 +53,12 @@ class WorkflowShareResponseDto { if (this.description != null) { json[r'description'] = this.description; } else { - // json[r'description'] = null; + json[r'description'] = null; } if (this.name != null) { json[r'name'] = this.name; } else { - // json[r'name'] = null; + json[r'name'] = null; } json[r'steps'] = this.steps; json[r'trigger'] = this.trigger; diff --git a/mobile/openapi/lib/model/workflow_share_step_dto.dart b/mobile/openapi/lib/model/workflow_share_step_dto.dart index 79c55ef716..eeb6ba4e51 100644 --- a/mobile/openapi/lib/model/workflow_share_step_dto.dart +++ b/mobile/openapi/lib/model/workflow_share_step_dto.dart @@ -14,7 +14,7 @@ class WorkflowShareStepDto { /// Returns a new [WorkflowShareStepDto] instance. WorkflowShareStepDto({ this.config = const {}, - this.enabled, + this.enabled = const Optional.absent(), required this.method, }); @@ -28,7 +28,7 @@ class WorkflowShareStepDto { /// 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; + Optional enabled; /// Step plugin method String method; @@ -54,12 +54,11 @@ class WorkflowShareStepDto { if (this.config != null) { json[r'config'] = this.config; } else { - // json[r'config'] = null; + json[r'config'] = null; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } json[r'method'] = this.method; return json; @@ -75,7 +74,7 @@ class WorkflowShareStepDto { return WorkflowShareStepDto( config: mapCastOfType(json, r'config'), - enabled: mapValueOfType(json, r'enabled'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), method: mapValueOfType(json, r'method')!, ); } diff --git a/mobile/openapi/lib/model/workflow_step_dto.dart b/mobile/openapi/lib/model/workflow_step_dto.dart index e881ad3150..c01e8e6b44 100644 --- a/mobile/openapi/lib/model/workflow_step_dto.dart +++ b/mobile/openapi/lib/model/workflow_step_dto.dart @@ -14,7 +14,7 @@ class WorkflowStepDto { /// Returns a new [WorkflowStepDto] instance. WorkflowStepDto({ this.config = const {}, - this.enabled, + this.enabled = const Optional.absent(), required this.method, }); @@ -28,7 +28,7 @@ class WorkflowStepDto { /// 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; + Optional enabled; /// Step plugin method String method; @@ -54,12 +54,11 @@ class WorkflowStepDto { if (this.config != null) { json[r'config'] = this.config; } else { - // json[r'config'] = null; + json[r'config'] = null; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } json[r'method'] = this.method; return json; @@ -75,7 +74,7 @@ class WorkflowStepDto { return WorkflowStepDto( config: mapCastOfType(json, r'config'), - enabled: mapValueOfType(json, r'enabled'), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), method: mapValueOfType(json, r'method')!, ); } diff --git a/mobile/openapi/lib/model/workflow_update_dto.dart b/mobile/openapi/lib/model/workflow_update_dto.dart index 0bce75283a..32759b6395 100644 --- a/mobile/openapi/lib/model/workflow_update_dto.dart +++ b/mobile/openapi/lib/model/workflow_update_dto.dart @@ -13,15 +13,15 @@ part of openapi.api; class WorkflowUpdateDto { /// Returns a new [WorkflowUpdateDto] instance. WorkflowUpdateDto({ - this.description, - this.enabled, - this.name, - this.steps = const [], - this.trigger, + this.description = const Optional.absent(), + this.enabled = const Optional.absent(), + this.name = const Optional.absent(), + this.steps = const Optional.present(const []), + this.trigger = const Optional.absent(), }); /// Workflow description - String? description; + Optional description; /// Workflow enabled /// @@ -30,12 +30,12 @@ class WorkflowUpdateDto { /// 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; + Optional enabled; /// Workflow name - String? name; + Optional name; - List steps; + Optional?> steps; /// /// Please note: This property should have been non-nullable! Since the specification file @@ -43,7 +43,7 @@ class WorkflowUpdateDto { /// source code must fall back to having a nullable type. /// Consider adding a "default:" property in the specification file to hide this note. /// - WorkflowTrigger? trigger; + Optional trigger; @override bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto && @@ -67,26 +67,25 @@ class WorkflowUpdateDto { Map toJson() { final json = {}; - if (this.description != null) { - json[r'description'] = this.description; - } else { - // json[r'description'] = null; + if (this.description.isPresent) { + final value = this.description.value; + json[r'description'] = value; } - if (this.enabled != null) { - json[r'enabled'] = this.enabled; - } else { - // json[r'enabled'] = null; + if (this.enabled.isPresent) { + final value = this.enabled.value; + json[r'enabled'] = value; } - if (this.name != null) { - json[r'name'] = this.name; - } else { - // json[r'name'] = null; + if (this.name.isPresent) { + final value = this.name.value; + json[r'name'] = value; } - json[r'steps'] = this.steps; - if (this.trigger != null) { - json[r'trigger'] = this.trigger; - } else { - // json[r'trigger'] = null; + if (this.steps.isPresent) { + final value = this.steps.value; + json[r'steps'] = value; + } + if (this.trigger.isPresent) { + final value = this.trigger.value; + json[r'trigger'] = value; } return json; } @@ -100,11 +99,11 @@ class WorkflowUpdateDto { final json = value.cast(); return WorkflowUpdateDto( - description: mapValueOfType(json, r'description'), - enabled: mapValueOfType(json, r'enabled'), - name: mapValueOfType(json, r'name'), - steps: WorkflowStepDto.listFromJson(json[r'steps']), - trigger: WorkflowTrigger.fromJson(json[r'trigger']), + description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), + enabled: json.containsKey(r'enabled') ? Optional.present(mapValueOfType(json, r'enabled')) : const Optional.absent(), + name: json.containsKey(r'name') ? Optional.present(mapValueOfType(json, r'name')) : const Optional.absent(), + steps: json.containsKey(r'steps') ? Optional.present(WorkflowStepDto.listFromJson(json[r'steps'])) : const Optional.absent(), + trigger: json.containsKey(r'trigger') ? Optional.present(WorkflowTrigger.fromJson(json[r'trigger'])) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/optional.dart b/mobile/openapi/lib/optional.dart new file mode 100644 index 0000000000..f260ec4a84 --- /dev/null +++ b/mobile/openapi/lib/optional.dart @@ -0,0 +1,119 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Represents an optional value that can be either absent or present. +/// +/// This is used to distinguish between three states in PATCH operations: +/// - Absent: Field is not set (omitted from JSON) +/// - Present with null: Field is explicitly set to null +/// - Present with value: Field has a value +/// +/// Example usage: +/// ```dart +/// // Field absent - not sent in request +/// final patch1 = Model(); +/// +/// // Field explicitly null - sends {"field": null} +/// final patch2 = Model(field: const Optional.present(null)); +/// +/// // Field has value - sends {"field": "value"} +/// final patch3 = Model(field: const Optional.present('value')); +/// ``` +abstract class Optional { + const Optional(); + + /// Creates an Optional with an absent value (not set). + const factory Optional.absent() = Absent; + + /// Creates an Optional with a present value (can be null). + const factory Optional.present(T value) = Present; + + /// Returns true if this Optional has a value (even if that value is null). + bool get isPresent; + + /// Returns true if this Optional does not have a value. + bool get isEmpty => !isPresent; + + /// Returns the value if present, throws if absent. + T get value; + + /// Returns the value if present, otherwise returns [defaultValue]. + T orElse(T defaultValue); + + /// Returns the value if present, otherwise returns the result of calling [defaultValue]. + T orElseGet(T Function() defaultValue); + + /// Maps the value if present using [transform], otherwise returns an absent Optional. + Optional map(R Function(T value) transform); +} + +/// Represents an absent Optional value. +class Absent extends Optional { + const Absent(); + + @override + bool get isPresent => false; + + @override + T get value => throw StateError('No value present'); + + @override + T orElse(T defaultValue) => defaultValue; + + @override + T orElseGet(T Function() defaultValue) => defaultValue(); + + @override + Optional map(R Function(T value) transform) => const Absent(); + + @override + bool operator ==(Object other) => other is Absent; + + @override + int get hashCode => 0; + + @override + String toString() => 'Optional.absent()'; +} + +/// Represents a present Optional value. +class Present extends Optional { + const Present(this._value); + + final T _value; + + @override + bool get isPresent => true; + + @override + T get value => _value; + + @override + T orElse(T defaultValue) => _value; + + @override + T orElseGet(T Function() defaultValue) => _value; + + @override + Optional map(R Function(T value) transform) => Optional.present(transform(_value)); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Present && _value == other._value); + + @override + int get hashCode => _value.hashCode; + + @override + String toString() => 'Optional.present($_value)'; +} diff --git a/mobile/pigeon/local_image_api.dart b/mobile/pigeon/local_image_api.dart index eb538d7b1a..46643b7956 100644 --- a/mobile/pigeon/local_image_api.dart +++ b/mobile/pigeon/local_image_api.dart @@ -5,8 +5,7 @@ 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 9775973694..433b154cd1 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -105,25 +105,26 @@ class CloudIdResult { @HostApi() abstract class NativeSyncApi { + @async bool shouldFullSync(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async SyncDelta getMediaChanges(); void checkpointSync(); void clearSyncCheckpoint(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAssetIdsForAlbum(String albumId); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAlbums(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) int getAssetsCountSince(String albumId, int timestamp); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) + @async List getAssetsForAlbum(String albumId, {int? updatedTimeCond}); @async @@ -132,6 +133,8 @@ abstract class NativeSyncApi { void cancelHashing(); + void cancelSync(); + @TaskQueue(type: TaskQueueType.serialBackgroundThread) Map> getTrashedAssets(); diff --git a/mobile/pigeon/permission_api.dart b/mobile/pigeon/permission_api.dart index a924f32e27..42099d4e7c 100644 --- a/mobile/pigeon/permission_api.dart +++ b/mobile/pigeon/permission_api.dart @@ -1,10 +1,12 @@ 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(), + 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(), @@ -13,6 +15,8 @@ import 'package:pigeon/pigeon.dart'; ) @HostApi() abstract class PermissionApi { + PermissionStatus isIgnoringBatteryOptimizations(); + bool hasManageMediaPermission(); @async diff --git a/mobile/pigeon/view_intent_api.dart b/mobile/pigeon/view_intent_api.dart new file mode 100644 index 0000000000..f6a5162fef --- /dev/null +++ b/mobile/pigeon/view_intent_api.dart @@ -0,0 +1,24 @@ +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 72bdc6b298..9323cedf11 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -370,14 +370,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.33.0" - drift_flutter: + drift_sqlite_async: dependency: "direct main" description: - name: drift_flutter - sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166" + name: drift_sqlite_async + sha256: "28c666847ecbc2e90dbcf8e8c2eecbf847a38af7e6480efc08f3a3c39e060f8d" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1" dynamic_color: dependency: "direct main" description: @@ -1627,30 +1627,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" - sqlcipher_flutter_libs: - dependency: transitive - description: - name: sqlcipher_flutter_libs - sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929" - url: "https://pub.dev" - source: hosted - version: "0.7.0+eol" sqlite3: - dependency: transitive + dependency: "direct main" description: name: sqlite3 - sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5" + sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189" url: "https://pub.dev" source: hosted - version: "3.3.1" - sqlite3_flutter_libs: + version: "3.3.2" + sqlite3_connection_pool: dependency: transitive description: - name: sqlite3_flutter_libs - sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454" + name: sqlite3_connection_pool + sha256: "9d2b3b398b03c96743fd071521fc665be73c33c9cd5c56d87196baff8d8b4398" url: "https://pub.dev" source: hosted - version: "0.6.0+eol" + version: "0.2.6" + sqlite3_web: + dependency: transitive + description: + name: sqlite3_web + sha256: fab6f6921f8fc3f703536c018d709ccb4ad5654d92f53e9b206c222f77c50021 + url: "https://pub.dev" + source: hosted + version: "0.8.1" + sqlite_async: + dependency: "direct main" + description: + name: sqlite_async + sha256: e3e486e536253e17260c52b6f824b749adff3f1fed878d4abe629796b858290b + url: "https://pub.dev" + source: hosted + version: "0.14.2" sqlparser: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 206f9ac2b3..67b9f2a17e 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: crypto: ^3.0.7 device_info_plus: ^12.4.0 drift: ^2.32.1 - drift_flutter: ^0.3.0 + drift_sqlite_async: 0.3.1 dynamic_color: ^1.8.1 easy_localization: ^3.0.8 ffi: ^2.2.0 @@ -66,6 +66,8 @@ dependencies: share_plus: ^10.1.4 sliver_tools: ^0.2.12 stream_transform: ^2.1.1 + sqlite3: ^3.3.2 + sqlite_async: 0.14.2 thumbhash: 0.1.0+1 timezone: ^0.9.4 url_launcher: ^6.3.2 diff --git a/mobile/test/api.mocks.dart b/mobile/test/api.mocks.dart index e1c32eaaee..91e27735ae 100644 --- a/mobile/test/api.mocks.dart +++ b/mobile/test/api.mocks.dart @@ -1,6 +1,9 @@ +import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; class MockSyncApi extends Mock implements SyncApi {} class MockServerApi extends Mock implements ServerApi {} + +class MockPartnerApiRepository extends Mock implements PartnerApiRepository {} diff --git a/mobile/test/domain/repositories/sync_stream_repository_test.dart b/mobile/test/domain/repositories/sync_stream_repository_test.dart index 4199a5b756..bd47f63da5 100644 --- a/mobile/test/domain/repositories/sync_stream_repository_test.dart +++ b/mobile/test/domain/repositories/sync_stream_repository_test.dart @@ -15,7 +15,7 @@ SyncUserV1 _createUser({String id = 'user-1'}) { name: 'Test User', email: 'test@test.com', deletedAt: null, - avatarColor: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2024, 1, 1), ); diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart index 89e85a3794..743d75f1bf 100644 --- a/mobile/test/domain/service.mock.dart +++ b/mobile/test/domain/service.mock.dart @@ -1,3 +1,4 @@ +import 'package:immich_mobile/domain/services/partner.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; @@ -11,3 +12,5 @@ class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {} class MockNativeSyncApi extends Mock implements NativeSyncApi {} class MockAppSettingsService extends Mock implements AppSettingsService {} + +class MockPartnerService extends Mock implements PartnerService {} diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 80272d9310..e033229408 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -36,13 +36,6 @@ 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; @@ -94,9 +87,13 @@ void main() { when(() => mockAbortCallbackWrapper()).thenReturn(false); - when(() => mockSyncApiRepo.streamChanges(any(), serverVersion: any(named: 'serverVersion'))).thenAnswer(( - invocation, - ) async { + when( + () => mockSyncApiRepo.streamChanges( + any(), + serverVersion: any(named: 'serverVersion'), + abortSignal: any(named: 'abortSignal'), + ), + ).thenAnswer((invocation) async { handleEventsCallback = invocation.positionalArguments.first; }); @@ -105,6 +102,7 @@ void main() { any(), onReset: any(named: 'onReset'), serverVersion: any(named: 'serverVersion'), + abortSignal: any(named: 'abortSignal'), ), ).thenAnswer((invocation) async { handleEventsCallback = invocation.positionalArguments.first; @@ -233,8 +231,7 @@ void main() { }); test("aborts and stops processing if cancelled during iteration", () async { - final cancellationChecker = _MockCancellationWrapper(); - when(() => cancellationChecker()).thenReturn(false); + final cancellation = Completer(); sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, @@ -243,7 +240,7 @@ void main() { trashedLocalAssetRepository: mockTrashedLocalAssetRepo, assetMediaRepository: mockAssetMediaRepo, permissionRepository: mockPermissionRepo, - cancelChecker: cancellationChecker.call, + cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -252,7 +249,7 @@ void main() { final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1]; when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async { - when(() => cancellationChecker()).thenReturn(true); + cancellation.complete(); }); await handleEventsCallback(events, mockAbortCallbackWrapper.call, mockResetCallbackWrapper.call); @@ -267,8 +264,7 @@ void main() { }); test("aborts and stops processing if cancelled before processing batch", () async { - final cancellationChecker = _MockCancellationWrapper(); - when(() => cancellationChecker()).thenReturn(false); + final cancellation = Completer(); final processingCompleter = Completer(); bool handler1Started = false; @@ -284,7 +280,7 @@ void main() { trashedLocalAssetRepository: mockTrashedLocalAssetRepo, assetMediaRepository: mockAssetMediaRepo, permissionRepository: mockPermissionRepo, - cancelChecker: cancellationChecker.call, + cancellation: cancellation, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -303,7 +299,7 @@ void main() { expect(handler1Started, isTrue); // Signal cancellation while handler 1 is waiting - when(() => cancellationChecker()).thenReturn(true); + cancellation.complete(); await pumpEventQueue(); processingCompleter.complete(); diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart index c5d57e9a4b..8f7efce826 100644 --- a/mobile/test/drift/main/generated/schema.dart +++ b/mobile/test/drift/main/generated/schema.dart @@ -31,6 +31,7 @@ import 'schema_v24.dart' as v24; import 'schema_v25.dart' as v25; import 'schema_v26.dart' as v26; import 'schema_v27.dart' as v27; +import 'schema_v28.dart' as v28; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -90,6 +91,8 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v26.DatabaseAtV26(db); case 27: return v27.DatabaseAtV27(db); + case 28: + return v28.DatabaseAtV28(db); default: throw MissingSchemaException(version, versions); } @@ -123,5 +126,6 @@ class GeneratedHelper implements SchemaInstantiationHelper { 25, 26, 27, + 28, ]; } diff --git a/mobile/test/drift/main/generated/schema_v28.dart b/mobile/test/drift/main/generated/schema_v28.dart new file mode 100644 index 0000000000..a18199ed4f --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v28.dart @@ -0,0 +1,9389 @@ +// dart format width=80 +import 'dart:typed_data' as i2; +// GENERATED BY drift_dev, DO NOT MODIFY. +// ignore_for_file: type=lint,unused_import +// +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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + 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.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final int hasProfileImage; + final String 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, + int? hasProfileImage, + String? 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn localDateTime = GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn uploadedAt = GeneratedColumn( + 'uploaded_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_edited IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + 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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + uploadedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uploaded_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.int, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String checksum; + final int isFavorite; + final String ownerId; + final String? localDateTime; + final String? thumbHash; + final String? deletedAt; + final String? uploadedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final int isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationMs, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.uploadedAt, + 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 || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + 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 || uploadedAt != null) { + map['uploaded_at'] = Variable(uploadedAt); + } + 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']), + durationMs: serializer.fromJson(json['durationMs']), + 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']), + uploadedAt: serializer.fromJson(json['uploadedAt']), + 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), + 'durationMs': serializer.toJson(durationMs), + '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), + 'uploadedAt': serializer.toJson(uploadedAt), + '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, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? checksum, + int? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value uploadedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + int? 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, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + 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, + uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt, + 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, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + 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, + uploadedAt: data.uploadedAt.present + ? data.uploadedAt.value + : this.uploadedAt, + 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('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..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, + durationMs, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + uploadedAt, + 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.durationMs == this.durationMs && + 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.uploadedAt == this.uploadedAt && + 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 durationMs; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value uploadedAt; + 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.durationMs = 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.uploadedAt = 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.durationMs = 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.uploadedAt = 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? durationMs, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? uploadedAt, + 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 (durationMs != null) 'duration_ms': durationMs, + 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 (uploadedAt != null) 'uploaded_at': uploadedAt, + 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? durationMs, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? uploadedAt, + 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, + durationMs: durationMs ?? this.durationMs, + 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, + uploadedAt: uploadedAt ?? this.uploadedAt, + 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 (durationMs.present) { + map['duration_ms'] = Variable(durationMs.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 (uploadedAt.present) { + map['uploaded_at'] = Variable(uploadedAt.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('durationMs: $durationMs, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('uploadedAt: $uploadedAt, ') + ..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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final String createdAt; + final String 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, + String? createdAt, + String? 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + 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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.int, + 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.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String? checksum; + final int isFavorite; + final int orientation; + final String? iCloudId; + final String? 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.durationMs, + 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 || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + 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']), + durationMs: serializer.fromJson(json['durationMs']), + 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), + 'durationMs': serializer.toJson(durationMs), + '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, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + int? 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, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + 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, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + 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('durationMs: $durationMs, ') + ..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, + durationMs, + 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.durationMs == this.durationMs && + 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 durationMs; + 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.durationMs = 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.durationMs = 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? durationMs, + 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 (durationMs != null) 'duration_ms': durationMs, + 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? durationMs, + 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, + durationMs: durationMs ?? this.durationMs, + 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 (durationMs.present) { + map['duration_ms'] = Variable(durationMs.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('durationMs: $durationMs, ') + ..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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT \'\'', + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: + 'NULL REFERENCES remote_asset_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 1 CHECK (is_activity_enabled IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + 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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.int, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final String createdAt; + final String updatedAt; + final String? thumbnailAssetId; + final int isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + 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); + 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']), + 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), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + String? createdAt, + String? updatedAt, + Value thumbnailAssetId = const Value.absent(), + int? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + 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, + 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('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + 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.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 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.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(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + 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 (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? 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, + 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 (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('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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 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, + $customConstraints: + 'NULL REFERENCES remote_album_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL 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.string, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String updatedAt; + final int backupSelection; + final int isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final int? 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, + String? updatedAt, + int? backupSelection, + int? 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, + $customConstraints: + 'NOT NULL REFERENCES local_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES local_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn marker = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL 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.int, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final int? 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_admin IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: + 'NOT NULL DEFAULT 0 CHECK (has_profile_image IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.int, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final int isAdmin; + final int hasProfileImage; + final String 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, + int? isAdmin, + int? hasProfileImage, + String? 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, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = + GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @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; + @override + List get customConstraints => const ['PRIMARY KEY(user_id, "key")']; + @override + bool get dontWriteConstraints => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final i2.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, + i2.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 i2.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, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 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.int, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const [ + 'PRIMARY KEY(shared_by_id, shared_with_id)', + ]; + @override + bool get dontWriteConstraints => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final int 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, + int? 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, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn dateTimeOriginal = GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final String? 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, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL 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; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, album_id)', + ]; + @override + bool get dontWriteConstraints => 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, + $customConstraints: + 'NOT NULL REFERENCES remote_album_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @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; + @override + List get customConstraints => const [ + 'PRIMARY KEY(album_id, user_id)', + ]; + @override + bool get dontWriteConstraints => 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, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn adjustmentTime = GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.string, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(asset_id)']; + @override + bool get dontWriteConstraints => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final String? createdAt; + final String? 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_saved IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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.int, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String? deletedAt; + final String ownerId; + final int type; + final String data; + final int isSaved; + final String memoryAt; + final String? seenAt; + final String? showAt; + final String? 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, + String? createdAt, + String? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + int? isSaved, + String? 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 String 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, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL 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; + @override + List get customConstraints => const [ + 'PRIMARY KEY(asset_id, memory_id)', + ]; + @override + bool get dontWriteConstraints => 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL REFERENCES user_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_favorite IN (0, 1))', + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL CHECK (is_hidden IN (0, 1))', + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + 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.int, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final String createdAt; + final String updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final int isFavorite; + final int isHidden; + final String? color; + final String? 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, + String? createdAt, + String? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + int? isFavorite, + int? 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 int isFavorite, + required int 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL REFERENCES person_entity(id)ON DELETE SET NULL', + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 1 CHECK (is_visible IN (0, 1))', + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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.int, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => 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 int isVisible; + final String? 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, + int? 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + @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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => 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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn durationMs = GeneratedColumn( + 'duration_ms', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NULL', + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1))', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT 0', + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationMs, + 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.string, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationMs: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_ms'], + ), + 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.int, + 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; + @override + List get customConstraints => const ['PRIMARY KEY(id, album_id)']; + @override + bool get dontWriteConstraints => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final String createdAt; + final String updatedAt; + final int? width; + final int? height; + final int? durationMs; + final String id; + final String albumId; + final String? checksum; + final int 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.durationMs, + 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 || durationMs != null) { + map['duration_ms'] = Variable(durationMs); + } + 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']), + durationMs: serializer.fromJson(json['durationMs']), + 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), + 'durationMs': serializer.toJson(durationMs), + '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, + String? createdAt, + String? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationMs = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + int? 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, + durationMs: durationMs.present ? durationMs.value : this.durationMs, + 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, + durationMs: data.durationMs.present + ? data.durationMs.value + : this.durationMs, + 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('durationMs: $durationMs, ') + ..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, + durationMs, + 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.durationMs == this.durationMs && + 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 durationMs; + 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.durationMs = 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.durationMs = 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? durationMs, + 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 (durationMs != null) 'duration_ms': durationMs, + 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? durationMs, + 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, + durationMs: durationMs ?? this.durationMs, + 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 (durationMs.present) { + map['duration_ms'] = Variable(durationMs.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('durationMs: $durationMs, ') + ..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, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: + 'NOT NULL REFERENCES remote_asset_entity(id)ON DELETE CASCADE', + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn parameters = + GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + @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; + @override + List get customConstraints => const ['PRIMARY KEY(id)']; + @override + bool get dontWriteConstraints => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final i2.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, + i2.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 i2.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 Settings extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Settings(this.attachedDatabase, [this._alias]); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL', + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL DEFAULT CURRENT_TIMESTAMP', + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [key, value, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + Set get $primaryKey => {key}; + @override + SettingsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsData( + key: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}value'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + Settings createAlias(String alias) { + return Settings(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; + @override + List get customConstraints => const ['PRIMARY KEY("key")']; + @override + bool get dontWriteConstraints => true; +} + +class SettingsData extends DataClass implements Insertable { + final String key; + final String value; + final String updatedAt; + const SettingsData({ + required this.key, + required this.value, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['key'] = Variable(key); + map['value'] = Variable(value); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory SettingsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsData( + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + SettingsData copyWith({String? key, String? value, String? updatedAt}) => + SettingsData( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + SettingsData copyWithCompanion(SettingsCompanion data) { + return SettingsData( + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsData(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(key, value, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsData && + other.key == this.key && + other.value == this.value && + other.updatedAt == this.updatedAt); +} + +class SettingsCompanion extends UpdateCompanion { + final Value key; + final Value value; + final Value updatedAt; + const SettingsCompanion({ + this.key = const Value.absent(), + this.value = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + SettingsCompanion.insert({ + required String key, + required String value, + this.updatedAt = const Value.absent(), + }) : key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? key, + Expression? value, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (key != null) 'key': key, + if (value != null) 'value': value, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + SettingsCompanion copyWith({ + Value? key, + Value? value, + Value? updatedAt, + }) { + return SettingsCompanion( + key: key ?? this.key, + value: value ?? this.value, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsCompanion(') + ..write('key: $key, ') + ..write('value: $value, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV28 extends GeneratedDatabase { + DatabaseAtV28(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 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 idxLocalAssetCreatedAt = Index( + 'idx_local_asset_created_at', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)', + ); + 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 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 idxRemoteAssetOwnerVisibilityDeletedCreated = Index( + 'idx_remote_asset_owner_visibility_deleted_created', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)', + ); + 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 Settings settings = Settings(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 idxRemoteExifCity = Index( + 'idx_remote_exif_city', + 'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL', + ); + 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 idxAssetFaceVisiblePerson = Index( + 'idx_asset_face_visible_person', + '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', + ); + 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, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxLocalAssetCreatedAt, + idxStackPrimaryAssetId, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetOwnerVisibilityDeletedCreated, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + settings, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteExifCity, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxAssetFaceVisiblePerson, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('stack_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('local_album_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('local_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('user_metadata_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('partner_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('remote_exif_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_asset_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_album_user_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [ + TableUpdate('remote_asset_cloud_id_entity', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('memory_asset_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('person_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_face_entity', kind: UpdateKind.update)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('asset_edit_entity', kind: UpdateKind.delete)], + ), + ]); + @override + int get schemaVersion => 28; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index 6d8c0bfdf2..1691fbdc49 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: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2025), ), @@ -22,7 +22,7 @@ abstract final class SyncStreamStub { email: "user@user", id: "5", name: "User", - avatarColor: null, + avatarColor: const Optional.absent(), hasProfileImage: false, profileChangedAt: DateTime(2025), ), diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 9c1cdae416..0688576682 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -2,6 +2,7 @@ import 'package:immich_mobile/infrastructure/repositories/backup.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/partner.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; @@ -11,6 +12,7 @@ import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.da 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/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; @@ -44,6 +46,10 @@ 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/partner_repository_test.dart b/mobile/test/medium/repositories/partner_repository_test.dart new file mode 100644 index 0000000000..298b8b852d --- /dev/null +++ b/mobile/test/medium/repositories/partner_repository_test.dart @@ -0,0 +1,100 @@ +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.update(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.delete(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/repository_context.dart b/mobile/test/medium/repository_context.dart index 13f9a0234e..436a58aaf8 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da 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/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'; @@ -68,6 +69,18 @@ class MediumRepositoryContext { ); } + 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), + ), + ); + } + Future newRemoteAsset({ String? id, String? checksum, diff --git a/mobile/test/medium/service_context.dart b/mobile/test/medium/service_context.dart new file mode 100644 index 0000000000..6f90b3e344 --- /dev/null +++ b/mobile/test/medium/service_context.dart @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000000..4c31c9212e --- /dev/null +++ b/mobile/test/medium/services/partner_service_test.dart @@ -0,0 +1,110 @@ +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/openapi_patches_coverage.dart b/mobile/test/openapi_patches_coverage.dart new file mode 100644 index 0000000000..c4225d82c6 --- /dev/null +++ b/mobile/test/openapi_patches_coverage.dart @@ -0,0 +1,111 @@ +// 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; + } + 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/providers/view_intent/view_intent_handler_android_test.dart b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart new file mode 100644 index 0000000000..f9c2c9d323 --- /dev/null +++ b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart @@ -0,0 +1,261 @@ +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/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/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/view_intent.service.dart'; +import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; +import 'package:immich_mobile/services/auth.service.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/secure_storage.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 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; + }), + ], + ); + + 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 new file mode 100644 index 0000000000..a3982e8029 --- /dev/null +++ b/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart @@ -0,0 +1,64 @@ +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/services/action.service_test.dart b/mobile/test/services/action.service_test.dart index f08a247a3e..a25ee9920d 100644 --- a/mobile/test/services/action.service_test.dart +++ b/mobile/test/services/action.service_test.dart @@ -73,6 +73,32 @@ void main() { await Store.clear(); }); + group('ActionService.updateRating', () { + const assetId = 'asset_id_1'; + + test('calls both repositories with the given rating', () async { + when(() => assetApiRepository.updateRating(assetId, 3)).thenAnswer((_) async {}); + when(() => remoteAssetRepository.updateRating(assetId, 3)).thenAnswer((_) async {}); + + final result = await sut.updateRating(assetId, 3); + + expect(result, isTrue); + verify(() => assetApiRepository.updateRating(assetId, 3)).called(1); + verify(() => remoteAssetRepository.updateRating(assetId, 3)).called(1); + }); + + test('calls both repositories with null to clear rating', () async { + when(() => assetApiRepository.updateRating(assetId, null)).thenAnswer((_) async {}); + when(() => remoteAssetRepository.updateRating(assetId, null)).thenAnswer((_) async {}); + + final result = await sut.updateRating(assetId, null); + + expect(result, isTrue); + verify(() => assetApiRepository.updateRating(assetId, null)).called(1); + verify(() => remoteAssetRepository.updateRating(assetId, null)).called(1); + }); + }); + group('ActionService.deleteLocal', () { test('routes deleted ids to trashed repository when Android trash handling is enabled', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); diff --git a/mobile/test/services/view_intent_asset_resolver_test.dart b/mobile/test/services/view_intent_asset_resolver_test.dart new file mode 100644 index 0000000000..38d2f71f88 --- /dev/null +++ b/mobile/test/services/view_intent_asset_resolver_test.dart @@ -0,0 +1,123 @@ +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/asset.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 MockDriftLocalAssetRepository mockLocalAssetRepository; + late MockTimelineFactory timelineFactory; + late List createdTimelineServices; + late ProviderContainer container; + + setUp(() { + mockLocalAssetRepository = MockDriftLocalAssetRepository(); + 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; + }); + + container = ProviderContainer( + overrides: [ + localAssetRepository.overrideWith((ref) => mockLocalAssetRepository), + 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 new file mode 100644 index 0000000000..7b3d0b85e7 --- /dev/null +++ b/mobile/test/services/view_intent_service_test.dart @@ -0,0 +1,119 @@ +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/unit/factories/local_album_factory.dart b/mobile/test/unit/factories/local_album_factory.dart index 8ac5c11eca..447001f971 100644 --- a/mobile/test/unit/factories/local_album_factory.dart +++ b/mobile/test/unit/factories/local_album_factory.dart @@ -19,7 +19,7 @@ class LocalAlbumFactory { id: id, name: name ?? 'local_album_$id', updatedAt: TestUtils.date(updatedAt), - backupSelection: backupSelection ?? BackupSelection.none, + 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 index 8ad35725c4..2f4391813f 100644 --- a/mobile/test/unit/factories/local_asset_factory.dart +++ b/mobile/test/unit/factories/local_asset_factory.dart @@ -14,7 +14,7 @@ class LocalAssetFactory { type: AssetType.image, createdAt: TestUtils.yesterday(), updatedAt: TestUtils.now(), - playbackStyle: AssetPlaybackStyle.image, + playbackStyle: .image, isEdited: false, ); } diff --git a/mobile/test/unit/factories/partner_user_factory.dart b/mobile/test/unit/factories/partner_user_factory.dart new file mode 100644 index 0000000000..63f94608ad --- /dev/null +++ b/mobile/test/unit/factories/partner_user_factory.dart @@ -0,0 +1,19 @@ +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}) { + id = TestUtils.uuid(id); + return Partner( + id: id, + email: email ?? '$id@test.com', + name: name ?? 'user_$id', + inTimeline: inTimeline ?? false, + hasProfileImage: false, + profileChangedAt: DateTime.now(), + ); + } +} diff --git a/mobile/test/unit/factories/user_factory.dart b/mobile/test/unit/factories/user_factory.dart new file mode 100644 index 0000000000..c89b03abfe --- /dev/null +++ b/mobile/test/unit/factories/user_factory.dart @@ -0,0 +1,26 @@ +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, + }) { + id = TestUtils.uuid(id); + return User( + id: id, + name: name ?? 'user_$id', + email: email ?? '$id@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 index b5d91527ea..4f8e608caa 100644 --- a/mobile/test/unit/mocks.dart +++ b/mobile/test/unit/mocks.dart @@ -5,26 +5,30 @@ import 'package:mocktail/mocktail.dart' as mocktail; import '../domain/service.mock.dart'; import '../infrastructure/repository.mock.dart'; -class UnitMocks { +void _registerFallbacks() { + mocktail.registerFallbackValue(LocalAlbum(id: '', name: '', updatedAt: DateTime.now())); + mocktail.registerFallbackValue( + LocalAsset( + id: '', + name: '', + type: AssetType.image, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + playbackStyle: AssetPlaybackStyle.image, + isEdited: false, + ), + ); +} + +class RepositoryMocks { final localAlbum = MockLocalAlbumRepository(); final localAsset = MockDriftLocalAssetRepository(); final trashedAsset = MockTrashedLocalAssetRepository(); final nativeApi = MockNativeSyncApi(); - UnitMocks() { - mocktail.registerFallbackValue(LocalAlbum(id: '', name: '', updatedAt: DateTime.now())); - mocktail.registerFallbackValue( - LocalAsset( - id: '', - name: '', - type: AssetType.image, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - playbackStyle: AssetPlaybackStyle.image, - isEdited: false, - ), - ); + RepositoryMocks() { + _registerFallbacks(); } void reset() { @@ -34,3 +38,15 @@ class UnitMocks { mocktail.reset(nativeApi); } } + +class ServiceMocks { + final partner = MockPartnerService(); + + ServiceMocks() { + _registerFallbacks(); + } + + void reset() { + mocktail.reset(partner); + } +} diff --git a/mobile/test/unit/presentation/partner_page_test.dart b/mobile/test/unit/presentation/partner_page_test.dart new file mode 100644 index 0000000000..957a915ad3 --- /dev/null +++ b/mobile/test/unit/presentation/partner_page_test.dart @@ -0,0 +1,105 @@ +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 '../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(PartnerSharedByList(partners: const [], onAdd: () {}, onRemove: (_) {})); + + expect(find.byType(ListView), findsNothing); + expect(find.widgetWithIcon(ElevatedButton, Icons.person_add), findsOneWidget); + }); + + testWidgets('renders a tile per partner with name and email', (tester) async { + final partner1 = PartnerFactory.create(); + final partner2 = PartnerFactory.create(); + await tester.pumpTestWidget(PartnerSharedByList(partners: [partner1, partner2], onAdd: () {}, onRemove: (_) {})); + + 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('invokes onRemovePartner with the tapped partner', (tester) async { + final partner1 = PartnerFactory.create(inTimeline: true); + final partner2 = PartnerFactory.create(); + Partner? removed; + await tester.pumpTestWidget( + PartnerSharedByList(partners: [partner1, partner2], onAdd: () {}, onRemove: (p) => removed = p), + ); + + await tester.tap(find.byIcon(Icons.person_remove).first); + await tester.pump(); + + expect(removed, partner1); + }); + }); + + 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(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(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(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_context.dart b/mobile/test/unit/presentation_context.dart new file mode 100644 index 0000000000..97b09ba85e --- /dev/null +++ b/mobile/test/unit/presentation_context.dart @@ -0,0 +1,68 @@ +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/store.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/store.repository.dart'; + +import '../test_utils.dart'; + +class PresentationContext { + const PresentationContext._(); + + static const String serverEndpoint = 'http://localhost:3000'; + + static Drift? _db; + + static Future create() async { + TestUtils.init(); + if (_db == null) { + final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db), listenUpdates: false); + await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint); + _db = db; + } + return const PresentationContext._(); + } + + Future dispose() async { + // TODO: Dispose the store and database after each test. + // This is currently not possible because the store is a singleton and is used across tests. + // Refactor the store to be created per test to allow proper disposal. + } +} + +extension PumpPresentationWidget on WidgetTester { + Future pumpTestWidget(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: overrides, + child: Builder( + builder: (context) => MaterialApp( + debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + home: Material(child: widget), + ), + ), + ), + ), + ); + await pumpAndSettle(); + } +} diff --git a/mobile/test/unit/services/hash_service_test.dart b/mobile/test/unit/services/hash_service_test.dart index 8c4a23c06a..223aaf49af 100644 --- a/mobile/test/unit/services/hash_service_test.dart +++ b/mobile/test/unit/services/hash_service_test.dart @@ -10,7 +10,7 @@ import '../mocks.dart'; void main() { late HashService sut; - final mocks = UnitMocks(); + final mocks = RepositoryMocks(); setUp(() { sut = HashService( diff --git a/mobile/test/unit/utils/editor_test.dart b/mobile/test/unit/utils/editor_test.dart index 16f1c08d05..82cf584f76 100644 --- a/mobile/test/unit/utils/editor_test.dart +++ b/mobile/test/unit/utils/editor_test.dart @@ -43,9 +43,7 @@ void main() { }); test('should handle a single 90° rotation', () { - final edits = [ - RotateEdit(RotateParameters(angle: 90)), - ]; + final edits = [RotateEdit(RotateParameters(angle: 90))]; final result = normalizeTransformEdits(edits); final normalizedEdits = normalizedToEdits(result); @@ -54,9 +52,7 @@ void main() { }); test('should handle a single 180° rotation', () { - final edits = [ - RotateEdit(RotateParameters(angle: 180)), - ]; + final edits = [RotateEdit(RotateParameters(angle: 180))]; final result = normalizeTransformEdits(edits); final normalizedEdits = normalizedToEdits(result); @@ -65,9 +61,7 @@ void main() { }); test('should handle a single 270° rotation', () { - final edits = [ - RotateEdit(RotateParameters(angle: 270)), - ]; + final edits = [RotateEdit(RotateParameters(angle: 270))]; final result = normalizeTransformEdits(edits); final normalizedEdits = normalizedToEdits(result); @@ -76,9 +70,7 @@ void main() { }); test('should handle a single horizontal mirror', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - ]; + final edits = [MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal))]; final result = normalizeTransformEdits(edits); final normalizedEdits = normalizedToEdits(result); @@ -87,9 +79,7 @@ void main() { }); test('should handle a single vertical mirror', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; + final edits = [MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical))]; final result = normalizeTransformEdits(edits); final normalizedEdits = normalizedToEdits(result); diff --git a/mobile/test/unit/utils/isolate_worker_test.dart b/mobile/test/unit/utils/isolate_worker_test.dart new file mode 100644 index 0000000000..5b79395f52 --- /dev/null +++ b/mobile/test/unit/utils/isolate_worker_test.dart @@ -0,0 +1,23 @@ +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/semver_test.dart b/mobile/test/unit/utils/semver_test.dart index 1e534af593..bbe8c9e7db 100644 --- a/mobile/test/unit/utils/semver_test.dart +++ b/mobile/test/unit/utils/semver_test.dart @@ -16,7 +16,7 @@ void main() { expect(() => SemVer.fromString('1.2.3.4'), throwsFormatException); }); - test('Compares equal versons correctly', () { + test('Compares equal versions correctly', () { final v1 = SemVer.fromString('1.2.3'); final v2 = SemVer.fromString('1.2.3'); expect(v1 == v2, isTrue); diff --git a/open-api/bin/generate-dart-sdk.sh b/open-api/bin/generate-dart-sdk.sh index e81d28096f..793c1f8df3 100755 --- a/open-api/bin/generate-dart-sdk.sh +++ b/open-api/bin/generate-dart-sdk.sh @@ -1,28 +1,29 @@ #!/usr/bin/env bash -OPENAPI_GENERATOR_VERSION=v7.12.0 +OPENAPI_GENERATOR_VERSION=v7.22.0 set -euo pipefail # usage: ./bin/generate-dart-sdk.sh 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 ratio; + + /// Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets) +- Optional>?> stack; ++ Optional?>?> stack; + + /// Array of BlurHash strings for generating asset previews (base64 encoded) + List thumbhash; diff --git a/open-api/templates/mobile/api.mustache b/open-api/templates/mobile/api.mustache index 2cd4c0f04e..8c6d2bb96e 100644 --- a/open-api/templates/mobile/api.mustache +++ b/open-api/templates/mobile/api.mustache @@ -1,5 +1,6 @@ {{>header}} {{>part_of}} + {{#operations}} class {{{classname}}} { diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache b/open-api/templates/mobile/serialization/native/native_class.mustache index 2d6e6d24f3..973c5d9462 100644 --- a/open-api/templates/mobile/serialization/native/native_class.mustache +++ b/open-api/templates/mobile/serialization/native/native_class.mustache @@ -1,5 +1,6 @@ class {{{classname}}} { {{>dart_constructor}} + {{#vars}} {{#description}} /// {{{.}}} @@ -32,7 +33,17 @@ class {{{classname}}} { {{/required}} {{/isNullable}} {{/isEnum}} - {{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{items.dataType}}}{{#items.isNullable}}?{{/items.isNullable}}>{{/isArray}}{{^isArray}}{{{datatypeWithEnum}}}{{/isArray}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; + {{#required}} + {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}} {{{name}}}; + {{/required}} + {{^required}} + {{#vendorExtensions.x-is-optional}} + {{{datatypeWithEnum}}} {{{name}}}; + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} + {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^defaultValue}}?{{/defaultValue}}{{/isNullable}} {{{name}}}; + {{/vendorExtensions.x-is-optional}} + {{/required}} {{/vars}} @override @@ -54,6 +65,37 @@ class {{{classname}}} { Map toJson() { final json = {}; {{#vars}} + {{#vendorExtensions.x-is-optional}} + if (this.{{{name}}}.isPresent) { + final value = this.{{{name}}}.value; + {{#isDateTime}} + {{#pattern}} + json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') + ? value.millisecondsSinceEpoch + : value.toUtc().toIso8601String()); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = value == null ? null : value.toUtc().toIso8601String(); + {{/pattern}} + {{/isDateTime}} + {{#isDate}} + {{#pattern}} + json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') + ? value.millisecondsSinceEpoch + : _dateFormatter.format(value.toUtc())); + {{/pattern}} + {{^pattern}} + json[r'{{{baseName}}}'] = value == null ? null : _dateFormatter.format(value.toUtc()); + {{/pattern}} + {{/isDate}} + {{^isDateTime}} + {{^isDate}} + json[r'{{{baseName}}}'] = value{{#isArray}}{{#uniqueItems}} == null ? null : value.toList(growable: false){{/uniqueItems}}{{/isArray}}; + {{/isDate}} + {{/isDateTime}} + } + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{#isNullable}} if (this.{{{name}}} != null) { {{/isNullable}} @@ -91,18 +133,19 @@ class {{{classname}}} { {{/isDateTime}} {{#isNullable}} } else { - // json[r'{{{baseName}}}'] = null; + json[r'{{{baseName}}}'] = null; } {{/isNullable}} {{^isNullable}} {{^required}} {{^defaultValue}} } else { - // json[r'{{{baseName}}}'] = null; + json[r'{{{baseName}}}'] = null; } {{/defaultValue}} {{/required}} {{/isNullable}} + {{/vendorExtensions.x-is-optional}} {{/vars}} return json; } @@ -118,58 +161,118 @@ class {{{classname}}} { return {{{classname}}}( {{#vars}} {{#isDateTime}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isDateTime}} {{#isDate}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isDate}} {{^isDateTime}} {{^isDate}} {{#complexType}} {{#isArray}} {{#items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is List + ? (json[r'{{{baseName}}}'] as List).map((e) => + {{#items.complexType}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} + {{/items.complexType}} + {{^items.complexType}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) + {{/items.complexType}} + ).toList() + : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] is List ? (json[r'{{{baseName}}}'] as List).map((e) => {{#items.complexType}} - {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} {{/items.complexType}} {{^items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) {{/items.complexType}} ).toList() : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{^items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{/isArray}} {{^isArray}} {{#isMap}} {{#items.isArray}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null + {{#items.complexType}} + : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/items.complexType}} + {{^items.complexType}} + : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false)))) : const Optional.absent(), + {{/items.complexType}} + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - {{#items.complexType}} + {{#items.complexType}} : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), - {{/items.complexType}} - {{^items.complexType}} - : mapCastOfType(json, r'{{{baseName}}}'), - {{/items.complexType}} + {{/items.complexType}} + {{^items.complexType}} + : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), + {{/items.complexType}} + {{/vendorExtensions.x-is-optional}} {{/items.isArray}} {{^items.isArray}} {{#items.isMap}} {{#items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{items.complexType}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{^items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{/items.isMap}} {{^items.isMap}} {{#items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{^items.complexType}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/items.complexType}} {{/items.isMap}} {{/items.isArray}} @@ -179,7 +282,12 @@ class {{{classname}}} { {{{name}}}: null, // No support for decoding binary content from JSON {{/isBinary}} {{^isBinary}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isBinary}} {{/isMap}} {{/isArray}} @@ -187,37 +295,74 @@ class {{{classname}}} { {{^complexType}} {{#isArray}} {{#isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, + {{/vendorExtensions.x-is-optional}} {{/isEnum}} {{^isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is Iterable + ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} + : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: json[r'{{{baseName}}}'] is Iterable ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, + {{/vendorExtensions.x-is-optional}} {{/isEnum}} {{/isArray}} {{^isArray}} {{#isMap}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} {{/isMap}} {{^isMap}} {{#isNumber}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), + {{/vendorExtensions.x-is-optional}} {{/isNumber}} - {{#isDouble}} - {{{name}}}: (mapValueOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}){{#isNullable}}?{{/isNullable}}.toDouble(), - {{/isDouble}} - {{^isDouble}} {{^isNumber}} - {{^isEnum}} + {{#vendorExtensions.x-original-is-integer}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : int.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-original-is-integer}} + {{^vendorExtensions.x-original-is-integer}} + {{#vendorExtensions.x-original-is-number}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), + {{/vendorExtensions.x-original-is-number}} + {{^vendorExtensions.x-original-is-number}} + {{^isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapValueOfType<{{#vendorExtensions.x-unwrapped-datatype}}{{{vendorExtensions.x-unwrapped-datatype}}}{{/vendorExtensions.x-unwrapped-datatype}}{{^vendorExtensions.x-unwrapped-datatype}}{{{datatypeWithEnum}}}{{/vendorExtensions.x-unwrapped-datatype}}>(json, r'{{{baseName}}}')) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} - {{#isEnum}} - {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} + {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{#isEnum}} + {{#vendorExtensions.x-is-optional}} + {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{enumName}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), + {{/vendorExtensions.x-is-optional}} + {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? const {{{enumName}}}._({{{.}}}){{/defaultValue}}{{/required}}, + {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{/vendorExtensions.x-original-is-number}} + {{/vendorExtensions.x-original-is-integer}} {{/isNumber}} - {{/isDouble}} {{/isMap}} {{/isArray}} {{/complexType}} @@ -284,11 +429,13 @@ class {{{classname}}} { {{^isContainer}} {{>serialization/native/native_enum_inline}} + {{/isContainer}} {{#isContainer}} {{#mostInnerItems}} {{>serialization/native/native_enum_inline}} + {{/mostInnerItems}} {{/isContainer}} {{/isEnum}} diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache.patch b/open-api/templates/mobile/serialization/native/native_class.mustache.patch index 8eeefdad97..6ab7a5228f 100644 --- a/open-api/templates/mobile/serialization/native/native_class.mustache.patch +++ b/open-api/templates/mobile/serialization/native/native_class.mustache.patch @@ -1,60 +1,180 @@ ---- native_class.mustache 2025-07-01 08:29:23.968133163 +0800 -+++ native_class_temp.mustache 2025-07-01 08:29:44.225850583 +0800 -@@ -91,14 +91,14 @@ - {{/isDateTime}} - {{#isNullable}} - } else { -- json[r'{{{baseName}}}'] = null; -+ // json[r'{{{baseName}}}'] = null; - } - {{/isNullable}} - {{^isNullable}} - {{^required}} - {{^defaultValue}} - } else { -- json[r'{{{baseName}}}'] = null; -+ // json[r'{{{baseName}}}'] = null; - } - {{/defaultValue}} - {{/required}} -@@ -111,20 +111,10 @@ +--- native_class.mustache 2026-06-03 12:23:39 ++++ native_class.mustache 2026-06-03 12:21:49 +@@ -154,24 +154,10 @@ /// [value] if it's a [Map], null otherwise. // ignore: prefer_constructors_over_static_methods static {{{classname}}}? fromJson(dynamic value) { + upgradeDto(value, "{{{classname}}}"); if (value is Map) { final json = value.cast(); - + - // Ensure that the map contains the required keys. - // Note 1: the values aren't checked for validity beyond being non-null. - // Note 2: this code is stripped in release mode! - assert(() { -- requiredKeys.forEach((key) { -- assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.'); -- assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.'); -- }); +- {{#vars}} +- {{#required}} +- assert(json.containsKey(r'{{{baseName}}}'), 'Required key "{{{classname}}}[{{{baseName}}}]" is missing from JSON.'); +- {{^isNullable}} +- assert(json[r'{{{baseName}}}'] != null, 'Required key "{{{classname}}}[{{{baseName}}}]" has a null value in JSON.'); +- {{/isNullable}} +- {{/required}} +- {{/vars}} - return true; - }()); - return {{{classname}}}( {{#vars}} {{#isDateTime}} -@@ -215,6 +205,10 @@ +@@ -195,48 +181,98 @@ + {{#complexType}} + {{#isArray}} + {{#items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is List ++ ? (json[r'{{{baseName}}}'] as List).map((e) => ++ {{#items.complexType}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} ++ {{/items.complexType}} ++ {{^items.complexType}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) ++ {{/items.complexType}} ++ ).toList() ++ : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] is List + ? (json[r'{{{baseName}}}'] as List).map((e) => + {{#items.complexType}} +- {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} + {{/items.complexType}} + {{^items.complexType}} +- e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() ++ e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) + {{/items.complexType}} + ).toList() + : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{^items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isArray}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null ++ {{#items.complexType}} ++ : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/items.complexType}} ++ {{^items.complexType}} ++ : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false)))) : const Optional.absent(), ++ {{/items.complexType}} ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] == null + ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} +- {{#items.complexType}} ++ {{#items.complexType}} + : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), +- {{/items.complexType}} +- {{^items.complexType}} +- : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (v as List).cast<{{items.items.dataType}}>())), +- {{/items.complexType}} ++ {{/items.complexType}} ++ {{^items.complexType}} ++ : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), ++ {{/items.complexType}} ++ {{/vendorExtensions.x-is-optional}} + {{/items.isArray}} + {{^items.isArray}} + {{#items.isMap}} + {{#items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{items.complexType}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{^items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{/items.isMap}} + {{^items.isMap}} + {{#items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{^items.complexType}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/items.complexType}} + {{/items.isMap}} + {{/items.isArray}} +@@ -259,23 +295,45 @@ + {{^complexType}} + {{#isArray}} + {{#isEnum}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{^isEnum}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is Iterable ++ ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} ++ : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: json[r'{{{baseName}}}'] is Iterable + ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} + : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isEnum}} + {{/isArray}} + {{^isArray}} + {{#isMap}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, ++ {{/vendorExtensions.x-is-optional}} + {{/isMap}} + {{^isMap}} + {{#isNumber}} ++ {{#vendorExtensions.x-is-optional}} ++ {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), ++ {{/vendorExtensions.x-is-optional}} ++ {{^vendorExtensions.x-is-optional}} + {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), ++ {{/vendorExtensions.x-is-optional}} {{/isNumber}} -+ {{#isDouble}} -+ {{{name}}}: (mapValueOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}){{#isNullable}}?{{/isNullable}}.toDouble(), -+ {{/isDouble}} -+ {{^isDouble}} {{^isNumber}} - {{^isEnum}} - {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, -@@ -223,6 +217,7 @@ - {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/isEnum}} - {{/isNumber}} -+ {{/isDouble}} - {{/isMap}} - {{/isArray}} - {{/complexType}} + {{#vendorExtensions.x-original-is-integer}} diff --git a/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch b/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch deleted file mode 100644 index a59e300913..0000000000 --- a/open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache b/open-api/templates/mobile/serialization/native/native_class.mustache -index 9a7b1439b..9f40d5b0b 100644 ---- a/open-api/templates/mobile/serialization/native/native_class.mustache -+++ b/open-api/templates/mobile/serialization/native/native_class.mustache -@@ -32,7 +32,7 @@ class {{{classname}}} { - {{/required}} - {{/isNullable}} - {{/isEnum}} -- {{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; -+ {{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{items.dataType}}}{{#items.isNullable}}?{{/items.isNullable}}>{{/isArray}}{{^isArray}}{{{datatypeWithEnum}}}{{/isArray}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}}; - - {{/vars}} - @override diff --git a/packages/cli/src/commands/asset.ts b/packages/cli/src/commands/asset.ts index 2c6430c83a..b0a9037289 100644 --- a/packages/cli/src/commands/asset.ts +++ b/packages/cli/src/commands/asset.ts @@ -426,6 +426,8 @@ const uploadFile = async (input: string, stats: Stats): Promise, body: formData, + // eslint-disable-next-line unicorn/no-null + window: null, }); if (response.status !== 200 && response.status !== 201) { throw new Error(await response.text()); diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index fdae7f9a2f..fced9ff6c4 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -2199,8 +2199,6 @@ export type SharedLinkEditDto = { allowDownload?: boolean; /** Allow uploads */ allowUpload?: boolean; - /** 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. */ - changeExpiryTime?: boolean; /** Link description */ description?: string | null; /** Expiration date */ diff --git a/server/src/controllers/asset.controller.spec.ts b/server/src/controllers/asset.controller.spec.ts index acdcb84403..b0191f7217 100644 --- a/server/src/controllers/asset.controller.spec.ts +++ b/server/src/controllers/asset.controller.spec.ts @@ -240,7 +240,7 @@ describe(AssetController.name, () => { for (const [test, errors] of [ [{ rating: 7 }, [{ path: ['rating'], message: 'Too big: expected number to be <=5' }]], [{ rating: 3.5 }, [{ path: ['rating'], message: 'Invalid input: expected int, received number' }]], - [{ rating: -2 }, [{ path: ['rating'], message: 'Too small: expected number to be >=-1' }]], + [{ rating: -2 }, [{ path: ['rating'], message: 'Too small: expected number to be >=1' }]], ] as const) { const { status, body } = await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}`).send(test); expect(status).toBe(400); @@ -248,16 +248,9 @@ describe(AssetController.name, () => { } }); - it('should convert rating 0 to null', async () => { - const assetId = factory.uuid(); - const { status } = await request(ctx.getHttpServer()).put(`/assets/${assetId}`).send({ rating: 0 }); - expect(service.update).toHaveBeenCalledWith(undefined, assetId, { rating: null }); - expect(status).toBe(200); - }); - it('should leave correct ratings as-is', async () => { const assetId = factory.uuid(); - for (const test of [{ rating: -1 }, { rating: 1 }, { rating: 5 }]) { + for (const test of [{ rating: 1 }, { rating: 5 }]) { const { status } = await request(ctx.getHttpServer()).put(`/assets/${assetId}`).send(test); expect(service.update).toHaveBeenCalledWith(undefined, assetId, test); expect(status).toBe(200); diff --git a/server/src/controllers/person.controller.spec.ts b/server/src/controllers/person.controller.spec.ts index cf3a5e56b0..64fe4f3554 100644 --- a/server/src/controllers/person.controller.spec.ts +++ b/server/src/controllers/person.controller.spec.ts @@ -53,16 +53,6 @@ describe(PersonController.name, () => { await request(ctx.getHttpServer()).post('/people'); expect(ctx.authenticate).toHaveBeenCalled(); }); - - it('should map an empty birthDate to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ birthDate: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { birthDate: null }); - }); - - it('should map an empty color to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ color: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { color: null }); - }); }); describe('DELETE /people', () => { @@ -153,12 +143,6 @@ describe(PersonController.name, () => { ); }); - it('should map an empty birthDate to null', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/people/${id}`).send({ birthDate: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, { birthDate: null }); - }); - it('should not accept an invalid birth date (false)', async () => { const { status, body } = await request(ctx.getHttpServer()) .put(`/people/${factory.uuid()}`) diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts index c9f3fa7825..b696db8212 100644 --- a/server/src/controllers/sync.controller.ts +++ b/server/src/controllers/sync.controller.ts @@ -1,6 +1,6 @@ -import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SyncAckDeleteDto, SyncAckDto, SyncAckSetDto, SyncStreamDto } from 'src/dtos/sync.dto'; @@ -27,12 +27,12 @@ export class SyncController { '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.', history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) - async getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) { + async getSyncStream(@Auth() auth: AuthDto, @Req() req: Request, @Res() res: Response, @Body() dto: SyncStreamDto) { try { await this.service.stream(auth, res, dto); } catch (error: Error | any) { res.setHeader('Content-Type', 'application/json'); - this.errorService.handleError(res, error); + this.errorService.handleError(req, res, error); } } diff --git a/server/src/controllers/tag.controller.spec.ts b/server/src/controllers/tag.controller.spec.ts index 907e99bb43..c2a2de95bd 100644 --- a/server/src/controllers/tag.controller.spec.ts +++ b/server/src/controllers/tag.controller.spec.ts @@ -63,11 +63,5 @@ describe(TagController.name, () => { await request(ctx.getHttpServer()).put(`/tags/${factory.uuid()}`); expect(ctx.authenticate).toHaveBeenCalled(); }); - - it('should allow setting a null color via an empty string', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/tags/${id}`).send({ color: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ color: null })); - }); }); }); diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index 100550659d..ee6f2c07eb 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -6,7 +6,7 @@ import { MapAsset } from 'src/dtos/asset-response.dto'; import { UserResponseSchema, mapUser } from 'src/dtos/user.dto'; import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { stringToBool } from 'src/validation'; import z from 'zod'; @@ -195,14 +195,14 @@ export const mapAlbum = (entity: MaybeDehydrated): AlbumResponseDto albumName: entity.albumName, description: entity.description, albumThumbnailAssetId: entity.albumThumbnailAssetId, - createdAt: asDateString(entity.createdAt), - updatedAt: asDateString(entity.updatedAt), + createdAt: asDateTimeString(entity.createdAt), + updatedAt: asDateTimeString(entity.updatedAt), id: entity.id, albumUsers, shared: hasSharedUser || hasSharedLink, hasSharedLink, - startDate: asDateString(startDate), - endDate: asDateString(endDate), + startDate: asDateTimeString(startDate), + endDate: asDateTimeString(endDate), assetCount: entity.assets?.length || 0, isActivityEnabled: entity.isActivityEnabled, order: entity.order, diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 6d72fd971a..b5b0b04a2d 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -18,7 +18,7 @@ import { } from 'src/enum'; import { MaybeDehydrated } from 'src/types'; import { hexOrBufferToBase64 } from 'src/utils/bytes'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { mimeTypes } from 'src/utils/mime-types'; import z from 'zod'; @@ -199,7 +199,7 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt type: entity.type, originalMimeType: mimeTypes.lookup(entity.originalFileName), thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - localDateTime: asDateString(entity.localDateTime), + localDateTime: asDateTimeString(entity.localDateTime), duration: entity.duration, livePhotoVideoId: entity.livePhotoVideoId, hasMetadata: false, @@ -211,7 +211,7 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt return { id: entity.id, - createdAt: asDateString(entity.createdAt), + createdAt: asDateTimeString(entity.createdAt), ownerId: entity.ownerId, owner: entity.owner ? mapUser(entity.owner) : undefined, libraryId: entity.libraryId, @@ -220,10 +220,10 @@ export function mapAsset(entity: MaybeDehydrated, options: AssetMapOpt originalFileName: entity.originalFileName, originalMimeType: mimeTypes.lookup(entity.originalFileName), thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - fileCreatedAt: asDateString(entity.fileCreatedAt), - fileModifiedAt: asDateString(entity.fileModifiedAt), - localDateTime: asDateString(entity.localDateTime), - updatedAt: asDateString(entity.updatedAt), + fileCreatedAt: asDateTimeString(entity.fileCreatedAt), + fileModifiedAt: asDateTimeString(entity.fileModifiedAt), + localDateTime: asDateTimeString(entity.localDateTime), + updatedAt: asDateTimeString(entity.updatedAt), isFavorite: options.auth?.user.id === entity.ownerId && entity.isFavorite, isArchived: entity.visibility === AssetVisibility.Archive, isTrashed: !!entity.deletedAt, diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 1f6124b7db..5072b3aa17 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -14,11 +14,9 @@ const UpdateAssetBaseSchema = z latitude: latitudeSchema.optional().describe('Latitude coordinate'), longitude: longitudeSchema.optional().describe('Longitude coordinate'), rating: z - .number() .int() - .min(-1) + .min(1) .max(5) - .transform((value) => (value === 0 ? null : value)) .nullish() .describe('Rating in range [1-5], or null for unrated') .meta({ @@ -26,6 +24,7 @@ const UpdateAssetBaseSchema = z .added('v1') .stable('v2') .updated('v2.6.0', 'Using -1 as a rating is deprecated and will be removed in the next major version.') + .updated('v3', 'Using -1 as a rating is no longer valid.') .getExtensions(), }), description: z.string().optional().describe('Asset description'), diff --git a/server/src/dtos/exif.dto.ts b/server/src/dtos/exif.dto.ts index 37274ee1f9..b918c4abd1 100644 --- a/server/src/dtos/exif.dto.ts +++ b/server/src/dtos/exif.dto.ts @@ -1,7 +1,7 @@ import { createZodDto } from 'nestjs-zod'; import { Exif } from 'src/database'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import z from 'zod'; export const ExifResponseSchema = z @@ -29,7 +29,7 @@ export const ExifResponseSchema = z country: z.string().nullish().default(null).describe('Country name'), description: z.string().nullish().default(null).describe('Image description'), projectionType: z.string().nullish().default(null).describe('Projection type'), - rating: z.int().nullish().default(null).describe('Rating'), + rating: z.int().min(1).max(5).nullish().default(null).describe('Rating'), }) .describe('EXIF response') .meta({ id: 'ExifResponseDto' }); @@ -44,8 +44,8 @@ export function mapExif(entity: MaybeDehydrated): ExifResponseDto { exifImageHeight: entity.exifImageHeight, fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null, orientation: entity.orientation, - dateTimeOriginal: asDateString(entity.dateTimeOriginal), - modifyDate: asDateString(entity.modifyDate), + dateTimeOriginal: asDateTimeString(entity.dateTimeOriginal), + modifyDate: asDateTimeString(entity.modifyDate), timeZone: entity.timeZone, lensModel: entity.lensModel, fNumber: entity.fNumber, diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index f39cfd1c88..38e856b8c1 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -7,22 +7,24 @@ import { AssetEditActionItem } from 'src/dtos/editing.dto'; import { SourceTypeSchema } from 'src/enum'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; import { ImageDimensions, MaybeDehydrated } from 'src/types'; -import { asBirthDateString, asDateString } from 'src/utils/date'; +import { asDateString, asDateTimeString } from 'src/utils/date'; import { transformFaceBoundingBox } from 'src/utils/transform'; -import { emptyStringToNull, hexColor, stringToBool } from 'src/validation'; +import { hexColor, stringToBool } from 'src/validation'; import z from 'zod'; const PersonCreateSchema = z .object({ name: z.string().optional().describe('Person name'), - // Note: the mobile app cannot currently set the birth date to null. - birthDate: emptyStringToNull(z.string().meta({ format: 'date' }).nullable()) + birthDate: z + .string() + .meta({ format: 'date' }) + .nullable() .optional() .refine((val) => (val ? new Date(val) <= new Date() : true), { error: 'Birth date cannot be in the future' }) .describe('Person date of birth'), isHidden: z.boolean().optional().describe('Person visibility (hidden)'), isFavorite: z.boolean().optional().describe('Mark as favorite'), - color: emptyStringToNull(hexColor.nullable()).optional().describe('Person color (hex)'), + color: hexColor.nullable().optional().describe('Person color (hex)'), }) .meta({ id: 'PersonCreateDto' }); @@ -173,12 +175,12 @@ export function mapPerson(person: MaybeDehydrated): PersonResponseDto { return { id: person.id, name: person.name, - birthDate: asBirthDateString(person.birthDate), + birthDate: asDateString(person.birthDate), thumbnailPath: person.thumbnailPath, isHidden: person.isHidden, isFavorite: person.isFavorite, color: person.color ?? undefined, - updatedAt: asDateString(person.updatedAt), + updatedAt: asDateTimeString(person.updatedAt), }; } diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index c9a92b165f..ec4d58dae3 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -4,7 +4,7 @@ import { HistoryBuilder } from 'src/decorators'; import { AlbumResponseSchema } from 'src/dtos/album.dto'; import { AssetResponseSchema } from 'src/dtos/asset-response.dto'; import { AssetOrder, AssetOrderSchema, AssetTypeSchema, AssetVisibilitySchema } from 'src/enum'; -import { emptyStringToNull, isoDatetimeToDate, stringToBool } from 'src/validation'; +import { isoDatetimeToDate, stringToBool } from 'src/validation'; import z from 'zod'; const BaseSearchSchema = z.object({ @@ -23,19 +23,19 @@ const BaseSearchSchema = z.object({ trashedAfter: isoDatetimeToDate.optional().describe('Filter by trash date (after)'), takenBefore: isoDatetimeToDate.optional().describe('Filter by taken date (before)'), takenAfter: isoDatetimeToDate.optional().describe('Filter by taken date (after)'), - city: emptyStringToNull(z.string().nullable()).optional().describe('Filter by city name'), - state: emptyStringToNull(z.string().nullable()).optional().describe('Filter by state/province name'), - country: emptyStringToNull(z.string().nullable()).optional().describe('Filter by country name'), - make: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera make'), - model: emptyStringToNull(z.string().nullable()).optional().describe('Filter by camera model'), - lensModel: emptyStringToNull(z.string().nullable()).optional().describe('Filter by lens model'), + city: z.string().nullable().optional().describe('Filter by city name'), + state: z.string().nullable().optional().describe('Filter by state/province name'), + country: z.string().nullable().optional().describe('Filter by country name'), + make: z.string().nullable().optional().describe('Filter by camera make'), + model: z.string().nullable().optional().describe('Filter by camera model'), + lensModel: z.string().nullable().optional().describe('Filter by lens model'), isNotInAlbum: z.boolean().optional().describe('Filter assets not in any album'), personIds: z.array(z.uuidv4()).optional().describe('Filter by person IDs'), tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'), albumIds: z.array(z.uuidv4()).optional().describe('Filter by album IDs'), rating: z .int() - .min(-1) + .min(1) .max(5) .nullish() .describe('Filter by rating [1-5], or null for unrated') @@ -44,6 +44,7 @@ const BaseSearchSchema = z.object({ .added('v1') .stable('v2') .updated('v2.6.0', 'Using -1 as a rating is deprecated and will be removed in the next major version.') + .updated('v3', 'Using -1 as a rating is no longer valid.') .getExtensions(), }), ocr: z.string().optional().describe('Filter by OCR text content'), diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts index 2e466c5014..aebe6fcab1 100644 --- a/server/src/dtos/shared-link.dto.ts +++ b/server/src/dtos/shared-link.dto.ts @@ -4,7 +4,7 @@ import { HistoryBuilder } from 'src/decorators'; import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto'; import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto'; import { SharedLinkTypeSchema } from 'src/enum'; -import { emptyStringToNull, isoDatetimeToDate } from 'src/validation'; +import { isoDatetimeToDate } from 'src/validation'; import z from 'zod'; const SharedLinkSearchSchema = z @@ -23,9 +23,9 @@ const SharedLinkCreateSchema = z type: SharedLinkTypeSchema, assetIds: z.array(z.uuidv4()).optional().describe('Asset IDs (for individual assets)'), albumId: z.uuidv4().optional().describe('Album ID (for album sharing)'), - description: emptyStringToNull(z.string().nullable()).optional().describe('Link description'), - password: emptyStringToNull(z.string().nullable()).optional().describe('Link password'), - slug: emptyStringToNull(z.string().nullable()).optional().describe('Custom URL slug'), + description: z.string().nullable().optional().describe('Link description'), + password: z.string().nullable().optional().describe('Link password'), + slug: z.string().nullable().optional().describe('Custom URL slug'), expiresAt: isoDatetimeToDate.nullable().describe('Expiration date').default(null).optional(), allowUpload: z.boolean().optional().describe('Allow uploads'), allowDownload: z.boolean().default(true).optional().describe('Allow downloads'), @@ -35,19 +35,13 @@ const SharedLinkCreateSchema = z const SharedLinkEditSchema = z .object({ - description: emptyStringToNull(z.string().nullable()).optional().describe('Link description'), - password: emptyStringToNull(z.string().nullable()).optional().describe('Link password'), - slug: emptyStringToNull(z.string().nullable()).optional().describe('Custom URL slug'), + description: z.string().nullable().optional().describe('Link description'), + password: z.string().nullable().optional().describe('Link password'), + slug: z.string().nullable().optional().describe('Custom URL slug'), expiresAt: isoDatetimeToDate.nullish().describe('Expiration date'), allowUpload: z.boolean().optional().describe('Allow uploads'), allowDownload: z.boolean().optional().describe('Allow downloads'), showMetadata: z.boolean().optional().describe('Show metadata'), - changeExpiryTime: z - .boolean() - .optional() - .describe( - '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.', - ), }) .meta({ id: 'SharedLinkEditDto' }); diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts index 67dbca9914..dd679c97cb 100644 --- a/server/src/dtos/tag.dto.ts +++ b/server/src/dtos/tag.dto.ts @@ -1,21 +1,21 @@ import { createZodDto } from 'nestjs-zod'; import { Tag } from 'src/database'; import { MaybeDehydrated } from 'src/types'; -import { asDateString } from 'src/utils/date'; -import { emptyStringToNull, hexColor } from 'src/validation'; +import { asDateTimeString } from 'src/utils/date'; +import { hexColor } from 'src/validation'; import z from 'zod'; const TagCreateSchema = z .object({ name: z.string().describe('Tag name'), parentId: z.uuidv4().nullish().describe('Parent tag ID'), - color: emptyStringToNull(hexColor.nullable()).optional().describe('Tag color (hex)'), + color: hexColor.nullable().optional().describe('Tag color (hex)'), }) .meta({ id: 'TagCreateDto' }); const TagUpdateSchema = z .object({ - color: emptyStringToNull(hexColor.nullable()).optional().describe('Tag color (hex)'), + color: hexColor.nullable().optional().describe('Tag color (hex)'), }) .meta({ id: 'TagUpdateDto' }); @@ -65,8 +65,8 @@ export function mapTag(entity: MaybeDehydrated): TagResponseDto { parentId: entity.parentId ?? undefined, name: entity.value.split('/').at(-1) as string, value: entity.value, - createdAt: asDateString(entity.createdAt), - updatedAt: asDateString(entity.updatedAt), + createdAt: asDateTimeString(entity.createdAt), + updatedAt: asDateTimeString(entity.updatedAt), color: entity.color ?? undefined, }; } diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 75256b9e1a..528163e57c 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -3,8 +3,8 @@ import { User, UserAdmin } from 'src/database'; import { pinCodeRegex } from 'src/dtos/auth.dto'; import { UserAvatarColor, UserAvatarColorSchema, UserMetadataKey, UserStatusSchema } from 'src/enum'; import { MaybeDehydrated, UserMetadataItem } from 'src/types'; -import { asDateString } from 'src/utils/date'; -import { emptyStringToNull, isoDatetimeToDate, sanitizeFilename, stringToBool, toEmail } from 'src/validation'; +import { asDateTimeString } from 'src/utils/date'; +import { isoDatetimeToDate, sanitizeFilename, stringToBool, toEmail } from 'src/validation'; import z from 'zod'; export const UserUpdateMeSchema = z @@ -61,7 +61,7 @@ export const mapUser = (entity: MaybeDehydrated): UserResponse name: entity.name, profileImagePath: entity.profileImagePath, avatarColor: entity.avatarColor ?? emailToAvatarColor(entity.email), - profileChangedAt: asDateString(entity.profileChangedAt), + profileChangedAt: asDateTimeString(entity.profileChangedAt), }; }; @@ -80,10 +80,7 @@ export const UserAdminCreateSchema = z password: z.string().describe('User password'), name: z.string().describe('User name'), avatarColor: UserAvatarColorSchema.nullish(), - pinCode: emptyStringToNull(z.string().regex(pinCodeRegex).nullable()) - .optional() - .describe('PIN code') - .meta({ example: '123456' }), + pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), quotaSizeInBytes: z.int().min(0).nullish().describe('Storage quota in bytes'), shouldChangePassword: z.boolean().optional().describe('Require password change on next login'), @@ -98,10 +95,7 @@ const UserAdminUpdateSchema = z .object({ email: toEmail.optional().describe('User email'), password: z.string().optional().describe('User password'), - pinCode: emptyStringToNull(z.string().regex(pinCodeRegex).nullable()) - .optional() - .describe('PIN code') - .meta({ example: '123456' }), + pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), name: z.string().optional().describe('User name'), avatarColor: UserAvatarColorSchema.nullish(), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 3c0c09aa54..2cf5369e98 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -1,14 +1,14 @@ import { CallHandler, ExecutionContext, - HttpException, Injectable, InternalServerErrorException, NestInterceptor, } from '@nestjs/common'; +import { Request } from 'express'; import { Observable, catchError, throwError } from 'rxjs'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; +import { isHttpException, onRequestError } from 'src/utils/logger'; import { routeToErrorMessage } from 'src/utils/misc'; @Injectable() @@ -18,14 +18,16 @@ export class ErrorInterceptor implements NestInterceptor { } intercept(context: ExecutionContext, next: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + return next.handle().pipe( catchError((error) => throwError(() => { - if (error instanceof HttpException) { + if (isHttpException(error)) { return error; } - logGlobalError(this.logger, error); + onRequestError(req, error, this.logger); const message = routeToErrorMessage(context.getHandler().name); return new InternalServerErrorException(message); diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 63acb13789..1bbab36a87 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -96,7 +96,11 @@ export class FileUploadInterceptor implements NestInterceptor { private handleFile(request: AuthRequest, file: Express.Multer.File, callback: Callback>) { request.on('error', (error) => { - this.logger.warn('Request error while uploading file, cleaning up', error); + if ('code' in error && error.code === 'ECONNRESET') { + this.logger.debug('Upload was cancelled'); + } else { + this.logger.error(`Upload failed with: ${error}`); + } this.assetService.onUploadError(request, file).catch(this.logger.error); }); diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index 7572274d15..67bba5c358 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -1,10 +1,10 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { ClsService } from 'nestjs-cls'; import { ZodSerializationException, ZodValidationException } from 'nestjs-zod'; import { ImmichHeader } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; +import { onRequestError } from 'src/utils/logger'; import { ZodError } from 'zod'; @Catch() @@ -17,10 +17,13 @@ export class GlobalExceptionFilter implements ExceptionFilter { } catch(error: Error, host: ArgumentsHost) { - this.handleError(host.switchToHttp().getResponse(), error); + const http = host.switchToHttp(); + this.handleError(http.getRequest(), http.getResponse(), error); } - handleError(res: Response, error: Error) { + handleError(req: Request, res: Response, error: Error) { + onRequestError(req, error, this.logger); + const { status, body } = this.fromError(error); if (!res.headersSent) { res.header(ImmichHeader.CorrelationId, this.cls.getId()).status(status).json(body); @@ -28,8 +31,6 @@ export class GlobalExceptionFilter implements ExceptionFilter { } private fromError(error: Error) { - logGlobalError(this.logger, error); - if (error instanceof HttpException) { const status = error.getStatus(); const response = error.getResponse(); diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index df69e85d84..a4e58c52ec 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -24,7 +24,7 @@ import { DB } from 'src/schema'; import { immich_uuid_v7 } from 'src/schema/functions'; import { ExtensionVersion, VectorExtension } from 'src/types'; import { vectorIndexQuery } from 'src/utils/database'; -import { isValidInteger } from 'src/validation'; +import z from 'zod'; export let cachedVectorExtension: VectorExtension | undefined; export async function getVectorExtension(runner: Kysely): Promise { @@ -292,7 +292,13 @@ export class DatabaseRepository { `.execute(this.db); const dimSize = rows[0]?.dimsize; - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { + if ( + !z + .int() + .min(1) + .max(2 ** 16) + .safeParse(dimSize).success + ) { this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`); return 512; } @@ -300,7 +306,13 @@ export class DatabaseRepository { } async setDimensionSize(dimSize: number): Promise { - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { + if ( + !z + .int() + .min(1) + .max(2 ** 16) + .safeParse(dimSize).success + ) { throw new Error(`Invalid CLIP dimension size: ${dimSize}`); } diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index 6f03c80ce1..da3f31555a 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -8,7 +8,7 @@ import { DB } from 'src/schema'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { anyUuid, searchAssetBuilder, withExifInner } from 'src/utils/database'; import { paginationHelper } from 'src/utils/pagination'; -import { isValidInteger } from 'src/validation'; +import z from 'zod'; export interface SearchAssetIdOptions { checksum?: Buffer; @@ -278,7 +278,7 @@ export class SearchRepository { ], }) searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions) { - if (!isValidInteger(pagination.size, { min: 1, max: 1000 })) { + if (!z.int().min(1).max(1000).safeParse(pagination.size).success) { throw new Error(`Invalid value for 'size': ${pagination.size}`); } @@ -313,7 +313,7 @@ export class SearchRepository { ], }) searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) { - if (!isValidInteger(numResults, { min: 1, max: 1000 })) { + if (!z.int().min(1).max(1000).safeParse(numResults).success) { throw new Error(`Invalid value for 'numResults': ${numResults}`); } diff --git a/server/src/schema/migrations/1780592070031-ConvertNegativeRatingToNull.ts b/server/src/schema/migrations/1780592070031-ConvertNegativeRatingToNull.ts new file mode 100644 index 0000000000..f54136dc2c --- /dev/null +++ b/server/src/schema/migrations/1780592070031-ConvertNegativeRatingToNull.ts @@ -0,0 +1,9 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`UPDATE "asset_exif" SET "rating" = NULL WHERE "rating" = -1;`.execute(db); +} + +export async function down(): Promise { + // not supported +} diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index 31c4ff2e38..564f4bfb3f 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -18,7 +18,7 @@ import { AlbumUserRole, Permission } from 'src/enum'; import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; -import { asDateString } from 'src/utils/date'; +import { asDateTimeString } from 'src/utils/date'; import { getPreferences } from 'src/utils/preferences'; @Injectable() @@ -59,11 +59,11 @@ export class AlbumService extends BaseService { return albums.map((album) => ({ ...mapAlbum(album), sharedLinks: undefined, - startDate: asDateString(albumMetadata[album.id]?.startDate ?? undefined), - endDate: asDateString(albumMetadata[album.id]?.endDate ?? undefined), + startDate: asDateTimeString(albumMetadata[album.id]?.startDate ?? undefined), + endDate: asDateTimeString(albumMetadata[album.id]?.endDate ?? undefined), assetCount: albumMetadata[album.id]?.assetCount ?? 0, // lastModifiedAssetTimestamp is only used in mobile app, please remove if not need - lastModifiedAssetTimestamp: asDateString(albumMetadata[album.id]?.lastModifiedAssetTimestamp ?? undefined), + lastModifiedAssetTimestamp: asDateTimeString(albumMetadata[album.id]?.lastModifiedAssetTimestamp ?? undefined), })); } @@ -79,10 +79,10 @@ export class AlbumService extends BaseService { return { ...mapAlbum(album), - startDate: asDateString(albumMetadataForIds?.startDate ?? undefined), - endDate: asDateString(albumMetadataForIds?.endDate ?? undefined), + startDate: asDateTimeString(albumMetadataForIds?.startDate ?? undefined), + endDate: asDateTimeString(albumMetadataForIds?.endDate ?? undefined), assetCount: albumMetadataForIds?.assetCount ?? 0, - lastModifiedAssetTimestamp: asDateString(albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined), + lastModifiedAssetTimestamp: asDateTimeString(albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined), contributorCounts: isShared ? await this.albumRepository.getContributorCounts(album.id) : undefined, }; } diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts index f5ffe52375..ffb92f3e00 100644 --- a/server/src/services/metadata.service.spec.ts +++ b/server/src/services/metadata.service.spec.ts @@ -1610,22 +1610,6 @@ describe(MetadataService.name, () => { ); }); - it('should handle valid negative rating value', async () => { - const asset = AssetFactory.create(); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset)); - mockReadTags({ Rating: -1 }); - - await sut.handleMetadataExtraction({ id: asset.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - exif: expect.objectContaining({ - rating: -1, - }), - lockedPropertiesBehavior: 'skip', - }), - ); - }); - it('should handle livePhotoCID not set', async () => { const asset = AssetFactory.create(); mocks.assetJob.getForMetadataExtraction.mockResolvedValue(getForMetadataExtraction(asset)); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index a3e9c19472..70e059d4bf 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -305,7 +305,7 @@ export class MetadataService extends BaseService { // comments description: String(exifTags.ImageDescription || exifTags.Description || '').trim(), profileDescription: exifTags.ProfileDescription || null, - rating: exifTags.Rating === 0 ? null : validateRange(exifTags.Rating, -1, 5), + rating: exifTags.Rating === 0 ? null : validateRange(exifTags.Rating, 1, 5), // grouping livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null, diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts index 0643a432b8..79e4b23e6a 100644 --- a/server/src/services/shared-link.service.ts +++ b/server/src/services/shared-link.service.ts @@ -124,7 +124,7 @@ export class SharedLinkService extends BaseService { userId: auth.user.id, description: dto.description, password: dto.password, - expiresAt: dto.changeExpiryTime && !dto.expiresAt ? null : dto.expiresAt, + expiresAt: dto.expiresAt, allowUpload: dto.allowUpload, allowDownload: dto.allowDownload, showExif: dto.showMetadata, diff --git a/server/src/utils/date.spec.ts b/server/src/utils/date.spec.ts new file mode 100644 index 0000000000..729b515467 --- /dev/null +++ b/server/src/utils/date.spec.ts @@ -0,0 +1,33 @@ +import { asDateString, asDateTimeString } from 'src/utils/date'; +import { describe, expect, it } from 'vitest'; + +describe('asDateString', () => { + it('should return null for null input', () => { + expect(asDateString(null)).toBeNull(); + }); + + it('should pass through a pre-serialized string unchanged', () => { + expect(asDateString('2000-01-15')).toBe('2000-01-15'); + }); + + it('should return the local calendar date, not the UTC date', () => { + const date = new Date(2000, 0, 15); // 15 Jan 2000, local midnight + expect(asDateString(date)).toBe('2000-01-15'); + }); +}); + +describe('asDateTimeString', () => { + it('should return null for null input', () => { + expect(asDateTimeString(null)).toBeNull(); + }); + + it('should pass through a pre-serialized string unchanged', () => { + const iso = '2000-01-15T12:00:00.000Z'; + expect(asDateTimeString(iso)).toBe(iso); + }); + + it('should return an ISO 8601 datetime string for a Date', () => { + const date = new Date('2000-01-15T12:00:00.000Z'); + expect(asDateTimeString(date)).toBe('2000-01-15T12:00:00.000Z'); + }); +}); diff --git a/server/src/utils/date.ts b/server/src/utils/date.ts index d4de1eba86..394bab8a02 100644 --- a/server/src/utils/date.ts +++ b/server/src/utils/date.ts @@ -1,23 +1,18 @@ import { DateTime } from 'luxon'; +import { isoDateToDate, isoDatetimeToDate } from 'src/validation'; /** * Convert a date to a ISO 8601 datetime string. - * @param x - The date to convert. - * @returns The ISO 8601 datetime string. - * @deprecated Remove this and all references when using `ZodSerializerDto` on the controllers. Then the codec in `isoDatetimeToDate` in validation.ts will handle the conversion instead. */ -export const asDateString = (x: T) => { - return x instanceof Date ? x.toISOString() : (x as Exclude); +export const asDateTimeString = (x: T) => { + return x instanceof Date ? isoDatetimeToDate.encode(x) : (x as Exclude); }; /** - * Convert a date to a date string. - * @param x - The date to convert. - * @returns The date string. - * @deprecated Remove this and all references when using `ZodSerializerDto` on the controllers. Then the codec in `isoDateToDate` in validation.ts will handle the conversion instead. + * Convert a date to a date string (yyyy-mm-dd). */ -export const asBirthDateString = (x: Date | string | null): string | null => { - return x instanceof Date ? x.toISOString().split('T')[0] : x; +export const asDateString = (x: Date | string | null): string | null => { + return x instanceof Date ? isoDateToDate.encode(x) : x; }; export const extractTimeZone = (dateTimeOriginal?: string | null) => { diff --git a/server/src/utils/duplicate.spec.ts b/server/src/utils/duplicate.spec.ts index 155438f1bd..0c353aedcb 100644 --- a/server/src/utils/duplicate.spec.ts +++ b/server/src/utils/duplicate.spec.ts @@ -78,7 +78,7 @@ describe('duplicate utils', () => { model: null, latitude: undefined, city: '', - rating: 0, + rating: null, }); // fileSizeInByte (1000) + make ('Canon') = 2 truthy values // model (null), latitude (undefined), city (''), rating (0) are all falsy diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index ecc8847043..df321f24c2 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,14 +1,23 @@ import { HttpException } from '@nestjs/common'; +import { Request } from 'express'; import { LoggingRepository } from 'src/repositories/logging.repository'; -export const logGlobalError = (logger: LoggingRepository, error: Error) => { - if (error instanceof HttpException) { +const isRequestAborted = (request: Request) => request.destroyed === true && request.complete === false; +export const isHttpException = (error: Error): error is HttpException => error instanceof HttpException; + +export const onRequestError = (req: Request, error: Error, logger: LoggingRepository) => { + if (isHttpException(error)) { const status = error.getStatus(); const response = error.getResponse(); logger.debug(`HttpException(${status}): ${JSON.stringify(response)}`); return; } + if (isRequestAborted(req)) { + logger.debug(`Client aborted request: ${error}`); + return; + } + if (error instanceof Error) { logger.error(`Unknown error: ${error}`, error?.stack); return; diff --git a/server/src/validation.ts b/server/src/validation.ts index 59131b3abe..95bfe003a4 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -125,11 +125,6 @@ const FilenameParamSchema = z.object({ export class FilenameParamDto extends createZodDto(FilenameParamSchema) {} -export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { - const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; - return Number.isInteger(value) && value >= min && value <= max; -}; - /** * Unified email validation * Converts email strings to lowercase and validates against HTML5 email regex @@ -171,7 +166,12 @@ export const isoDateToDate = z z.date(), { decode: (isoString) => new Date(isoString), - encode: (date) => date.toISOString().slice(0, 10), + encode: (date) => { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; + }, }, ) .meta({ example: '2024-01-01' }); @@ -251,16 +251,4 @@ export const hexColor = z .regex(hexColorRegex) .transform((val) => (val.startsWith('#') ? val : `#${val}`)); -/** - * Transform empty strings to null. Inner schema passed to this function must accept null. - * @docs https://zod.dev/api?id=preprocess - * @example emptyStringToNull(z.string().nullable()).optional() // [encouraged] final schema is optional - * @example emptyStringToNull(z.string().nullable()) // [encouraged] same as the one above, but final schema is not optional - * @example emptyStringToNull(z.string().nullish()) // [discouraged] same as the one above, might be confusing - * @example emptyStringToNull(z.string().optional()) // fails: string schema rejects null - * @example emptyStringToNull(z.string().nullable()).nullish() // [discouraged] passes, null is duplicated. use the first example instead - */ -export const emptyStringToNull = (schema: T) => - z.preprocess((val) => (val === '' ? null : val), schema); - export const sanitizeFilename = z.string().transform((val) => sanitize(val.replaceAll('.', ''))); diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte index 16f7c6cfb0..f12caa8c79 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte @@ -476,16 +476,18 @@ - + navigateStack('previous') }, + { shortcut: { key: 'ArrowDown' }, onShortcut: () => navigateStack('next') }, + ]} +/>
navigateStack('previous') }, - { shortcut: { key: 'ArrowDown' }, onShortcut: () => navigateStack('next') }, - ]} bind:this={assetViewerHtmlElement} > diff --git a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte index 8f84466295..d218b512b6 100644 --- a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte +++ b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte @@ -4,10 +4,12 @@ import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte'; + import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; + import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte'; import { autoPlayVideo, lang, loopVideo as loopVideoPreference } from '$lib/stores/preferences.store'; import { getAssetHlsSessionUrl, getAssetHlsUrl, getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils'; import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk'; - import { Icon, LoadingSpinner } from '@immich/ui'; + import { Icon, LoadingSpinner, shortcuts } from '@immich/ui'; import { mdiCheck, mdiChevronLeft, @@ -21,9 +23,9 @@ mdiVolumeMedium, mdiVolumeMute, } from '@mdi/js'; - import Hls, { AbrController, Events, type FragLoadedData, type FragLoadingData, type HlsConfig } from 'hls.js'; import 'hls-video-element'; import type HlsVideoElement from 'hls-video-element'; + import Hls, { AbrController, Events, type FragLoadedData, type FragLoadingData, type HlsConfig } from 'hls.js'; import 'media-chrome/media-control-bar'; import 'media-chrome/media-controller'; import 'media-chrome/media-fullscreen-button'; @@ -31,7 +33,6 @@ import 'media-chrome/media-play-button'; import 'media-chrome/media-playback-rate-button'; import 'media-chrome/media-time-display'; - import './immich-time-range'; import 'media-chrome/media-volume-range'; import 'media-chrome/menu/media-playback-rate-menu'; import 'media-chrome/menu/media-rendition-menu'; @@ -42,8 +43,7 @@ import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; - import { mediaCapabilitiesManager } from '$lib/managers/media-capabilities-manager.svelte'; + import './immich-time-range'; interface Props { asset: AssetResponseDto; @@ -318,6 +318,15 @@ const onSeeking = (event: Event) => event.currentTarget?.dispatchEvent(new Event('timeupdate')); + (videoPlayer?.paused ? videoPlayer?.play() : videoPlayer?.pause()), + }, + ]} +/> + {#if showVideo}
({ ...step, id: generateId() }))); let savedWorkflow = $state(cloneDeep(data.workflow)); let allowNavigation = $state(false); let isShowingNavigationDialog = $state(false); let isSaving = $state(false); let editMode = $state('visual'); + let dragSourceId: string | undefined; const workflowSummary = $derived({ name, description, trigger, steps }); const workflowJsonContent = $derived({ name, description, enabled, trigger, steps }); @@ -77,20 +80,23 @@ name !== savedWorkflow.name || description !== savedWorkflow.description || !isEqual(trigger, savedWorkflow.trigger) || - !isEqual(steps, savedWorkflow.steps), + !isEqual( + steps.map(({ id: _, ...step }) => step), + savedWorkflow.steps, + ), ); const handleAddStep = async () => { const step = await modalManager.show(WorkflowAddStepModal, { trigger }); if (step) { - steps.push(step); + steps.push({ ...step, id: generateId() }); } }; const handleInsertStep = async (index: number) => { const step = await modalManager.show(WorkflowAddStepModal, { trigger }); if (step) { - steps = [...steps.slice(0, index), step, ...steps.slice(index)]; + steps = [...steps.slice(0, index), { ...step, id: generateId() }, ...steps.slice(index)]; } }; @@ -102,20 +108,53 @@ const result = await modalManager.show(WorkflowEditStepModal, { trigger, step: cloneDeep(step) }); if (result) { - steps[index] = result; + steps[index] = { ...result, id: generateId() }; } }; - const handleDrop = (index: number, event: DragEvent) => { - if (!event.dataTransfer) { + const handleDrop = (event: DragEvent) => { + if (!event.dataTransfer || !dragSourceId) { return; } - const from = Number(event.dataTransfer.getData('text/plain')); + const ghostIndex = steps.findIndex(({ id }) => id === 'ghost'); + if (ghostIndex === -1) { + return; + } + const from = steps.findIndex(({ id }) => id === dragSourceId); const next = [...steps]; - const [moved] = next.splice(from, 1); - next.splice(index, 0, moved); + const [step] = next.splice(from, 1); + next[ghostIndex > from ? ghostIndex - 1 : ghostIndex] = step; + steps = next; + dragSourceId = undefined; + }; + + const handleDragOver = (index: number, event: DragEvent, boundingRect: DOMRect) => { + if (!event.dataTransfer || !dragSourceId) { + return; + } + + const fromIndex = steps.findIndex(({ id }) => dragSourceId === id); + const ghostIndex = steps.findIndex(({ id }) => id === 'ghost'); + const shiftedIndex = event.clientY > boundingRect.top + boundingRect.height / 2 ? index + 1 : index; + + if (index === fromIndex || shiftedIndex === fromIndex) { + if (ghostIndex !== -1) { + steps.splice(ghostIndex, 1); + } + return; + } + + if ( + (ghostIndex !== -1 && Math.abs(shiftedIndex - ghostIndex) <= (ghostIndex > shiftedIndex ? 0 : 1)) || + Math.abs(shiftedIndex - fromIndex) <= (fromIndex > shiftedIndex ? 0 : 1) + ) { + return; + } + + const next = steps.filter(({ id }) => id !== 'ghost'); + next.splice(shiftedIndex, 0, { ...steps[fromIndex], id: 'ghost' }); steps = next; }; @@ -131,7 +170,7 @@ name = content.name; description = content.description; trigger = content.trigger; - steps = cloneDeep(content.steps); + steps = cloneDeep(content.steps).map((step) => ({ ...step, id: generateId() })); }; const onClose = () => goto(Route.workflows()); @@ -214,6 +253,8 @@ }); }); + $effect(() => console.log(steps)); + const { Download, Duplicate, CopyJson, Delete } = $derived( getWorkflowActions($t, { ...savedWorkflow, name, description, enabled, trigger, steps }), ); @@ -344,15 +385,20 @@ - {#each steps as step, index (step.method + index)} - + {#each steps as step, index (step.id)} +
+ (steps = steps.filter(({ id }) => id !== 'ghost'))} + onDragStart={(event) => (dragSourceId = event.dataTransfer?.getData('text/plain'))} + /> +
{/each}