From 96d521e149a09184af2b413fe4dd0348dfaeced5 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 3 Jun 2026 14:13:17 +0200 Subject: [PATCH 01/24] feat(mobile): add three-state field serialization (#27231) * bump to v7.22.0 and update patching * gen client * migrate mobile call sites --- mobile/lib/extensions/asset_extensions.dart | 10 +- .../repositories/search_api.repository.dart | 90 +-- .../repositories/sync_api.repository.dart | 2 +- .../repositories/sync_stream.repository.dart | 4 +- .../infrastructure/utils/exif.converter.dart | 36 +- .../infrastructure/utils/user.converter.dart | 2 +- .../models/shared_link/shared_link.model.dart | 4 +- .../search/search_page_state.provider.dart | 2 +- .../repositories/activity_api.repository.dart | 6 +- .../repositories/asset_api.repository.dart | 24 +- .../lib/repositories/auth_api.repository.dart | 4 +- .../drift_album_api_repository.dart | 26 +- .../repositories/partner_api.repository.dart | 2 +- .../repositories/person_api.repository.dart | 5 +- .../repositories/sessions_api.repository.dart | 10 +- mobile/lib/routing/locked_guard.dart | 2 +- mobile/lib/services/oauth.service.dart | 8 +- mobile/lib/services/shared_link.service.dart | 48 +- mobile/openapi/.openapi-generator/VERSION | 2 +- mobile/openapi/README.md | 2 +- mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api_helper.dart | 3 + .../lib/model/activity_create_dto.dart | 26 +- .../lib/model/activity_response_dto.dart | 15 +- .../openapi/lib/model/album_response_dto.dart | 65 +- .../openapi/lib/model/album_user_add_dto.dart | 13 +- .../model/albums_add_assets_response_dto.dart | 13 +- mobile/openapi/lib/model/albums_update.dart | 13 +- .../openapi/lib/model/api_key_create_dto.dart | 13 +- .../openapi/lib/model/api_key_update_dto.dart | 24 +- .../lib/model/asset_bulk_delete_dto.dart | 13 +- .../lib/model/asset_bulk_update_dto.dart | 130 ++-- .../model/asset_bulk_upload_check_result.dart | 39 +- mobile/openapi/lib/model/asset_copy_dto.dart | 55 +- .../lib/model/asset_face_response_dto.dart | 15 +- .../lib/model/asset_ids_response_dto.dart | 13 +- .../lib/model/asset_ocr_response_dto.dart | 20 +- .../openapi/lib/model/asset_response_dto.dart | 134 ++-- .../lib/model/auth_status_response_dto.dart | 26 +- mobile/openapi/lib/model/avatar_update.dart | 13 +- .../lib/model/bulk_id_response_dto.dart | 26 +- mobile/openapi/lib/model/cast_update.dart | 13 +- .../lib/model/change_password_dto.dart | 11 +- .../openapi/lib/model/create_album_dto.dart | 37 +- .../openapi/lib/model/create_library_dto.dart | 39 +- .../lib/model/download_archive_dto.dart | 13 +- .../openapi/lib/model/download_info_dto.dart | 52 +- mobile/openapi/lib/model/download_update.dart | 26 +- .../lib/model/duplicate_detection_config.dart | 2 +- .../lib/model/email_notifications_update.dart | 39 +- .../openapi/lib/model/exif_response_dto.dart | 294 ++++----- .../lib/model/facial_recognition_config.dart | 4 +- mobile/openapi/lib/model/folders_update.dart | 26 +- .../lib/model/library_response_dto.dart | 2 +- .../lib/model/maintenance_login_dto.dart | 13 +- .../maintenance_status_response_dto.dart | 39 +- .../lib/model/map_marker_response_dto.dart | 10 +- .../map_reverse_geocode_response_dto.dart | 6 +- mobile/openapi/lib/model/memories_update.dart | 26 +- .../openapi/lib/model/memory_create_dto.dart | 79 ++- .../lib/model/memory_response_dto.dart | 68 +- .../openapi/lib/model/memory_update_dto.dart | 47 +- .../lib/model/metadata_search_dto.dart | 580 +++++++++--------- .../lib/model/notification_create_dto.dart | 67 +- .../openapi/lib/model/notification_dto.dart | 41 +- .../model/notification_update_all_dto.dart | 17 +- .../lib/model/notification_update_dto.dart | 17 +- .../lib/model/o_auth_callback_dto.dart | 26 +- .../openapi/lib/model/o_auth_config_dto.dart | 26 +- .../o_auth_token_endpoint_auth_method.dart | 12 +- mobile/openapi/lib/model/ocr_config.dart | 4 +- .../openapi/lib/model/partner_direction.dart | 12 +- .../lib/model/partner_response_dto.dart | 13 +- mobile/openapi/lib/model/people_response.dart | 13 +- .../lib/model/people_response_dto.dart | 13 +- mobile/openapi/lib/model/people_update.dart | 39 +- .../openapi/lib/model/people_update_item.dart | 78 ++- .../openapi/lib/model/person_create_dto.dart | 65 +- .../lib/model/person_response_dto.dart | 41 +- .../openapi/lib/model/person_update_dto.dart | 78 ++- .../lib/model/pin_code_change_dto.dart | 26 +- .../openapi/lib/model/pin_code_reset_dto.dart | 26 +- .../lib/model/places_response_dto.dart | 26 +- .../lib/model/plugin_method_response_dto.dart | 13 +- .../plugin_template_step_response_dto.dart | 15 +- mobile/openapi/lib/model/purchase_update.dart | 26 +- .../openapi/lib/model/queue_command_dto.dart | 13 +- .../openapi/lib/model/queue_delete_dto.dart | 13 +- .../lib/model/queue_job_response_dto.dart | 13 +- .../openapi/lib/model/queue_update_dto.dart | 13 +- .../openapi/lib/model/random_search_dto.dart | 450 +++++++------- mobile/openapi/lib/model/ratings_update.dart | 13 +- .../reverse_geocoding_state_response_dto.dart | 4 +- .../lib/model/search_asset_response_dto.dart | 2 +- .../lib/model/server_about_response_dto.dart | 234 ++++--- .../model/server_storage_response_dto.dart | 2 +- .../model/server_version_response_dto.dart | 2 +- .../openapi/lib/model/session_create_dto.dart | 39 +- .../model/session_create_response_dto.dart | 15 +- .../lib/model/session_response_dto.dart | 15 +- .../openapi/lib/model/session_unlock_dto.dart | 26 +- .../openapi/lib/model/session_update_dto.dart | 13 +- .../lib/model/set_maintenance_mode_dto.dart | 13 +- .../lib/model/shared_link_create_dto.dart | 117 ++-- .../lib/model/shared_link_edit_dto.dart | 108 ++-- .../lib/model/shared_link_response_dto.dart | 21 +- .../lib/model/shared_links_update.dart | 26 +- .../openapi/lib/model/smart_search_dto.dart | 476 +++++++------- .../openapi/lib/model/stack_update_dto.dart | 13 +- .../lib/model/statistics_search_dto.dart | 398 ++++++------ .../lib/model/sync_ack_delete_dto.dart | 11 +- mobile/openapi/lib/model/sync_album_v1.dart | 2 +- mobile/openapi/lib/model/sync_album_v2.dart | 2 +- .../openapi/lib/model/sync_asset_exif_v1.dart | 58 +- .../openapi/lib/model/sync_asset_face_v1.dart | 2 +- .../openapi/lib/model/sync_asset_face_v2.dart | 4 +- mobile/openapi/lib/model/sync_asset_v1.dart | 24 +- mobile/openapi/lib/model/sync_asset_v2.dart | 24 +- .../openapi/lib/model/sync_auth_user_v1.dart | 21 +- mobile/openapi/lib/model/sync_memory_v1.dart | 8 +- mobile/openapi/lib/model/sync_person_v1.dart | 6 +- mobile/openapi/lib/model/sync_stream_dto.dart | 13 +- mobile/openapi/lib/model/sync_user_v1.dart | 15 +- ...m_config_generated_fullsize_image_dto.dart | 13 +- .../system_config_generated_image_dto.dart | 13 +- .../lib/model/system_config_o_auth_dto.dart | 2 +- mobile/openapi/lib/model/tag_create_dto.dart | 26 +- .../openapi/lib/model/tag_response_dto.dart | 26 +- mobile/openapi/lib/model/tag_update_dto.dart | 13 +- mobile/openapi/lib/model/tags_update.dart | 26 +- .../model/time_bucket_asset_response_dto.dart | 75 ++- .../openapi/lib/model/update_album_dto.dart | 65 +- .../openapi/lib/model/update_asset_dto.dart | 104 ++-- .../openapi/lib/model/update_library_dto.dart | 39 +- .../openapi/lib/model/usage_by_user_dto.dart | 2 +- .../lib/model/user_admin_create_dto.dart | 91 ++- .../lib/model/user_admin_delete_dto.dart | 13 +- .../lib/model/user_admin_response_dto.dart | 10 +- .../lib/model/user_admin_update_dto.dart | 117 ++-- .../model/user_preferences_update_dto.dart | 156 +++-- .../openapi/lib/model/user_update_me_dto.dart | 52 +- .../lib/model/validate_library_dto.dart | 26 +- ...date_library_import_path_response_dto.dart | 13 +- .../model/validate_library_response_dto.dart | 11 +- .../version_check_state_response_dto.dart | 4 +- .../lib/model/workflow_create_dto.dart | 50 +- .../lib/model/workflow_response_dto.dart | 4 +- .../model/workflow_share_response_dto.dart | 4 +- .../lib/model/workflow_share_step_dto.dart | 15 +- .../openapi/lib/model/workflow_step_dto.dart | 15 +- .../lib/model/workflow_update_dto.dart | 63 +- mobile/openapi/lib/optional.dart | 119 ++++ .../sync_stream_repository_test.dart | 2 +- mobile/test/fixtures/sync_stream.stub.dart | 4 +- open-api/bin/generate-dart-sdk.sh | 7 +- open-api/openapitools.json | 2 +- .../time_bucket_asset_response_dto.dart.patch | 9 + open-api/templates/mobile/api.mustache | 1 + .../native/native_class.mustache | 187 +++++- .../native/native_class.mustache.patch | 200 ++++-- ...ative_class_nullable_items_in_arrays.patch | 13 - 161 files changed, 3531 insertions(+), 3369 deletions(-) create mode 100644 mobile/openapi/lib/optional.dart create mode 100644 open-api/patch/time_bucket_asset_response_dto.dart.patch delete mode 100644 open-api/templates/mobile/serialization/native/native_class_nullable_items_in_arrays.patch 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/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart index bcfddfce6e..e2f2af17eb 100644 --- a/mobile/lib/infrastructure/repositories/search_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart @@ -20,50 +20,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 == null ? const Optional.absent() : Optional.present(filter.rating.rating!), + 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 == null ? const Optional.absent() : Optional.present(filter.rating.rating!), + 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..8b8475d31f 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( 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/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/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/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/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..4a23ced504 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)); + 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..445f5763a2 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); @@ -73,11 +79,13 @@ class DriftAlbumApiRepository extends ApiRepository { _api.updateAlbumInfo( albumId, UpdateAlbumDto( - albumName: name, - description: description, - albumThumbnailAssetId: thumbnailAssetId, - isActivityEnabled: isActivityEnabled, - order: apiOrder, + albumName: name == null ? const Optional.absent() : Optional.present(name), + description: description == null ? const Optional.absent() : Optional.present(description), + albumThumbnailAssetId: thumbnailAssetId == null + ? const Optional.absent() + : Optional.present(thumbnailAssetId), + isActivityEnabled: isActivityEnabled == null ? const Optional.absent() : Optional.present(isActivityEnabled), + order: apiOrder == null ? const Optional.absent() : Optional.present(apiOrder), ), ), ); @@ -99,7 +107,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 +126,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/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..009e922b38 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), ); } @@ -98,14 +98,14 @@ class SharedLinkService { final responseDto = await _apiService.sharedLinksApi.updateSharedLink( id, SharedLinkEditDto( - showMetadata: showMeta, - allowDownload: allowDownload, - allowUpload: allowUpload, - expiresAt: expiresAt, - description: description, - password: password, - slug: slug, - changeExpiryTime: changeExpiry, + 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), + 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), + changeExpiryTime: changeExpiry == null ? const Optional.absent() : Optional.present(changeExpiry), ), ); if (responseDto != null) { 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 e92b885904..af40910b4d 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 3af18e5fe8..1dbe28cc0f 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..4ba1546c44 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 /// 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..8a0ac0909c 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; + 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..0d93ed3173 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 /// 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..89d0fd4d35 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 /// 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..0d6df3c8f9 100644 --- a/mobile/openapi/lib/model/shared_link_edit_dto.dart +++ b/mobile/openapi/lib/model/shared_link_edit_dto.dart @@ -13,14 +13,14 @@ 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.changeExpiryTime = 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 +30,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,7 +39,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? allowUpload; + Optional 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. /// @@ -48,16 +48,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? changeExpiryTime; + Optional changeExpiryTime; /// Link description - String? description; + Optional description; /// Expiration date - DateTime? expiresAt; + Optional expiresAt; /// Link password - String? password; + Optional password; /// Show metadata /// @@ -66,10 +66,10 @@ 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 && @@ -99,47 +99,39 @@ class SharedLinkEditDto { 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.changeExpiryTime.isPresent) { + final value = this.changeExpiryTime.value; + json[r'changeExpiryTime'] = 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.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.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 != 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.showMetadata != null) { - json[r'showMetadata'] = this.showMetadata; - } else { - // json[r'showMetadata'] = null; + if (this.showMetadata.isPresent) { + final value = this.showMetadata.value; + json[r'showMetadata'] = value; } - 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 +145,14 @@ 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(), + changeExpiryTime: json.containsKey(r'changeExpiryTime') ? Optional.present(mapValueOfType(json, r'changeExpiryTime')) : 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..aff0030102 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 /// 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..5b88d136bb 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 /// 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..bd64124673 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 /// 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/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/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/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 From 963862b1b9967c589f7474037a2abbbe6e05b495 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Wed, 3 Jun 2026 08:16:19 -0400 Subject: [PATCH 02/24] fix(mobile): proper background task cleanup (#28694) * event-based cancellation wire hash cancellation await cleanup remove forced kill add regression tests abort sync requests fix cleanup ordering in teardown exit isolate test background sync test sigabrt crash cleanup * abort local sync --- .../app/alextran/immich/sync/Messages.g.kt | 100 +++++++---- .../alextran/immich/sync/MessagesImpl26.kt | 12 +- .../alextran/immich/sync/MessagesImpl30.kt | 15 +- .../alextran/immich/sync/MessagesImplBase.kt | 39 ++++- .../background_sync_teardown_test.dart | 154 +++++++++++++++++ .../test_utils/fake_immich_server.dart | 115 ++++++++++++ .../Runner/Background/BackgroundWorker.swift | 10 +- mobile/ios/Runner/Sync/Messages.g.swift | 100 ++++++----- mobile/ios/Runner/Sync/MessagesImpl.swift | 147 +++++++++++----- .../services/background_worker.service.dart | 20 +-- mobile/lib/domain/services/hash.service.dart | 14 +- .../domain/services/local_sync.service.dart | 37 +++- mobile/lib/domain/services/log.service.dart | 12 +- mobile/lib/domain/services/store.service.dart | 6 + .../services/sync_linked_album.service.dart | 16 +- .../domain/services/sync_stream.service.dart | 23 ++- mobile/lib/domain/utils/background_sync.dart | 56 ++---- .../lib/domain/utils/migrate_cloud_ids.dart | 38 +++- .../repositories/sync_api.repository.dart | 3 +- mobile/lib/platform/native_sync_api.g.dart | 14 ++ .../infrastructure/cancel.provider.dart | 7 +- .../infrastructure/sync.provider.dart | 4 +- .../drift_album_api_repository.dart | 10 +- mobile/lib/utils/isolate.dart | 66 +++---- mobile/lib/utils/isolate_worker.dart | 163 ++++++++++++++++++ mobile/lib/wm_executor.dart | 140 ++++----------- mobile/pigeon/native_sync_api.dart | 11 +- .../services/sync_stream_service_test.dart | 32 ++-- .../test/unit/utils/isolate_worker_test.dart | 23 +++ 29 files changed, 990 insertions(+), 397 deletions(-) create mode 100644 mobile/integration_test/background_sync_teardown_test.dart create mode 100644 mobile/integration_test/test_utils/fake_immich_server.dart create mode 100644 mobile/lib/utils/isolate_worker.dart create mode 100644 mobile/test/unit/utils/isolate_worker_test.dart 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/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/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/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index d28f7ff14b..eadcf7c9db 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -188,20 +188,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/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/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 8b8475d31f..65214f3846 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -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/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/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/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/repositories/drift_album_api_repository.dart b/mobile/lib/repositories/drift_album_api_repository.dart index 445f5763a2..ee57352fb1 100644 --- a/mobile/lib/repositories/drift_album_api_repository.dart +++ b/mobile/lib/repositories/drift_album_api_repository.dart @@ -47,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) { 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/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/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/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/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', + ); + }); +} From afa836181c2e12b4e33e1c1560da601ed5d12666 Mon Sep 17 00:00:00 2001 From: moversity <148445403+moversity@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:19:35 +0000 Subject: [PATCH 03/24] fix(cli): prevent out-of-memory on file upload due to undici storing the request body (#28723) fix(cli): add fetch param to prevent OOM of upload Issue due to undici storing the entire request body in memory. Related undici bug report: https://github.com/nodejs/undici/issues/4058 Fixes https://github.com/immich-app/immich/issues/28720 Signed-off-by: moversity <148445403+moversity@users.noreply.github.com> --- packages/cli/src/commands/asset.ts | 2 ++ 1 file changed, 2 insertions(+) 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()); From 911dde39c939ab8fb165f7067ffb255bb583274a Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:51:23 +0530 Subject: [PATCH 04/24] ci: verify mobile backward compatibility (#28786) Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .github/workflows/check-openapi.yml | 33 +++++++ mobile/lib/utils/openapi_patching.dart | 115 ++++++++++------------ mobile/test/openapi_patches_coverage.dart | 111 +++++++++++++++++++++ 3 files changed, 196 insertions(+), 63 deletions(-) create mode 100644 mobile/test/openapi_patches_coverage.dart diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index f2b3e3c248..ddd33925fa 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,35 @@ 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 }} + + - 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/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/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; +} From e4352a7817e5658cc112e63303cefe09b30d0f83 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 3 Jun 2026 12:47:38 -0400 Subject: [PATCH 05/24] fix: error log on aborted uploads (#28806) --- server/src/controllers/sync.controller.ts | 8 ++++---- server/src/middleware/error.interceptor.ts | 10 ++++++---- server/src/middleware/file-upload.interceptor.ts | 6 +++++- server/src/middleware/global-exception.filter.ts | 13 +++++++------ server/src/utils/logger.ts | 13 +++++++++++-- 5 files changed, 33 insertions(+), 17 deletions(-) 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/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/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; From 9d4a6614b149f23f6a6edb0b78d11ccccfa531f7 Mon Sep 17 00:00:00 2001 From: Peter Ombodi Date: Wed, 3 Jun 2026 20:05:52 +0300 Subject: [PATCH 06/24] feat(mobile): Android. Immich as a gallery / image viewer app (#26109) * feat(mobile): handle Android ACTION_VIEW intent - add ViewIntent Pigeon API and generated bindings - implement Android ViewIntentPlugin + iOS no-op host - route ExternalMediaViewer by ViewIntentAttachment - buffer pending view intents and flush on user ready/resume * feat(mobile): fallback to computed checksum for timeline match - hash local asset on-demand when checksum missing - search main timeline by localId or checksum before standalone viewer - persist computed hash into local_asset_entity * fix(mobile): proper handling is user authenticated * feat(mobile): open ACTION_VIEW fallback in AssetViewer drop ExternalMediaViewer route * feat(mobile): add logger * test(mobile): add unit tests for view intent pending/flush flow * fix(mobile): fix format * fix(mobile): remove redundant iOS code update code related to LocalAsset model and asset viewer * refactor(mobile): simplify view intent flow and support file-backed ACTION_VIEW assets remove redundant view intent model/repository layer handle transient ACTION_VIEW files in viewer/upload flow clean up managed temp files for fallback assets * refactor(mobile): extract MediaStore utils and resolve view intents via merged assets * refactor(mobile): move deferred view intents into providers, split view-intent providers, and clean up ACTION_VIEW handling * refactor(mobile): resolve merge conflicts use NativeSyncApi for hash files instead method from removed BackgroundServicePlugin.kt * style(mobile): format files * style(mobile): format files #2 * refactor(mobile): lazily materialize view-intent files and clean up temp-file handling * fix(mobile): flush pending view intents after login navigation * refactor(mobile): split view intent handler by platform and trigger it from app events * refactor(mobile): move view intent handling behind platform-specific factories * refactor(mobile): simplify code * fix(mobile): hand off deep-link viewer to main timeline after upload Add MainTimelineHandoffCoordinator to switch the asset viewer to the main timeline once a view-intent asset is uploaded and becomes available, and guard viewer reload/navigation transitions to avoid race conditions and crashes. * refactor(mobile): use remote asset ids for view intent handoff and simplify resolver * refactor(mobile): resolve merge conflicts * style(mobile): reformat code * style(mobile): reformat code #2 * fix(mobile): stabilize Android view intent asset resolution and fallback viewer * refactor(mobile): share AssetViewer pre-navigation state preparation * fix(mobile): wait for main timeline before deferred view intent handoff * refactor(mobile): decouple view intent asset resolver from providers * fix(mobile): avoid double pop when canceling upload dialog * fix(mobile): resolve view intent MIME type with fallbacks * docs(mobile): clarify view intent fallback asset TODO * fix(mobile): resolve merge conflicts * cleanup * lint --------- Co-authored-by: Peter Ombodi Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran --- .../android/app/src/main/AndroidManifest.xml | 14 + .../app/alextran/immich/MainActivity.kt | 8 + .../immich/viewintent/ViewIntent.g.kt | 292 ++++++++++++++++++ .../immich/viewintent/ViewIntentPlugin.kt | 201 ++++++++++++ mobile/lib/main.dart | 3 + .../view_intent_payload.extension.dart | 35 +++ .../lib/pages/common/splash_screen.page.dart | 4 + mobile/lib/platform/view_intent_api.g.dart | 191 ++++++++++++ .../upload_action_button.widget.dart | 63 +++- .../asset_viewer/asset_page.widget.dart | 16 +- .../asset_viewer/video_viewer.widget.dart | 16 + .../widgets/images/image_provider.dart | 12 +- .../share_intent_upload.provider.dart | 202 ++++++------ .../infrastructure/action.provider.dart | 10 +- .../view_intent_file_path.provider.dart | 31 ++ .../view_intent_handler.provider.dart | 23 ++ .../view_intent_handler_android.dart | 103 ++++++ .../view_intent/view_intent_handler_stub.dart | 18 ++ .../view_intent_pending.provider.dart | 39 +++ .../services/foreground_upload.service.dart | 4 +- mobile/lib/services/view_intent.service.dart | 108 +++++++ .../view_intent_asset_resolver.service.dart | 65 ++++ .../lib/widgets/forms/login/login_form.dart | 7 +- mobile/mise.toml | 3 +- mobile/pigeon/local_image_api.dart | 3 +- mobile/pigeon/view_intent_api.dart | 24 ++ .../view_intent_handler_android_test.dart | 261 ++++++++++++++++ .../view_intent_pending_provider_test.dart | 64 ++++ .../view_intent_asset_resolver_test.dart | 123 ++++++++ .../services/view_intent_service_test.dart | 119 +++++++ 30 files changed, 1932 insertions(+), 130 deletions(-) create mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt create mode 100644 mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt create mode 100644 mobile/lib/models/view_intent/view_intent_payload.extension.dart create mode 100644 mobile/lib/platform/view_intent_api.g.dart create mode 100644 mobile/lib/providers/view_intent/view_intent_file_path.provider.dart create mode 100644 mobile/lib/providers/view_intent/view_intent_handler.provider.dart create mode 100644 mobile/lib/providers/view_intent/view_intent_handler_android.dart create mode 100644 mobile/lib/providers/view_intent/view_intent_handler_stub.dart create mode 100644 mobile/lib/providers/view_intent/view_intent_pending.provider.dart create mode 100644 mobile/lib/services/view_intent.service.dart create mode 100644 mobile/lib/services/view_intent_asset_resolver.service.dart create mode 100644 mobile/pigeon/view_intent_api.dart create mode 100644 mobile/test/providers/view_intent/view_intent_handler_android_test.dart create mode 100644 mobile/test/providers/view_intent/view_intent_pending_provider_test.dart create mode 100644 mobile/test/services/view_intent_asset_resolver_test.dart create mode 100644 mobile/test/services/view_intent_service_test.dart 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/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/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/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/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/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/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_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/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/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..6fdd9fc5c9 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 { @@ -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/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/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/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/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/mise.toml b/mobile/mise.toml index a0f25718ac..07210d9141 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -29,7 +29,8 @@ run = [ "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", + "dart run pigeon --input pigeon/view_intent_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 lib/platform/view_intent_api.g.dart", ] [tasks."codegen:translation"] 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/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/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/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); + }); +} From 137687bc0f0068520b0299a67c09cda31de94ad3 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:07:23 -0400 Subject: [PATCH 07/24] fix(web): set src for progressive video player (#28813) set src --- web/src/lib/components/asset-viewer/VideoNativeViewer.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte index 8f84466295..1462fd4a92 100644 --- a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte +++ b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte @@ -370,6 +370,7 @@