Merge remote-tracking branch 'origin/main' into feature/rearrange-buttons-2

This commit is contained in:
idubnori
2025-11-17 09:35:03 +09:00
505 changed files with 34122 additions and 8163 deletions
@@ -143,7 +143,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
val mediaUrls = call.argument<List<String>>("mediaUrls")
if (mediaUrls != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
moveToTrash(mediaUrls, result)
moveToTrash(mediaUrls, result)
} else {
result.error("PERMISSION_DENIED", "Media permission required", null)
}
@@ -155,15 +155,23 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
"restoreFromTrash" -> {
val fileName = call.argument<String>("fileName")
val type = call.argument<Int>("type")
val mediaId = call.argument<String>("mediaId")
if (fileName != null && type != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
restoreFromTrash(fileName, type, result)
} else {
result.error("PERMISSION_DENIED", "Media permission required", null)
}
} else {
result.error("INVALID_NAME", "The file name is not specified.", null)
}
} else
if (mediaId != null && type != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) {
restoreFromTrashById(mediaId, type, result)
} else {
result.error("PERMISSION_DENIED", "Media permission required", null)
}
} else {
result.error("INVALID_PARAMS", "Required params are not specified.", null)
}
}
"requestManageMediaPermission" -> {
@@ -175,6 +183,17 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
"hasManageMediaPermission" -> {
if (hasManageMediaPermission()) {
Log.i("Manage storage permission", "Permission already granted")
result.success(true)
} else {
result.success(false)
}
}
"manageMediaPermission" -> requestManageMediaPermission(result)
else -> result.notImplemented()
}
}
@@ -224,25 +243,47 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
@RequiresApi(Build.VERSION_CODES.R)
private fun toggleTrash(contentUris: List<Uri>, isTrashed: Boolean, result: Result) {
val activity = activityBinding?.activity
val contentResolver = context?.contentResolver
if (activity == null || contentResolver == null) {
result.error("TrashError", "Activity or ContentResolver not available", null)
return
}
private fun restoreFromTrashById(mediaId: String, type: Int, result: Result) {
val id = mediaId.toLongOrNull()
if (id == null) {
result.error("INVALID_ID", "The file id is not a valid number: $mediaId", null)
return
}
if (!isInTrash(id)) {
result.error("TrashNotFound", "Item with id=$id not found in trash", null)
return
}
try {
val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed)
pendingResult = result // Store for onActivityResult
activity.startIntentSenderForResult(
pendingIntent.intentSender,
trashRequestCode,
null, 0, 0, 0
)
} catch (e: Exception) {
Log.e("TrashError", "Error creating or starting trash request", e)
result.error("TrashError", "Error creating or starting trash request", null)
val uri = ContentUris.withAppendedId(contentUriForType(type), id)
try {
Log.i(TAG, "restoreFromTrashById: uri=$uri (type=$type,id=$id)")
restoreUris(listOf(uri), result)
} catch (e: Exception) {
Log.w(TAG, "restoreFromTrashById failed", e)
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun toggleTrash(contentUris: List<Uri>, isTrashed: Boolean, result: Result) {
val activity = activityBinding?.activity
val contentResolver = context?.contentResolver
if (activity == null || contentResolver == null) {
result.error("TrashError", "Activity or ContentResolver not available", null)
return
}
try {
val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed)
pendingResult = result // Store for onActivityResult
activity.startIntentSenderForResult(
pendingIntent.intentSender,
trashRequestCode,
null, 0, 0, 0
)
} catch (e: Exception) {
Log.e("TrashError", "Error creating or starting trash request", e)
result.error("TrashError", "Error creating or starting trash request", null)
}
}
@@ -264,14 +305,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
contentResolver.query(queryUri, projection, queryArgs, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
// same order as AssetType from dart
val contentUri = when (type) {
1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
else -> queryUri
}
return ContentUris.withAppendedId(contentUri, id)
return ContentUris.withAppendedId(contentUriForType(type), id)
}
}
return null
@@ -315,6 +349,40 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
return false
}
@RequiresApi(Build.VERSION_CODES.R)
private fun isInTrash(id: Long): Boolean {
val contentResolver = context?.contentResolver ?: return false
val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
val args = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?")
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString()))
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
putInt(ContentResolver.QUERY_ARG_LIMIT, 1)
}
return contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null)
?.use { it.moveToFirst() } == true
}
@RequiresApi(Build.VERSION_CODES.R)
private fun restoreUris(uris: List<Uri>, result: Result) {
if (uris.isEmpty()) {
result.error("TrashError", "No URIs to restore", null)
return
}
Log.i(TAG, "restoreUris: count=${uris.size}, first=${uris.first()}")
toggleTrash(uris, false, result)
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun contentUriForType(type: Int): Uri =
when (type) {
// same order as AssetType from dart
1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
}
}
private const val TAG = "BackgroundServicePlugin"
@@ -305,6 +305,7 @@ interface NativeSyncApi {
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List<PlatformAsset>
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
fun cancelHashing()
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
companion object {
/** The codec used by NativeSyncApi. */
@@ -483,6 +484,21 @@ interface NativeSyncApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getTrashedAssets())
} catch (exception: Throwable) {
MessagesPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -21,4 +21,9 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na
override fun getMediaChanges(): SyncDelta {
throw IllegalStateException("Method not supported on this Android version.")
}
override fun getTrashedAssets(): Map<String, List<PlatformAsset>> {
//Method not supported on this Android version.
return emptyMap()
}
}
@@ -1,7 +1,9 @@
package app.alextran.immich.sync
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresExtension
@@ -86,4 +88,29 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na
// Unmounted volumes are handled in dart when the album is removed
return SyncDelta(hasChanges, changed, deleted, assetAlbums)
}
override fun getTrashedAssets(): Map<String, List<PlatformAsset>> {
val result = LinkedHashMap<String, MutableList<PlatformAsset>>()
val volumes = MediaStore.getExternalVolumeNames(ctx)
for (volume in volumes) {
val queryArgs = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MEDIA_SELECTION)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, MEDIA_SELECTION_ARGS)
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}
getCursor(volume, queryArgs).use { cursor ->
getAssets(cursor).forEach { res ->
if (res is AssetResult.ValidAsset) {
result.getOrPut(res.albumId) { mutableListOf() }.add(res.asset)
}
}
}
}
return result.mapValues { it.value.toList() }
}
}
@@ -4,6 +4,8 @@ import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Base64
import androidx.core.database.getStringOrNull
@@ -81,6 +83,16 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
sortOrder,
)
protected fun getCursor(
volume: String,
queryArgs: Bundle
): Cursor? = ctx.contentResolver.query(
MediaStore.Files.getContentUri(volume),
ASSET_PROJECTION,
queryArgs,
null
)
protected fun getAssets(cursor: Cursor?): Sequence<AssetResult> {
return sequence {
cursor?.use { c ->
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 3025,
"android.injected.version.name" => "2.2.2",
"android.injected.version.code" => 3026,
"android.injected.version.name" => "2.2.3",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
File diff suppressed because one or more lines are too long
+57 -1
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -32,6 +32,9 @@
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */; };
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */; };
FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; };
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; };
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -153,6 +156,13 @@
path = WidgetExtension;
sourceTree = "<group>";
};
FEE084F22EC172080045228E /* Schemas */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Schemas;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -160,6 +170,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FEE084F82EC172460045228E /* SQLiteData in Frameworks */,
FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */,
FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */,
D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -254,6 +267,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
FEE084F22EC172080045228E /* Schemas */,
B231F52D2E93A44A00BC45D1 /* Core */,
B25D37792E72CA15008B6CA7 /* Connectivity */,
B21E34A62E5AF9760031FDB9 /* Background */,
@@ -341,6 +355,7 @@
fileSystemSynchronizedGroups = (
B231F52D2E93A44A00BC45D1 /* Core */,
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
FEE084F22EC172080045228E /* Schemas */,
);
name = Runner;
productName = Runner;
@@ -419,6 +434,10 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
@@ -1201,6 +1220,43 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/sqlite-data";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
};
};
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-http-structured-headers.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.5.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
FEE084F72EC172460045228E /* SQLiteData */ = {
isa = XCSwiftPackageProductDependency;
package = FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */;
productName = SQLiteData;
};
FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */ = {
isa = XCSwiftPackageProductDependency;
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
productName = RawStructuredFieldValues;
};
FEE084FC2EC1725A0045228E /* StructuredFieldValues */ = {
isa = XCSwiftPackageProductDependency;
package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */;
productName = StructuredFieldValues;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,177 @@
{
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "fd16d76fd8b9a976d88bfb6cacc05ca8d19c91b6",
"version" : "1.1.0"
}
},
{
"identity" : "grdb.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/GRDB.swift",
"state" : {
"revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d",
"version" : "7.8.0"
}
},
{
"identity" : "opencombine",
"kind" : "remoteSourceControl",
"location" : "https://github.com/OpenCombine/OpenCombine.git",
"state" : {
"revision" : "8576f0d579b27020beccbccc3ea6844f3ddfc2c2",
"version" : "0.14.0"
}
},
{
"identity" : "sqlite-data",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/sqlite-data",
"state" : {
"revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9",
"version" : "1.3.0"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "6989976265be3f8d2b5802c722f9ba168e227c71",
"version" : "1.7.2"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e",
"version" : "1.0.6"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
"version" : "1.3.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "5a3825302b1a0d744183200915a47b508c828e6f",
"version" : "1.3.2"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
"version" : "1.3.3"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc",
"version" : "1.10.0"
}
},
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "a9f3c352f4d46afd155e00b3c6e85decae6bcbeb",
"version" : "1.5.0"
}
},
{
"identity" : "swift-identified-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-identified-collections",
"state" : {
"revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597",
"version" : "1.1.1"
}
},
{
"identity" : "swift-perception",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-perception",
"state" : {
"revision" : "4f47ebafed5f0b0172cf5c661454fa8e28fb2ac4",
"version" : "2.0.9"
}
},
{
"identity" : "swift-sharing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-sharing",
"state" : {
"revision" : "3bfc408cc2d0bee2287c174da6b1c76768377818",
"version" : "2.7.4"
}
},
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b",
"version" : "1.18.7"
}
},
{
"identity" : "swift-structured-queries",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-structured-queries",
"state" : {
"revision" : "9c84335373bae5f5c9f7b5f0adf3ae10f2cab5b9",
"version" : "0.25.2"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "4799286537280063c85a32f09884cfbca301b1a1",
"version" : "602.0.0"
}
},
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-tagged",
"state" : {
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
"version" : "0.10.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618",
"version" : "1.7.0"
}
}
],
"version" : 3
}
@@ -0,0 +1,168 @@
{
"originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49",
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "5928286acce13def418ec36d05a001a9641086f2",
"version" : "1.0.3"
}
},
{
"identity" : "grdb.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/GRDB.swift",
"state" : {
"revision" : "18497b68fdbb3a09528d260a0a0e1e7e61c8c53d",
"version" : "7.8.0"
}
},
{
"identity" : "sqlite-data",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/sqlite-data",
"state" : {
"revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9",
"version" : "1.3.0"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "6989976265be3f8d2b5802c722f9ba168e227c71",
"version" : "1.7.2"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e",
"version" : "1.0.6"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
"version" : "1.3.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "5a3825302b1a0d744183200915a47b508c828e6f",
"version" : "1.3.2"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
"version" : "1.3.3"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc",
"version" : "1.10.0"
}
},
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "a9f3c352f4d46afd155e00b3c6e85decae6bcbeb",
"version" : "1.5.0"
}
},
{
"identity" : "swift-identified-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-identified-collections",
"state" : {
"revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597",
"version" : "1.1.1"
}
},
{
"identity" : "swift-perception",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-perception",
"state" : {
"revision" : "4f47ebafed5f0b0172cf5c661454fa8e28fb2ac4",
"version" : "2.0.9"
}
},
{
"identity" : "swift-sharing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-sharing",
"state" : {
"revision" : "3bfc408cc2d0bee2287c174da6b1c76768377818",
"version" : "2.7.4"
}
},
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b",
"version" : "1.18.7"
}
},
{
"identity" : "swift-structured-queries",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-structured-queries",
"state" : {
"revision" : "1447ea20550f6f02c4b48cc80931c3ed40a9c756",
"version" : "0.25.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "4799286537280063c85a32f09884cfbca301b1a1",
"version" : "602.0.0"
}
},
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-tagged",
"state" : {
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
"version" : "0.10.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "4c27acf5394b645b70d8ba19dc249c0472d5f618",
"version" : "1.7.0"
}
}
],
"version" : 3
}
+177
View File
@@ -0,0 +1,177 @@
import SQLiteData
struct Endpoint: Codable {
let url: URL
let status: Status
enum Status: String, Codable {
case loading, valid, error, unknown
}
}
enum StoreKey: Int, CaseIterable, QueryBindable {
// MARK: - Int
case _version = 0
static let version = Typed<Int>(rawValue: ._version)
case _deviceIdHash = 3
static let deviceIdHash = Typed<Int>(rawValue: ._deviceIdHash)
case _backupTriggerDelay = 8
static let backupTriggerDelay = Typed<Int>(rawValue: ._backupTriggerDelay)
case _tilesPerRow = 103
static let tilesPerRow = Typed<Int>(rawValue: ._tilesPerRow)
case _groupAssetsBy = 105
static let groupAssetsBy = Typed<Int>(rawValue: ._groupAssetsBy)
case _uploadErrorNotificationGracePeriod = 106
static let uploadErrorNotificationGracePeriod = Typed<Int>(rawValue: ._uploadErrorNotificationGracePeriod)
case _thumbnailCacheSize = 110
static let thumbnailCacheSize = Typed<Int>(rawValue: ._thumbnailCacheSize)
case _imageCacheSize = 111
static let imageCacheSize = Typed<Int>(rawValue: ._imageCacheSize)
case _albumThumbnailCacheSize = 112
static let albumThumbnailCacheSize = Typed<Int>(rawValue: ._albumThumbnailCacheSize)
case _selectedAlbumSortOrder = 113
static let selectedAlbumSortOrder = Typed<Int>(rawValue: ._selectedAlbumSortOrder)
case _logLevel = 115
static let logLevel = Typed<Int>(rawValue: ._logLevel)
case _mapRelativeDate = 119
static let mapRelativeDate = Typed<Int>(rawValue: ._mapRelativeDate)
case _mapThemeMode = 124
static let mapThemeMode = Typed<Int>(rawValue: ._mapThemeMode)
// MARK: - String
case _assetETag = 1
static let assetETag = Typed<String>(rawValue: ._assetETag)
case _currentUser = 2
static let currentUser = Typed<String>(rawValue: ._currentUser)
case _deviceId = 4
static let deviceId = Typed<String>(rawValue: ._deviceId)
case _accessToken = 11
static let accessToken = Typed<String>(rawValue: ._accessToken)
case _serverEndpoint = 12
static let serverEndpoint = Typed<String>(rawValue: ._serverEndpoint)
case _sslClientCertData = 15
static let sslClientCertData = Typed<String>(rawValue: ._sslClientCertData)
case _sslClientPasswd = 16
static let sslClientPasswd = Typed<String>(rawValue: ._sslClientPasswd)
case _themeMode = 102
static let themeMode = Typed<String>(rawValue: ._themeMode)
case _customHeaders = 127
static let customHeaders = Typed<[String: String]>(rawValue: ._customHeaders)
case _primaryColor = 128
static let primaryColor = Typed<String>(rawValue: ._primaryColor)
case _preferredWifiName = 133
static let preferredWifiName = Typed<String>(rawValue: ._preferredWifiName)
// MARK: - Endpoint
case _externalEndpointList = 135
static let externalEndpointList = Typed<[Endpoint]>(rawValue: ._externalEndpointList)
// MARK: - URL
case _localEndpoint = 134
static let localEndpoint = Typed<URL>(rawValue: ._localEndpoint)
case _serverUrl = 10
static let serverUrl = Typed<URL>(rawValue: ._serverUrl)
// MARK: - Date
case _backupFailedSince = 5
static let backupFailedSince = Typed<Date>(rawValue: ._backupFailedSince)
// MARK: - Bool
case _backupRequireWifi = 6
static let backupRequireWifi = Typed<Bool>(rawValue: ._backupRequireWifi)
case _backupRequireCharging = 7
static let backupRequireCharging = Typed<Bool>(rawValue: ._backupRequireCharging)
case _autoBackup = 13
static let autoBackup = Typed<Bool>(rawValue: ._autoBackup)
case _backgroundBackup = 14
static let backgroundBackup = Typed<Bool>(rawValue: ._backgroundBackup)
case _loadPreview = 100
static let loadPreview = Typed<Bool>(rawValue: ._loadPreview)
case _loadOriginal = 101
static let loadOriginal = Typed<Bool>(rawValue: ._loadOriginal)
case _dynamicLayout = 104
static let dynamicLayout = Typed<Bool>(rawValue: ._dynamicLayout)
case _backgroundBackupTotalProgress = 107
static let backgroundBackupTotalProgress = Typed<Bool>(rawValue: ._backgroundBackupTotalProgress)
case _backgroundBackupSingleProgress = 108
static let backgroundBackupSingleProgress = Typed<Bool>(rawValue: ._backgroundBackupSingleProgress)
case _storageIndicator = 109
static let storageIndicator = Typed<Bool>(rawValue: ._storageIndicator)
case _advancedTroubleshooting = 114
static let advancedTroubleshooting = Typed<Bool>(rawValue: ._advancedTroubleshooting)
case _preferRemoteImage = 116
static let preferRemoteImage = Typed<Bool>(rawValue: ._preferRemoteImage)
case _loopVideo = 117
static let loopVideo = Typed<Bool>(rawValue: ._loopVideo)
case _mapShowFavoriteOnly = 118
static let mapShowFavoriteOnly = Typed<Bool>(rawValue: ._mapShowFavoriteOnly)
case _selfSignedCert = 120
static let selfSignedCert = Typed<Bool>(rawValue: ._selfSignedCert)
case _mapIncludeArchived = 121
static let mapIncludeArchived = Typed<Bool>(rawValue: ._mapIncludeArchived)
case _ignoreIcloudAssets = 122
static let ignoreIcloudAssets = Typed<Bool>(rawValue: ._ignoreIcloudAssets)
case _selectedAlbumSortReverse = 123
static let selectedAlbumSortReverse = Typed<Bool>(rawValue: ._selectedAlbumSortReverse)
case _mapwithPartners = 125
static let mapwithPartners = Typed<Bool>(rawValue: ._mapwithPartners)
case _enableHapticFeedback = 126
static let enableHapticFeedback = Typed<Bool>(rawValue: ._enableHapticFeedback)
case _dynamicTheme = 129
static let dynamicTheme = Typed<Bool>(rawValue: ._dynamicTheme)
case _colorfulInterface = 130
static let colorfulInterface = Typed<Bool>(rawValue: ._colorfulInterface)
case _syncAlbums = 131
static let syncAlbums = Typed<Bool>(rawValue: ._syncAlbums)
case _autoEndpointSwitching = 132
static let autoEndpointSwitching = Typed<Bool>(rawValue: ._autoEndpointSwitching)
case _loadOriginalVideo = 136
static let loadOriginalVideo = Typed<Bool>(rawValue: ._loadOriginalVideo)
case _manageLocalMediaAndroid = 137
static let manageLocalMediaAndroid = Typed<Bool>(rawValue: ._manageLocalMediaAndroid)
case _readonlyModeEnabled = 138
static let readonlyModeEnabled = Typed<Bool>(rawValue: ._readonlyModeEnabled)
case _autoPlayVideo = 139
static let autoPlayVideo = Typed<Bool>(rawValue: ._autoPlayVideo)
case _photoManagerCustomFilter = 1000
static let photoManagerCustomFilter = Typed<Bool>(rawValue: ._photoManagerCustomFilter)
case _betaPromptShown = 1001
static let betaPromptShown = Typed<Bool>(rawValue: ._betaPromptShown)
case _betaTimeline = 1002
static let betaTimeline = Typed<Bool>(rawValue: ._betaTimeline)
case _enableBackup = 1003
static let enableBackup = Typed<Bool>(rawValue: ._enableBackup)
case _useWifiForUploadVideos = 1004
static let useWifiForUploadVideos = Typed<Bool>(rawValue: ._useWifiForUploadVideos)
case _useWifiForUploadPhotos = 1005
static let useWifiForUploadPhotos = Typed<Bool>(rawValue: ._useWifiForUploadPhotos)
case _needBetaMigration = 1006
static let needBetaMigration = Typed<Bool>(rawValue: ._needBetaMigration)
case _shouldResetSync = 1007
static let shouldResetSync = Typed<Bool>(rawValue: ._shouldResetSync)
struct Typed<T>: RawRepresentable {
let rawValue: StoreKey
@_transparent
init(rawValue value: StoreKey) {
self.rawValue = value
}
}
}
enum BackupSelection: Int, QueryBindable {
case selected, none, excluded
}
enum AvatarColor: Int, QueryBindable {
case primary, pink, red, yellow, blue, green, purple, orange, gray, amber
}
enum AlbumUserRole: Int, QueryBindable {
case editor, viewer
}
enum MemoryType: Int, QueryBindable {
case onThisDay
}
+146
View File
@@ -0,0 +1,146 @@
import SQLiteData
enum StoreError: Error {
case invalidJSON(String)
case invalidURL(String)
case encodingFailed
}
protocol StoreConvertible {
associatedtype StorageType
static func fromValue(_ value: StorageType) throws(StoreError) -> Self
static func toValue(_ value: Self) throws(StoreError) -> StorageType
}
extension Int: StoreConvertible {
static func fromValue(_ value: Int) -> Int { value }
static func toValue(_ value: Int) -> Int { value }
}
extension Bool: StoreConvertible {
static func fromValue(_ value: Int) -> Bool { value == 1 }
static func toValue(_ value: Bool) -> Int { value ? 1 : 0 }
}
extension Date: StoreConvertible {
static func fromValue(_ value: Int) -> Date { Date(timeIntervalSince1970: TimeInterval(value) / 1000) }
static func toValue(_ value: Date) -> Int { Int(value.timeIntervalSince1970 * 1000) }
}
extension String: StoreConvertible {
static func fromValue(_ value: String) -> String { value }
static func toValue(_ value: String) -> String { value }
}
extension URL: StoreConvertible {
static func fromValue(_ value: String) throws(StoreError) -> URL {
guard let url = URL(string: value) else {
throw StoreError.invalidURL(value)
}
return url
}
static func toValue(_ value: URL) -> String { value.absoluteString }
}
extension StoreConvertible where Self: Codable, StorageType == String {
static var jsonDecoder: JSONDecoder { JSONDecoder() }
static var jsonEncoder: JSONEncoder { JSONEncoder() }
static func fromValue(_ value: String) throws(StoreError) -> Self {
do {
return try jsonDecoder.decode(Self.self, from: Data(value.utf8))
} catch {
throw StoreError.invalidJSON(value)
}
}
static func toValue(_ value: Self) throws(StoreError) -> String {
let encoded: Data
do {
encoded = try jsonEncoder.encode(value)
} catch {
throw StoreError.encodingFailed
}
guard let string = String(data: encoded, encoding: .utf8) else {
throw StoreError.encodingFailed
}
return string
}
}
extension Array: StoreConvertible where Element: Codable {
typealias StorageType = String
}
extension Dictionary: StoreConvertible where Key == String, Value: Codable {
typealias StorageType = String
}
class StoreRepository {
private let db: DatabasePool
init(db: DatabasePool) {
self.db = db
}
func get<T: StoreConvertible>(_ key: StoreKey.Typed<T>) throws -> T? where T.StorageType == Int {
let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) }
if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil {
return try T.fromValue(value)
}
return nil
}
func get<T: StoreConvertible>(_ key: StoreKey.Typed<T>) throws -> T? where T.StorageType == String {
let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) }
if let value = try db.read({ conn in try query.fetchOne(conn) }) ?? nil {
return try T.fromValue(value)
}
return nil
}
func get<T: StoreConvertible>(_ key: StoreKey.Typed<T>) async throws -> T? where T.StorageType == Int {
let query = Store.select(\.intValue).where { $0.id.eq(key.rawValue) }
if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil {
return try T.fromValue(value)
}
return nil
}
func get<T: StoreConvertible>(_ key: StoreKey.Typed<T>) async throws -> T? where T.StorageType == String {
let query = Store.select(\.stringValue).where { $0.id.eq(key.rawValue) }
if let value = try await db.read({ conn in try query.fetchOne(conn) }) ?? nil {
return try T.fromValue(value)
}
return nil
}
func set<T: StoreConvertible>(_ key: StoreKey.Typed<T>, value: T) throws where T.StorageType == Int {
let value = try T.toValue(value)
try db.write { conn in
try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn)
}
}
func set<T: StoreConvertible>(_ key: StoreKey.Typed<T>, value: T) throws where T.StorageType == String {
let value = try T.toValue(value)
try db.write { conn in
try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn)
}
}
func set<T: StoreConvertible>(_ key: StoreKey.Typed<T>, value: T) async throws where T.StorageType == Int {
let value = try T.toValue(value)
try await db.write { conn in
try Store.upsert { Store(id: key.rawValue, stringValue: nil, intValue: value) }.execute(conn)
}
}
func set<T: StoreConvertible>(_ key: StoreKey.Typed<T>, value: T) async throws where T.StorageType == String {
let value = try T.toValue(value)
try await db.write { conn in
try Store.upsert { Store(id: key.rawValue, stringValue: value, intValue: nil) }.execute(conn)
}
}
}
+237
View File
@@ -0,0 +1,237 @@
import GRDB
import SQLiteData
@Table("asset_face_entity")
struct AssetFace {
let id: String
let assetId: String
let personId: String?
let imageWidth: Int
let imageHeight: Int
let boundingBoxX1: Int
let boundingBoxY1: Int
let boundingBoxX2: Int
let boundingBoxY2: Int
let sourceType: String
}
@Table("auth_user_entity")
struct AuthUser {
let id: String
let name: String
let email: String
let isAdmin: Bool
let hasProfileImage: Bool
let profileChangedAt: Date
let avatarColor: AvatarColor
let quotaSizeInBytes: Int
let quotaUsageInBytes: Int
let pinCode: String?
}
@Table("local_album_entity")
struct LocalAlbum {
let id: String
let backupSelection: BackupSelection
let linkedRemoteAlbumId: String?
let marker_: Bool?
let name: String
let isIosSharedAlbum: Bool
let updatedAt: Date
}
@Table("local_album_asset_entity")
struct LocalAlbumAsset {
let id: ID
let marker_: String?
@Selection
struct ID {
let assetId: String
let albumId: String
}
}
@Table("local_asset_entity")
struct LocalAsset {
let id: String
let checksum: String?
let createdAt: Date
let durationInSeconds: Int?
let height: Int?
let isFavorite: Bool
let name: String
let orientation: String
let type: Int
let updatedAt: Date
let width: Int?
}
@Table("memory_asset_entity")
struct MemoryAsset {
let id: ID
@Selection
struct ID {
let assetId: String
let albumId: String
}
}
@Table("memory_entity")
struct Memory {
let id: String
let createdAt: Date
let updatedAt: Date
let deletedAt: Date?
let ownerId: String
let type: MemoryType
let data: String
let isSaved: Bool
let memoryAt: Date
let seenAt: Date?
let showAt: Date?
let hideAt: Date?
}
@Table("partner_entity")
struct Partner {
let id: ID
let inTimeline: Bool
@Selection
struct ID {
let sharedById: String
let sharedWithId: String
}
}
@Table("person_entity")
struct Person {
let id: String
let createdAt: Date
let updatedAt: Date
let ownerId: String
let name: String
let faceAssetId: String?
let isFavorite: Bool
let isHidden: Bool
let color: String?
let birthDate: Date?
}
@Table("remote_album_entity")
struct RemoteAlbum {
let id: String
let createdAt: Date
let description: String?
let isActivityEnabled: Bool
let name: String
let order: Int
let ownerId: String
let thumbnailAssetId: String?
let updatedAt: Date
}
@Table("remote_album_asset_entity")
struct RemoteAlbumAsset {
let id: ID
@Selection
struct ID {
let assetId: String
let albumId: String
}
}
@Table("remote_album_user_entity")
struct RemoteAlbumUser {
let id: ID
let role: AlbumUserRole
@Selection
struct ID {
let albumId: String
let userId: String
}
}
@Table("remote_asset_entity")
struct RemoteAsset {
let id: String
let checksum: String?
let deletedAt: Date?
let isFavorite: Int
let libraryId: String?
let livePhotoVideoId: String?
let localDateTime: Date?
let orientation: String
let ownerId: String
let stackId: String?
let visibility: Int
}
@Table("remote_exif_entity")
struct RemoteExif {
@Column(primaryKey: true)
let assetId: String
let city: String?
let state: String?
let country: String?
let dateTimeOriginal: Date?
let description: String?
let height: Int?
let width: Int?
let exposureTime: String?
let fNumber: Double?
let fileSize: Int?
let focalLength: Double?
let latitude: Double?
let longitude: Double?
let iso: Int?
let make: String?
let model: String?
let lens: String?
let orientation: String?
let timeZone: String?
let rating: Int?
let projectionType: String?
}
@Table("stack_entity")
struct Stack {
let id: String
let createdAt: Date
let updatedAt: Date
let ownerId: String
let primaryAssetId: String
}
@Table("store_entity")
struct Store {
let id: StoreKey
let stringValue: String?
let intValue: Int?
}
@Table("user_entity")
struct User {
let id: String
let name: String
let email: String
let hasProfileImage: Bool
let profileChangedAt: Date
let avatarColor: AvatarColor
}
@Table("user_metadata_entity")
struct UserMetadata {
let id: ID
let value: Data
@Selection
struct ID {
let userId: String
let key: Date
}
}
+16
View File
@@ -364,6 +364,7 @@ protocol NativeSyncApi {
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset]
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
func cancelHashing() throws
func getTrashedAssets() throws -> [String: [PlatformAsset]]
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -532,5 +533,20 @@ class NativeSyncApiSetup {
} else {
cancelHashingChannel.setMessageHandler(nil)
}
let getTrashedAssetsChannel = taskQueue == nil
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
if let api = api {
getTrashedAssetsChannel.setMessageHandler { _, reply in
do {
let result = try api.getTrashedAssets()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getTrashedAssetsChannel.setMessageHandler(nil)
}
}
}
+68 -64
View File
@@ -3,15 +3,15 @@ import CryptoKit
struct AssetWrapper: Hashable, Equatable {
let asset: PlatformAsset
init(with asset: PlatformAsset) {
self.asset = asset
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.asset.id)
}
static func == (lhs: AssetWrapper, rhs: AssetWrapper) -> Bool {
return lhs.asset.id == rhs.asset.id
}
@@ -19,31 +19,31 @@ struct AssetWrapper: Hashable, Equatable {
class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
static let name = "NativeSyncApi"
static func register(with registrar: any FlutterPluginRegistrar) {
let instance = NativeSyncApiImpl()
NativeSyncApiSetup.setUp(binaryMessenger: registrar.messenger(), api: instance)
registrar.publish(instance)
}
func detachFromEngine(for registrar: any FlutterPluginRegistrar) {
super.detachFromEngine()
}
private let defaults: UserDefaults
private let changeTokenKey = "immich:changeToken"
private let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum]
private let recoveredAlbumSubType = 1000000219
private var hashTask: Task<Void?, Error>?
private static let hashCancelledCode = "HASH_CANCELLED"
private static let hashCancelled = Result<[HashResult], Error>.failure(PigeonError(code: hashCancelledCode, message: "Hashing cancelled", details: nil))
init(with defaults: UserDefaults = .standard) {
self.defaults = defaults
}
@available(iOS 16, *)
private func getChangeToken() -> PHPersistentChangeToken? {
guard let data = defaults.data(forKey: changeTokenKey) else {
@@ -51,7 +51,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: PHPersistentChangeToken.self, from: data)
}
@available(iOS 16, *)
private func saveChangeToken(token: PHPersistentChangeToken) -> Void {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: token, requiringSecureCoding: true) else {
@@ -59,18 +59,18 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
defaults.set(data, forKey: changeTokenKey)
}
func clearSyncCheckpoint() -> Void {
defaults.removeObject(forKey: changeTokenKey)
}
func checkpointSync() {
guard #available(iOS 16, *) else {
return
}
saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken)
}
func shouldFullSync() -> Bool {
guard #available(iOS 16, *),
PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized,
@@ -78,36 +78,36 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
// When we do not have access to photo library, older iOS version or No token available, fallback to full sync
return true
}
guard let _ = try? PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) else {
// Cannot fetch persistent changes
return true
}
return false
}
func getAlbums() throws -> [PlatformAlbum] {
var albums: [PlatformAlbum] = []
albumTypes.forEach { type in
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
for i in 0..<collections.count {
let album = collections.object(at: i)
// Ignore recovered album
if(album.assetCollectionSubtype.rawValue == self.recoveredAlbumSubType) {
continue;
}
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "modificationDate", ascending: false)]
options.includeHiddenAssets = false
let assets = getAssetsFromAlbum(in: album, options: options)
let isCloud = album.assetCollectionSubtype == .albumCloudShared || album.assetCollectionSubtype == .albumMyPhotoStream
var domainAlbum = PlatformAlbum(
id: album.localIdentifier,
name: album.localizedTitle!,
@@ -115,57 +115,57 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
isCloud: isCloud,
assetCount: Int64(assets.count)
)
if let firstAsset = assets.firstObject {
domainAlbum.updatedAt = firstAsset.modificationDate.map { Int64($0.timeIntervalSince1970) }
}
albums.append(domainAlbum)
}
}
return albums.sorted { $0.id < $1.id }
}
func getMediaChanges() throws -> SyncDelta {
guard #available(iOS 16, *) else {
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil)
}
guard PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized else {
throw PigeonError(code: "NO_AUTH", message: "No photo library access", details: nil)
}
guard let storedToken = getChangeToken() else {
// No token exists, definitely need a full sync
print("MediaManager::getMediaChanges: No token found")
throw PigeonError(code: "NO_TOKEN", message: "No stored change token", details: nil)
}
let currentToken = PHPhotoLibrary.shared().currentChangeToken
if storedToken == currentToken {
return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:])
}
do {
let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken)
var updatedAssets: Set<AssetWrapper> = []
var deletedAssets: Set<String> = []
for change in changes {
guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue }
let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers)
deletedAssets.formUnion(details.deletedLocalIdentifiers)
if (updated.isEmpty) { continue }
let options = PHFetchOptions()
options.includeHiddenAssets = false
let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options)
for i in 0..<result.count {
let asset = result.object(at: i)
// Asset wrapper only uses the id for comparison. Multiple change can contain the same asset, skip duplicate changes
let predicate = PlatformAsset(
id: asset.localIdentifier,
@@ -178,25 +178,25 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
continue
}
let domainAsset = AssetWrapper(with: asset.toPlatformAsset())
updatedAssets.insert(domainAsset)
}
}
let updates = Array(updatedAssets.map { $0.asset })
return SyncDelta(hasChanges: true, updates: updates, deletes: Array(deletedAssets), assetAlbums: buildAssetAlbumsMap(assets: updates))
}
}
private func buildAssetAlbumsMap(assets: Array<PlatformAsset>) -> [String: [String]] {
guard !assets.isEmpty else {
return [:]
}
var albumAssets: [String: [String]] = [:]
for type in albumTypes {
let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil)
collections.enumerateObjects { (album, _, _) in
@@ -211,13 +211,13 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return albumAssets
}
func getAssetIdsForAlbum(albumId: String) throws -> [String] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return []
}
var ids: [String] = []
let options = PHFetchOptions()
options.includeHiddenAssets = false
@@ -227,13 +227,13 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
}
return ids
}
func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return 0
}
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
@@ -241,32 +241,32 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
let assets = getAssetsFromAlbum(in: album, options: options)
return Int64(assets.count)
}
func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] {
let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil)
guard let album = collections.firstObject else {
return []
}
let options = PHFetchOptions()
options.includeHiddenAssets = false
if(updatedTimeCond != nil) {
let date = NSDate(timeIntervalSince1970: TimeInterval(updatedTimeCond!))
options.predicate = NSPredicate(format: "creationDate > %@ OR modificationDate > %@", date, date)
}
let result = getAssetsFromAlbum(in: album, options: options)
if(result.count == 0) {
return []
}
var assets: [PlatformAsset] = []
result.enumerateObjects { (asset, _, _) in
assets.append(asset.toPlatformAsset())
}
return assets
}
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) {
if let prevTask = hashTask {
prevTask.cancel()
@@ -284,11 +284,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
missingAssetIds.remove(asset.localIdentifier)
assets.append(asset)
}
if Task.isCancelled {
return self?.completeWhenActive(for: completion, with: Self.hashCancelled)
}
await withTaskGroup(of: HashResult?.self) { taskGroup in
var results = [HashResult]()
results.reserveCapacity(assets.count)
@@ -301,28 +301,28 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
return await self.hashAsset(asset, allowNetworkAccess: allowNetworkAccess)
}
}
for await result in taskGroup {
guard let result = result else {
return self?.completeWhenActive(for: completion, with: Self.hashCancelled)
}
results.append(result)
}
for missing in missingAssetIds {
results.append(HashResult(assetId: missing, error: "Asset not found in library", hash: nil))
}
return self?.completeWhenActive(for: completion, with: .success(results))
}
}
}
func cancelHashing() {
hashTask?.cancel()
hashTask = nil
}
private func hashAsset(_ asset: PHAsset, allowNetworkAccess: Bool) async -> HashResult? {
class RequestRef {
var id: PHAssetResourceDataRequestID?
@@ -332,21 +332,21 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
if Task.isCancelled {
return nil
}
guard let resource = asset.getResource() else {
return HashResult(assetId: asset.localIdentifier, error: "Cannot get asset resource", hash: nil)
}
if Task.isCancelled {
return nil
}
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = allowNetworkAccess
return await withCheckedContinuation { continuation in
var hasher = Insecure.SHA1()
requestRef.id = PHAssetResourceManager.default().requestData(
for: resource,
options: options,
@@ -377,7 +377,11 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
PHAssetResourceManager.default().cancelDataRequest(requestId)
})
}
func getTrashedAssets() throws -> [String: [PlatformAsset]] {
throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil)
}
private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult<PHAsset> {
// Ensure to actually getting all assets for the Recents album
if (album.assetCollectionSubtype == .smartAlbumUserLibrary) {
+50 -4
View File
@@ -32,6 +32,17 @@ platform :ios do
)
end
# Helper method to get version from pubspec.yaml
def get_version_from_pubspec
require 'yaml'
pubspec_path = File.join(Dir.pwd, "../..", "pubspec.yaml")
pubspec = YAML.load_file(pubspec_path)
version_string = pubspec['version']
version_string ? version_string.split('+').first : nil
end
# Helper method to configure code signing for all targets
def configure_code_signing(bundle_id_suffix: "")
bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}"
@@ -101,7 +112,7 @@ platform :ios do
workspace: "Runner.xcworkspace",
configuration: configuration,
export_method: "app-store",
xcargs: "CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
export_options: {
provisioningProfiles: {
"#{app_identifier}" => "#{app_identifier} AppStore",
@@ -158,7 +169,8 @@ platform :ios do
# Build and upload with version number
build_and_upload(
api_key: api_key,
version_number: "2.1.0"
version_number: get_version_from_pubspec,
distribute_external: false,
)
end
@@ -168,8 +180,9 @@ platform :ios do
path: "./Runner.xcodeproj",
targets: ["Runner", "ShareExtension", "WidgetExtension"]
)
increment_version_number(
version_number: "2.2.2"
version_number: get_version_from_pubspec
)
increment_build_number(
build_number: latest_testflight_build_number + 1,
@@ -182,7 +195,7 @@ platform :ios do
configuration: "Release",
export_method: "app-store",
skip_package_ipa: false,
xcargs: "-allowProvisioningUpdates",
xcargs: "-skipMacroValidation -allowProvisioningUpdates",
export_options: {
method: "app-store",
signingStyle: "automatic",
@@ -197,4 +210,37 @@ platform :ios do
)
end
desc "iOS Build Only (no TestFlight upload)"
lane :gha_build_only do
# Use the same build process as production, just skip the upload
# This ensures PR builds validate the same way as production builds
# Install provisioning profiles (use development profiles for PR builds)
install_provisioning_profile(path: "profile_dev.mobileprovision")
install_provisioning_profile(path: "profile_dev_share.mobileprovision")
install_provisioning_profile(path: "profile_dev_widget.mobileprovision")
# Configure code signing for dev bundle IDs
configure_code_signing(bundle_id_suffix: "development")
# Build the app (same as gha_testflight_dev but without upload)
build_app(
scheme: "Runner",
workspace: "Runner.xcworkspace",
configuration: "Release",
export_method: "app-store",
skip_package_ipa: true,
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
export_options: {
provisioningProfiles: {
"#{BASE_BUNDLE_ID}.development" => "#{BASE_BUNDLE_ID}.development AppStore",
"#{BASE_BUNDLE_ID}.development.ShareExtension" => "#{BASE_BUNDLE_ID}.development.ShareExtension AppStore",
"#{BASE_BUNDLE_ID}.development.Widget" => "#{BASE_BUNDLE_ID}.development.Widget AppStore"
},
signingStyle: "manual",
signingCertificate: CODE_SIGN_IDENTITY
}
)
end
end
+3
View File
@@ -58,3 +58,6 @@ const int kPhotoTabIndex = 0;
const int kSearchTabIndex = 1;
const int kAlbumTabIndex = 2;
const int kLibraryTabIndex = 3;
// Workaround for SQLite's variable limit (SQLITE_MAX_VARIABLE_NUMBER = 32766)
const int kDriftMaxChunk = 32000;
@@ -239,7 +239,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
final networkCapabilities = await _ref?.read(connectivityApiProvider).getCapabilities() ?? [];
return _ref
?.read(uploadServiceProvider)
.startBackupWithHttpClient(currentUser.id, networkCapabilities.hasWifi, _cancellationToken);
.startBackupWithHttpClient(currentUser.id, networkCapabilities.isUnmetered, _cancellationToken);
},
(error, stack) {
dPrint(() => "Error in backup zone $error, $stack");
+22 -6
View File
@@ -2,8 +2,10 @@ import 'package:flutter/services.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:logging/logging.dart';
@@ -13,6 +15,7 @@ class HashService {
final int _batchSize;
final DriftLocalAlbumRepository _localAlbumRepository;
final DriftLocalAssetRepository _localAssetRepository;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final NativeSyncApi _nativeSyncApi;
final bool Function()? _cancelChecker;
final _log = Logger('HashService');
@@ -20,11 +23,13 @@ class HashService {
HashService({
required DriftLocalAlbumRepository localAlbumRepository,
required DriftLocalAssetRepository localAssetRepository,
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
required NativeSyncApi nativeSyncApi,
bool Function()? cancelChecker,
int? batchSize,
}) : _localAlbumRepository = localAlbumRepository,
_localAssetRepository = localAssetRepository,
_trashedLocalAssetRepository = trashedLocalAssetRepository,
_cancelChecker = cancelChecker,
_nativeSyncApi = nativeSyncApi,
_batchSize = batchSize ?? kBatchHashFileLimit;
@@ -49,6 +54,14 @@ class HashService {
await _hashAssets(album, assetsToHash);
}
}
if (CurrentPlatform.isAndroid && localAlbums.isNotEmpty) {
final backupAlbumIds = localAlbums.map((e) => e.id);
final trashedToHash = await _trashedLocalAssetRepository.getAssetsToHash(backupAlbumIds);
if (trashedToHash.isNotEmpty) {
final pseudoAlbum = LocalAlbum(id: '-pseudoAlbum', name: 'Trash', updatedAt: DateTime.now());
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
}
}
} on PlatformException catch (e) {
if (e.code == _kHashCancelledCode) {
_log.warning("Hashing cancelled by platform");
@@ -65,7 +78,7 @@ class HashService {
/// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB
/// with hash for those that were successfully hashed. Hashes are looked up in a table
/// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB.
Future<void> _hashAssets(LocalAlbum album, List<LocalAsset> assetsToHash) async {
Future<void> _hashAssets(LocalAlbum album, List<LocalAsset> assetsToHash, {bool isTrashed = false}) async {
final toHash = <String, LocalAsset>{};
for (final asset in assetsToHash) {
@@ -76,16 +89,16 @@ class HashService {
toHash[asset.id] = asset;
if (toHash.length == _batchSize) {
await _processBatch(album, toHash);
await _processBatch(album, toHash, isTrashed);
toHash.clear();
}
}
await _processBatch(album, toHash);
await _processBatch(album, toHash, isTrashed);
}
/// Processes a batch of assets.
Future<void> _processBatch(LocalAlbum album, Map<String, LocalAsset> toHash) async {
Future<void> _processBatch(LocalAlbum album, Map<String, LocalAsset> toHash, bool isTrashed) async {
if (toHash.isEmpty) {
return;
}
@@ -120,7 +133,10 @@ class HashService {
}
_log.fine("Hashed ${hashed.length}/${toHash.length} assets");
await _localAssetRepository.updateHashes(hashed);
if (isTrashed) {
await _trashedLocalAssetRepository.updateHashes(hashed);
} else {
await _localAssetRepository.updateHashes(hashed);
}
}
}
@@ -4,9 +4,14 @@ import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/platform/native_sync_api.g.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/utils/datetime_helpers.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:logging/logging.dart';
@@ -14,15 +19,34 @@ import 'package:logging/logging.dart';
class LocalSyncService {
final DriftLocalAlbumRepository _localAlbumRepository;
final NativeSyncApi _nativeSyncApi;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final LocalFilesManagerRepository _localFilesManager;
final StorageRepository _storageRepository;
final Logger _log = Logger("DeviceSyncService");
LocalSyncService({required DriftLocalAlbumRepository localAlbumRepository, required NativeSyncApi nativeSyncApi})
: _localAlbumRepository = localAlbumRepository,
_nativeSyncApi = nativeSyncApi;
LocalSyncService({
required DriftLocalAlbumRepository localAlbumRepository,
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
required LocalFilesManagerRepository localFilesManager,
required StorageRepository storageRepository,
required NativeSyncApi nativeSyncApi,
}) : _localAlbumRepository = localAlbumRepository,
_trashedLocalAssetRepository = trashedLocalAssetRepository,
_localFilesManager = localFilesManager,
_storageRepository = storageRepository,
_nativeSyncApi = nativeSyncApi;
Future<void> sync({bool full = false}) async {
final Stopwatch stopwatch = Stopwatch()..start();
try {
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
final hasPermission = await _localFilesManager.hasManageMediaPermission();
if (hasPermission) {
await _syncTrashedAssets();
} else {
_log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing");
}
}
if (full || await _nativeSyncApi.shouldFullSync()) {
_log.fine("Full sync request from ${full ? "user" : "native"}");
return await fullSync();
@@ -69,7 +93,6 @@ class LocalSyncService {
await updateAlbum(dbAlbum, album);
}
}
await _nativeSyncApi.checkpointSync();
} catch (e, s) {
_log.severe("Error performing device sync", e, s);
@@ -273,6 +296,48 @@ class LocalSyncService {
bool _albumsEqual(LocalAlbum a, LocalAlbum b) {
return a.name == b.name && a.assetCount == b.assetCount && a.updatedAt.isAtSameMomentAs(b.updatedAt);
}
Future<void> _syncTrashedAssets() async {
final trashedAssetMap = await _nativeSyncApi.getTrashedAssets();
await processTrashedAssets(trashedAssetMap);
}
@visibleForTesting
Future<void> processTrashedAssets(Map<String, List<PlatformAsset>> trashedAssetMap) async {
if (trashedAssetMap.isEmpty) {
_log.info("syncTrashedAssets, No trashed assets found");
}
final trashedAssets = trashedAssetMap.cast<String, List<Object?>>().entries.expand(
(entry) => entry.value.cast<PlatformAsset>().toTrashedAssets(entry.key),
);
_log.fine("syncTrashedAssets, trashedAssets: ${trashedAssets.map((e) => e.asset.id)}");
await _trashedLocalAssetRepository.processTrashSnapshot(trashedAssets);
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
if (assetsToRestore.isNotEmpty) {
final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
} else {
_log.info("syncTrashedAssets, No remote assets found for restoration");
}
final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash();
if (localAssetsToTrash.isNotEmpty) {
final mediaUrls = await Future.wait(
localAssetsToTrash.values
.expand((e) => e)
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
);
_log.info("Moving to trash ${mediaUrls.join(", ")} assets");
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
if (result) {
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
}
} else {
_log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash");
}
}
}
extension on Iterable<PlatformAlbum> {
@@ -290,20 +355,26 @@ extension on Iterable<PlatformAlbum> {
extension on Iterable<PlatformAsset> {
List<LocalAsset> toLocalAssets() {
return map(
(e) => LocalAsset(
id: e.id,
name: e.name,
checksum: null,
type: AssetType.values.elementAtOrNull(e.type) ?? AssetType.other,
createdAt: tryFromSecondsSinceEpoch(e.createdAt, isUtc: true) ?? DateTime.timestamp(),
updatedAt: tryFromSecondsSinceEpoch(e.updatedAt, isUtc: true) ?? DateTime.timestamp(),
width: e.width,
height: e.height,
durationInSeconds: e.durationInSeconds,
orientation: e.orientation,
isFavorite: e.isFavorite,
),
).toList();
return map((e) => e.toLocalAsset()).toList();
}
Iterable<TrashedAsset> toTrashedAssets(String albumId) {
return map((e) => (albumId: albumId, asset: e.toLocalAsset()));
}
}
extension on PlatformAsset {
LocalAsset toLocalAsset() => LocalAsset(
id: id,
name: name,
checksum: null,
type: AssetType.values.elementAtOrNull(type) ?? AssetType.other,
createdAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
updatedAt: tryFromSecondsSinceEpoch(createdAt, isUtc: true) ?? DateTime.timestamp(),
width: width,
height: height,
durationInSeconds: durationInSeconds,
isFavorite: isFavorite,
orientation: orientation,
);
}
@@ -1,8 +1,15 @@
import 'dart:async';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/sync_event.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
@@ -11,14 +18,26 @@ class SyncStreamService {
final SyncApiRepository _syncApiRepository;
final SyncStreamRepository _syncStreamRepository;
final DriftLocalAssetRepository _localAssetRepository;
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
final LocalFilesManagerRepository _localFilesManager;
final StorageRepository _storageRepository;
final bool Function()? _cancelChecker;
SyncStreamService({
required SyncApiRepository syncApiRepository,
required SyncStreamRepository syncStreamRepository,
required DriftLocalAssetRepository localAssetRepository,
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
required LocalFilesManagerRepository localFilesManager,
required StorageRepository storageRepository,
bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository,
_localAssetRepository = localAssetRepository,
_trashedLocalAssetRepository = trashedLocalAssetRepository,
_localFilesManager = localFilesManager,
_storageRepository = storageRepository,
_cancelChecker = cancelChecker;
bool get isCancelled => _cancelChecker?.call() ?? false;
@@ -83,7 +102,18 @@ class SyncStreamService {
case SyncEntityType.partnerDeleteV1:
return _syncStreamRepository.deletePartnerV1(data.cast());
case SyncEntityType.assetV1:
return _syncStreamRepository.updateAssetsV1(data.cast());
final remoteSyncAssets = data.cast<SyncAssetV1>();
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
final hasPermission = await _localFilesManager.hasManageMediaPermission();
if (hasPermission) {
await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum));
await _applyRemoteRestoreToLocal();
} else {
_logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing");
}
}
return;
case SyncEntityType.assetDeleteV1:
return _syncStreamRepository.deleteAssetsV1(data.cast());
case SyncEntityType.assetExifV1:
@@ -212,4 +242,36 @@ class SyncStreamService {
_logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
}
}
Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
if (checksums.isEmpty) {
return Future.value();
} else {
final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums);
if (localAssetsToTrash.isNotEmpty) {
final mediaUrls = await Future.wait(
localAssetsToTrash.values
.expand((e) => e)
.map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())),
);
_logger.info("Moving to trash ${mediaUrls.join(", ")} assets");
final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList());
if (result) {
await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash);
}
} else {
_logger.info("No assets found in backup-enabled albums for assets: $checksums");
}
}
}
Future<void> _applyRemoteRestoreToLocal() async {
final assetsToRestore = await _trashedLocalAssetRepository.getToRestore();
if (assetsToRestore.isNotEmpty) {
final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore);
await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds);
} else {
_logger.info("No remote assets found for restoration");
}
}
}
@@ -0,0 +1,40 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)')
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)')
class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
const TrashedLocalAssetEntity();
TextColumn get id => text()();
TextColumn get albumId => text()();
TextColumn get checksum => text().nullable()();
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
IntColumn get orientation => integer().withDefault(const Constant(0))();
@override
Set<Column> get primaryKey => {id, albumId};
}
extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityData {
LocalAsset toLocalAsset() => LocalAsset(
id: id,
name: name,
checksum: checksum,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
durationInSeconds: durationInSeconds,
isFavorite: isFavorite,
height: height,
width: width,
orientation: orientation,
);
}
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
@@ -62,6 +63,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
PersonEntity,
AssetFaceEntity,
StoreEntity,
TrashedLocalAssetEntity,
],
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
)
@@ -93,7 +95,7 @@ class Drift extends $Drift implements IDatabaseRepository {
}
@override
int get schemaVersion => 12;
int get schemaVersion => 13;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -178,6 +180,11 @@ class Drift extends $Drift implements IDatabaseRepository {
);
}
},
from12To13: (m, v13) async {
await m.create(v13.trashedLocalAssetEntity);
await m.createIndex(v13.idxTrashedLocalAssetChecksum);
await m.createIndex(v13.idxTrashedLocalAssetAlbum);
},
),
);
@@ -37,9 +37,11 @@ import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.da
as i17;
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
as i18;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
as i19;
import 'package:drift/internal/modular.dart' as i20;
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
as i20;
import 'package:drift/internal/modular.dart' as i21;
abstract class $Drift extends i0.GeneratedDatabase {
$Drift(i0.QueryExecutor e) : super(e);
@@ -77,9 +79,11 @@ abstract class $Drift extends i0.GeneratedDatabase {
late final i17.$AssetFaceEntityTable assetFaceEntity = i17
.$AssetFaceEntityTable(this);
late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this);
i19.MergedAssetDrift get mergedAssetDrift => i20.ReadDatabaseContainer(
late final i19.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i19
.$TrashedLocalAssetEntityTable(this);
i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer(
this,
).accessor<i19.MergedAssetDrift>(i19.MergedAssetDrift.new);
).accessor<i20.MergedAssetDrift>(i20.MergedAssetDrift.new);
@override
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
@@ -108,7 +112,10 @@ abstract class $Drift extends i0.GeneratedDatabase {
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
i11.idxLatLng,
i19.idxTrashedLocalAssetChecksum,
i19.idxTrashedLocalAssetAlbum,
];
@override
i0.StreamQueryUpdateRules
@@ -336,4 +343,9 @@ class $DriftManager {
i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
i18.$$StoreEntityTableTableManager get storeEntity =>
i18.$$StoreEntityTableTableManager(_db, _db.storeEntity);
i19.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity =>
i19.$$TrashedLocalAssetEntityTableTableManager(
_db,
_db.trashedLocalAssetEntity,
);
}
@@ -5037,6 +5037,454 @@ final class Schema12 extends i0.VersionedSchema {
);
}
final class Schema13 extends i0.VersionedSchema {
Schema13({required super.database}) : super(version: 13);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
stackEntity,
localAssetEntity,
remoteAlbumEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAssetChecksum,
idxRemoteAssetOwnerChecksum,
uQRemoteAssetsOwnerChecksum,
uQRemoteAssetsOwnerLibraryChecksum,
idxRemoteAssetChecksum,
authUserEntity,
userMetadataEntity,
partnerEntity,
remoteExifEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
idxLatLng,
idxTrashedLocalAssetChecksum,
idxTrashedLocalAssetAlbum,
];
late final Shape20 userEntity = Shape20(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_84,
_column_85,
_column_91,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape17 remoteAssetEntity = Shape17(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_13,
_column_14,
_column_15,
_column_16,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_86,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 stackEntity = Shape3(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
attachedDatabase: database,
),
alias: null,
);
late final Shape2 localAssetEntity = Shape2(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_22,
_column_14,
_column_23,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape9 remoteAlbumEntity = Shape9(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_56,
_column_9,
_column_5,
_column_15,
_column_57,
_column_58,
_column_59,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape19 localAlbumEntity = Shape19(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_5,
_column_31,
_column_32,
_column_90,
_column_33,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape22 localAlbumAssetEntity = Shape22(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_34, _column_35, _column_33],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLocalAssetChecksum = i1.Index(
'idx_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
);
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
'idx_remote_asset_owner_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
);
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
'UQ_remote_assets_owner_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
);
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
'UQ_remote_assets_owner_library_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
);
final i1.Index idxRemoteAssetChecksum = i1.Index(
'idx_remote_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
);
late final Shape21 authUserEntity = Shape21(
source: i0.VersionedTable(
entityName: 'auth_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_2,
_column_84,
_column_85,
_column_92,
_column_93,
_column_7,
_column_94,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
columns: [_column_25, _column_26, _column_27],
attachedDatabase: database,
),
alias: null,
);
late final Shape5 partnerEntity = Shape5(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
columns: [_column_28, _column_29, _column_30],
attachedDatabase: database,
),
alias: null,
);
late final Shape8 remoteExifEntity = Shape8(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_36,
_column_37,
_column_38,
_column_39,
_column_40,
_column_41,
_column_11,
_column_10,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
_column_48,
_column_49,
_column_50,
_column_51,
_column_52,
_column_53,
_column_54,
_column_55,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_36, _column_60],
attachedDatabase: database,
),
alias: null,
);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
columns: [_column_60, _column_25, _column_61],
attachedDatabase: database,
),
alias: null,
);
late final Shape11 memoryEntity = Shape11(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_18,
_column_15,
_column_8,
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_67,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
columns: [_column_36, _column_68],
attachedDatabase: database,
),
alias: null,
);
late final Shape14 personEntity = Shape14(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_1,
_column_69,
_column_71,
_column_72,
_column_73,
_column_74,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape15 assetFaceEntity = Shape15(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_36,
_column_76,
_column_77,
_column_78,
_column_79,
_column_80,
_column_81,
_column_82,
_column_83,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape18 storeEntity = Shape18(
source: i0.VersionedTable(
entityName: 'store_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_87, _column_88, _column_89],
attachedDatabase: database,
),
alias: null,
);
late final Shape23 trashedLocalAssetEntity = Shape23(
source: i0.VersionedTable(
entityName: 'trashed_local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_95,
_column_22,
_column_14,
_column_23,
],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLatLng = i1.Index(
'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
);
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
'idx_trashed_local_asset_album',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
);
}
class Shape23 extends i0.VersionedTable {
Shape23({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get type =>
columnsByName['type']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get width =>
columnsByName['width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get height =>
columnsByName['height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get durationInSeconds =>
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get albumId =>
columnsByName['album_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isFavorite =>
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<int> get orientation =>
columnsByName['orientation']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<String> _column_95(String aliasedName) =>
i1.GeneratedColumn<String>(
'album_id',
aliasedName,
false,
type: i1.DriftSqlType.string,
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -5049,6 +5497,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@@ -5107,6 +5556,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from11To12(migrator, schema);
return 12;
case 12:
final schema = Schema13(database: database);
final migrator = i1.Migrator(database, schema);
await from12To13(migrator, schema);
return 13;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@@ -5125,6 +5579,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
@@ -5138,5 +5593,6 @@ i1.OnUpgrade stepByStep({
from9To10: from9To10,
from10To11: from10To11,
from11To12: from11To12,
from12To13: from12To13,
),
);
@@ -1,4 +1,6 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
@@ -8,6 +10,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
class DriftLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftLocalAssetRepository(this._db) : super(_db);
SingleOrNullSelectable<LocalAsset?> _assetSelectable(String id) {
@@ -95,4 +98,32 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}
return query.map((localAlbum) => localAlbum.toDto()).get();
}
Future<Map<String, List<LocalAsset>>> getAssetsFromBackupAlbums(Iterable<String> checksums) async {
if (checksums.isEmpty) {
return {};
}
final result = <String, List<LocalAsset>>{};
for (final slice in checksums.toSet().slices(kDriftMaxChunk)) {
final rows =
await (_db.select(_db.localAlbumAssetEntity).join([
innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
])..where(
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
_db.localAssetEntity.checksum.isIn(slice),
))
.get();
for (final row in rows) {
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
final assetData = row.readTable(_db.localAssetEntity);
final asset = assetData.toDto();
(result[albumId] ??= <LocalAsset>[]).add(asset);
}
}
return result;
}
}
@@ -12,6 +12,7 @@ import 'package:maplibre_gl/maplibre_gl.dart';
class RemoteAssetRepository extends DriftDatabaseRepository {
final Drift _db;
const RemoteAssetRepository(this._db) : super(_db);
/// For testing purposes
@@ -0,0 +1,252 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
typedef TrashedAsset = ({String albumId, LocalAsset asset});
class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftTrashedLocalAssetRepository(this._db) : super(_db);
Future<void> updateHashes(Map<String, String> hashes) {
if (hashes.isEmpty) {
return Future.value();
}
return _db.batch((batch) async {
for (final entry in hashes.entries) {
batch.update(
_db.trashedLocalAssetEntity,
TrashedLocalAssetEntityCompanion(checksum: Value(entry.value)),
where: (e) => e.id.equals(entry.key),
);
}
});
}
Future<List<LocalAsset>> getAssetsToHash(Iterable<String> albumIds) {
final query = _db.trashedLocalAssetEntity.select()..where((r) => r.albumId.isIn(albumIds) & r.checksum.isNull());
return query.map((row) => row.toLocalAsset()).get();
}
Future<Iterable<LocalAsset>> getToRestore() async {
final selectedAlbumIds = (_db.selectOnly(_db.localAlbumEntity)
..addColumns([_db.localAlbumEntity.id])
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected)));
final rows =
await (_db.select(_db.trashedLocalAssetEntity).join([
innerJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum),
),
])..where(
_db.trashedLocalAssetEntity.albumId.isInQuery(selectedAlbumIds) &
_db.remoteAssetEntity.deletedAt.isNull(),
))
.get();
return rows.map((result) => result.readTable(_db.trashedLocalAssetEntity).toLocalAsset());
}
/// Applies resulted snapshot of trashed assets:
/// - upserts incoming rows
/// - deletes rows that are not present in the snapshot
Future<void> processTrashSnapshot(Iterable<TrashedAsset> trashedAssets) async {
if (trashedAssets.isEmpty) {
await _db.delete(_db.trashedLocalAssetEntity).go();
return;
}
final assetIds = trashedAssets.map((e) => e.asset.id).toSet();
Map<String, String> localChecksumById = await _getCachedChecksums(assetIds);
return _db.transaction(() async {
await _db.batch((batch) {
for (final item in trashedAssets) {
final effectiveChecksum = localChecksumById[item.asset.id] ?? item.asset.checksum;
final companion = TrashedLocalAssetEntityCompanion.insert(
id: item.asset.id,
albumId: item.albumId,
checksum: Value(effectiveChecksum),
name: item.asset.name,
type: item.asset.type,
createdAt: Value(item.asset.createdAt),
updatedAt: Value(item.asset.updatedAt),
width: Value(item.asset.width),
height: Value(item.asset.height),
durationInSeconds: Value(item.asset.durationInSeconds),
isFavorite: Value(item.asset.isFavorite),
orientation: Value(item.asset.orientation),
);
batch.insert<$TrashedLocalAssetEntityTable, TrashedLocalAssetEntityData>(
_db.trashedLocalAssetEntity,
companion,
onConflict: DoUpdate((_) => companion, where: (old) => old.updatedAt.isNotValue(item.asset.updatedAt)),
);
}
});
if (assetIds.length <= kDriftMaxChunk) {
await (_db.delete(_db.trashedLocalAssetEntity)..where((row) => row.id.isNotIn(assetIds))).go();
} else {
final existingIds = await (_db.selectOnly(
_db.trashedLocalAssetEntity,
)..addColumns([_db.trashedLocalAssetEntity.id])).map((r) => r.read(_db.trashedLocalAssetEntity.id)!).get();
final idToDelete = existingIds.where((id) => !assetIds.contains(id));
for (final slice in idToDelete.slices(kDriftMaxChunk)) {
await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go();
}
}
});
}
Stream<int> watchCount() {
return (_db.selectOnly(_db.trashedLocalAssetEntity)..addColumns([_db.trashedLocalAssetEntity.id.count()]))
.watchSingle()
.map((row) => row.read<int>(_db.trashedLocalAssetEntity.id.count()) ?? 0);
}
Stream<int> watchHashedCount() {
return (_db.selectOnly(_db.trashedLocalAssetEntity)
..addColumns([_db.trashedLocalAssetEntity.id.count()])
..where(_db.trashedLocalAssetEntity.checksum.isNotNull()))
.watchSingle()
.map((row) => row.read<int>(_db.trashedLocalAssetEntity.id.count()) ?? 0);
}
Future<void> trashLocalAsset(Map<String, List<LocalAsset>> assetsByAlbums) async {
if (assetsByAlbums.isEmpty) {
return;
}
final companions = <TrashedLocalAssetEntityCompanion>[];
final idToDelete = <String>{};
for (final entry in assetsByAlbums.entries) {
for (final asset in entry.value) {
idToDelete.add(asset.id);
companions.add(
TrashedLocalAssetEntityCompanion(
id: Value(asset.id),
name: Value(asset.name),
albumId: Value(entry.key),
checksum: Value(asset.checksum),
type: Value(asset.type),
width: Value(asset.width),
height: Value(asset.height),
durationInSeconds: Value(asset.durationInSeconds),
isFavorite: Value(asset.isFavorite),
orientation: Value(asset.orientation),
createdAt: Value(asset.createdAt),
updatedAt: Value(asset.updatedAt),
),
);
}
}
await _db.transaction(() async {
for (final companion in companions) {
await _db.into(_db.trashedLocalAssetEntity).insertOnConflictUpdate(companion);
}
for (final slice in idToDelete.slices(kDriftMaxChunk)) {
await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go();
}
});
}
Future<void> applyRestoredAssets(List<String> idList) async {
if (idList.isEmpty) {
return;
}
final trashedAssets = <TrashedLocalAssetEntityData>[];
for (final slice in idList.slices(kDriftMaxChunk)) {
final q = _db.select(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice));
trashedAssets.addAll(await q.get());
}
if (trashedAssets.isEmpty) {
return;
}
final companions = trashedAssets.map((e) {
return LocalAssetEntityCompanion.insert(
id: e.id,
name: e.name,
type: e.type,
createdAt: Value(e.createdAt),
updatedAt: Value(e.updatedAt),
width: Value(e.width),
height: Value(e.height),
durationInSeconds: Value(e.durationInSeconds),
checksum: Value(e.checksum),
isFavorite: Value(e.isFavorite),
orientation: Value(e.orientation),
);
});
await _db.transaction(() async {
for (final companion in companions) {
await _db.into(_db.localAssetEntity).insertOnConflictUpdate(companion);
}
for (final slice in idList.slices(kDriftMaxChunk)) {
await (_db.delete(_db.trashedLocalAssetEntity)..where((t) => t.id.isIn(slice))).go();
}
});
}
Future<Map<String, List<LocalAsset>>> getToTrash() async {
final result = <String, List<LocalAsset>>{};
final rows =
await (_db.select(_db.localAlbumAssetEntity).join([
innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
),
])..where(
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.deletedAt.isNotNull(),
))
.get();
for (final row in rows) {
final albumId = row.readTable(_db.localAlbumAssetEntity).albumId;
final asset = row.readTable(_db.localAssetEntity).toDto();
(result[albumId] ??= <LocalAsset>[]).add(asset);
}
return result;
}
//attempt to reuse existing checksums
Future<Map<String, String>> _getCachedChecksums(Set<String> assetIds) async {
final localChecksumById = <String, String>{};
for (final slice in assetIds.slices(kDriftMaxChunk)) {
final rows =
await (_db.selectOnly(_db.localAssetEntity)
..where(_db.localAssetEntity.id.isIn(slice) & _db.localAssetEntity.checksum.isNotNull())
..addColumns([_db.localAssetEntity.id, _db.localAssetEntity.checksum]))
.get();
for (final r in rows) {
localChecksumById[r.read(_db.localAssetEntity.id)!] = r.read(_db.localAssetEntity.checksum)!;
}
}
return localChecksumById;
}
}
@@ -65,7 +65,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
if (Store.isBetaTimelineEnabled) {
bool syncSuccess = false;
await Future.wait([
backgroundManager.syncLocal(),
backgroundManager.syncLocal(full: true),
backgroundManager.syncRemote().then((success) => syncSuccess = success),
]);
+28
View File
@@ -562,4 +562,32 @@ class NativeSyncApi {
return;
}
}
Future<Map<String, List<PlatformAsset>>> getTrashedAssets() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as Map<Object?, Object?>?)!.cast<String, List<PlatformAsset>>();
}
}
}
@@ -3,21 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/widgets/activities/comment_bubble.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart';
import 'package:immich_mobile/providers/activity.provider.dart';
import 'package:immich_mobile/providers/activity_service.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
@RoutePage()
class DriftActivitiesPage extends HookConsumerWidget {
@@ -27,10 +19,8 @@ class DriftActivitiesPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asset = ref.read(currentAssetNotifier) as RemoteAsset?;
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
final activities = ref.watch(albumActivityProvider(album.id, asset?.id));
final activityNotifier = ref.read(albumActivityProvider(album.id).notifier);
final activities = ref.watch(albumActivityProvider(album.id));
final listViewScrollController = useScrollController();
void scrollToBottom() {
@@ -46,7 +36,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
overrides: [currentRemoteAlbumScopedProvider.overrideWithValue(album)],
child: Scaffold(
appBar: AppBar(
title: asset == null ? Text(album.name) : null,
title: Text(album.name),
actions: [const LikeActivityActionButton(menuItem: true)],
actionsPadding: const EdgeInsets.only(right: 8),
),
@@ -57,7 +47,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
activityWidgets.add(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: _CommentBubble(activity: activity),
child: CommentBubble(activity: activity),
),
);
}
@@ -91,139 +81,3 @@ class DriftActivitiesPage extends HookConsumerWidget {
);
}
}
class _CommentBubble extends ConsumerWidget {
final Activity activity;
const _CommentBubble({required this.activity});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(currentUserProvider);
final album = ref.watch(currentRemoteAlbumProvider)!;
final isOwn = activity.user.id == user?.id;
final canDelete = isOwn || album.ownerId == user?.id;
final hasAsset = activity.assetId != null && activity.assetId!.isNotEmpty;
final isLike = activity.type == ActivityType.like;
final bgColor = isOwn ? context.colorScheme.primaryContainer : context.colorScheme.surfaceContainer;
final activityNotifier = ref.read(albumActivityProvider(album.id, activity.assetId).notifier);
Future<void> openAssetViewer() async {
final activityService = ref.read(activityServiceProvider);
final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref);
if (route != null) await context.pushRoute(route);
}
Widget avatar() {
if (isOwn) {
return const SizedBox.shrink();
}
return UserCircleAvatar(user: activity.user, size: 28, radius: 14);
}
Widget? thumbnail() {
if (!hasAsset) {
return null;
}
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150),
child: Stack(
children: [
GestureDetector(
onTap: openAssetViewer,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(10)),
child: Image(
image: ImmichRemoteThumbnailProvider(assetId: activity.assetId!),
fit: BoxFit.cover,
),
),
),
if (isLike)
Positioned(
right: 6,
bottom: 6,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
),
),
],
),
);
}
// Likes Album widget (for likes without asset)
Widget? likesToAlbum() {
if (!isLike || hasAsset) {
return null;
}
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
);
}
Widget? commentBubble() {
if (activity.comment == null || activity.comment!.isEmpty) {
return null;
}
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: bgColor, borderRadius: const BorderRadius.all(Radius.circular(12))),
child: Text(
activity.comment ?? '',
style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurface),
),
),
);
}
// Combined content widgets
final List<Widget> contentChildren = [thumbnail(), likesToAlbum(), commentBubble()].whereType<Widget>().toList();
return DismissibleActivity(
onDismiss: canDelete ? (id) async => await activityNotifier.removeActivity(id) : null,
activity.id,
Align(
alignment: isOwn ? Alignment.centerRight : Alignment.centerLeft,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.86),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isOwn) ...[avatar(), const SizedBox(width: 8)],
// Content column
Column(
crossAxisAlignment: isOwn ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
...contentChildren.map((w) => Padding(padding: const EdgeInsets.only(bottom: 8.0), child: w)),
Text(
'${activity.user.name}${activity.createdAt.timeAgo()}',
style: context.textTheme.labelMedium?.copyWith(
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
),
if (isOwn) const SizedBox(width: 8),
],
),
),
),
),
);
}
}
@@ -15,6 +15,7 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/search/quick_date_picker.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
@@ -54,6 +55,7 @@ class DriftSearchPage extends HookConsumerWidget {
);
final previousFilter = useState<SearchFilter?>(null);
final dateInputFilter = useState<DateFilterInputModel?>(null);
final peopleCurrentFilterWidget = useState<Widget?>(null);
final dateRangeCurrentFilterWidget = useState<Widget?>(null);
@@ -245,19 +247,54 @@ class DriftSearchPage extends HookConsumerWidget {
);
}
datePicked(DateFilterInputModel? selectedDate) {
dateInputFilter.value = selectedDate;
if (selectedDate == null) {
filter.value = filter.value.copyWith(date: SearchDateFilter());
dateRangeCurrentFilterWidget.value = null;
unawaited(search());
return;
}
final date = selectedDate.asDateTimeRange();
filter.value = filter.value.copyWith(
date: SearchDateFilter(
takenAfter: date.start,
takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)),
),
);
dateRangeCurrentFilterWidget.value = Text(
selectedDate.asHumanReadable(context),
style: context.textTheme.labelLarge,
);
unawaited(search());
}
showDatePicker() async {
final firstDate = DateTime(1900);
final lastDate = DateTime.now();
var dateRange = DateTimeRange(
start: filter.value.date.takenAfter ?? lastDate,
end: filter.value.date.takenBefore ?? lastDate,
);
// datePicked() may increase the date, this will make the date picker fail an assertion
// Fixup the end date to be at most now.
if (dateRange.end.isAfter(lastDate)) {
dateRange = DateTimeRange(start: dateRange.start, end: lastDate);
}
final date = await showDateRangePicker(
context: context,
firstDate: firstDate,
lastDate: lastDate,
currentDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: filter.value.date.takenAfter ?? lastDate,
end: filter.value.date.takenBefore ?? lastDate,
),
initialDateRange: dateRange,
helpText: 'search_filter_date_title'.t(context: context),
cancelText: 'cancel'.t(context: context),
confirmText: 'select'.t(context: context),
@@ -271,40 +308,32 @@ class DriftSearchPage extends HookConsumerWidget {
);
if (date == null) {
filter.value = filter.value.copyWith(date: SearchDateFilter());
dateRangeCurrentFilterWidget.value = null;
unawaited(search());
return;
}
filter.value = filter.value.copyWith(
date: SearchDateFilter(
takenAfter: date.start,
takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)),
),
);
// If date range is less than 24 hours, set the end date to the end of the day
if (date.end.difference(date.start).inHours < 24) {
dateRangeCurrentFilterWidget.value = Text(
DateFormat.yMMMd().format(date.start.toLocal()),
style: context.textTheme.labelLarge,
);
datePicked(null);
} else {
dateRangeCurrentFilterWidget.value = Text(
'search_filter_date_interval'.t(
context: context,
args: {
"start": DateFormat.yMMMd().format(date.start.toLocal()),
"end": DateFormat.yMMMd().format(date.end.toLocal()),
datePicked(CustomDateFilter.fromRange(date));
}
}
showQuickDatePicker() {
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: "pick_date_range".tr(),
expanded: true,
onClear: () => datePicked(null),
child: QuickDatePicker(
currentInput: dateInputFilter.value,
onRequestPicker: () {
context.pop();
showDatePicker();
},
onSelect: (date) {
context.pop();
datePicked(date);
},
),
style: context.textTheme.labelLarge,
);
}
unawaited(search());
),
);
}
// MEDIA PICKER
@@ -589,7 +618,7 @@ class DriftSearchPage extends HookConsumerWidget {
),
SearchFilterChip(
icon: Icons.date_range_outlined,
onTap: showDatePicker,
onTap: showQuickDatePicker,
label: 'search_filter_date'.t(context: context),
currentFilter: dateRangeCurrentFilterWidget.value,
),
@@ -0,0 +1,191 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
enum AddToMenuItem { album, archive, unarchive, lockedFolder }
class AddActionButton extends ConsumerWidget {
const AddActionButton({super.key});
Future<void> _showAddOptions(BuildContext context, WidgetRef ref) async {
final asset = ref.read(currentAssetNotifier);
if (asset == null) return;
final user = ref.read(currentUserProvider);
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
final isInLockedView = ref.watch(inLockedViewProvider);
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
final hasRemote = asset is RemoteAsset;
final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived;
final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived;
final menuItemHeight = 30.0;
final List<PopupMenuEntry<AddToMenuItem>> items = [
PopupMenuItem(
enabled: false,
textStyle: context.textTheme.labelMedium,
height: 40,
child: Text("add_to_bottom_bar".tr()),
),
PopupMenuItem(
height: menuItemHeight,
value: AddToMenuItem.album,
child: ListTile(leading: const Icon(Icons.photo_album_outlined), title: Text("album".tr())),
),
const PopupMenuDivider(),
PopupMenuItem(enabled: false, textStyle: context.textTheme.labelMedium, height: 40, child: Text("move_to".tr())),
if (isOwner) ...[
if (showArchive)
PopupMenuItem(
height: menuItemHeight,
value: AddToMenuItem.archive,
child: ListTile(leading: const Icon(Icons.archive_outlined), title: Text("archive".tr())),
),
if (showUnarchive)
PopupMenuItem(
height: menuItemHeight,
value: AddToMenuItem.unarchive,
child: ListTile(leading: const Icon(Icons.unarchive_outlined), title: Text("unarchive".tr())),
),
PopupMenuItem(
height: menuItemHeight,
value: AddToMenuItem.lockedFolder,
child: ListTile(leading: const Icon(Icons.lock_outline), title: Text("locked_folder".tr())),
),
],
];
final AddToMenuItem? selected = await showMenu<AddToMenuItem>(
context: context,
color: context.themeData.scaffoldBackgroundColor,
position: _menuPosition(context),
items: items,
);
if (selected == null) {
return;
}
switch (selected) {
case AddToMenuItem.album:
_openAlbumSelector(context, ref);
break;
case AddToMenuItem.archive:
await performArchiveAction(context, ref, source: ActionSource.viewer);
break;
case AddToMenuItem.unarchive:
await performUnArchiveAction(context, ref, source: ActionSource.viewer);
break;
case AddToMenuItem.lockedFolder:
await performMoveToLockFolderAction(context, ref, source: ActionSource.viewer);
break;
}
}
RelativeRect _menuPosition(BuildContext context) {
final renderObject = context.findRenderObject();
if (renderObject is! RenderBox) {
return RelativeRect.fill;
}
final size = renderObject.size;
final position = renderObject.localToGlobal(Offset.zero);
return RelativeRect.fromLTRB(position.dx, position.dy - size.height - 200, position.dx + size.width, position.dy);
}
void _openAlbumSelector(BuildContext context, WidgetRef ref) {
final currentAsset = ref.read(currentAssetNotifier);
if (currentAsset == null) {
ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error);
return;
}
final List<Widget> slivers = [
AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(context, ref, album)),
];
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (_) {
return BaseBottomSheet(
actions: const [],
slivers: slivers,
initialChildSize: 0.6,
minChildSize: 0.3,
maxChildSize: 0.95,
expand: false,
backgroundColor: context.isDarkTheme ? Colors.black : Colors.white,
);
},
);
}
Future<void> _addCurrentAssetToAlbum(BuildContext context, WidgetRef ref, RemoteAlbum album) async {
final latest = ref.read(currentAssetNotifier);
if (latest == null) {
ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error);
return;
}
final addedCount = await ref.read(remoteAlbumProvider.notifier).addAssets(album.id, [latest.remoteId!]);
if (!context.mounted) {
return;
}
if (addedCount == 0) {
ImmichToast.show(
context: context,
msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}),
);
} else {
ImmichToast.show(
context: context,
msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}),
);
}
if (!context.mounted) {
return;
}
await Navigator.of(context).maybePop();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final asset = ref.watch(currentAssetNotifier);
if (asset == null) {
return const SizedBox.shrink();
}
return Builder(
builder: (buttonContext) {
return BaseActionButton(
iconData: Icons.add,
label: "add_to_bottom_bar".tr(),
onPressed: () => _showAddOptions(buttonContext, ref),
);
},
);
}
}
@@ -10,33 +10,36 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
// used to allow performing archive action from different sources (without duplicating code)
Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
if (!context.mounted) return;
final result = await ref.read(actionProvider.notifier).archive(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
class ArchiveActionButton extends ConsumerWidget {
final ActionSource source;
const ArchiveActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).archive(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performArchiveAction(context, ref, source: source);
}
@override
@@ -10,36 +10,39 @@ import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
// Reusable helper: move to locked folder from any source (e.g called from menu)
Future<void> performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
if (!context.mounted) return;
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'move_to_lock_folder_action_prompt'.t(
context: context,
args: {'count': result.count.toString()},
);
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
class MoveToLockFolderActionButton extends ConsumerWidget {
final ActionSource source;
const MoveToLockFolderActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'move_to_lock_folder_action_prompt'.t(
context: context,
args: {'count': result.count.toString()},
);
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performMoveToLockFolderAction(context, ref, source: source);
}
@override
@@ -1,3 +1,5 @@
// dart
// File: `lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart`
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -7,30 +9,39 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
// used to allow performing unarchive action from different sources (without duplicating code)
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
if (!context.mounted) return;
final result = await ref.read(actionProvider.notifier).unArchive(source);
ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
class UnArchiveActionButton extends ConsumerWidget {
final ActionSource source;
const UnArchiveActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result = await ref.read(actionProvider.notifier).unArchive(source);
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
await performUnArchiveAction(context, ref, source: source);
}
@override
@@ -3,14 +3,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/widgets/activities/comment_bubble.dart';
import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/providers/activity.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/activities/activity_tile.dart';
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
class ActivitiesBottomSheet extends HookConsumerWidget {
final DraggableScrollableController controller;
@@ -28,7 +26,6 @@ class ActivitiesBottomSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final album = ref.watch(currentRemoteAlbumProvider)!;
final asset = ref.watch(currentAssetNotifier) as RemoteAsset?;
final user = ref.watch(currentUserProvider);
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
final activities = ref.watch(albumActivityProvider(album.id, asset?.id));
@@ -47,16 +44,9 @@ class ActivitiesBottomSheet extends HookConsumerWidget {
return const SizedBox.shrink();
}
final activity = data[data.length - 1 - index];
final canDelete = activity.user.id == user?.id || album.ownerId == user?.id;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 1),
child: DismissibleActivity(
activity.id,
ActivityTile(activity, isBottomSheet: true),
onDismiss: canDelete
? (activityId) async => await activityNotifier.removeActivity(activity.id)
: null,
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: CommentBubble(activity: activity, isAssetActivity: true),
);
}, childCount: data.length + 1),
);
@@ -627,10 +627,10 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
// Rebuild the widget when the asset viewer state changes
// Using multiple selectors to avoid unnecessary rebuilds for other state changes
ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
ref.watch(assetViewerProvider.select((s) => s.showingControls));
ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity));
ref.watch(assetViewerProvider.select((s) => s.stackIndex));
ref.watch(isPlayingMotionVideoProvider);
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
// Listen for casting changes and send initial asset to the cast provider
ref.listen(castProvider.select((value) => value.isCasting), (_, isCasting) async {
@@ -663,7 +663,14 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
appBar: const ViewerTopAppBar(),
extendBody: true,
extendBodyBehindAppBar: true,
floatingActionButton: const DownloadStatusFloatingButton(),
floatingActionButton: IgnorePointer(
ignoring: !showingControls,
child: AnimatedOpacity(
opacity: showingControls ? 1.0 : 0.0,
duration: Durations.short2,
child: const DownloadStatusFloatingButton(),
),
),
body: Stack(
children: [
PhotoViewGallery.builder(
@@ -127,13 +127,18 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
if (exifInfo == null) {
return null;
}
final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
final exposureTime = exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null;
final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
final iso = exifInfo.iso != null ? 'ISO ${exifInfo.iso}' : null;
return [exposureTime, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
}
return [fNumber, exposureTime, focalLength, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
String? _getLensInfoSubtitle(ExifInfo? exifInfo) {
if (exifInfo == null) {
return null;
}
final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
}
Future<void> _editDateTime(BuildContext context, WidgetRef ref) async {
@@ -141,20 +146,20 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
}
Widget _buildAppearsInList(WidgetRef ref, BuildContext context) {
final aseet = ref.watch(currentAssetNotifier);
if (aseet == null) {
final asset = ref.watch(currentAssetNotifier);
if (asset == null) {
return const SizedBox.shrink();
}
if (!aseet.hasRemote) {
if (!asset.hasRemote) {
return const SizedBox.shrink();
}
String? remoteAssetId;
if (aseet is RemoteAsset) {
remoteAssetId = aseet.id;
} else if (aseet is LocalAsset) {
remoteAssetId = aseet.remoteAssetId;
if (asset is RemoteAsset) {
remoteAssetId = asset.id;
} else if (asset is LocalAsset) {
remoteAssetId = asset.remoteAssetId;
}
if (remoteAssetId == null) {
@@ -217,6 +222,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
final cameraTitle = _getCameraInfoTitle(exifInfo);
final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null;
final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : null);
// Build file info tile based on asset type
@@ -287,12 +293,23 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
_SheetTile(
title: cameraTitle,
titleStyle: context.textTheme.labelLarge,
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
subtitle: _getCameraInfoSubtitle(exifInfo),
subtitleStyle: context.textTheme.bodyMedium?.copyWith(
color: context.textTheme.bodyMedium?.color?.withAlpha(155),
),
),
// Lens info
if (lensTitle != null)
_SheetTile(
title: lensTitle,
titleStyle: context.textTheme.labelLarge,
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
subtitle: _getLensInfoSubtitle(exifInfo),
subtitleStyle: context.textTheme.bodyMedium?.copyWith(
color: context.textTheme.bodyMedium?.color?.withAlpha(155),
),
),
// Appears in (Albums)
_buildAppearsInList(ref, context),
// padding at the bottom to avoid cut-off
@@ -86,13 +86,9 @@ class _BaseDraggableScrollableSheetState extends ConsumerState<BaseBottomSheet>
SliverToBoxAdapter(
child: Column(
children: [
SizedBox(
height: 115,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: widget.actions,
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: widget.actions),
),
const Divider(indent: 16, endIndent: 16),
const SizedBox(height: 16),
@@ -0,0 +1,208 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
sealed class DateFilterInputModel {
DateTimeRange<DateTime> asDateTimeRange();
String asHumanReadable(BuildContext context) {
// General implementation for arbitrary date and time ranges
// If date range is less than 24 hours, set the end date to the end of the day
final date = asDateTimeRange();
if (date.end.difference(date.start).inHours < 24) {
return DateFormat.yMMMd().format(date.start.toLocal());
} else {
return 'search_filter_date_interval'.t(
context: context,
args: {
"start": DateFormat.yMMMd().format(date.start.toLocal()),
"end": DateFormat.yMMMd().format(date.end.toLocal()),
},
);
}
}
}
class RecentMonthRangeFilter extends DateFilterInputModel {
final int monthDelta;
RecentMonthRangeFilter(this.monthDelta);
@override
DateTimeRange<DateTime> asDateTimeRange() {
final now = DateTime.now();
// Note that DateTime's constructor properly handles month overflow.
final from = DateTime(now.year, now.month - monthDelta, 1);
return DateTimeRange<DateTime>(start: from, end: now);
}
@override
String asHumanReadable(BuildContext context) {
return 'last_months'.t(context: context, args: {"count": monthDelta.toString()});
}
}
class YearFilter extends DateFilterInputModel {
final int year;
YearFilter(this.year);
@override
DateTimeRange<DateTime> asDateTimeRange() {
final now = DateTime.now();
final from = DateTime(year, 1, 1);
if (now.year == year) {
// To not go beyond today if the user picks the current year
return DateTimeRange<DateTime>(start: from, end: now);
}
final to = DateTime(year, 12, 31, 23, 59, 59);
return DateTimeRange<DateTime>(start: from, end: to);
}
@override
String asHumanReadable(BuildContext context) {
return 'in_year'.tr(namedArgs: {"year": year.toString()});
}
}
class CustomDateFilter extends DateFilterInputModel {
final DateTime start;
final DateTime end;
CustomDateFilter(this.start, this.end);
factory CustomDateFilter.fromRange(DateTimeRange<DateTime> range) {
return CustomDateFilter(range.start, range.end);
}
@override
DateTimeRange<DateTime> asDateTimeRange() {
return DateTimeRange<DateTime>(start: start, end: end);
}
}
enum _QuickPickerType { last1Month, last3Months, last9Months, year, custom }
class QuickDatePicker extends HookWidget {
QuickDatePicker({super.key, required this.currentInput, required this.onSelect, required this.onRequestPicker})
: _selection = _selectionFromModel(currentInput),
_initialYear = _initialYearFromModel(currentInput);
final Function() onRequestPicker;
final Function(DateFilterInputModel range) onSelect;
final DateFilterInputModel? currentInput;
final _QuickPickerType? _selection;
final int _initialYear;
// Generate a list of recent years from 2000 to the current year (including the current one)
final List<int> _recentYears = List.generate(1 + DateTime.now().year - 2000, (index) {
return index + 2000;
});
static int _initialYearFromModel(DateFilterInputModel? model) {
return model?.asDateTimeRange().start.year ?? DateTime.now().year;
}
static _QuickPickerType? _selectionFromModel(DateFilterInputModel? model) {
if (model is RecentMonthRangeFilter) {
return switch (model.monthDelta) {
1 => _QuickPickerType.last1Month,
3 => _QuickPickerType.last3Months,
9 => _QuickPickerType.last9Months,
_ => _QuickPickerType.custom,
};
} else if (model is YearFilter) {
return _QuickPickerType.year;
} else if (model is CustomDateFilter) {
return _QuickPickerType.custom;
}
return null;
}
Text _monthLabel(BuildContext context, int monthsFromNow) =>
const Text('last_months').t(context: context, args: {"count": monthsFromNow.toString()});
Widget _yearPicker(BuildContext context) {
final size = MediaQuery.of(context).size;
return Row(
children: [
const Text("in_year_selector").tr(),
const SizedBox(width: 15),
Expanded(
child: DropdownMenu(
initialSelection: _initialYear,
menuStyle: MenuStyle(maximumSize: WidgetStateProperty.all(Size(size.width, size.height * 0.5))),
dropdownMenuEntries: _recentYears.map((e) => DropdownMenuEntry(value: e, label: e.toString())).toList(),
onSelected: (year) {
if (year == null) return;
onSelect(YearFilter(year));
},
),
),
],
);
}
// We want the exact date picker to always be selectable.
// Even if it's already toggled it should always open the full date picker, RadioListTiles don't do that by default
// so we wrap it in a InkWell
Widget _exactPicker(BuildContext context) {
final hasPreviousInput = currentInput != null && currentInput is CustomDateFilter;
return InkWell(
onTap: onRequestPicker,
child: IgnorePointer(
ignoring: true,
child: RadioListTile(
title: const Text('pick_custom_range').tr(),
subtitle: hasPreviousInput ? Text(currentInput!.asHumanReadable(context)) : null,
secondary: hasPreviousInput ? const Icon(Icons.edit) : null,
value: _QuickPickerType.custom,
toggleable: true,
),
),
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Scrollbar(
// Depending on the screen size the last option might get cut off
// Add a clear visual cue that there are more options when scrolling
// When the screen size is large enough the scrollbar is hidden automatically
trackVisibility: true,
thumbVisibility: true,
child: SingleChildScrollView(
child: RadioGroup(
onChanged: (value) {
if (value == null) return;
final _ = switch (value) {
_QuickPickerType.custom => onRequestPicker(),
_QuickPickerType.last1Month => onSelect(RecentMonthRangeFilter(1)),
_QuickPickerType.last3Months => onSelect(RecentMonthRangeFilter(3)),
_QuickPickerType.last9Months => onSelect(RecentMonthRangeFilter(9)),
// When a year is selected the combobox triggers onSelect() on its own.
// Here we handle the radio button being selected which can only ever be the initial year
_QuickPickerType.year => onSelect(YearFilter(_initialYear)),
};
},
groupValue: _selection,
child: Column(
children: [
RadioListTile(title: _monthLabel(context, 1), value: _QuickPickerType.last1Month, toggleable: true),
RadioListTile(title: _monthLabel(context, 3), value: _QuickPickerType.last3Months, toggleable: true),
RadioListTile(title: _monthLabel(context, 9), value: _QuickPickerType.last9Months, toggleable: true),
RadioListTile(title: _yearPicker(context), value: _QuickPickerType.year, toggleable: true),
_exactPicker(context),
],
),
),
),
),
);
}
}
+44 -9
View File
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/activity_service.provider.dart';
import 'package:immich_mobile/providers/activity_statistics.provider.dart';
@@ -16,13 +17,20 @@ class AlbumActivity extends _$AlbumActivity {
Future<void> removeActivity(String id) async {
if (await ref.watch(activityServiceProvider).removeActivity(id)) {
final activities = state.valueOrNull ?? [];
final removedActivity = activities.firstWhere((a) => a.id == id);
activities.remove(removedActivity);
state = AsyncData(activities);
// Decrement activity count only for comments
final removedActivity = _removeFromState(id);
if (removedActivity == null) {
return;
}
if (assetId != null) {
ref.read(albumActivityProvider(albumId).notifier)._removeFromState(id);
}
if (removedActivity.type == ActivityType.comment) {
ref.watch(activityStatisticsProvider(albumId, assetId).notifier).removeActivity();
if (assetId != null) {
ref.watch(activityStatisticsProvider(albumId).notifier).removeActivity();
}
}
}
}
@@ -30,8 +38,10 @@ class AlbumActivity extends _$AlbumActivity {
Future<void> addLike() async {
final activity = await ref.watch(activityServiceProvider).addActivity(albumId, ActivityType.like, assetId: assetId);
if (activity.hasValue) {
final activities = state.asData?.value ?? [];
state = AsyncData([...activities, activity.requireValue]);
_addToState(activity.requireValue);
if (assetId != null) {
ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue);
}
}
}
@@ -41,8 +51,10 @@ class AlbumActivity extends _$AlbumActivity {
.addActivity(albumId, ActivityType.comment, assetId: assetId, comment: comment);
if (activity.hasValue) {
final activities = state.valueOrNull ?? [];
state = AsyncData([...activities, activity.requireValue]);
_addToState(activity.requireValue);
if (assetId != null) {
ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue);
}
ref.watch(activityStatisticsProvider(albumId, assetId).notifier).addActivity();
// The previous addActivity call would increase the count of an asset if assetId != null
// To also increase the activity count of the album, calling it once again with assetId set to null
@@ -51,6 +63,29 @@ class AlbumActivity extends _$AlbumActivity {
}
}
}
void _addToState(Activity activity) {
final activities = state.valueOrNull ?? [];
if (activities.any((a) => a.id == activity.id)) {
return;
}
state = AsyncData([...activities, activity]);
}
Activity? _removeFromState(String id) {
final activities = state.valueOrNull;
if (activities == null) {
return null;
}
final activity = activities.firstWhereOrNull((a) => a.id == id);
if (activity == null) {
return null;
}
final updated = [...activities]..remove(activity);
state = AsyncData(updated);
return activity;
}
}
/// Mock class for testing
+1 -1
View File
@@ -6,7 +6,7 @@ part of 'activity.provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$albumActivityHash() => r'3b0d7acee4d41c84b3f220784c3b904c83f836e6';
String _$albumActivityHash() => r'154e8ae98da3efc142369eae46d4005468fd67da';
/// Copied from Dart SDK
class _SystemHash {
@@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/asset.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
@@ -13,6 +14,10 @@ final remoteAssetRepositoryProvider = Provider<RemoteAssetRepository>(
(ref) => RemoteAssetRepository(ref.watch(driftProvider)),
);
final trashedLocalAssetRepository = Provider<DriftTrashedLocalAssetRepository>(
(ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)),
);
final assetServiceProvider = Provider(
(ref) => AssetService(
remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider),
@@ -10,11 +10,17 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
final syncStreamServiceProvider = Provider(
(ref) => SyncStreamService(
syncApiRepository: ref.watch(syncApiRepositoryProvider),
syncStreamRepository: ref.watch(syncStreamRepositoryProvider),
localAssetRepository: ref.watch(localAssetRepository),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
storageRepository: ref.watch(storageRepositoryProvider),
cancelChecker: ref.watch(cancellationProvider),
),
);
@@ -26,6 +32,9 @@ final syncStreamRepositoryProvider = Provider((ref) => SyncStreamRepository(ref.
final localSyncServiceProvider = Provider(
(ref) => LocalSyncService(
localAlbumRepository: ref.watch(localAlbumRepository),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
localFilesManager: ref.watch(localFilesManagerRepositoryProvider),
storageRepository: ref.watch(storageRepositoryProvider),
nativeSyncApi: ref.watch(nativeSyncApiProvider),
),
);
@@ -35,5 +44,6 @@ final hashServiceProvider = Provider(
localAlbumRepository: ref.watch(localAlbumRepository),
localAssetRepository: ref.watch(localAssetRepository),
nativeSyncApi: ref.watch(nativeSyncApiProvider),
trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository),
),
);
@@ -0,0 +1,12 @@
import 'package:async/async.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
typedef TrashedAssetsCount = ({int total, int hashed});
final trashedAssetsCountProvider = StreamProvider<TrashedAssetsCount>((ref) {
final repo = ref.watch(trashedLocalAssetRepository);
final total$ = repo.watchCount();
final hashed$ = repo.watchHashedCount();
return StreamZip<int>([total$, hashed$]).map((values) => (total: values[0], hashed: values[1]));
});
@@ -67,7 +67,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
return;
}
if (clientVersion < serverVersion) {
if (clientVersion < serverVersion && clientVersion.differenceType(serverVersion) != SemVerType.patch) {
state = state.copyWith(versionStatus: VersionStatus.clientOutOfDate);
return;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/services/trash.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/services/trash.service.dart';
import 'package:logging/logging.dart';
class TrashNotifier extends StateNotifier<bool> {
@@ -89,9 +89,16 @@ class AssetMediaRepository {
return null;
}
// titleAsync gets the correct original filename for some assets on iOS
// otherwise using the `entity.title` would return a random GUID
return await entity.titleAsync;
try {
// titleAsync gets the correct original filename for some assets on iOS
// otherwise using the `entity.title` would return a random GUID
final originalFilename = await entity.titleAsync;
// treat empty filename as missing
return originalFilename.isNotEmpty ? originalFilename : null;
} catch (e) {
_log.warning("Failed to get original filename for asset: $id. Error: $e");
return null;
}
}
// TODO: make this more efficient
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
final folderApiRepositoryProvider = Provider((ref) => FolderApiRepository(ref.watch(apiServiceProvider).viewApi));
class FolderApiRepository extends ApiRepository {
final ViewApi _api;
final ViewsApi _api;
final Logger _log = Logger("FolderApiRepository");
FolderApiRepository(this._api);
@@ -1,13 +1,16 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/services/local_files_manager.service.dart';
import 'package:logging/logging.dart';
final localFilesManagerRepositoryProvider = Provider(
(ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)),
);
class LocalFilesManagerRepository {
const LocalFilesManagerRepository(this._service);
LocalFilesManagerRepository(this._service);
final Logger _logger = Logger('SyncStreamService');
final LocalFilesManagerService _service;
Future<bool> moveToTrash(List<String> mediaUrls) async {
@@ -21,4 +24,26 @@ class LocalFilesManagerRepository {
Future<bool> requestManageMediaPermission() async {
return await _service.requestManageMediaPermission();
}
Future<bool> hasManageMediaPermission() async {
return await _service.hasManageMediaPermission();
}
Future<bool> manageMediaPermission() async {
return await _service.manageMediaPermission();
}
Future<List<String>> restoreAssetsFromTrash(Iterable<LocalAsset> assets) async {
final restoredIds = <String>[];
for (final asset in assets) {
_logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}");
try {
await _service.restoreFromTrashById(asset.id, asset.type.index);
restoredIds.add(asset.id);
} catch (e) {
_logger.warning("Restoring failure: $e");
}
}
return restoredIds;
}
}
+1
View File
@@ -313,6 +313,7 @@ class AppRouter extends RootStackRouter {
settings: page,
pageBuilder: (_, __, ___) => child,
opaque: false,
transitionsBuilder: TransitionsBuilders.fadeIn,
),
),
),
+4 -4
View File
@@ -17,7 +17,7 @@ class ApiService implements Authentication {
late UsersApi usersApi;
late AuthenticationApi authenticationApi;
late OAuthApi oAuthApi;
late AuthenticationApi oAuthApi;
late AlbumsApi albumsApi;
late AssetsApi assetsApi;
late SearchApi searchApi;
@@ -32,7 +32,7 @@ class ApiService implements Authentication {
late DownloadApi downloadApi;
late TrashApi trashApi;
late StacksApi stacksApi;
late ViewApi viewApi;
late ViewsApi viewApi;
late MemoriesApi memoriesApi;
late SessionsApi sessionsApi;
@@ -56,7 +56,7 @@ class ApiService implements Authentication {
}
usersApi = UsersApi(_apiClient);
authenticationApi = AuthenticationApi(_apiClient);
oAuthApi = OAuthApi(_apiClient);
oAuthApi = AuthenticationApi(_apiClient);
albumsApi = AlbumsApi(_apiClient);
assetsApi = AssetsApi(_apiClient);
serverInfoApi = ServerApi(_apiClient);
@@ -71,7 +71,7 @@ class ApiService implements Authentication {
downloadApi = DownloadApi(_apiClient);
trashApi = TrashApi(_apiClient);
stacksApi = StacksApi(_apiClient);
viewApi = ViewApi(_apiClient);
viewApi = ViewsApi(_apiClient);
memoriesApi = MemoriesApi(_apiClient);
sessionsApi = SessionsApi(_apiClient);
}
@@ -77,6 +77,7 @@ class DeepLinkService {
"memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''),
"asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref),
"album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''),
"activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''),
_ => null,
};
@@ -185,4 +186,18 @@ class DeepLinkService {
return AlbumViewerRoute(albumId: album.id);
}
}
Future<PageRouteInfo?> _buildActivityDeepLink(String albumId) async {
if (Store.isBetaTimelineEnabled == false) {
return null;
}
final album = await _betaRemoteAlbumService.get(albumId);
if (album == null || album.isActivityEnabled == false) {
return null;
}
return DriftActivitiesRoute(album: album);
}
}
@@ -6,6 +6,7 @@ final localFileManagerServiceProvider = Provider<LocalFilesManagerService>((ref)
class LocalFilesManagerService {
const LocalFilesManagerService();
static final Logger _logger = Logger('LocalFilesManager');
static const MethodChannel _channel = MethodChannel('file_trash');
@@ -27,6 +28,15 @@ class LocalFilesManagerService {
}
}
Future<bool> restoreFromTrashById(String mediaId, int type) async {
try {
return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type});
} catch (e, s) {
_logger.warning('Error restore file from trash by Id', e, s);
return false;
}
}
Future<bool> requestManageMediaPermission() async {
try {
return await _channel.invokeMethod('requestManageMediaPermission');
@@ -35,4 +45,22 @@ class LocalFilesManagerService {
return false;
}
}
Future<bool> hasManageMediaPermission() async {
try {
return await _channel.invokeMethod('hasManageMediaPermission');
} catch (e, s) {
_logger.warning('Error requesting manage media permission state', e, s);
return false;
}
}
Future<bool> manageMediaPermission() async {
try {
return await _channel.invokeMethod('manageMediaPermission');
} catch (e, s) {
_logger.warning('Error requesting manage media permission settings', e, s);
return false;
}
}
}
+5
View File
@@ -51,6 +51,11 @@ dynamic upgradeDto(dynamic value, String targetType) {
addDefault(value, 'ocr', false);
}
break;
case 'MemoriesResponse':
if (value is Map) {
addDefault(value, 'duration', 5);
}
break;
}
}
+29 -1
View File
@@ -1,3 +1,5 @@
enum SemVerType { major, minor, patch }
class SemVer {
final int major;
final int minor;
@@ -15,8 +17,20 @@ class SemVer {
}
factory SemVer.fromString(String version) {
if (version.toLowerCase().startsWith("v")) {
version = version.substring(1);
}
final parts = version.split("-")[0].split('.');
return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2]));
if (parts.length != 3) {
throw FormatException('Invalid semantic version string: $version');
}
try {
return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2]));
} catch (e) {
throw FormatException('Invalid semantic version string: $version');
}
}
bool operator >(SemVer other) {
@@ -54,6 +68,20 @@ class SemVer {
return other is SemVer && other.major == major && other.minor == minor && other.patch == patch;
}
SemVerType? differenceType(SemVer other) {
if (major != other.major) {
return SemVerType.major;
}
if (minor != other.minor) {
return SemVerType.minor;
}
if (patch != other.patch) {
return SemVerType.patch;
}
return null;
}
@override
int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode;
}
@@ -0,0 +1,143 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/activity.provider.dart';
import 'package:immich_mobile/providers/activity_service.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
class CommentBubble extends ConsumerWidget {
final Activity activity;
final bool isAssetActivity;
const CommentBubble({super.key, required this.activity, this.isAssetActivity = false});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(currentUserProvider);
final album = ref.watch(currentRemoteAlbumProvider)!;
final isOwn = activity.user.id == user?.id;
final canDelete = isOwn || album.ownerId == user?.id;
final showThumbnail = !isAssetActivity && activity.assetId != null && activity.assetId!.isNotEmpty;
final isLike = activity.type == ActivityType.like;
final bgColor = isOwn ? context.colorScheme.primaryContainer : context.colorScheme.surfaceContainer;
final activityNotifier = ref.read(
albumActivityProvider(album.id, isAssetActivity ? activity.assetId : null).notifier,
);
Future<void> openAssetViewer() async {
final activityService = ref.read(activityServiceProvider);
final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref);
if (route != null) await context.pushRoute(route);
}
// avatar (hidden for own messages)
Widget avatar = const SizedBox.shrink();
if (!isOwn) {
avatar = UserCircleAvatar(user: activity.user, size: 28, radius: 14);
}
// Thumbnail with tappable behavior and optional heart overlay
Widget? thumbnail;
if (showThumbnail) {
thumbnail = ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150),
child: Stack(
children: [
GestureDetector(
onTap: openAssetViewer,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(10)),
child: Image(
image: ImmichRemoteThumbnailProvider(assetId: activity.assetId!),
fit: BoxFit.cover,
),
),
),
if (isLike)
Positioned(
right: 6,
bottom: 6,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
),
),
],
),
);
}
// Likes widget
Widget? likes;
if (isLike && !showThumbnail) {
likes = Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
);
}
// Comment bubble, comment-only
Widget? commentBubble;
if (activity.comment != null && activity.comment!.isNotEmpty) {
commentBubble = ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: bgColor, borderRadius: const BorderRadius.all(Radius.circular(12))),
child: Text(
activity.comment ?? '',
style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurface),
),
),
);
}
// Combined content widgets
final List<Widget> contentChildren = [thumbnail, likes, commentBubble].whereType<Widget>().toList();
return DismissibleActivity(
onDismiss: canDelete ? (id) async => await activityNotifier.removeActivity(id) : null,
activity.id,
Align(
alignment: isOwn ? Alignment.centerRight : Alignment.centerLeft,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.86),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isOwn) ...[avatar, const SizedBox(width: 8)],
// Content column
Column(
crossAxisAlignment: isOwn ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
...contentChildren.map((w) => Padding(padding: const EdgeInsets.only(bottom: 8.0), child: w)),
Text(
'${activity.user.name}${activity.createdAt.timeAgo()}',
style: context.textTheme.labelMedium?.copyWith(
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
),
if (isOwn) const SizedBox(width: 8),
],
),
),
),
),
);
}
}
@@ -21,6 +21,7 @@ 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/websocket.provider.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/provider_utils.dart';
import 'package:immich_mobile/utils/url_helper.dart';
@@ -177,6 +178,55 @@ class LoginForm extends HookConsumerWidget {
}
}
getManageMediaPermission() async {
final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission();
if (!hasPermission) {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 5,
title: Text(
'manage_media_access_title',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor),
).tr(),
content: SingleChildScrollView(
child: ListBody(
children: [
const Text('manage_media_access_subtitle', style: TextStyle(fontSize: 14)).tr(),
const SizedBox(height: 4),
const Text('manage_media_access_rationale', style: TextStyle(fontSize: 12)).tr(),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'cancel'.tr(),
style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor),
),
),
TextButton(
onPressed: () {
ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
Navigator.of(context).pop();
},
child: Text(
'manage_media_access_settings'.tr(),
style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor),
),
),
],
);
},
);
}
}
bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false);
login() async {
TextInput.finishAutofillContext();
@@ -194,6 +244,9 @@ class LoginForm extends HookConsumerWidget {
final isBeta = Store.isBetaTimelineEnabled;
if (isBeta) {
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
if (isSyncRemoteDeletionsMode()) {
await getManageMediaPermission();
}
unawaited(handleSyncFlow());
ref.read(websocketProvider.notifier).connect();
unawaited(context.replaceRoute(const TabShellRoute()));
@@ -293,6 +346,9 @@ class LoginForm extends HookConsumerWidget {
}
if (isBeta) {
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
if (isSyncRemoteDeletionsMode()) {
await getManageMediaPermission();
}
unawaited(handleSyncFlow());
unawaited(context.replaceRoute(const TabShellRoute()));
return;
@@ -6,7 +6,7 @@ class FilterBottomSheetScaffold extends StatelessWidget {
const FilterBottomSheetScaffold({
super.key,
required this.child,
required this.onSearch,
this.onSearch,
required this.onClear,
required this.title,
this.expanded,
@@ -15,7 +15,7 @@ class FilterBottomSheetScaffold extends StatelessWidget {
final bool? expanded;
final String title;
final Widget child;
final Function() onSearch;
final Function()? onSearch;
final Function() onClear;
@override
@@ -48,15 +48,16 @@ class FilterBottomSheetScaffold extends StatelessWidget {
},
child: const Text('clear').tr(),
),
const SizedBox(width: 8),
ElevatedButton(
key: const Key('search_filter_apply'),
onPressed: () {
onSearch();
context.pop();
},
child: const Text('search_filter_apply').tr(),
),
if (onSearch != null) const SizedBox(width: 8),
if (onSearch != null)
ElevatedButton(
key: const Key('search_filter_apply'),
onPressed: () {
onSearch!();
context.pop();
},
child: const Text('search_filter_apply').tr(),
),
],
),
),
@@ -8,8 +8,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@@ -17,6 +17,7 @@ import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
import 'package:immich_mobile/widgets/settings/local_storage_settings.dart';
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
@@ -25,12 +26,15 @@ import 'package:logging/logging.dart';
class AdvancedSettings extends HookConsumerWidget {
const AdvancedSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
bool isLoggedIn = ref.read(currentUserProvider) != null;
final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
final isManageMediaSupported = useState(false);
final manageMediaAndroidPermission = useState(false);
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
final allowSelfSignedSSLCert = useAppSettingsState(AppSettingsEnum.allowSelfSignedSSLCert);
@@ -51,6 +55,18 @@ class AdvancedSettings extends HookConsumerWidget {
return false;
}
useEffect(() {
() async {
isManageMediaSupported.value = await checkAndroidVersion();
if (isManageMediaSupported.value) {
manageMediaAndroidPermission.value = await ref
.read(localFilesManagerRepositoryProvider)
.hasManageMediaPermission();
}
}();
return null;
}, []);
final advancedSettings = [
SettingsSwitchListTile(
enabled: true,
@@ -58,11 +74,10 @@ class AdvancedSettings extends HookConsumerWidget {
title: "advanced_settings_troubleshooting_title".tr(),
subtitle: "advanced_settings_troubleshooting_subtitle".tr(),
),
FutureBuilder<bool>(
future: checkAndroidVersion(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return SettingsSwitchListTile(
if (isManageMediaSupported.value)
Column(
children: [
SettingsSwitchListTile(
enabled: true,
valueNotifier: manageLocalMediaAndroid,
title: "advanced_settings_sync_remote_deletions_title".tr(),
@@ -71,14 +86,24 @@ class AdvancedSettings extends HookConsumerWidget {
if (value) {
final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
manageLocalMediaAndroid.value = result;
manageMediaAndroidPermission.value = result;
}
},
);
} else {
return const SizedBox.shrink();
}
},
),
),
SettingsActionTile(
title: "manage_media_access_title".tr(),
statusText: manageMediaAndroidPermission.value ? "allowed".tr() : "not_allowed".tr(),
subtitle: "manage_media_access_rationale".tr(),
statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value
? const Color.fromARGB(255, 243, 188, 106)
: null,
onActionTap: () async {
final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission();
manageMediaAndroidPermission.value = result;
},
),
],
),
SettingsSliderListTile(
text: "advanced_settings_log_level_title".tr(namedArgs: {'level': logLevel}),
valueNotifier: levelId,
@@ -3,14 +3,18 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/platform_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart';
import 'package:immich_mobile/providers/sync_status.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
@@ -229,6 +233,7 @@ class _SyncStatsCounts extends ConsumerWidget {
final localAlbumService = ref.watch(localAlbumServiceProvider);
final remoteAlbumService = ref.watch(remoteAlbumServiceProvider);
final memoryService = ref.watch(driftMemoryServiceProvider);
final appSettingsService = ref.watch(appSettingsServiceProvider);
Future<List<dynamic>> loadCounts() async {
final assetCounts = assetService.getAssetCounts();
@@ -351,6 +356,44 @@ class _SyncStatsCounts extends ConsumerWidget {
],
),
),
// To be removed once the experimental feature is stable
if (CurrentPlatform.isAndroid &&
appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) ...[
_SectionHeaderText(text: "trash".t(context: context)),
Consumer(
builder: (context, ref, _) {
final counts = ref.watch(trashedAssetsCountProvider);
return counts.when(
data: (c) => Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 8.0,
children: [
Expanded(
child: EntitiyCountTile(
label: "local".t(context: context),
count: c.total,
icon: Icons.delete_outline,
),
),
Expanded(
child: EntitiyCountTile(
label: "hashed_assets".t(context: context),
count: c.hashed,
icon: Icons.tag,
),
),
],
),
),
loading: () => const CircularProgressIndicator(),
error: (e, st) => Text('Error: $e'),
);
},
),
],
],
);
},
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class SettingsActionTile extends StatelessWidget {
const SettingsActionTile({
super.key,
required this.title,
required this.subtitle,
required this.onActionTap,
this.statusText,
this.statusColor,
this.contentPadding,
this.titleStyle,
this.subtitleStyle,
});
final String title;
final String subtitle;
final String? statusText;
final Color? statusColor;
final VoidCallback onActionTap;
final EdgeInsets? contentPadding;
final TextStyle? titleStyle;
final TextStyle? subtitleStyle;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ListTile(
isThreeLine: true,
onTap: onActionTap,
titleAlignment: ListTileTitleAlignment.center,
title: Row(
children: [
Expanded(
child: Text(
title,
style: titleStyle ?? theme.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
),
if (statusText != null)
Padding(
padding: const EdgeInsets.only(left: 8),
child: Chip(
label: Text(
statusText!,
style: theme.textTheme.labelMedium?.copyWith(
color: statusColor ?? theme.colorScheme.onSurfaceVariant,
),
),
backgroundColor: theme.colorScheme.surface,
side: BorderSide(color: statusColor ?? theme.colorScheme.outlineVariant),
shape: StadiumBorder(side: BorderSide(color: statusColor ?? theme.colorScheme.outlineVariant)),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
),
),
],
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4.0, right: 18.0),
child: Text(
subtitle,
style: subtitleStyle ?? theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurfaceSecondary),
),
),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: theme.colorScheme.onSurfaceVariant),
contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8.0),
);
}
}
+185
View File
@@ -0,0 +1,185 @@
[tools]
flutter = "3.35.7"
[tools."github:CQLabs/homebrew-dcm"]
version = "1.30.0"
bin = "dcm"
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
[tasks."codegen:dart"]
alias = "codegen"
description = "Execute build_runner to auto-generate dart code"
sources = [
"pubspec.yaml",
"build.yaml",
"lib/**/*.dart",
"infrastructure/**/*.drift",
]
outputs = { auto = true }
run = "dart run build_runner build --delete-conflicting-outputs"
[tasks."codegen:pigeon"]
alias = "pigeon"
description = "Generate pigeon platform code"
depends = [
"pigeon:native-sync",
"pigeon:thumbnail",
"pigeon:background-worker",
"pigeon:background-worker-lock",
"pigeon:connectivity",
]
[tasks."codegen:translation"]
alias = "translation"
description = "Generate translations from i18n JSONs"
run = [
{ task = "//i18n:format-fix" },
{ tasks = [
"i18n:loader",
"i18n:keys",
] },
]
[tasks."codegen:app-icon"]
description = "Generate app icons"
run = "flutter pub run flutter_launcher_icons:main"
[tasks."codegen:splash"]
description = "Generate splash screen"
run = "flutter pub run flutter_native_splash:create"
[tasks.test]
description = "Run mobile tests"
run = "flutter test"
[tasks.lint]
description = "Analyze Dart code"
depends = ["analyze:dart", "analyze:dcm"]
[tasks."lint-fix"]
description = "Auto-fix Dart code"
depends = ["analyze:fix:dart", "analyze:fix:dcm"]
[tasks.format]
description = "Format Dart code"
run = "dart format --set-exit-if-changed $(find lib -name '*.dart' -not \\( -name '*.g.dart' -o -name '*.drift.dart' -o -name '*.gr.dart' \\))"
[tasks."build:android"]
description = "Build Android release"
run = "flutter build appbundle"
[tasks."drift:migration"]
alias = "migration"
description = "Generate database migrations"
run = "dart run drift_dev make-migrations"
# Internal tasks
[tasks."pigeon:native-sync"]
description = "Generate native sync API pigeon code"
hide = true
sources = ["pigeon/native_sync_api.dart"]
outputs = [
"lib/platform/native_sync_api.g.dart",
"ios/Runner/Sync/Messages.g.swift",
"android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt",
]
run = [
"dart run pigeon --input pigeon/native_sync_api.dart",
"dart format lib/platform/native_sync_api.g.dart",
]
[tasks."pigeon:thumbnail"]
description = "Generate thumbnail API pigeon code"
hide = true
sources = ["pigeon/thumbnail_api.dart"]
outputs = [
"lib/platform/thumbnail_api.g.dart",
"ios/Runner/Images/Thumbnails.g.swift",
"android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt",
]
run = [
"dart run pigeon --input pigeon/thumbnail_api.dart",
"dart format lib/platform/thumbnail_api.g.dart",
]
[tasks."pigeon:background-worker"]
description = "Generate background worker API pigeon code"
hide = true
sources = ["pigeon/background_worker_api.dart"]
outputs = [
"lib/platform/background_worker_api.g.dart",
"ios/Runner/Background/BackgroundWorker.g.swift",
"android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt",
]
run = [
"dart run pigeon --input pigeon/background_worker_api.dart",
"dart format lib/platform/background_worker_api.g.dart",
]
[tasks."pigeon:background-worker-lock"]
description = "Generate background worker lock API pigeon code"
hide = true
sources = ["pigeon/background_worker_lock_api.dart"]
outputs = [
"lib/platform/background_worker_lock_api.g.dart",
"android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt",
]
run = [
"dart run pigeon --input pigeon/background_worker_lock_api.dart",
"dart format lib/platform/background_worker_lock_api.g.dart",
]
[tasks."pigeon:connectivity"]
description = "Generate connectivity API pigeon code"
hide = true
sources = ["pigeon/connectivity_api.dart"]
outputs = [
"lib/platform/connectivity_api.g.dart",
"ios/Runner/Connectivity/Connectivity.g.swift",
"android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt",
]
run = [
"dart run pigeon --input pigeon/connectivity_api.dart",
"dart format lib/platform/connectivity_api.g.dart",
]
[tasks."i18n:loader"]
description = "Generate i18n loader"
hide = true
sources = ["i18n/"]
outputs = "lib/generated/codegen_loader.g.dart"
run = [
"dart run easy_localization:generate -S ../i18n",
"dart format lib/generated/codegen_loader.g.dart",
]
[tasks."i18n:keys"]
description = "Generate i18n keys"
hide = true
sources = ["i18n/en.json"]
outputs = "lib/generated/intl_keys.g.dart"
run = [
"dart run bin/generate_keys.dart",
"dart format lib/generated/intl_keys.g.dart",
]
[tasks."analyze:dart"]
description = "Run Dart analysis"
hide = true
run = "dart analyze --fatal-infos"
[tasks."analyze:dcm"]
description = "Run Dart Code Metrics"
hide = true
run = "dcm analyze lib --fatal-style --fatal-warnings"
[tasks."analyze:fix:dart"]
description = "Auto-fix Dart analysis"
hide = true
run = "dart fix --apply"
[tasks."analyze:fix:dcm"]
description = "Auto-fix Dart Code Metrics"
hide = true
run = "dcm fix lib"
+249 -226
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 2.2.2
- API version: 2.2.3
- Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
@@ -73,225 +73,235 @@ All URIs are relative to */api*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*APIKeysApi* | [**createApiKey**](doc//APIKeysApi.md#createapikey) | **POST** /api-keys |
*APIKeysApi* | [**deleteApiKey**](doc//APIKeysApi.md#deleteapikey) | **DELETE** /api-keys/{id} |
*APIKeysApi* | [**getApiKey**](doc//APIKeysApi.md#getapikey) | **GET** /api-keys/{id} |
*APIKeysApi* | [**getApiKeys**](doc//APIKeysApi.md#getapikeys) | **GET** /api-keys |
*APIKeysApi* | [**getMyApiKey**](doc//APIKeysApi.md#getmyapikey) | **GET** /api-keys/me |
*APIKeysApi* | [**updateApiKey**](doc//APIKeysApi.md#updateapikey) | **PUT** /api-keys/{id} |
*ActivitiesApi* | [**createActivity**](doc//ActivitiesApi.md#createactivity) | **POST** /activities |
*ActivitiesApi* | [**deleteActivity**](doc//ActivitiesApi.md#deleteactivity) | **DELETE** /activities/{id} |
*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities |
*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics |
*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets |
*AlbumsApi* | [**addAssetsToAlbums**](doc//AlbumsApi.md#addassetstoalbums) | **PUT** /albums/assets |
*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users |
*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums |
*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} |
*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} |
*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics |
*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums |
*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets |
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} |
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} |
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} |
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | checkBulkUpload
*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | checkExistingAssets
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy |
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} |
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets |
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original |
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | getAllUserAssetsByDeviceId
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} |
*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata |
*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} |
*AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr |
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics |
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random |
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback |
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs |
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} |
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata |
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets |
*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets |
*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail |
*AuthAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all |
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password |
*AuthenticationApi* | [**changePinCode**](doc//AuthenticationApi.md#changepincode) | **PUT** /auth/pin-code |
*AuthenticationApi* | [**getAuthStatus**](doc//AuthenticationApi.md#getauthstatus) | **GET** /auth/status |
*AuthenticationApi* | [**lockAuthSession**](doc//AuthenticationApi.md#lockauthsession) | **POST** /auth/session/lock |
*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login |
*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout |
*AuthenticationApi* | [**resetPinCode**](doc//AuthenticationApi.md#resetpincode) | **DELETE** /auth/pin-code |
*AuthenticationApi* | [**setupPinCode**](doc//AuthenticationApi.md#setuppincode) | **POST** /auth/pin-code |
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock |
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random |
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace the asset with new file, without changing its id
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive |
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info |
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} |
*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates |
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates |
*FacesApi* | [**createFace**](doc//FacesApi.md#createface) | **POST** /faces |
*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} |
*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces |
*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} |
*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs |
*JobsApi* | [**getAllJobsStatus**](doc//JobsApi.md#getalljobsstatus) | **GET** /jobs |
*JobsApi* | [**sendJobCommand**](doc//JobsApi.md#sendjobcommand) | **PUT** /jobs/{id} |
*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries |
*LibrariesApi* | [**deleteLibrary**](doc//LibrariesApi.md#deletelibrary) | **DELETE** /libraries/{id} |
*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries |
*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} |
*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics |
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan |
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} |
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate |
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers |
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode |
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets |
*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories |
*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} |
*MemoriesApi* | [**getMemory**](doc//MemoriesApi.md#getmemory) | **GET** /memories/{id} |
*MemoriesApi* | [**memoriesStatistics**](doc//MemoriesApi.md#memoriesstatistics) | **GET** /memories/statistics |
*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets |
*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |
*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |
*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} |
*NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications |
*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} |
*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications |
*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} |
*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications |
*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications |
*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} |
*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email |
*OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |
*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
*OAuthApi* | [**startOAuth**](doc//OAuthApi.md#startoauth) | **POST** /oauth/authorize |
*OAuthApi* | [**unlinkOAuthAccount**](doc//OAuthApi.md#unlinkoauthaccount) | **POST** /oauth/unlink |
*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners |
*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} |
*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners |
*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} |
*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} |
*PeopleApi* | [**createPerson**](doc//PeopleApi.md#createperson) | **POST** /people |
*PeopleApi* | [**deletePeople**](doc//PeopleApi.md#deletepeople) | **DELETE** /people |
*PeopleApi* | [**deletePerson**](doc//PeopleApi.md#deleteperson) | **DELETE** /people/{id} |
*PeopleApi* | [**getAllPeople**](doc//PeopleApi.md#getallpeople) | **GET** /people |
*PeopleApi* | [**getPerson**](doc//PeopleApi.md#getperson) | **GET** /people/{id} |
*PeopleApi* | [**getPersonStatistics**](doc//PeopleApi.md#getpersonstatistics) | **GET** /people/{id}/statistics |
*PeopleApi* | [**getPersonThumbnail**](doc//PeopleApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail |
*PeopleApi* | [**mergePerson**](doc//PeopleApi.md#mergeperson) | **POST** /people/{id}/merge |
*PeopleApi* | [**reassignFaces**](doc//PeopleApi.md#reassignfaces) | **PUT** /people/{id}/reassign |
*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people |
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} |
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions |
*SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics |
*SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata |
*SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets |
*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person |
*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places |
*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random |
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart |
*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license |
*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about |
*ServerApi* | [**getApkLinks**](doc//ServerApi.md#getapklinks) | **GET** /server/apk-links |
*ServerApi* | [**getServerConfig**](doc//ServerApi.md#getserverconfig) | **GET** /server/config |
*ServerApi* | [**getServerFeatures**](doc//ServerApi.md#getserverfeatures) | **GET** /server/features |
*ServerApi* | [**getServerLicense**](doc//ServerApi.md#getserverlicense) | **GET** /server/license |
*ServerApi* | [**getServerStatistics**](doc//ServerApi.md#getserverstatistics) | **GET** /server/statistics |
*ServerApi* | [**getServerVersion**](doc//ServerApi.md#getserverversion) | **GET** /server/version |
*ServerApi* | [**getStorage**](doc//ServerApi.md#getstorage) | **GET** /server/storage |
*ServerApi* | [**getSupportedMediaTypes**](doc//ServerApi.md#getsupportedmediatypes) | **GET** /server/media-types |
*ServerApi* | [**getTheme**](doc//ServerApi.md#gettheme) | **GET** /server/theme |
*ServerApi* | [**getVersionCheck**](doc//ServerApi.md#getversioncheck) | **GET** /server/version-check |
*ServerApi* | [**getVersionHistory**](doc//ServerApi.md#getversionhistory) | **GET** /server/version-history |
*ServerApi* | [**pingServer**](doc//ServerApi.md#pingserver) | **GET** /server/ping |
*ServerApi* | [**setServerLicense**](doc//ServerApi.md#setserverlicense) | **PUT** /server/license |
*SessionsApi* | [**createSession**](doc//SessionsApi.md#createsession) | **POST** /sessions |
*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions |
*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} |
*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions |
*SessionsApi* | [**lockSession**](doc//SessionsApi.md#locksession) | **POST** /sessions/{id}/lock |
*SessionsApi* | [**updateSession**](doc//SessionsApi.md#updatesession) | **PUT** /sessions/{id} |
*SharedLinksApi* | [**addSharedLinkAssets**](doc//SharedLinksApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets |
*SharedLinksApi* | [**createSharedLink**](doc//SharedLinksApi.md#createsharedlink) | **POST** /shared-links |
*SharedLinksApi* | [**getAllSharedLinks**](doc//SharedLinksApi.md#getallsharedlinks) | **GET** /shared-links |
*SharedLinksApi* | [**getMySharedLink**](doc//SharedLinksApi.md#getmysharedlink) | **GET** /shared-links/me |
*SharedLinksApi* | [**getSharedLinkById**](doc//SharedLinksApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} |
*SharedLinksApi* | [**removeSharedLink**](doc//SharedLinksApi.md#removesharedlink) | **DELETE** /shared-links/{id} |
*SharedLinksApi* | [**removeSharedLinkAssets**](doc//SharedLinksApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets |
*SharedLinksApi* | [**updateSharedLink**](doc//SharedLinksApi.md#updatesharedlink) | **PATCH** /shared-links/{id} |
*StacksApi* | [**createStack**](doc//StacksApi.md#createstack) | **POST** /stacks |
*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} |
*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks |
*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} |
*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} |
*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks |
*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} |
*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack |
*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync |
*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync |
*SyncApi* | [**getSyncAck**](doc//SyncApi.md#getsyncack) | **GET** /sync/ack |
*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream |
*SyncApi* | [**sendSyncAck**](doc//SyncApi.md#sendsyncack) | **POST** /sync/ack |
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults |
*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options |
*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config |
*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding |
*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state |
*SystemMetadataApi* | [**getVersionCheckState**](doc//SystemMetadataApi.md#getversioncheckstate) | **GET** /system-metadata/version-check-state |
*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding |
*TagsApi* | [**bulkTagAssets**](doc//TagsApi.md#bulktagassets) | **PUT** /tags/assets |
*TagsApi* | [**createTag**](doc//TagsApi.md#createtag) | **POST** /tags |
*TagsApi* | [**deleteTag**](doc//TagsApi.md#deletetag) | **DELETE** /tags/{id} |
*TagsApi* | [**getAllTags**](doc//TagsApi.md#getalltags) | **GET** /tags |
*TagsApi* | [**getTagById**](doc//TagsApi.md#gettagbyid) | **GET** /tags/{id} |
*TagsApi* | [**tagAssets**](doc//TagsApi.md#tagassets) | **PUT** /tags/{id}/assets |
*TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets |
*TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} |
*TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags |
*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket |
*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets |
*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty |
*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets |
*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore |
*UsersApi* | [**createProfileImage**](doc//UsersApi.md#createprofileimage) | **POST** /users/profile-image |
*UsersApi* | [**deleteProfileImage**](doc//UsersApi.md#deleteprofileimage) | **DELETE** /users/profile-image |
*UsersApi* | [**deleteUserLicense**](doc//UsersApi.md#deleteuserlicense) | **DELETE** /users/me/license |
*UsersApi* | [**deleteUserOnboarding**](doc//UsersApi.md#deleteuseronboarding) | **DELETE** /users/me/onboarding |
*UsersApi* | [**getMyPreferences**](doc//UsersApi.md#getmypreferences) | **GET** /users/me/preferences |
*UsersApi* | [**getMyUser**](doc//UsersApi.md#getmyuser) | **GET** /users/me |
*UsersApi* | [**getProfileImage**](doc//UsersApi.md#getprofileimage) | **GET** /users/{id}/profile-image |
*UsersApi* | [**getUser**](doc//UsersApi.md#getuser) | **GET** /users/{id} |
*UsersApi* | [**getUserLicense**](doc//UsersApi.md#getuserlicense) | **GET** /users/me/license |
*UsersApi* | [**getUserOnboarding**](doc//UsersApi.md#getuseronboarding) | **GET** /users/me/onboarding |
*UsersApi* | [**searchUsers**](doc//UsersApi.md#searchusers) | **GET** /users |
*UsersApi* | [**setUserLicense**](doc//UsersApi.md#setuserlicense) | **PUT** /users/me/license |
*UsersApi* | [**setUserOnboarding**](doc//UsersApi.md#setuseronboarding) | **PUT** /users/me/onboarding |
*UsersApi* | [**updateMyPreferences**](doc//UsersApi.md#updatemypreferences) | **PUT** /users/me/preferences |
*UsersApi* | [**updateMyUser**](doc//UsersApi.md#updatemyuser) | **PUT** /users/me |
*UsersAdminApi* | [**createUserAdmin**](doc//UsersAdminApi.md#createuseradmin) | **POST** /admin/users |
*UsersAdminApi* | [**deleteUserAdmin**](doc//UsersAdminApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} |
*UsersAdminApi* | [**getUserAdmin**](doc//UsersAdminApi.md#getuseradmin) | **GET** /admin/users/{id} |
*UsersAdminApi* | [**getUserPreferencesAdmin**](doc//UsersAdminApi.md#getuserpreferencesadmin) | **GET** /admin/users/{id}/preferences |
*UsersAdminApi* | [**getUserSessionsAdmin**](doc//UsersAdminApi.md#getusersessionsadmin) | **GET** /admin/users/{id}/sessions |
*UsersAdminApi* | [**getUserStatisticsAdmin**](doc//UsersAdminApi.md#getuserstatisticsadmin) | **GET** /admin/users/{id}/statistics |
*UsersAdminApi* | [**restoreUserAdmin**](doc//UsersAdminApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore |
*UsersAdminApi* | [**searchUsersAdmin**](doc//UsersAdminApi.md#searchusersadmin) | **GET** /admin/users |
*UsersAdminApi* | [**updateUserAdmin**](doc//UsersAdminApi.md#updateuseradmin) | **PUT** /admin/users/{id} |
*UsersAdminApi* | [**updateUserPreferencesAdmin**](doc//UsersAdminApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences |
*ViewApi* | [**getAssetsByOriginalPath**](doc//ViewApi.md#getassetsbyoriginalpath) | **GET** /view/folder |
*ViewApi* | [**getUniqueOriginalPaths**](doc//ViewApi.md#getuniqueoriginalpaths) | **GET** /view/folder/unique-paths |
*APIKeysApi* | [**createApiKey**](doc//APIKeysApi.md#createapikey) | **POST** /api-keys | Create an API key
*APIKeysApi* | [**deleteApiKey**](doc//APIKeysApi.md#deleteapikey) | **DELETE** /api-keys/{id} | Delete an API key
*APIKeysApi* | [**getApiKey**](doc//APIKeysApi.md#getapikey) | **GET** /api-keys/{id} | Retrieve an API key
*APIKeysApi* | [**getApiKeys**](doc//APIKeysApi.md#getapikeys) | **GET** /api-keys | List all API keys
*APIKeysApi* | [**getMyApiKey**](doc//APIKeysApi.md#getmyapikey) | **GET** /api-keys/me | Retrieve the current API key
*APIKeysApi* | [**updateApiKey**](doc//APIKeysApi.md#updateapikey) | **PUT** /api-keys/{id} | Update an API key
*ActivitiesApi* | [**createActivity**](doc//ActivitiesApi.md#createactivity) | **POST** /activities | Create an activity
*ActivitiesApi* | [**deleteActivity**](doc//ActivitiesApi.md#deleteactivity) | **DELETE** /activities/{id} | Delete an activity
*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities | List all activities
*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics | Retrieve activity statistics
*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets | Add assets to an album
*AlbumsApi* | [**addAssetsToAlbums**](doc//AlbumsApi.md#addassetstoalbums) | **PUT** /albums/assets | Add assets to albums
*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users | Share album with users
*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums | Create an album
*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} | Delete an album
*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} | Retrieve an album
*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | Retrieve album statistics
*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | List all albums
*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | Remove assets from an album
*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | Remove user from album
*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | Update an album
*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | Update user role
*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Check bulk upload
*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | Check existing assets
*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset
*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key
*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets
*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset
*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset
*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | Get asset metadata
*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key
*AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr | Retrieve asset OCR data
*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics
*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets
*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video
*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job
*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata
*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets
*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset
*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password
*AuthenticationApi* | [**changePinCode**](doc//AuthenticationApi.md#changepincode) | **PUT** /auth/pin-code | Change pin code
*AuthenticationApi* | [**finishOAuth**](doc//AuthenticationApi.md#finishoauth) | **POST** /oauth/callback | Finish OAuth
*AuthenticationApi* | [**getAuthStatus**](doc//AuthenticationApi.md#getauthstatus) | **GET** /auth/status | Retrieve auth status
*AuthenticationApi* | [**linkOAuthAccount**](doc//AuthenticationApi.md#linkoauthaccount) | **POST** /oauth/link | Link OAuth account
*AuthenticationApi* | [**lockAuthSession**](doc//AuthenticationApi.md#lockauthsession) | **POST** /auth/session/lock | Lock auth session
*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | Login
*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout | Logout
*AuthenticationApi* | [**redirectOAuthToMobile**](doc//AuthenticationApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | Redirect OAuth to mobile
*AuthenticationApi* | [**resetPinCode**](doc//AuthenticationApi.md#resetpincode) | **DELETE** /auth/pin-code | Reset pin code
*AuthenticationApi* | [**setupPinCode**](doc//AuthenticationApi.md#setuppincode) | **POST** /auth/pin-code | Setup pin code
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up | Register admin
*AuthenticationApi* | [**startOAuth**](doc//AuthenticationApi.md#startoauth) | **POST** /oauth/authorize | Start OAuth
*AuthenticationApi* | [**unlinkOAuthAccount**](doc//AuthenticationApi.md#unlinkoauthaccount) | **POST** /oauth/unlink | Unlink OAuth account
*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock | Unlock auth session
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | Validate access token
*AuthenticationAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthenticationAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all | Unlink all OAuth accounts
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner
*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID
*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user
*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets
*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate
*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates
*FacesApi* | [**createFace**](doc//FacesApi.md#createface) | **POST** /faces | Create a face
*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} | Delete a face
*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces | Retrieve faces for asset
*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} | Re-assign a face to another person
*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs | Create a manual job
*JobsApi* | [**getQueuesLegacy**](doc//JobsApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
*JobsApi* | [**runQueueCommandLegacy**](doc//JobsApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries | Create a library
*LibrariesApi* | [**deleteLibrary**](doc//LibrariesApi.md#deletelibrary) | **DELETE** /libraries/{id} | Delete a library
*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries | Retrieve libraries
*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} | Retrieve a library
*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics | Retrieve library statistics
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory
*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories | Create a memory
*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} | Delete a memory
*MemoriesApi* | [**getMemory**](doc//MemoriesApi.md#getmemory) | **GET** /memories/{id} | Retrieve a memory
*MemoriesApi* | [**memoriesStatistics**](doc//MemoriesApi.md#memoriesstatistics) | **GET** /memories/statistics | Retrieve memories statistics
*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | Remove assets from a memory
*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories | Retrieve memories
*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} | Update a memory
*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} | Delete a notification
*NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications | Delete notifications
*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} | Get a notification
*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications | Retrieve notifications
*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} | Update a notification
*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications | Update notifications
*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications | Create a notification
*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} | Render email template
*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email | Send test email
*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners | Create a partner
*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner
*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners | Retrieve partners
*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} | Remove a partner
*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} | Update a partner
*PeopleApi* | [**createPerson**](doc//PeopleApi.md#createperson) | **POST** /people | Create a person
*PeopleApi* | [**deletePeople**](doc//PeopleApi.md#deletepeople) | **DELETE** /people | Delete people
*PeopleApi* | [**deletePerson**](doc//PeopleApi.md#deleteperson) | **DELETE** /people/{id} | Delete person
*PeopleApi* | [**getAllPeople**](doc//PeopleApi.md#getallpeople) | **GET** /people | Get all people
*PeopleApi* | [**getPerson**](doc//PeopleApi.md#getperson) | **GET** /people/{id} | Get a person
*PeopleApi* | [**getPersonStatistics**](doc//PeopleApi.md#getpersonstatistics) | **GET** /people/{id}/statistics | Get person statistics
*PeopleApi* | [**getPersonThumbnail**](doc//PeopleApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail | Get person thumbnail
*PeopleApi* | [**mergePerson**](doc//PeopleApi.md#mergeperson) | **POST** /people/{id}/merge | Merge people
*PeopleApi* | [**reassignFaces**](doc//PeopleApi.md#reassignfaces) | **PUT** /people/{id}/reassign | Reassign faces
*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions
*SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics | Search asset statistics
*SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata | Search assets by metadata
*SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets | Search large assets
*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | Search people
*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | Search places
*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random | Search random assets
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart | Smart asset search
*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license | Delete server product key
*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about | Get server information
*ServerApi* | [**getApkLinks**](doc//ServerApi.md#getapklinks) | **GET** /server/apk-links | Get APK links
*ServerApi* | [**getServerConfig**](doc//ServerApi.md#getserverconfig) | **GET** /server/config | Get config
*ServerApi* | [**getServerFeatures**](doc//ServerApi.md#getserverfeatures) | **GET** /server/features | Get features
*ServerApi* | [**getServerLicense**](doc//ServerApi.md#getserverlicense) | **GET** /server/license | Get product key
*ServerApi* | [**getServerStatistics**](doc//ServerApi.md#getserverstatistics) | **GET** /server/statistics | Get statistics
*ServerApi* | [**getServerVersion**](doc//ServerApi.md#getserverversion) | **GET** /server/version | Get server version
*ServerApi* | [**getStorage**](doc//ServerApi.md#getstorage) | **GET** /server/storage | Get storage
*ServerApi* | [**getSupportedMediaTypes**](doc//ServerApi.md#getsupportedmediatypes) | **GET** /server/media-types | Get supported media types
*ServerApi* | [**getTheme**](doc//ServerApi.md#gettheme) | **GET** /server/theme | Get theme
*ServerApi* | [**getVersionCheck**](doc//ServerApi.md#getversioncheck) | **GET** /server/version-check | Get version check status
*ServerApi* | [**getVersionHistory**](doc//ServerApi.md#getversionhistory) | **GET** /server/version-history | Get version history
*ServerApi* | [**pingServer**](doc//ServerApi.md#pingserver) | **GET** /server/ping | Ping
*ServerApi* | [**setServerLicense**](doc//ServerApi.md#setserverlicense) | **PUT** /server/license | Set server product key
*SessionsApi* | [**createSession**](doc//SessionsApi.md#createsession) | **POST** /sessions | Create a session
*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions | Delete all sessions
*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} | Delete a session
*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions | Retrieve sessions
*SessionsApi* | [**lockSession**](doc//SessionsApi.md#locksession) | **POST** /sessions/{id}/lock | Lock a session
*SessionsApi* | [**updateSession**](doc//SessionsApi.md#updatesession) | **PUT** /sessions/{id} | Update a session
*SharedLinksApi* | [**addSharedLinkAssets**](doc//SharedLinksApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets | Add assets to a shared link
*SharedLinksApi* | [**createSharedLink**](doc//SharedLinksApi.md#createsharedlink) | **POST** /shared-links | Create a shared link
*SharedLinksApi* | [**getAllSharedLinks**](doc//SharedLinksApi.md#getallsharedlinks) | **GET** /shared-links | Retrieve all shared links
*SharedLinksApi* | [**getMySharedLink**](doc//SharedLinksApi.md#getmysharedlink) | **GET** /shared-links/me | Retrieve current shared link
*SharedLinksApi* | [**getSharedLinkById**](doc//SharedLinksApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} | Retrieve a shared link
*SharedLinksApi* | [**removeSharedLink**](doc//SharedLinksApi.md#removesharedlink) | **DELETE** /shared-links/{id} | Delete a shared link
*SharedLinksApi* | [**removeSharedLinkAssets**](doc//SharedLinksApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets | Remove assets from a shared link
*SharedLinksApi* | [**updateSharedLink**](doc//SharedLinksApi.md#updatesharedlink) | **PATCH** /shared-links/{id} | Update a shared link
*StacksApi* | [**createStack**](doc//StacksApi.md#createstack) | **POST** /stacks | Create a stack
*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} | Delete a stack
*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks | Delete stacks
*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} | Retrieve a stack
*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} | Remove an asset from a stack
*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks | Retrieve stacks
*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} | Update a stack
*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack | Delete acknowledgements
*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user
*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user
*SyncApi* | [**getSyncAck**](doc//SyncApi.md#getsyncack) | **GET** /sync/ack | Retrieve acknowledgements
*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream | Stream sync changes
*SyncApi* | [**sendSyncAck**](doc//SyncApi.md#sendsyncack) | **POST** /sync/ack | Acknowledge changes
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | Get system configuration
*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | Get system configuration defaults
*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | Get storage template options
*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | Update system configuration
*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding | Retrieve admin onboarding
*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state | Retrieve reverse geocoding state
*SystemMetadataApi* | [**getVersionCheckState**](doc//SystemMetadataApi.md#getversioncheckstate) | **GET** /system-metadata/version-check-state | Retrieve version check state
*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding | Update admin onboarding
*TagsApi* | [**bulkTagAssets**](doc//TagsApi.md#bulktagassets) | **PUT** /tags/assets | Tag assets
*TagsApi* | [**createTag**](doc//TagsApi.md#createtag) | **POST** /tags | Create a tag
*TagsApi* | [**deleteTag**](doc//TagsApi.md#deletetag) | **DELETE** /tags/{id} | Delete a tag
*TagsApi* | [**getAllTags**](doc//TagsApi.md#getalltags) | **GET** /tags | Retrieve tags
*TagsApi* | [**getTagById**](doc//TagsApi.md#gettagbyid) | **GET** /tags/{id} | Retrieve a tag
*TagsApi* | [**tagAssets**](doc//TagsApi.md#tagassets) | **PUT** /tags/{id}/assets | Tag assets
*TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets | Untag assets
*TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} | Update a tag
*TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags | Upsert tags
*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket | Get time bucket
*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets | Get time buckets
*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty | Empty trash
*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets | Restore assets
*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore | Restore trash
*UsersApi* | [**createProfileImage**](doc//UsersApi.md#createprofileimage) | **POST** /users/profile-image | Create user profile image
*UsersApi* | [**deleteProfileImage**](doc//UsersApi.md#deleteprofileimage) | **DELETE** /users/profile-image | Delete user profile image
*UsersApi* | [**deleteUserLicense**](doc//UsersApi.md#deleteuserlicense) | **DELETE** /users/me/license | Delete user product key
*UsersApi* | [**deleteUserOnboarding**](doc//UsersApi.md#deleteuseronboarding) | **DELETE** /users/me/onboarding | Delete user onboarding
*UsersApi* | [**getMyPreferences**](doc//UsersApi.md#getmypreferences) | **GET** /users/me/preferences | Get my preferences
*UsersApi* | [**getMyUser**](doc//UsersApi.md#getmyuser) | **GET** /users/me | Get current user
*UsersApi* | [**getProfileImage**](doc//UsersApi.md#getprofileimage) | **GET** /users/{id}/profile-image | Retrieve user profile image
*UsersApi* | [**getUser**](doc//UsersApi.md#getuser) | **GET** /users/{id} | Retrieve a user
*UsersApi* | [**getUserLicense**](doc//UsersApi.md#getuserlicense) | **GET** /users/me/license | Retrieve user product key
*UsersApi* | [**getUserOnboarding**](doc//UsersApi.md#getuseronboarding) | **GET** /users/me/onboarding | Retrieve user onboarding
*UsersApi* | [**searchUsers**](doc//UsersApi.md#searchusers) | **GET** /users | Get all users
*UsersApi* | [**setUserLicense**](doc//UsersApi.md#setuserlicense) | **PUT** /users/me/license | Set user product key
*UsersApi* | [**setUserOnboarding**](doc//UsersApi.md#setuseronboarding) | **PUT** /users/me/onboarding | Update user onboarding
*UsersApi* | [**updateMyPreferences**](doc//UsersApi.md#updatemypreferences) | **PUT** /users/me/preferences | Update my preferences
*UsersApi* | [**updateMyUser**](doc//UsersApi.md#updatemyuser) | **PUT** /users/me | Update current user
*UsersAdminApi* | [**createUserAdmin**](doc//UsersAdminApi.md#createuseradmin) | **POST** /admin/users | Create a user
*UsersAdminApi* | [**deleteUserAdmin**](doc//UsersAdminApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} | Delete a user
*UsersAdminApi* | [**getUserAdmin**](doc//UsersAdminApi.md#getuseradmin) | **GET** /admin/users/{id} | Retrieve a user
*UsersAdminApi* | [**getUserPreferencesAdmin**](doc//UsersAdminApi.md#getuserpreferencesadmin) | **GET** /admin/users/{id}/preferences | Retrieve user preferences
*UsersAdminApi* | [**getUserSessionsAdmin**](doc//UsersAdminApi.md#getusersessionsadmin) | **GET** /admin/users/{id}/sessions | Retrieve user sessions
*UsersAdminApi* | [**getUserStatisticsAdmin**](doc//UsersAdminApi.md#getuserstatisticsadmin) | **GET** /admin/users/{id}/statistics | Retrieve user statistics
*UsersAdminApi* | [**restoreUserAdmin**](doc//UsersAdminApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore | Restore a deleted user
*UsersAdminApi* | [**searchUsersAdmin**](doc//UsersAdminApi.md#searchusersadmin) | **GET** /admin/users | Search users
*UsersAdminApi* | [**updateUserAdmin**](doc//UsersAdminApi.md#updateuseradmin) | **PUT** /admin/users/{id} | Update a user
*UsersAdminApi* | [**updateUserPreferencesAdmin**](doc//UsersAdminApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences | Update user preferences
*ViewsApi* | [**getAssetsByOriginalPath**](doc//ViewsApi.md#getassetsbyoriginalpath) | **GET** /view/folder | Retrieve assets by original path
*ViewsApi* | [**getUniqueOriginalPaths**](doc//ViewsApi.md#getuniqueoriginalpaths) | **GET** /view/folder/unique-paths | Retrieve unique paths
*WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow
*WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow
*WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow
*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows
*WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow
## Documentation For Models
@@ -315,7 +325,6 @@ Class | Method | HTTP request | Description
- [AlbumsAddAssetsResponseDto](doc//AlbumsAddAssetsResponseDto.md)
- [AlbumsResponse](doc//AlbumsResponse.md)
- [AlbumsUpdate](doc//AlbumsUpdate.md)
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
- [AssetBulkDeleteDto](doc//AssetBulkDeleteDto.md)
- [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md)
- [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md)
@@ -384,13 +393,8 @@ Class | Method | HTTP request | Description
- [FoldersResponse](doc//FoldersResponse.md)
- [FoldersUpdate](doc//FoldersUpdate.md)
- [ImageFormat](doc//ImageFormat.md)
- [JobCommand](doc//JobCommand.md)
- [JobCommandDto](doc//JobCommandDto.md)
- [JobCountsDto](doc//JobCountsDto.md)
- [JobCreateDto](doc//JobCreateDto.md)
- [JobName](doc//JobName.md)
- [JobSettingsDto](doc//JobSettingsDto.md)
- [JobStatusDto](doc//JobStatusDto.md)
- [LibraryResponseDto](doc//LibraryResponseDto.md)
- [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md)
- [LicenseKeyDto](doc//LicenseKeyDto.md)
@@ -407,6 +411,7 @@ Class | Method | HTTP request | Description
- [MemoriesUpdate](doc//MemoriesUpdate.md)
- [MemoryCreateDto](doc//MemoryCreateDto.md)
- [MemoryResponseDto](doc//MemoryResponseDto.md)
- [MemorySearchOrder](doc//MemorySearchOrder.md)
- [MemoryStatisticsResponseDto](doc//MemoryStatisticsResponseDto.md)
- [MemoryType](doc//MemoryType.md)
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
@@ -446,9 +451,20 @@ Class | Method | HTTP request | Description
- [PinCodeResetDto](doc//PinCodeResetDto.md)
- [PinCodeSetupDto](doc//PinCodeSetupDto.md)
- [PlacesResponseDto](doc//PlacesResponseDto.md)
- [PluginActionResponseDto](doc//PluginActionResponseDto.md)
- [PluginContext](doc//PluginContext.md)
- [PluginFilterResponseDto](doc//PluginFilterResponseDto.md)
- [PluginResponseDto](doc//PluginResponseDto.md)
- [PluginTriggerType](doc//PluginTriggerType.md)
- [PurchaseResponse](doc//PurchaseResponse.md)
- [PurchaseUpdate](doc//PurchaseUpdate.md)
- [QueueCommand](doc//QueueCommand.md)
- [QueueCommandDto](doc//QueueCommandDto.md)
- [QueueName](doc//QueueName.md)
- [QueueResponseDto](doc//QueueResponseDto.md)
- [QueueStatisticsDto](doc//QueueStatisticsDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [QueuesResponseDto](doc//QueuesResponseDto.md)
- [RandomSearchDto](doc//RandomSearchDto.md)
- [RatingsResponse](doc//RatingsResponse.md)
- [RatingsUpdate](doc//RatingsUpdate.md)
@@ -599,6 +615,13 @@ Class | Method | HTTP request | Description
- [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md)
- [VideoCodec](doc//VideoCodec.md)
- [VideoContainer](doc//VideoContainer.md)
- [WorkflowActionItemDto](doc//WorkflowActionItemDto.md)
- [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md)
- [WorkflowCreateDto](doc//WorkflowCreateDto.md)
- [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md)
- [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md)
- [WorkflowResponseDto](doc//WorkflowResponseDto.md)
- [WorkflowUpdateDto](doc//WorkflowUpdateDto.md)
## Documentation For Authorization
+23 -9
View File
@@ -34,8 +34,8 @@ part 'api/api_keys_api.dart';
part 'api/activities_api.dart';
part 'api/albums_api.dart';
part 'api/assets_api.dart';
part 'api/auth_admin_api.dart';
part 'api/authentication_api.dart';
part 'api/authentication_admin_api.dart';
part 'api/deprecated_api.dart';
part 'api/download_api.dart';
part 'api/duplicates_api.dart';
@@ -46,9 +46,9 @@ part 'api/map_api.dart';
part 'api/memories_api.dart';
part 'api/notifications_api.dart';
part 'api/notifications_admin_api.dart';
part 'api/o_auth_api.dart';
part 'api/partners_api.dart';
part 'api/people_api.dart';
part 'api/plugins_api.dart';
part 'api/search_api.dart';
part 'api/server_api.dart';
part 'api/sessions_api.dart';
@@ -62,7 +62,8 @@ part 'api/timeline_api.dart';
part 'api/trash_api.dart';
part 'api/users_api.dart';
part 'api/users_admin_api.dart';
part 'api/view_api.dart';
part 'api/views_api.dart';
part 'api/workflows_api.dart';
part 'model/api_key_create_dto.dart';
part 'model/api_key_create_response_dto.dart';
@@ -83,7 +84,6 @@ part 'model/albums_add_assets_dto.dart';
part 'model/albums_add_assets_response_dto.dart';
part 'model/albums_response.dart';
part 'model/albums_update.dart';
part 'model/all_job_status_response_dto.dart';
part 'model/asset_bulk_delete_dto.dart';
part 'model/asset_bulk_update_dto.dart';
part 'model/asset_bulk_upload_check_dto.dart';
@@ -152,13 +152,8 @@ part 'model/facial_recognition_config.dart';
part 'model/folders_response.dart';
part 'model/folders_update.dart';
part 'model/image_format.dart';
part 'model/job_command.dart';
part 'model/job_command_dto.dart';
part 'model/job_counts_dto.dart';
part 'model/job_create_dto.dart';
part 'model/job_name.dart';
part 'model/job_settings_dto.dart';
part 'model/job_status_dto.dart';
part 'model/library_response_dto.dart';
part 'model/library_stats_response_dto.dart';
part 'model/license_key_dto.dart';
@@ -175,6 +170,7 @@ part 'model/memories_response.dart';
part 'model/memories_update.dart';
part 'model/memory_create_dto.dart';
part 'model/memory_response_dto.dart';
part 'model/memory_search_order.dart';
part 'model/memory_statistics_response_dto.dart';
part 'model/memory_type.dart';
part 'model/memory_update_dto.dart';
@@ -214,9 +210,20 @@ part 'model/pin_code_change_dto.dart';
part 'model/pin_code_reset_dto.dart';
part 'model/pin_code_setup_dto.dart';
part 'model/places_response_dto.dart';
part 'model/plugin_action_response_dto.dart';
part 'model/plugin_context.dart';
part 'model/plugin_filter_response_dto.dart';
part 'model/plugin_response_dto.dart';
part 'model/plugin_trigger_type.dart';
part 'model/purchase_response.dart';
part 'model/purchase_update.dart';
part 'model/queue_command.dart';
part 'model/queue_command_dto.dart';
part 'model/queue_name.dart';
part 'model/queue_response_dto.dart';
part 'model/queue_statistics_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/queues_response_dto.dart';
part 'model/random_search_dto.dart';
part 'model/ratings_response.dart';
part 'model/ratings_update.dart';
@@ -367,6 +374,13 @@ part 'model/validate_library_response_dto.dart';
part 'model/version_check_state_response_dto.dart';
part 'model/video_codec.dart';
part 'model/video_container.dart';
part 'model/workflow_action_item_dto.dart';
part 'model/workflow_action_response_dto.dart';
part 'model/workflow_create_dto.dart';
part 'model/workflow_filter_item_dto.dart';
part 'model/workflow_filter_response_dto.dart';
part 'model/workflow_response_dto.dart';
part 'model/workflow_update_dto.dart';
/// An [ApiClient] instance that uses the default values obtained from
+24 -8
View File
@@ -16,7 +16,9 @@ class ActivitiesApi {
final ApiClient apiClient;
/// This endpoint requires the `activity.create` permission.
/// Create an activity
///
/// Create a like or a comment for an album, or an asset in an album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class ActivitiesApi {
);
}
/// This endpoint requires the `activity.create` permission.
/// Create an activity
///
/// Create a like or a comment for an album, or an asset in an album.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class ActivitiesApi {
return null;
}
/// This endpoint requires the `activity.delete` permission.
/// Delete an activity
///
/// Removes a like or comment from a given album or asset in an album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -101,7 +107,9 @@ class ActivitiesApi {
);
}
/// This endpoint requires the `activity.delete` permission.
/// Delete an activity
///
/// Removes a like or comment from a given album or asset in an album.
///
/// Parameters:
///
@@ -113,7 +121,9 @@ class ActivitiesApi {
}
}
/// This endpoint requires the `activity.read` permission.
/// List all activities
///
/// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first.
///
/// Note: This method returns the HTTP [Response].
///
@@ -167,7 +177,9 @@ class ActivitiesApi {
);
}
/// This endpoint requires the `activity.read` permission.
/// List all activities
///
/// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first.
///
/// Parameters:
///
@@ -198,7 +210,9 @@ class ActivitiesApi {
return null;
}
/// This endpoint requires the `activity.statistics` permission.
/// Retrieve activity statistics
///
/// Returns the number of likes and comments for a given album or asset in an album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -237,7 +251,9 @@ class ActivitiesApi {
);
}
/// This endpoint requires the `activity.statistics` permission.
/// Retrieve activity statistics
///
/// Returns the number of likes and comments for a given album or asset in an album.
///
/// Parameters:
///
+72 -24
View File
@@ -16,7 +16,9 @@ class AlbumsApi {
final ApiClient apiClient;
/// This endpoint requires the `albumAsset.create` permission.
/// Add assets to an album
///
/// Add multiple assets to a specific album by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -62,7 +64,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumAsset.create` permission.
/// Add assets to an album
///
/// Add multiple assets to a specific album by its ID.
///
/// Parameters:
///
@@ -91,7 +95,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `albumAsset.create` permission.
/// Add assets to albums
///
/// Send a list of asset IDs and album IDs to add each asset to each album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -134,7 +140,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumAsset.create` permission.
/// Add assets to albums
///
/// Send a list of asset IDs and album IDs to add each asset to each album.
///
/// Parameters:
///
@@ -158,7 +166,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `albumUser.create` permission.
/// Share album with users
///
/// Share an album with multiple users. Each user can be given a specific role in the album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -193,7 +203,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumUser.create` permission.
/// Share album with users
///
/// Share an album with multiple users. Each user can be given a specific role in the album.
///
/// Parameters:
///
@@ -215,7 +227,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `album.create` permission.
/// Create an album
///
/// Create a new album. The album can also be created with initial users and assets.
///
/// Note: This method returns the HTTP [Response].
///
@@ -247,7 +261,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.create` permission.
/// Create an album
///
/// Create a new album. The album can also be created with initial users and assets.
///
/// Parameters:
///
@@ -267,7 +283,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `album.delete` permission.
/// Delete an album
///
/// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process.
///
/// Note: This method returns the HTTP [Response].
///
@@ -300,7 +318,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.delete` permission.
/// Delete an album
///
/// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process.
///
/// Parameters:
///
@@ -312,7 +332,9 @@ class AlbumsApi {
}
}
/// This endpoint requires the `album.read` permission.
/// Retrieve an album
///
/// Retrieve information about a specific album by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -361,7 +383,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.read` permission.
/// Retrieve an album
///
/// Retrieve information about a specific album by its ID.
///
/// Parameters:
///
@@ -387,7 +411,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `album.statistics` permission.
/// Retrieve album statistics
///
/// Returns statistics about the albums available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAlbumStatisticsWithHttpInfo() async {
@@ -415,7 +441,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.statistics` permission.
/// Retrieve album statistics
///
/// Returns statistics about the albums available to the authenticated user.
Future<AlbumStatisticsResponseDto?> getAlbumStatistics() async {
final response = await getAlbumStatisticsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -431,7 +459,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `album.read` permission.
/// List all albums
///
/// Retrieve a list of albums available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -473,7 +503,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.read` permission.
/// List all albums
///
/// Retrieve a list of albums available to the authenticated user.
///
/// Parameters:
///
@@ -499,7 +531,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `albumAsset.delete` permission.
/// Remove assets from an album
///
/// Remove multiple assets from a specific album by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -534,7 +568,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumAsset.delete` permission.
/// Remove assets from an album
///
/// Remove multiple assets from a specific album by its ID.
///
/// Parameters:
///
@@ -559,7 +595,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `albumUser.delete` permission.
/// Remove user from album
///
/// Remove a user from an album. Use an ID of \"me\" to leave a shared album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -595,7 +633,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumUser.delete` permission.
/// Remove user from album
///
/// Remove a user from an album. Use an ID of \"me\" to leave a shared album.
///
/// Parameters:
///
@@ -609,7 +649,9 @@ class AlbumsApi {
}
}
/// This endpoint requires the `album.update` permission.
/// Update an album
///
/// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -644,7 +686,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `album.update` permission.
/// Update an album
///
/// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album.
///
/// Parameters:
///
@@ -666,7 +710,9 @@ class AlbumsApi {
return null;
}
/// This endpoint requires the `albumUser.update` permission.
/// Update user role
///
/// Change the role for a specific user in a specific album.
///
/// Note: This method returns the HTTP [Response].
///
@@ -704,7 +750,9 @@ class AlbumsApi {
);
}
/// This endpoint requires the `albumUser.update` permission.
/// Update user role
///
/// Change the role for a specific user in a specific album.
///
/// Parameters:
///
+38 -11
View File
@@ -16,7 +16,9 @@ class APIKeysApi {
final ApiClient apiClient;
/// This endpoint requires the `apiKey.create` permission.
/// Create an API key
///
/// Creates a new API key. It will be limited to the permissions specified.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class APIKeysApi {
);
}
/// This endpoint requires the `apiKey.create` permission.
/// Create an API key
///
/// Creates a new API key. It will be limited to the permissions specified.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class APIKeysApi {
return null;
}
/// This endpoint requires the `apiKey.delete` permission.
/// Delete an API key
///
/// Deletes an API key identified by its ID. The current user must own this API key.
///
/// Note: This method returns the HTTP [Response].
///
@@ -101,7 +107,9 @@ class APIKeysApi {
);
}
/// This endpoint requires the `apiKey.delete` permission.
/// Delete an API key
///
/// Deletes an API key identified by its ID. The current user must own this API key.
///
/// Parameters:
///
@@ -113,7 +121,9 @@ class APIKeysApi {
}
}
/// This endpoint requires the `apiKey.read` permission.
/// Retrieve an API key
///
/// Retrieve an API key by its ID. The current user must own this API key.
///
/// Note: This method returns the HTTP [Response].
///
@@ -146,7 +156,9 @@ class APIKeysApi {
);
}
/// This endpoint requires the `apiKey.read` permission.
/// Retrieve an API key
///
/// Retrieve an API key by its ID. The current user must own this API key.
///
/// Parameters:
///
@@ -166,7 +178,9 @@ class APIKeysApi {
return null;
}
/// This endpoint requires the `apiKey.read` permission.
/// List all API keys
///
/// Retrieve all API keys of the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getApiKeysWithHttpInfo() async {
@@ -194,7 +208,9 @@ class APIKeysApi {
);
}
/// This endpoint requires the `apiKey.read` permission.
/// List all API keys
///
/// Retrieve all API keys of the current user.
Future<List<APIKeyResponseDto>?> getApiKeys() async {
final response = await getApiKeysWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -213,7 +229,11 @@ class APIKeysApi {
return null;
}
/// Performs an HTTP 'GET /api-keys/me' operation and returns the [Response].
/// Retrieve the current API key
///
/// Retrieve the API key that is used to access this endpoint.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMyApiKeyWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/api-keys/me';
@@ -239,6 +259,9 @@ class APIKeysApi {
);
}
/// Retrieve the current API key
///
/// Retrieve the API key that is used to access this endpoint.
Future<APIKeyResponseDto?> getMyApiKey() async {
final response = await getMyApiKeyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -254,7 +277,9 @@ class APIKeysApi {
return null;
}
/// This endpoint requires the `apiKey.update` permission.
/// Update an API key
///
/// Updates the name and permissions of an API key by its ID. The current user must own this API key.
///
/// Note: This method returns the HTTP [Response].
///
@@ -289,7 +314,9 @@ class APIKeysApi {
);
}
/// This endpoint requires the `apiKey.update` permission.
/// Update an API key
///
/// Updates the name and permissions of an API key by its ID. The current user must own this API key.
///
/// Parameters:
///
+118 -45
View File
@@ -16,9 +16,9 @@ class AssetsApi {
final ApiClient apiClient;
/// checkBulkUpload
/// Check bulk upload
///
/// Checks if assets exist by checksums. This endpoint requires the `asset.upload` permission.
/// Determine which assets have already been uploaded to the server based on their SHA1 checksums.
///
/// Note: This method returns the HTTP [Response].
///
@@ -50,9 +50,9 @@ class AssetsApi {
);
}
/// checkBulkUpload
/// Check bulk upload
///
/// Checks if assets exist by checksums. This endpoint requires the `asset.upload` permission.
/// Determine which assets have already been uploaded to the server based on their SHA1 checksums.
///
/// Parameters:
///
@@ -72,7 +72,7 @@ class AssetsApi {
return null;
}
/// checkExistingAssets
/// Check existing assets
///
/// Checks if multiple assets exist on the server and returns all existing - used by background backup
///
@@ -106,7 +106,7 @@ class AssetsApi {
);
}
/// checkExistingAssets
/// Check existing assets
///
/// Checks if multiple assets exist on the server and returns all existing - used by background backup
///
@@ -128,7 +128,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.copy` permission.
/// Copy asset
///
/// Copy asset information like albums, tags, etc. from one asset to another.
///
/// Note: This method returns the HTTP [Response].
///
@@ -160,7 +162,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.copy` permission.
/// Copy asset
///
/// Copy asset information like albums, tags, etc. from one asset to another.
///
/// Parameters:
///
@@ -172,7 +176,9 @@ class AssetsApi {
}
}
/// This endpoint requires the `asset.update` permission.
/// Delete asset metadata by key
///
/// Delete a specific metadata key-value pair associated with the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -208,7 +214,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.update` permission.
/// Delete asset metadata by key
///
/// Delete a specific metadata key-value pair associated with the specified asset.
///
/// Parameters:
///
@@ -222,7 +230,9 @@ class AssetsApi {
}
}
/// This endpoint requires the `asset.delete` permission.
/// Delete assets
///
/// Deletes multiple assets at the same time.
///
/// Note: This method returns the HTTP [Response].
///
@@ -254,7 +264,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.delete` permission.
/// Delete assets
///
/// Deletes multiple assets at the same time.
///
/// Parameters:
///
@@ -266,7 +278,9 @@ class AssetsApi {
}
}
/// This endpoint requires the `asset.download` permission.
/// Download original asset
///
/// Downloads the original file of the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -310,7 +324,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.download` permission.
/// Download original asset
///
/// Downloads the original file of the specified asset.
///
/// Parameters:
///
@@ -334,7 +350,7 @@ class AssetsApi {
return null;
}
/// getAllUserAssetsByDeviceId
/// Retrieve assets by device ID
///
/// Get all asset of a device that are in the database, ID only.
///
@@ -369,7 +385,7 @@ class AssetsApi {
);
}
/// getAllUserAssetsByDeviceId
/// Retrieve assets by device ID
///
/// Get all asset of a device that are in the database, ID only.
///
@@ -394,7 +410,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve an asset
///
/// Retrieve detailed information about a specific asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -438,7 +456,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve an asset
///
/// Retrieve detailed information about a specific asset.
///
/// Parameters:
///
@@ -462,7 +482,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Get asset metadata
///
/// Retrieve all metadata key-value pairs associated with the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -495,7 +517,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Get asset metadata
///
/// Retrieve all metadata key-value pairs associated with the specified asset.
///
/// Parameters:
///
@@ -518,7 +542,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve asset metadata by key
///
/// Retrieve the value of a specific metadata key associated with the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -554,7 +580,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve asset metadata by key
///
/// Retrieve the value of a specific metadata key associated with the specified asset.
///
/// Parameters:
///
@@ -576,7 +604,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve asset OCR data
///
/// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -609,7 +639,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve asset OCR data
///
/// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset.
///
/// Parameters:
///
@@ -632,7 +664,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.statistics` permission.
/// Get asset statistics
///
/// Retrieve various statistics about the assets owned by the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -678,7 +712,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.statistics` permission.
/// Get asset statistics
///
/// Retrieve various statistics about the assets owned by the authenticated user.
///
/// Parameters:
///
@@ -702,7 +738,9 @@ class AssetsApi {
return null;
}
/// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission.
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -738,7 +776,9 @@ class AssetsApi {
);
}
/// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission.
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
///
/// Parameters:
///
@@ -761,7 +801,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.view` permission.
/// Play asset video
///
/// Streams the video file for the specified asset. This endpoint also supports byte range requests.
///
/// Note: This method returns the HTTP [Response].
///
@@ -805,7 +847,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.view` permission.
/// Play asset video
///
/// Streams the video file for the specified asset. This endpoint also supports byte range requests.
///
/// Parameters:
///
@@ -829,9 +873,9 @@ class AssetsApi {
return null;
}
/// Replace the asset with new file, without changing its id
/// Replace asset
///
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
/// Replace the asset with new file, without changing its id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -923,9 +967,9 @@ class AssetsApi {
);
}
/// Replace the asset with new file, without changing its id
/// Replace asset
///
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
/// Replace the asset with new file, without changing its id.
///
/// Parameters:
///
@@ -963,7 +1007,12 @@ class AssetsApi {
return null;
}
/// Performs an HTTP 'POST /assets/jobs' operation and returns the [Response].
/// Run an asset job
///
/// Run a specific job on a set of assets.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [AssetJobsDto] assetJobsDto (required):
@@ -992,6 +1041,10 @@ class AssetsApi {
);
}
/// Run an asset job
///
/// Run a specific job on a set of assets.
///
/// Parameters:
///
/// * [AssetJobsDto] assetJobsDto (required):
@@ -1002,7 +1055,9 @@ class AssetsApi {
}
}
/// This endpoint requires the `asset.update` permission.
/// Update an asset
///
/// Update information of a specific asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -1037,7 +1092,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.update` permission.
/// Update an asset
///
/// Update information of a specific asset.
///
/// Parameters:
///
@@ -1059,7 +1116,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.update` permission.
/// Update asset metadata
///
/// Update or add metadata key-value pairs for the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -1094,7 +1153,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.update` permission.
/// Update asset metadata
///
/// Update or add metadata key-value pairs for the specified asset.
///
/// Parameters:
///
@@ -1119,7 +1180,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.update` permission.
/// Update assets
///
/// Updates multiple assets at the same time.
///
/// Note: This method returns the HTTP [Response].
///
@@ -1151,7 +1214,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.update` permission.
/// Update assets
///
/// Updates multiple assets at the same time.
///
/// Parameters:
///
@@ -1163,7 +1228,9 @@ class AssetsApi {
}
}
/// This endpoint requires the `asset.upload` permission.
/// Upload asset
///
/// Uploads a new asset to the server.
///
/// Note: This method returns the HTTP [Response].
///
@@ -1290,7 +1357,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.upload` permission.
/// Upload asset
///
/// Uploads a new asset to the server.
///
/// Parameters:
///
@@ -1339,7 +1408,9 @@ class AssetsApi {
return null;
}
/// This endpoint requires the `asset.view` permission.
/// View asset thumbnail
///
/// Retrieve the thumbnail image for the specified asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -1388,7 +1459,9 @@ class AssetsApi {
);
}
/// This endpoint requires the `asset.view` permission.
/// View asset thumbnail
///
/// Retrieve the thumbnail image for the specified asset.
///
/// Parameters:
///
@@ -11,12 +11,14 @@
part of openapi.api;
class AuthAdminApi {
AuthAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
class AuthenticationAdminApi {
AuthenticationAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission.
/// Unlink all OAuth accounts
///
/// Unlinks all OAuth accounts associated with user accounts in the system.
///
/// Note: This method returns the HTTP [Response].
Future<Response> unlinkAllOAuthAccountsAdminWithHttpInfo() async {
@@ -44,7 +46,9 @@ class AuthAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminAuth.unlinkAll` permission.
/// Unlink all OAuth accounts
///
/// Unlinks all OAuth accounts associated with user accounts in the system.
Future<void> unlinkAllOAuthAccountsAdmin() async {
final response = await unlinkAllOAuthAccountsAdminWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
+342 -15
View File
@@ -16,7 +16,9 @@ class AuthenticationApi {
final ApiClient apiClient;
/// This endpoint requires the `auth.changePassword` permission.
/// Change password
///
/// Change the password of the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class AuthenticationApi {
);
}
/// This endpoint requires the `auth.changePassword` permission.
/// Change password
///
/// Change the password of the current user.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class AuthenticationApi {
return null;
}
/// This endpoint requires the `pinCode.update` permission.
/// Change pin code
///
/// Change the pin code for the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -100,7 +106,9 @@ class AuthenticationApi {
);
}
/// This endpoint requires the `pinCode.update` permission.
/// Change pin code
///
/// Change the pin code for the current user.
///
/// Parameters:
///
@@ -112,7 +120,67 @@ class AuthenticationApi {
}
}
/// Performs an HTTP 'GET /auth/status' operation and returns the [Response].
/// Finish OAuth
///
/// Complete the OAuth authorization process by exchanging the authorization code for a session token.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
Future<Response> finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/oauth/callback';
// ignore: prefer_final_locals
Object? postBody = oAuthCallbackDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Finish OAuth
///
/// Complete the OAuth authorization process by exchanging the authorization code for a session token.
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
Future<LoginResponseDto?> finishOAuth(OAuthCallbackDto oAuthCallbackDto,) async {
final response = await finishOAuthWithHttpInfo(oAuthCallbackDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto;
}
return null;
}
/// Retrieve auth status
///
/// Get information about the current session, including whether the user has a password, and if the session can access locked assets.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAuthStatusWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/auth/status';
@@ -138,6 +206,9 @@ class AuthenticationApi {
);
}
/// Retrieve auth status
///
/// Get information about the current session, including whether the user has a password, and if the session can access locked assets.
Future<AuthStatusResponseDto?> getAuthStatus() async {
final response = await getAuthStatusWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -153,7 +224,67 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/session/lock' operation and returns the [Response].
/// Link OAuth account
///
/// Link an OAuth account to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
Future<Response> linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/oauth/link';
// ignore: prefer_final_locals
Object? postBody = oAuthCallbackDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Link OAuth account
///
/// Link an OAuth account to the authenticated user.
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
Future<UserAdminResponseDto?> linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async {
final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
}
return null;
}
/// Lock auth session
///
/// Remove elevated access to locked assets from the current session.
///
/// Note: This method returns the HTTP [Response].
Future<Response> lockAuthSessionWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/auth/session/lock';
@@ -179,6 +310,9 @@ class AuthenticationApi {
);
}
/// Lock auth session
///
/// Remove elevated access to locked assets from the current session.
Future<void> lockAuthSession() async {
final response = await lockAuthSessionWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -186,7 +320,12 @@ class AuthenticationApi {
}
}
/// Performs an HTTP 'POST /auth/login' operation and returns the [Response].
/// Login
///
/// Login with username and password and receive a session token.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [LoginCredentialDto] loginCredentialDto (required):
@@ -215,6 +354,10 @@ class AuthenticationApi {
);
}
/// Login
///
/// Login with username and password and receive a session token.
///
/// Parameters:
///
/// * [LoginCredentialDto] loginCredentialDto (required):
@@ -233,7 +376,11 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/logout' operation and returns the [Response].
/// Logout
///
/// Logout the current user and invalidate the session token.
///
/// Note: This method returns the HTTP [Response].
Future<Response> logoutWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/auth/logout';
@@ -259,6 +406,9 @@ class AuthenticationApi {
);
}
/// Logout
///
/// Logout the current user and invalidate the session token.
Future<LogoutResponseDto?> logout() async {
final response = await logoutWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -274,7 +424,49 @@ class AuthenticationApi {
return null;
}
/// This endpoint requires the `pinCode.delete` permission.
/// Redirect OAuth to mobile
///
/// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting.
///
/// Note: This method returns the HTTP [Response].
Future<Response> redirectOAuthToMobileWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/oauth/mobile-redirect';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Redirect OAuth to mobile
///
/// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting.
Future<void> redirectOAuthToMobile() async {
final response = await redirectOAuthToMobileWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Reset pin code
///
/// Reset the pin code for the current user by providing the account password
///
/// Note: This method returns the HTTP [Response].
///
@@ -306,7 +498,9 @@ class AuthenticationApi {
);
}
/// This endpoint requires the `pinCode.delete` permission.
/// Reset pin code
///
/// Reset the pin code for the current user by providing the account password
///
/// Parameters:
///
@@ -318,7 +512,9 @@ class AuthenticationApi {
}
}
/// This endpoint requires the `pinCode.create` permission.
/// Setup pin code
///
/// Setup a new pin code for the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -350,7 +546,9 @@ class AuthenticationApi {
);
}
/// This endpoint requires the `pinCode.create` permission.
/// Setup pin code
///
/// Setup a new pin code for the current user.
///
/// Parameters:
///
@@ -362,7 +560,12 @@ class AuthenticationApi {
}
}
/// Performs an HTTP 'POST /auth/admin-sign-up' operation and returns the [Response].
/// Register admin
///
/// Create the first admin user in the system.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SignUpDto] signUpDto (required):
@@ -391,6 +594,10 @@ class AuthenticationApi {
);
}
/// Register admin
///
/// Create the first admin user in the system.
///
/// Parameters:
///
/// * [SignUpDto] signUpDto (required):
@@ -409,7 +616,116 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/session/unlock' operation and returns the [Response].
/// Start OAuth
///
/// Initiate the OAuth authorization process.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthConfigDto] oAuthConfigDto (required):
Future<Response> startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/oauth/authorize';
// ignore: prefer_final_locals
Object? postBody = oAuthConfigDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Start OAuth
///
/// Initiate the OAuth authorization process.
///
/// Parameters:
///
/// * [OAuthConfigDto] oAuthConfigDto (required):
Future<OAuthAuthorizeResponseDto?> startOAuth(OAuthConfigDto oAuthConfigDto,) async {
final response = await startOAuthWithHttpInfo(oAuthConfigDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto;
}
return null;
}
/// Unlink OAuth account
///
/// Unlink the OAuth account from the authenticated user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> unlinkOAuthAccountWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/oauth/unlink';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Unlink OAuth account
///
/// Unlink the OAuth account from the authenticated user.
Future<UserAdminResponseDto?> unlinkOAuthAccount() async {
final response = await unlinkOAuthAccountWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
}
return null;
}
/// Unlock auth session
///
/// Temporarily grant the session elevated access to locked assets by providing the correct PIN code.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SessionUnlockDto] sessionUnlockDto (required):
@@ -438,6 +754,10 @@ class AuthenticationApi {
);
}
/// Unlock auth session
///
/// Temporarily grant the session elevated access to locked assets by providing the correct PIN code.
///
/// Parameters:
///
/// * [SessionUnlockDto] sessionUnlockDto (required):
@@ -448,7 +768,11 @@ class AuthenticationApi {
}
}
/// Performs an HTTP 'POST /auth/validateToken' operation and returns the [Response].
/// Validate access token
///
/// Validate the current authorization method is still valid.
///
/// Note: This method returns the HTTP [Response].
Future<Response> validateAccessTokenWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/auth/validateToken';
@@ -474,6 +798,9 @@ class AuthenticationApi {
);
}
/// Validate access token
///
/// Validate the current authorization method is still valid.
Future<ValidateAccessTokenResponseDto?> validateAccessToken() async {
final response = await validateAccessTokenWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
+191 -8
View File
@@ -16,7 +16,9 @@ class DeprecatedApi {
final ApiClient apiClient;
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Note: This method returns the HTTP [Response].
///
@@ -49,7 +51,9 @@ class DeprecatedApi {
);
}
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Parameters:
///
@@ -69,7 +73,184 @@ class DeprecatedApi {
return null;
}
/// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission.
/// Retrieve assets by device ID
///
/// Get all asset of a device that are in the database, ID only.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] deviceId (required):
Future<Response> getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async {
// ignore: prefer_const_declarations
final apiPath = r'/assets/device/{deviceId}'
.replaceAll('{deviceId}', deviceId);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve assets by device ID
///
/// Get all asset of a device that are in the database, ID only.
///
/// Parameters:
///
/// * [String] deviceId (required):
Future<List<String>?> getAllUserAssetsByDeviceId(String deviceId,) async {
final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<String>') as List)
.cast<String>()
.toList(growable: false);
}
return null;
}
/// Get delta sync for user
///
/// Retrieve changed assets since the last sync for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [AssetDeltaSyncDto] assetDeltaSyncDto (required):
Future<Response> getDeltaSyncWithHttpInfo(AssetDeltaSyncDto assetDeltaSyncDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/sync/delta-sync';
// ignore: prefer_final_locals
Object? postBody = assetDeltaSyncDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get delta sync for user
///
/// Retrieve changed assets since the last sync for the authenticated user.
///
/// Parameters:
///
/// * [AssetDeltaSyncDto] assetDeltaSyncDto (required):
Future<AssetDeltaSyncResponseDto?> getDeltaSync(AssetDeltaSyncDto assetDeltaSyncDto,) async {
final response = await getDeltaSyncWithHttpInfo(assetDeltaSyncDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetDeltaSyncResponseDto',) as AssetDeltaSyncResponseDto;
}
return null;
}
/// Get full sync for user
///
/// Retrieve all assets for a full synchronization for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [AssetFullSyncDto] assetFullSyncDto (required):
Future<Response> getFullSyncForUserWithHttpInfo(AssetFullSyncDto assetFullSyncDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/sync/full-sync';
// ignore: prefer_final_locals
Object? postBody = assetFullSyncDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
apiPath,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Get full sync for user
///
/// Retrieve all assets for a full synchronization for the authenticated user.
///
/// Parameters:
///
/// * [AssetFullSyncDto] assetFullSyncDto (required):
Future<List<AssetResponseDto>?> getFullSyncForUser(AssetFullSyncDto assetFullSyncDto,) async {
final response = await getFullSyncForUserWithHttpInfo(assetFullSyncDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
.cast<AssetResponseDto>()
.toList(growable: false);
}
return null;
}
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -105,7 +286,9 @@ class DeprecatedApi {
);
}
/// This property was deprecated in v1.116.0. This endpoint requires the `asset.read` permission.
/// Get random assets
///
/// Retrieve a specified number of random assets for the authenticated user.
///
/// Parameters:
///
@@ -128,9 +311,9 @@ class DeprecatedApi {
return null;
}
/// Replace the asset with new file, without changing its id
/// Replace asset
///
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
/// Replace the asset with new file, without changing its id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -222,9 +405,9 @@ class DeprecatedApi {
);
}
/// Replace the asset with new file, without changing its id
/// Replace asset
///
/// This property was deprecated in v1.142.0. Replace the asset with new file, without changing its id. This endpoint requires the `asset.replace` permission.
/// Replace the asset with new file, without changing its id.
///
/// Parameters:
///
+12 -4
View File
@@ -16,7 +16,9 @@ class DownloadApi {
final ApiClient apiClient;
/// This endpoint requires the `asset.download` permission.
/// Download asset archive
///
/// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint.
///
/// Note: This method returns the HTTP [Response].
///
@@ -59,7 +61,9 @@ class DownloadApi {
);
}
/// This endpoint requires the `asset.download` permission.
/// Download asset archive
///
/// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint.
///
/// Parameters:
///
@@ -83,7 +87,9 @@ class DownloadApi {
return null;
}
/// This endpoint requires the `asset.download` permission.
/// Retrieve download information
///
/// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together.
///
/// Note: This method returns the HTTP [Response].
///
@@ -126,7 +132,9 @@ class DownloadApi {
);
}
/// This endpoint requires the `asset.download` permission.
/// Retrieve download information
///
/// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together.
///
/// Parameters:
///
+18 -6
View File
@@ -16,7 +16,9 @@ class DuplicatesApi {
final ApiClient apiClient;
/// This endpoint requires the `duplicate.delete` permission.
/// Delete a duplicate
///
/// Delete a single duplicate asset specified by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -49,7 +51,9 @@ class DuplicatesApi {
);
}
/// This endpoint requires the `duplicate.delete` permission.
/// Delete a duplicate
///
/// Delete a single duplicate asset specified by its ID.
///
/// Parameters:
///
@@ -61,7 +65,9 @@ class DuplicatesApi {
}
}
/// This endpoint requires the `duplicate.delete` permission.
/// Delete duplicates
///
/// Delete multiple duplicate assets specified by their IDs.
///
/// Note: This method returns the HTTP [Response].
///
@@ -93,7 +99,9 @@ class DuplicatesApi {
);
}
/// This endpoint requires the `duplicate.delete` permission.
/// Delete duplicates
///
/// Delete multiple duplicate assets specified by their IDs.
///
/// Parameters:
///
@@ -105,7 +113,9 @@ class DuplicatesApi {
}
}
/// This endpoint requires the `duplicate.read` permission.
/// Retrieve duplicates
///
/// Retrieve a list of duplicate assets available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAssetDuplicatesWithHttpInfo() async {
@@ -133,7 +143,9 @@ class DuplicatesApi {
);
}
/// This endpoint requires the `duplicate.read` permission.
/// Retrieve duplicates
///
/// Retrieve a list of duplicate assets available to the authenticated user.
Future<List<DuplicateResponseDto>?> getAssetDuplicates() async {
final response = await getAssetDuplicatesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
+24 -8
View File
@@ -16,7 +16,9 @@ class FacesApi {
final ApiClient apiClient;
/// This endpoint requires the `face.create` permission.
/// Create a face
///
/// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class FacesApi {
);
}
/// This endpoint requires the `face.create` permission.
/// Create a face
///
/// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face.
///
/// Parameters:
///
@@ -60,7 +64,9 @@ class FacesApi {
}
}
/// This endpoint requires the `face.delete` permission.
/// Delete a face
///
/// Delete a face identified by the id. Optionally can be force deleted.
///
/// Note: This method returns the HTTP [Response].
///
@@ -95,7 +101,9 @@ class FacesApi {
);
}
/// This endpoint requires the `face.delete` permission.
/// Delete a face
///
/// Delete a face identified by the id. Optionally can be force deleted.
///
/// Parameters:
///
@@ -109,7 +117,9 @@ class FacesApi {
}
}
/// This endpoint requires the `face.read` permission.
/// Retrieve faces for asset
///
/// Retrieve all faces belonging to an asset.
///
/// Note: This method returns the HTTP [Response].
///
@@ -143,7 +153,9 @@ class FacesApi {
);
}
/// This endpoint requires the `face.read` permission.
/// Retrieve faces for asset
///
/// Retrieve all faces belonging to an asset.
///
/// Parameters:
///
@@ -166,7 +178,9 @@ class FacesApi {
return null;
}
/// This endpoint requires the `face.update` permission.
/// Re-assign a face to another person
///
/// Re-assign the face provided in the body to the person identified by the id in the path parameter.
///
/// Note: This method returns the HTTP [Response].
///
@@ -201,7 +215,9 @@ class FacesApi {
);
}
/// This endpoint requires the `face.update` permission.
/// Re-assign a face to another person
///
/// Re-assign the face provided in the body to the person identified by the id in the path parameter.
///
/// Parameters:
///
+33 -21
View File
@@ -16,7 +16,9 @@ class JobsApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `job.create` permission.
/// Create a manual job
///
/// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class JobsApi {
);
}
/// This endpoint is an admin-only route, and requires the `job.create` permission.
/// Create a manual job
///
/// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.
///
/// Parameters:
///
@@ -60,10 +64,12 @@ class JobsApi {
}
}
/// This endpoint is an admin-only route, and requires the `job.read` permission.
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAllJobsStatusWithHttpInfo() async {
Future<Response> getQueuesLegacyWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs';
@@ -88,9 +94,11 @@ class JobsApi {
);
}
/// This endpoint is an admin-only route, and requires the `job.read` permission.
Future<AllJobStatusResponseDto?> getAllJobsStatus() async {
final response = await getAllJobsStatusWithHttpInfo();
/// Retrieve queue counts and status
///
/// Retrieve the counts of the current queue, as well as the current status.
Future<QueuesResponseDto?> getQueuesLegacy() async {
final response = await getQueuesLegacyWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -98,28 +106,30 @@ class JobsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AllJobStatusResponseDto',) as AllJobStatusResponseDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseDto',) as QueuesResponseDto;
}
return null;
}
/// This endpoint is an admin-only route, and requires the `job.create` permission.
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [JobName] id (required):
/// * [QueueName] name (required):
///
/// * [JobCommandDto] jobCommandDto (required):
Future<Response> sendJobCommandWithHttpInfo(JobName id, JobCommandDto jobCommandDto,) async {
/// * [QueueCommandDto] queueCommandDto (required):
Future<Response> runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async {
// ignore: prefer_const_declarations
final apiPath = r'/jobs/{id}'
.replaceAll('{id}', id.toString());
final apiPath = r'/jobs/{name}'
.replaceAll('{name}', name.toString());
// ignore: prefer_final_locals
Object? postBody = jobCommandDto;
Object? postBody = queueCommandDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
@@ -139,15 +149,17 @@ class JobsApi {
);
}
/// This endpoint is an admin-only route, and requires the `job.create` permission.
/// Run jobs
///
/// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.
///
/// Parameters:
///
/// * [JobName] id (required):
/// * [QueueName] name (required):
///
/// * [JobCommandDto] jobCommandDto (required):
Future<JobStatusDto?> sendJobCommand(JobName id, JobCommandDto jobCommandDto,) async {
final response = await sendJobCommandWithHttpInfo(id, jobCommandDto,);
/// * [QueueCommandDto] queueCommandDto (required):
Future<QueueResponseDto?> runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async {
final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -155,7 +167,7 @@ class JobsApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'JobStatusDto',) as JobStatusDto;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto;
}
return null;
+52 -15
View File
@@ -16,7 +16,9 @@ class LibrariesApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `library.create` permission.
/// Create a library
///
/// Create a new external library.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.create` permission.
/// Create a library
///
/// Create a new external library.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class LibrariesApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `library.delete` permission.
/// Delete a library
///
/// Delete an external library by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -101,7 +107,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.delete` permission.
/// Delete a library
///
/// Delete an external library by its ID.
///
/// Parameters:
///
@@ -113,7 +121,9 @@ class LibrariesApi {
}
}
/// This endpoint is an admin-only route, and requires the `library.read` permission.
/// Retrieve libraries
///
/// Retrieve a list of external libraries.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAllLibrariesWithHttpInfo() async {
@@ -141,7 +151,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.read` permission.
/// Retrieve libraries
///
/// Retrieve a list of external libraries.
Future<List<LibraryResponseDto>?> getAllLibraries() async {
final response = await getAllLibrariesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -160,7 +172,9 @@ class LibrariesApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `library.read` permission.
/// Retrieve a library
///
/// Retrieve an external library by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -193,7 +207,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.read` permission.
/// Retrieve a library
///
/// Retrieve an external library by its ID.
///
/// Parameters:
///
@@ -213,7 +229,9 @@ class LibrariesApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `library.statistics` permission.
/// Retrieve library statistics
///
/// Retrieve statistics for a specific external library, including number of videos, images, and storage usage.
///
/// Note: This method returns the HTTP [Response].
///
@@ -246,7 +264,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.statistics` permission.
/// Retrieve library statistics
///
/// Retrieve statistics for a specific external library, including number of videos, images, and storage usage.
///
/// Parameters:
///
@@ -266,7 +286,9 @@ class LibrariesApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `library.update` permission.
/// Scan a library
///
/// Queue a scan for the external library to find and import new assets.
///
/// Note: This method returns the HTTP [Response].
///
@@ -299,7 +321,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.update` permission.
/// Scan a library
///
/// Queue a scan for the external library to find and import new assets.
///
/// Parameters:
///
@@ -311,7 +335,9 @@ class LibrariesApi {
}
}
/// This endpoint is an admin-only route, and requires the `library.update` permission.
/// Update a library
///
/// Update an existing external library.
///
/// Note: This method returns the HTTP [Response].
///
@@ -346,7 +372,9 @@ class LibrariesApi {
);
}
/// This endpoint is an admin-only route, and requires the `library.update` permission.
/// Update a library
///
/// Update an existing external library.
///
/// Parameters:
///
@@ -368,7 +396,12 @@ class LibrariesApi {
return null;
}
/// Performs an HTTP 'POST /libraries/{id}/validate' operation and returns the [Response].
/// Validate library settings
///
/// Validate the settings of an external library.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -400,6 +433,10 @@ class LibrariesApi {
);
}
/// Validate library settings
///
/// Validate the settings of an external library.
///
/// Parameters:
///
/// * [String] id (required):
+37 -19
View File
@@ -16,21 +16,26 @@ class MapApi {
final ApiClient apiClient;
/// Performs an HTTP 'GET /map/markers' operation and returns the [Response].
/// Retrieve map markers
///
/// Retrieve a list of latitude and longitude coordinates for every asset with location data.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [DateTime] fileCreatedAfter:
///
/// * [DateTime] fileCreatedBefore:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] withPartners:
///
/// * [bool] withSharedAlbums:
Future<Response> getMapMarkersWithHttpInfo({ bool? isArchived, bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? withPartners, bool? withSharedAlbums, }) async {
Future<Response> getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/map/markers';
@@ -41,18 +46,18 @@ class MapApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (fileCreatedAfter != null) {
queryParams.addAll(_queryParams('', 'fileCreatedAfter', fileCreatedAfter));
}
if (fileCreatedBefore != null) {
queryParams.addAll(_queryParams('', 'fileCreatedBefore', fileCreatedBefore));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
}
@@ -74,21 +79,25 @@ class MapApi {
);
}
/// Retrieve map markers
///
/// Retrieve a list of latitude and longitude coordinates for every asset with location data.
///
/// Parameters:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [DateTime] fileCreatedAfter:
///
/// * [DateTime] fileCreatedBefore:
///
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
///
/// * [bool] withPartners:
///
/// * [bool] withSharedAlbums:
Future<List<MapMarkerResponseDto>?> getMapMarkers({ bool? isArchived, bool? isFavorite, DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? withPartners, bool? withSharedAlbums, }) async {
final response = await getMapMarkersWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, withPartners: withPartners, withSharedAlbums: withSharedAlbums, );
Future<List<MapMarkerResponseDto>?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async {
final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -105,7 +114,12 @@ class MapApi {
return null;
}
/// Performs an HTTP 'GET /map/reverse-geocode' operation and returns the [Response].
/// Reverse geocode coordinates
///
/// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [double] lat (required):
@@ -139,6 +153,10 @@ class MapApi {
);
}
/// Reverse geocode coordinates
///
/// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates.
///
/// Parameters:
///
/// * [double] lat (required):
+86 -22
View File
@@ -16,7 +16,9 @@ class MemoriesApi {
final ApiClient apiClient;
/// This endpoint requires the `memoryAsset.create` permission.
/// Add assets to a memory
///
/// Add a list of asset IDs to a specific memory.
///
/// Note: This method returns the HTTP [Response].
///
@@ -51,7 +53,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memoryAsset.create` permission.
/// Add assets to a memory
///
/// Add a list of asset IDs to a specific memory.
///
/// Parameters:
///
@@ -76,7 +80,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memory.create` permission.
/// Create a memory
///
/// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory.
///
/// Note: This method returns the HTTP [Response].
///
@@ -108,7 +114,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.create` permission.
/// Create a memory
///
/// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory.
///
/// Parameters:
///
@@ -128,7 +136,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memory.delete` permission.
/// Delete a memory
///
/// Delete a specific memory by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -161,7 +171,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.delete` permission.
/// Delete a memory
///
/// Delete a specific memory by its ID.
///
/// Parameters:
///
@@ -173,7 +185,9 @@ class MemoriesApi {
}
}
/// This endpoint requires the `memory.read` permission.
/// Retrieve a memory
///
/// Retrieve a specific memory by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -206,7 +220,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.read` permission.
/// Retrieve a memory
///
/// Retrieve a specific memory by its ID.
///
/// Parameters:
///
@@ -226,7 +242,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memory.statistics` permission.
/// Retrieve memories statistics
///
/// Retrieve statistics about memories, such as total count and other relevant metrics.
///
/// Note: This method returns the HTTP [Response].
///
@@ -238,8 +256,13 @@ class MemoriesApi {
///
/// * [bool] isTrashed:
///
/// * [MemorySearchOrder] order:
///
/// * [int] size:
/// Number of memories to return
///
/// * [MemoryType] type:
Future<Response> memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async {
Future<Response> memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/memories/statistics';
@@ -259,6 +282,12 @@ class MemoriesApi {
if (isTrashed != null) {
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
}
if (order != null) {
queryParams.addAll(_queryParams('', 'order', order));
}
if (size != null) {
queryParams.addAll(_queryParams('', 'size', size));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
@@ -277,7 +306,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.statistics` permission.
/// Retrieve memories statistics
///
/// Retrieve statistics about memories, such as total count and other relevant metrics.
///
/// Parameters:
///
@@ -287,9 +318,14 @@ class MemoriesApi {
///
/// * [bool] isTrashed:
///
/// * [MemorySearchOrder] order:
///
/// * [int] size:
/// Number of memories to return
///
/// * [MemoryType] type:
Future<MemoryStatisticsResponseDto?> memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async {
final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, type: type, );
Future<MemoryStatisticsResponseDto?> memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -303,7 +339,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memoryAsset.delete` permission.
/// Remove assets from a memory
///
/// Remove a list of asset IDs from a specific memory.
///
/// Note: This method returns the HTTP [Response].
///
@@ -338,7 +376,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memoryAsset.delete` permission.
/// Remove assets from a memory
///
/// Remove a list of asset IDs from a specific memory.
///
/// Parameters:
///
@@ -363,7 +403,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memory.read` permission.
/// Retrieve memories
///
/// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly.
///
/// Note: This method returns the HTTP [Response].
///
@@ -375,8 +417,13 @@ class MemoriesApi {
///
/// * [bool] isTrashed:
///
/// * [MemorySearchOrder] order:
///
/// * [int] size:
/// Number of memories to return
///
/// * [MemoryType] type:
Future<Response> searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async {
Future<Response> searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/memories';
@@ -396,6 +443,12 @@ class MemoriesApi {
if (isTrashed != null) {
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
}
if (order != null) {
queryParams.addAll(_queryParams('', 'order', order));
}
if (size != null) {
queryParams.addAll(_queryParams('', 'size', size));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
@@ -414,7 +467,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.read` permission.
/// Retrieve memories
///
/// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly.
///
/// Parameters:
///
@@ -424,9 +479,14 @@ class MemoriesApi {
///
/// * [bool] isTrashed:
///
/// * [MemorySearchOrder] order:
///
/// * [int] size:
/// Number of memories to return
///
/// * [MemoryType] type:
Future<List<MemoryResponseDto>?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemoryType? type, }) async {
final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, type: type, );
Future<List<MemoryResponseDto>?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async {
final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -443,7 +503,9 @@ class MemoriesApi {
return null;
}
/// This endpoint requires the `memory.update` permission.
/// Update a memory
///
/// Update an existing memory by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -478,7 +540,9 @@ class MemoriesApi {
);
}
/// This endpoint requires the `memory.update` permission.
/// Update a memory
///
/// Update an existing memory by its ID.
///
/// Parameters:
///
+30 -3
View File
@@ -16,7 +16,12 @@ class NotificationsAdminApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /admin/notifications' operation and returns the [Response].
/// Create a notification
///
/// Create a new notification for a specific user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [NotificationCreateDto] notificationCreateDto (required):
@@ -45,6 +50,10 @@ class NotificationsAdminApi {
);
}
/// Create a notification
///
/// Create a new notification for a specific user.
///
/// Parameters:
///
/// * [NotificationCreateDto] notificationCreateDto (required):
@@ -63,7 +72,12 @@ class NotificationsAdminApi {
return null;
}
/// Performs an HTTP 'POST /admin/notifications/templates/{name}' operation and returns the [Response].
/// Render email template
///
/// Retrieve a preview of the provided email template.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] name (required):
@@ -95,6 +109,10 @@ class NotificationsAdminApi {
);
}
/// Render email template
///
/// Retrieve a preview of the provided email template.
///
/// Parameters:
///
/// * [String] name (required):
@@ -115,7 +133,12 @@ class NotificationsAdminApi {
return null;
}
/// Performs an HTTP 'POST /admin/notifications/test-email' operation and returns the [Response].
/// Send test email
///
/// Send a test email using the provided SMTP configuration.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SystemConfigSmtpDto] systemConfigSmtpDto (required):
@@ -144,6 +167,10 @@ class NotificationsAdminApi {
);
}
/// Send test email
///
/// Send a test email using the provided SMTP configuration.
///
/// Parameters:
///
/// * [SystemConfigSmtpDto] systemConfigSmtpDto (required):
+36 -12
View File
@@ -16,7 +16,9 @@ class NotificationsApi {
final ApiClient apiClient;
/// This endpoint requires the `notification.delete` permission.
/// Delete a notification
///
/// Delete a specific notification.
///
/// Note: This method returns the HTTP [Response].
///
@@ -49,7 +51,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.delete` permission.
/// Delete a notification
///
/// Delete a specific notification.
///
/// Parameters:
///
@@ -61,7 +65,9 @@ class NotificationsApi {
}
}
/// This endpoint requires the `notification.delete` permission.
/// Delete notifications
///
/// Delete a list of notifications at once.
///
/// Note: This method returns the HTTP [Response].
///
@@ -93,7 +99,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.delete` permission.
/// Delete notifications
///
/// Delete a list of notifications at once.
///
/// Parameters:
///
@@ -105,7 +113,9 @@ class NotificationsApi {
}
}
/// This endpoint requires the `notification.read` permission.
/// Get a notification
///
/// Retrieve a specific notification identified by id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -138,7 +148,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.read` permission.
/// Get a notification
///
/// Retrieve a specific notification identified by id.
///
/// Parameters:
///
@@ -158,7 +170,9 @@ class NotificationsApi {
return null;
}
/// This endpoint requires the `notification.read` permission.
/// Retrieve notifications
///
/// Retrieve a list of notifications.
///
/// Note: This method returns the HTTP [Response].
///
@@ -209,7 +223,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.read` permission.
/// Retrieve notifications
///
/// Retrieve a list of notifications.
///
/// Parameters:
///
@@ -238,7 +254,9 @@ class NotificationsApi {
return null;
}
/// This endpoint requires the `notification.update` permission.
/// Update a notification
///
/// Update a specific notification to set its read status.
///
/// Note: This method returns the HTTP [Response].
///
@@ -273,7 +291,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.update` permission.
/// Update a notification
///
/// Update a specific notification to set its read status.
///
/// Parameters:
///
@@ -295,7 +315,9 @@ class NotificationsApi {
return null;
}
/// This endpoint requires the `notification.update` permission.
/// Update notifications
///
/// Update a list of notifications. Allows to bulk-set the read status of notifications.
///
/// Note: This method returns the HTTP [Response].
///
@@ -327,7 +349,9 @@ class NotificationsApi {
);
}
/// This endpoint requires the `notification.update` permission.
/// Update notifications
///
/// Update a list of notifications. Allows to bulk-set the read status of notifications.
///
/// Parameters:
///
+30 -10
View File
@@ -16,7 +16,9 @@ class PartnersApi {
final ApiClient apiClient;
/// This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class PartnersApi {
);
}
/// This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class PartnersApi {
return null;
}
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Note: This method returns the HTTP [Response].
///
@@ -101,7 +107,9 @@ class PartnersApi {
);
}
/// This property was deprecated in v1.141.0. This endpoint requires the `partner.create` permission.
/// Create a partner
///
/// Create a new partner to share assets with.
///
/// Parameters:
///
@@ -121,7 +129,9 @@ class PartnersApi {
return null;
}
/// This endpoint requires the `partner.read` permission.
/// Retrieve partners
///
/// Retrieve a list of partners with whom assets are shared.
///
/// Note: This method returns the HTTP [Response].
///
@@ -155,7 +165,9 @@ class PartnersApi {
);
}
/// This endpoint requires the `partner.read` permission.
/// Retrieve partners
///
/// Retrieve a list of partners with whom assets are shared.
///
/// Parameters:
///
@@ -178,7 +190,9 @@ class PartnersApi {
return null;
}
/// This endpoint requires the `partner.delete` permission.
/// Remove a partner
///
/// Stop sharing assets with a partner.
///
/// Note: This method returns the HTTP [Response].
///
@@ -211,7 +225,9 @@ class PartnersApi {
);
}
/// This endpoint requires the `partner.delete` permission.
/// Remove a partner
///
/// Stop sharing assets with a partner.
///
/// Parameters:
///
@@ -223,7 +239,9 @@ class PartnersApi {
}
}
/// This endpoint requires the `partner.update` permission.
/// Update a partner
///
/// Specify whether a partner's assets should appear in the user's timeline.
///
/// Note: This method returns the HTTP [Response].
///
@@ -258,7 +276,9 @@ class PartnersApi {
);
}
/// This endpoint requires the `partner.update` permission.
/// Update a partner
///
/// Specify whether a partner's assets should appear in the user's timeline.
///
/// Parameters:
///
+66 -22
View File
@@ -16,7 +16,9 @@ class PeopleApi {
final ApiClient apiClient;
/// This endpoint requires the `person.create` permission.
/// Create a person
///
/// Create a new person that can have multiple faces assigned to them.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.create` permission.
/// Create a person
///
/// Create a new person that can have multiple faces assigned to them.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.delete` permission.
/// Delete people
///
/// Bulk delete a list of people at once.
///
/// Note: This method returns the HTTP [Response].
///
@@ -100,7 +106,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.delete` permission.
/// Delete people
///
/// Bulk delete a list of people at once.
///
/// Parameters:
///
@@ -112,7 +120,9 @@ class PeopleApi {
}
}
/// This endpoint requires the `person.delete` permission.
/// Delete person
///
/// Delete an individual person.
///
/// Note: This method returns the HTTP [Response].
///
@@ -145,7 +155,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.delete` permission.
/// Delete person
///
/// Delete an individual person.
///
/// Parameters:
///
@@ -157,7 +169,9 @@ class PeopleApi {
}
}
/// This endpoint requires the `person.read` permission.
/// Get all people
///
/// Retrieve a list of all people.
///
/// Note: This method returns the HTTP [Response].
///
@@ -215,7 +229,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.read` permission.
/// Get all people
///
/// Retrieve a list of all people.
///
/// Parameters:
///
@@ -245,7 +261,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.read` permission.
/// Get a person
///
/// Retrieve a person by id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -278,7 +296,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.read` permission.
/// Get a person
///
/// Retrieve a person by id.
///
/// Parameters:
///
@@ -298,7 +318,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.statistics` permission.
/// Get person statistics
///
/// Retrieve statistics about a specific person.
///
/// Note: This method returns the HTTP [Response].
///
@@ -331,7 +353,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.statistics` permission.
/// Get person statistics
///
/// Retrieve statistics about a specific person.
///
/// Parameters:
///
@@ -351,7 +375,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.read` permission.
/// Get person thumbnail
///
/// Retrieve the thumbnail file for a person.
///
/// Note: This method returns the HTTP [Response].
///
@@ -384,7 +410,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.read` permission.
/// Get person thumbnail
///
/// Retrieve the thumbnail file for a person.
///
/// Parameters:
///
@@ -404,7 +432,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.merge` permission.
/// Merge people
///
/// Merge a list of people into the person specified in the path parameter.
///
/// Note: This method returns the HTTP [Response].
///
@@ -439,7 +469,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.merge` permission.
/// Merge people
///
/// Merge a list of people into the person specified in the path parameter.
///
/// Parameters:
///
@@ -464,7 +496,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.reassign` permission.
/// Reassign faces
///
/// Bulk reassign a list of faces to a different person.
///
/// Note: This method returns the HTTP [Response].
///
@@ -499,7 +533,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.reassign` permission.
/// Reassign faces
///
/// Bulk reassign a list of faces to a different person.
///
/// Parameters:
///
@@ -524,7 +560,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.update` permission.
/// Update people
///
/// Bulk update multiple people at once.
///
/// Note: This method returns the HTTP [Response].
///
@@ -556,7 +594,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.update` permission.
/// Update people
///
/// Bulk update multiple people at once.
///
/// Parameters:
///
@@ -579,7 +619,9 @@ class PeopleApi {
return null;
}
/// This endpoint requires the `person.update` permission.
/// Update person
///
/// Update an individual person.
///
/// Note: This method returns the HTTP [Response].
///
@@ -614,7 +656,9 @@ class PeopleApi {
);
}
/// This endpoint requires the `person.update` permission.
/// Update person
///
/// Update an individual person.
///
/// Parameters:
///
+126
View File
@@ -0,0 +1,126 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class PluginsApi {
PluginsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Retrieve a plugin
///
/// Retrieve information about a specific plugin by its ID.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
Future<Response> getPluginWithHttpInfo(String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/plugins/{id}'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Retrieve a plugin
///
/// Retrieve information about a specific plugin by its ID.
///
/// Parameters:
///
/// * [String] id (required):
Future<PluginResponseDto?> getPlugin(String id,) async {
final response = await getPluginWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PluginResponseDto',) as PluginResponseDto;
}
return null;
}
/// List all plugins
///
/// Retrieve a list of plugins available to the authenticated user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getPluginsWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/plugins';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// List all plugins
///
/// Retrieve a list of plugins available to the authenticated user.
Future<List<PluginResponseDto>?> getPlugins() async {
final response = await getPluginsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<PluginResponseDto>') as List)
.cast<PluginResponseDto>()
.toList(growable: false);
}
return null;
}
}
+60 -22
View File
@@ -16,7 +16,9 @@ class SearchApi {
final ApiClient apiClient;
/// This endpoint requires the `asset.read` permission.
/// Retrieve assets by city
///
/// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAssetsByCityWithHttpInfo() async {
@@ -44,7 +46,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve assets by city
///
/// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.
Future<List<AssetResponseDto>?> getAssetsByCity() async {
final response = await getAssetsByCityWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -63,7 +67,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve explore data
///
/// Retrieve data for the explore section, such as popular people and places.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getExploreDataWithHttpInfo() async {
@@ -91,7 +97,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve explore data
///
/// Retrieve data for the explore section, such as popular people and places.
Future<List<SearchExploreResponseDto>?> getExploreData() async {
final response = await getExploreDataWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -110,7 +118,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve search suggestions
///
/// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features.
///
/// Note: This method returns the HTTP [Response].
///
@@ -121,7 +131,6 @@ class SearchApi {
/// * [String] country:
///
/// * [bool] includeNull:
/// This property was added in v111.0.0
///
/// * [String] lensModel:
///
@@ -175,7 +184,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Retrieve search suggestions
///
/// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features.
///
/// Parameters:
///
@@ -184,7 +195,6 @@ class SearchApi {
/// * [String] country:
///
/// * [bool] includeNull:
/// This property was added in v111.0.0
///
/// * [String] lensModel:
///
@@ -211,7 +221,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.statistics` permission.
/// Search asset statistics
///
/// Retrieve statistical data about assets based on search criteria, such as the total matching count.
///
/// Note: This method returns the HTTP [Response].
///
@@ -243,7 +255,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.statistics` permission.
/// Search asset statistics
///
/// Retrieve statistical data about assets based on search criteria, such as the total matching count.
///
/// Parameters:
///
@@ -263,7 +277,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Search assets by metadata
///
/// Search for assets based on various metadata criteria.
///
/// Note: This method returns the HTTP [Response].
///
@@ -295,7 +311,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Search assets by metadata
///
/// Search for assets based on various metadata criteria.
///
/// Parameters:
///
@@ -315,7 +333,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Search large assets
///
/// Search for assets that are considered large based on specified criteria.
///
/// Note: This method returns the HTTP [Response].
///
@@ -506,7 +526,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Search large assets
///
/// Search for assets that are considered large based on specified criteria.
///
/// Parameters:
///
@@ -591,7 +613,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `person.read` permission.
/// Search people
///
/// Search for people by name.
///
/// Note: This method returns the HTTP [Response].
///
@@ -630,7 +654,9 @@ class SearchApi {
);
}
/// This endpoint requires the `person.read` permission.
/// Search people
///
/// Search for people by name.
///
/// Parameters:
///
@@ -655,7 +681,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Search places
///
/// Search for places by name.
///
/// Note: This method returns the HTTP [Response].
///
@@ -689,7 +717,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Search places
///
/// Search for places by name.
///
/// Parameters:
///
@@ -712,7 +742,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Search random assets
///
/// Retrieve a random selection of assets based on the provided criteria.
///
/// Note: This method returns the HTTP [Response].
///
@@ -744,7 +776,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Search random assets
///
/// Retrieve a random selection of assets based on the provided criteria.
///
/// Parameters:
///
@@ -767,7 +801,9 @@ class SearchApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Smart asset search
///
/// Perform a smart search for assets by using machine learning vectors to determine relevance.
///
/// Note: This method returns the HTTP [Response].
///
@@ -799,7 +835,9 @@ class SearchApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Smart asset search
///
/// Perform a smart search for assets by using machine learning vectors to determine relevance.
///
/// Parameters:
///
+104 -23
View File
@@ -16,7 +16,9 @@ class ServerApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `serverLicense.delete` permission.
/// Delete server product key
///
/// Delete the currently set server product key.
///
/// Note: This method returns the HTTP [Response].
Future<Response> deleteServerLicenseWithHttpInfo() async {
@@ -44,7 +46,9 @@ class ServerApi {
);
}
/// This endpoint is an admin-only route, and requires the `serverLicense.delete` permission.
/// Delete server product key
///
/// Delete the currently set server product key.
Future<void> deleteServerLicense() async {
final response = await deleteServerLicenseWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -52,7 +56,9 @@ class ServerApi {
}
}
/// This endpoint requires the `server.about` permission.
/// Get server information
///
/// Retrieve a list of information about the server.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAboutInfoWithHttpInfo() async {
@@ -80,7 +86,9 @@ class ServerApi {
);
}
/// This endpoint requires the `server.about` permission.
/// Get server information
///
/// Retrieve a list of information about the server.
Future<ServerAboutResponseDto?> getAboutInfo() async {
final response = await getAboutInfoWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -96,7 +104,9 @@ class ServerApi {
return null;
}
/// This endpoint requires the `server.apkLinks` permission.
/// Get APK links
///
/// Retrieve links to the APKs for the current server version.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getApkLinksWithHttpInfo() async {
@@ -124,7 +134,9 @@ class ServerApi {
);
}
/// This endpoint requires the `server.apkLinks` permission.
/// Get APK links
///
/// Retrieve links to the APKs for the current server version.
Future<ServerApkLinksDto?> getApkLinks() async {
final response = await getApkLinksWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -140,7 +152,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/config' operation and returns the [Response].
/// Get config
///
/// Retrieve the current server configuration.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerConfigWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/config';
@@ -166,6 +182,9 @@ class ServerApi {
);
}
/// Get config
///
/// Retrieve the current server configuration.
Future<ServerConfigDto?> getServerConfig() async {
final response = await getServerConfigWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -181,7 +200,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/features' operation and returns the [Response].
/// Get features
///
/// Retrieve available features supported by this server.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerFeaturesWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/features';
@@ -207,6 +230,9 @@ class ServerApi {
);
}
/// Get features
///
/// Retrieve available features supported by this server.
Future<ServerFeaturesDto?> getServerFeatures() async {
final response = await getServerFeaturesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -222,7 +248,9 @@ class ServerApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `serverLicense.read` permission.
/// Get product key
///
/// Retrieve information about whether the server currently has a product key registered.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerLicenseWithHttpInfo() async {
@@ -250,7 +278,9 @@ class ServerApi {
);
}
/// This endpoint is an admin-only route, and requires the `serverLicense.read` permission.
/// Get product key
///
/// Retrieve information about whether the server currently has a product key registered.
Future<LicenseResponseDto?> getServerLicense() async {
final response = await getServerLicenseWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -266,7 +296,9 @@ class ServerApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `server.statistics` permission.
/// Get statistics
///
/// Retrieve statistics about the entire Immich instance such as asset counts.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerStatisticsWithHttpInfo() async {
@@ -294,7 +326,9 @@ class ServerApi {
);
}
/// This endpoint is an admin-only route, and requires the `server.statistics` permission.
/// Get statistics
///
/// Retrieve statistics about the entire Immich instance such as asset counts.
Future<ServerStatsResponseDto?> getServerStatistics() async {
final response = await getServerStatisticsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -310,7 +344,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/version' operation and returns the [Response].
/// Get server version
///
/// Retrieve the current server version in semantic versioning (semver) format.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerVersionWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/version';
@@ -336,6 +374,9 @@ class ServerApi {
);
}
/// Get server version
///
/// Retrieve the current server version in semantic versioning (semver) format.
Future<ServerVersionResponseDto?> getServerVersion() async {
final response = await getServerVersionWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -351,7 +392,9 @@ class ServerApi {
return null;
}
/// This endpoint requires the `server.storage` permission.
/// Get storage
///
/// Retrieve the current storage utilization information of the server.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getStorageWithHttpInfo() async {
@@ -379,7 +422,9 @@ class ServerApi {
);
}
/// This endpoint requires the `server.storage` permission.
/// Get storage
///
/// Retrieve the current storage utilization information of the server.
Future<ServerStorageResponseDto?> getStorage() async {
final response = await getStorageWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -395,7 +440,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/media-types' operation and returns the [Response].
/// Get supported media types
///
/// Retrieve all media types supported by the server.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getSupportedMediaTypesWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/media-types';
@@ -421,6 +470,9 @@ class ServerApi {
);
}
/// Get supported media types
///
/// Retrieve all media types supported by the server.
Future<ServerMediaTypesResponseDto?> getSupportedMediaTypes() async {
final response = await getSupportedMediaTypesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -436,7 +488,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/theme' operation and returns the [Response].
/// Get theme
///
/// Retrieve the custom CSS, if existent.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getThemeWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/theme';
@@ -462,6 +518,9 @@ class ServerApi {
);
}
/// Get theme
///
/// Retrieve the custom CSS, if existent.
Future<ServerThemeDto?> getTheme() async {
final response = await getThemeWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -477,7 +536,9 @@ class ServerApi {
return null;
}
/// This endpoint requires the `server.versionCheck` permission.
/// Get version check status
///
/// Retrieve information about the last time the version check ran.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getVersionCheckWithHttpInfo() async {
@@ -505,7 +566,9 @@ class ServerApi {
);
}
/// This endpoint requires the `server.versionCheck` permission.
/// Get version check status
///
/// Retrieve information about the last time the version check ran.
Future<VersionCheckStateResponseDto?> getVersionCheck() async {
final response = await getVersionCheckWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -521,7 +584,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/version-history' operation and returns the [Response].
/// Get version history
///
/// Retrieve a list of past versions the server has been on.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getVersionHistoryWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/version-history';
@@ -547,6 +614,9 @@ class ServerApi {
);
}
/// Get version history
///
/// Retrieve a list of past versions the server has been on.
Future<List<ServerVersionHistoryResponseDto>?> getVersionHistory() async {
final response = await getVersionHistoryWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -565,7 +635,11 @@ class ServerApi {
return null;
}
/// Performs an HTTP 'GET /server/ping' operation and returns the [Response].
/// Ping
///
/// Pong
///
/// Note: This method returns the HTTP [Response].
Future<Response> pingServerWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/server/ping';
@@ -591,6 +665,9 @@ class ServerApi {
);
}
/// Ping
///
/// Pong
Future<ServerPingResponse?> pingServer() async {
final response = await pingServerWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -606,7 +683,9 @@ class ServerApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `serverLicense.update` permission.
/// Set server product key
///
/// Validate and set the server product key if successful.
///
/// Note: This method returns the HTTP [Response].
///
@@ -638,7 +717,9 @@ class ServerApi {
);
}
/// This endpoint is an admin-only route, and requires the `serverLicense.update` permission.
/// Set server product key
///
/// Validate and set the server product key if successful.
///
/// Parameters:
///
+36 -12
View File
@@ -16,7 +16,9 @@ class SessionsApi {
final ApiClient apiClient;
/// This endpoint requires the `session.create` permission.
/// Create a session
///
/// Create a session as a child to the current session. This endpoint is used for casting.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.create` permission.
/// Create a session
///
/// Create a session as a child to the current session. This endpoint is used for casting.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class SessionsApi {
return null;
}
/// This endpoint requires the `session.delete` permission.
/// Delete all sessions
///
/// Delete all sessions for the user. This will not delete the current session.
///
/// Note: This method returns the HTTP [Response].
Future<Response> deleteAllSessionsWithHttpInfo() async {
@@ -96,7 +102,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.delete` permission.
/// Delete all sessions
///
/// Delete all sessions for the user. This will not delete the current session.
Future<void> deleteAllSessions() async {
final response = await deleteAllSessionsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -104,7 +112,9 @@ class SessionsApi {
}
}
/// This endpoint requires the `session.delete` permission.
/// Delete a session
///
/// Delete a specific session by id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -137,7 +147,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.delete` permission.
/// Delete a session
///
/// Delete a specific session by id.
///
/// Parameters:
///
@@ -149,7 +161,9 @@ class SessionsApi {
}
}
/// This endpoint requires the `session.read` permission.
/// Retrieve sessions
///
/// Retrieve a list of sessions for the user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getSessionsWithHttpInfo() async {
@@ -177,7 +191,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.read` permission.
/// Retrieve sessions
///
/// Retrieve a list of sessions for the user.
Future<List<SessionResponseDto>?> getSessions() async {
final response = await getSessionsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -196,7 +212,9 @@ class SessionsApi {
return null;
}
/// This endpoint requires the `session.lock` permission.
/// Lock a session
///
/// Lock a specific session by id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -229,7 +247,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.lock` permission.
/// Lock a session
///
/// Lock a specific session by id.
///
/// Parameters:
///
@@ -241,7 +261,9 @@ class SessionsApi {
}
}
/// This endpoint requires the `session.update` permission.
/// Update a session
///
/// Update a specific session identified by id.
///
/// Note: This method returns the HTTP [Response].
///
@@ -276,7 +298,9 @@ class SessionsApi {
);
}
/// This endpoint requires the `session.update` permission.
/// Update a session
///
/// Update a specific session identified by id.
///
/// Parameters:
///
+77 -30
View File
@@ -16,7 +16,12 @@ class SharedLinksApi {
final ApiClient apiClient;
/// Performs an HTTP 'PUT /shared-links/{id}/assets' operation and returns the [Response].
/// Add assets to a shared link
///
/// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -59,6 +64,10 @@ class SharedLinksApi {
);
}
/// Add assets to a shared link
///
/// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.
///
/// Parameters:
///
/// * [String] id (required):
@@ -86,7 +95,9 @@ class SharedLinksApi {
return null;
}
/// This endpoint requires the `sharedLink.create` permission.
/// Create a shared link
///
/// Create a new shared link.
///
/// Note: This method returns the HTTP [Response].
///
@@ -118,7 +129,9 @@ class SharedLinksApi {
);
}
/// This endpoint requires the `sharedLink.create` permission.
/// Create a shared link
///
/// Create a new shared link.
///
/// Parameters:
///
@@ -138,7 +151,9 @@ class SharedLinksApi {
return null;
}
/// This endpoint requires the `sharedLink.read` permission.
/// Retrieve all shared links
///
/// Retrieve a list of all shared links.
///
/// Note: This method returns the HTTP [Response].
///
@@ -174,7 +189,9 @@ class SharedLinksApi {
);
}
/// This endpoint requires the `sharedLink.read` permission.
/// Retrieve all shared links
///
/// Retrieve a list of all shared links.
///
/// Parameters:
///
@@ -197,17 +214,22 @@ class SharedLinksApi {
return null;
}
/// Performs an HTTP 'GET /shared-links/me' operation and returns the [Response].
/// Retrieve current shared link
///
/// Retrieve the current shared link associated with authentication method.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] password:
///
/// * [String] token:
///
/// * [String] key:
///
/// * [String] password:
///
/// * [String] slug:
Future<Response> getMySharedLinkWithHttpInfo({ String? password, String? token, String? key, String? slug, }) async {
///
/// * [String] token:
Future<Response> getMySharedLinkWithHttpInfo({ String? key, String? password, String? slug, String? token, }) async {
// ignore: prefer_const_declarations
final apiPath = r'/shared-links/me';
@@ -218,18 +240,18 @@ class SharedLinksApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (password != null) {
queryParams.addAll(_queryParams('', 'password', password));
}
if (token != null) {
queryParams.addAll(_queryParams('', 'token', token));
}
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
if (password != null) {
queryParams.addAll(_queryParams('', 'password', password));
}
if (slug != null) {
queryParams.addAll(_queryParams('', 'slug', slug));
}
if (token != null) {
queryParams.addAll(_queryParams('', 'token', token));
}
const contentTypes = <String>[];
@@ -245,17 +267,21 @@ class SharedLinksApi {
);
}
/// Retrieve current shared link
///
/// Retrieve the current shared link associated with authentication method.
///
/// Parameters:
///
/// * [String] password:
///
/// * [String] token:
///
/// * [String] key:
///
/// * [String] password:
///
/// * [String] slug:
Future<SharedLinkResponseDto?> getMySharedLink({ String? password, String? token, String? key, String? slug, }) async {
final response = await getMySharedLinkWithHttpInfo( password: password, token: token, key: key, slug: slug, );
///
/// * [String] token:
Future<SharedLinkResponseDto?> getMySharedLink({ String? key, String? password, String? slug, String? token, }) async {
final response = await getMySharedLinkWithHttpInfo( key: key, password: password, slug: slug, token: token, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -269,7 +295,9 @@ class SharedLinksApi {
return null;
}
/// This endpoint requires the `sharedLink.read` permission.
/// Retrieve a shared link
///
/// Retrieve a specific shared link by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -302,7 +330,9 @@ class SharedLinksApi {
);
}
/// This endpoint requires the `sharedLink.read` permission.
/// Retrieve a shared link
///
/// Retrieve a specific shared link by its ID.
///
/// Parameters:
///
@@ -322,7 +352,9 @@ class SharedLinksApi {
return null;
}
/// This endpoint requires the `sharedLink.delete` permission.
/// Delete a shared link
///
/// Delete a specific shared link by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -355,7 +387,9 @@ class SharedLinksApi {
);
}
/// This endpoint requires the `sharedLink.delete` permission.
/// Delete a shared link
///
/// Delete a specific shared link by its ID.
///
/// Parameters:
///
@@ -367,7 +401,12 @@ class SharedLinksApi {
}
}
/// Performs an HTTP 'DELETE /shared-links/{id}/assets' operation and returns the [Response].
/// Remove assets from a shared link
///
/// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -410,6 +449,10 @@ class SharedLinksApi {
);
}
/// Remove assets from a shared link
///
/// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.
///
/// Parameters:
///
/// * [String] id (required):
@@ -437,7 +480,9 @@ class SharedLinksApi {
return null;
}
/// This endpoint requires the `sharedLink.update` permission.
/// Update a shared link
///
/// Update an existing shared link by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -472,7 +517,9 @@ class SharedLinksApi {
);
}
/// This endpoint requires the `sharedLink.update` permission.
/// Update a shared link
///
/// Update an existing shared link by its ID.
///
/// Parameters:
///
+42 -14
View File
@@ -16,7 +16,9 @@ class StacksApi {
final ApiClient apiClient;
/// This endpoint requires the `stack.create` permission.
/// Create a stack
///
/// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.create` permission.
/// Create a stack
///
/// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class StacksApi {
return null;
}
/// This endpoint requires the `stack.delete` permission.
/// Delete a stack
///
/// Delete a specific stack by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -101,7 +107,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.delete` permission.
/// Delete a stack
///
/// Delete a specific stack by its ID.
///
/// Parameters:
///
@@ -113,7 +121,9 @@ class StacksApi {
}
}
/// This endpoint requires the `stack.delete` permission.
/// Delete stacks
///
/// Delete multiple stacks by providing a list of stack IDs.
///
/// Note: This method returns the HTTP [Response].
///
@@ -145,7 +155,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.delete` permission.
/// Delete stacks
///
/// Delete multiple stacks by providing a list of stack IDs.
///
/// Parameters:
///
@@ -157,7 +169,9 @@ class StacksApi {
}
}
/// This endpoint requires the `stack.read` permission.
/// Retrieve a stack
///
/// Retrieve a specific stack by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -190,7 +204,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.read` permission.
/// Retrieve a stack
///
/// Retrieve a specific stack by its ID.
///
/// Parameters:
///
@@ -210,7 +226,9 @@ class StacksApi {
return null;
}
/// This endpoint requires the `stack.update` permission.
/// Remove an asset from a stack
///
/// Remove a specific asset from a stack by providing the stack ID and asset ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -246,7 +264,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.update` permission.
/// Remove an asset from a stack
///
/// Remove a specific asset from a stack by providing the stack ID and asset ID.
///
/// Parameters:
///
@@ -260,7 +280,9 @@ class StacksApi {
}
}
/// This endpoint requires the `stack.read` permission.
/// Retrieve stacks
///
/// Retrieve a list of stacks.
///
/// Note: This method returns the HTTP [Response].
///
@@ -296,7 +318,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.read` permission.
/// Retrieve stacks
///
/// Retrieve a list of stacks.
///
/// Parameters:
///
@@ -319,7 +343,9 @@ class StacksApi {
return null;
}
/// This endpoint requires the `stack.update` permission.
/// Update a stack
///
/// Update an existing stack by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -354,7 +380,9 @@ class StacksApi {
);
}
/// This endpoint requires the `stack.update` permission.
/// Update a stack
///
/// Update an existing stack by its ID.
///
/// Parameters:
///
+44 -10
View File
@@ -16,7 +16,9 @@ class SyncApi {
final ApiClient apiClient;
/// This endpoint requires the `syncCheckpoint.delete` permission.
/// Delete acknowledgements
///
/// Delete specific synchronization acknowledgments.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class SyncApi {
);
}
/// This endpoint requires the `syncCheckpoint.delete` permission.
/// Delete acknowledgements
///
/// Delete specific synchronization acknowledgments.
///
/// Parameters:
///
@@ -60,7 +64,12 @@ class SyncApi {
}
}
/// Performs an HTTP 'POST /sync/delta-sync' operation and returns the [Response].
/// Get delta sync for user
///
/// Retrieve changed assets since the last sync for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [AssetDeltaSyncDto] assetDeltaSyncDto (required):
@@ -89,6 +98,10 @@ class SyncApi {
);
}
/// Get delta sync for user
///
/// Retrieve changed assets since the last sync for the authenticated user.
///
/// Parameters:
///
/// * [AssetDeltaSyncDto] assetDeltaSyncDto (required):
@@ -107,7 +120,12 @@ class SyncApi {
return null;
}
/// Performs an HTTP 'POST /sync/full-sync' operation and returns the [Response].
/// Get full sync for user
///
/// Retrieve all assets for a full synchronization for the authenticated user.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [AssetFullSyncDto] assetFullSyncDto (required):
@@ -136,6 +154,10 @@ class SyncApi {
);
}
/// Get full sync for user
///
/// Retrieve all assets for a full synchronization for the authenticated user.
///
/// Parameters:
///
/// * [AssetFullSyncDto] assetFullSyncDto (required):
@@ -157,7 +179,9 @@ class SyncApi {
return null;
}
/// This endpoint requires the `syncCheckpoint.read` permission.
/// Retrieve acknowledgements
///
/// Retrieve the synchronization acknowledgments for the current session.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getSyncAckWithHttpInfo() async {
@@ -185,7 +209,9 @@ class SyncApi {
);
}
/// This endpoint requires the `syncCheckpoint.read` permission.
/// Retrieve acknowledgements
///
/// Retrieve the synchronization acknowledgments for the current session.
Future<List<SyncAckDto>?> getSyncAck() async {
final response = await getSyncAckWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -204,7 +230,9 @@ class SyncApi {
return null;
}
/// This endpoint requires the `sync.stream` permission.
/// Stream sync changes
///
/// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes.
///
/// Note: This method returns the HTTP [Response].
///
@@ -236,7 +264,9 @@ class SyncApi {
);
}
/// This endpoint requires the `sync.stream` permission.
/// Stream sync changes
///
/// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes.
///
/// Parameters:
///
@@ -248,7 +278,9 @@ class SyncApi {
}
}
/// This endpoint requires the `syncCheckpoint.update` permission.
/// Acknowledge changes
///
/// Send a list of synchronization acknowledgements to confirm that the latest changes have been received.
///
/// Note: This method returns the HTTP [Response].
///
@@ -280,7 +312,9 @@ class SyncApi {
);
}
/// This endpoint requires the `syncCheckpoint.update` permission.
/// Acknowledge changes
///
/// Send a list of synchronization acknowledgements to confirm that the latest changes have been received.
///
/// Parameters:
///
+24 -8
View File
@@ -16,7 +16,9 @@ class SystemConfigApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get system configuration
///
/// Retrieve the current system configuration.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getConfigWithHttpInfo() async {
@@ -44,7 +46,9 @@ class SystemConfigApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get system configuration
///
/// Retrieve the current system configuration.
Future<SystemConfigDto?> getConfig() async {
final response = await getConfigWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -60,7 +64,9 @@ class SystemConfigApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get system configuration defaults
///
/// Retrieve the default values for the system configuration.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getConfigDefaultsWithHttpInfo() async {
@@ -88,7 +94,9 @@ class SystemConfigApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get system configuration defaults
///
/// Retrieve the default values for the system configuration.
Future<SystemConfigDto?> getConfigDefaults() async {
final response = await getConfigDefaultsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -104,7 +112,9 @@ class SystemConfigApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get storage template options
///
/// Retrieve exemplary storage template options.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getStorageTemplateOptionsWithHttpInfo() async {
@@ -132,7 +142,9 @@ class SystemConfigApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemConfig.read` permission.
/// Get storage template options
///
/// Retrieve exemplary storage template options.
Future<SystemConfigTemplateStorageOptionDto?> getStorageTemplateOptions() async {
final response = await getStorageTemplateOptionsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -148,7 +160,9 @@ class SystemConfigApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemConfig.update` permission.
/// Update system configuration
///
/// Update the system configuration with a new system configuration.
///
/// Note: This method returns the HTTP [Response].
///
@@ -180,7 +194,9 @@ class SystemConfigApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemConfig.update` permission.
/// Update system configuration
///
/// Update the system configuration with a new system configuration.
///
/// Parameters:
///
+24 -8
View File
@@ -16,7 +16,9 @@ class SystemMetadataApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve admin onboarding
///
/// Retrieve the current admin onboarding status.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAdminOnboardingWithHttpInfo() async {
@@ -44,7 +46,9 @@ class SystemMetadataApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve admin onboarding
///
/// Retrieve the current admin onboarding status.
Future<AdminOnboardingUpdateDto?> getAdminOnboarding() async {
final response = await getAdminOnboardingWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -60,7 +64,9 @@ class SystemMetadataApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve reverse geocoding state
///
/// Retrieve the current state of the reverse geocoding import.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getReverseGeocodingStateWithHttpInfo() async {
@@ -88,7 +94,9 @@ class SystemMetadataApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve reverse geocoding state
///
/// Retrieve the current state of the reverse geocoding import.
Future<ReverseGeocodingStateResponseDto?> getReverseGeocodingState() async {
final response = await getReverseGeocodingStateWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -104,7 +112,9 @@ class SystemMetadataApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve version check state
///
/// Retrieve the current state of the version check process.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getVersionCheckStateWithHttpInfo() async {
@@ -132,7 +142,9 @@ class SystemMetadataApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.read` permission.
/// Retrieve version check state
///
/// Retrieve the current state of the version check process.
Future<VersionCheckStateResponseDto?> getVersionCheckState() async {
final response = await getVersionCheckStateWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -148,7 +160,9 @@ class SystemMetadataApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.update` permission.
/// Update admin onboarding
///
/// Update the admin onboarding status.
///
/// Note: This method returns the HTTP [Response].
///
@@ -180,7 +194,9 @@ class SystemMetadataApi {
);
}
/// This endpoint is an admin-only route, and requires the `systemMetadata.update` permission.
/// Update admin onboarding
///
/// Update the admin onboarding status.
///
/// Parameters:
///
+54 -18
View File
@@ -16,7 +16,9 @@ class TagsApi {
final ApiClient apiClient;
/// This endpoint requires the `tag.asset` permission.
/// Tag assets
///
/// Add multiple tags to multiple assets in a single request.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.asset` permission.
/// Tag assets
///
/// Add multiple tags to multiple assets in a single request.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.create` permission.
/// Create a tag
///
/// Create a new tag by providing a name and optional color.
///
/// Note: This method returns the HTTP [Response].
///
@@ -100,7 +106,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.create` permission.
/// Create a tag
///
/// Create a new tag by providing a name and optional color.
///
/// Parameters:
///
@@ -120,7 +128,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.delete` permission.
/// Delete a tag
///
/// Delete a specific tag by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -153,7 +163,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.delete` permission.
/// Delete a tag
///
/// Delete a specific tag by its ID.
///
/// Parameters:
///
@@ -165,7 +177,9 @@ class TagsApi {
}
}
/// This endpoint requires the `tag.read` permission.
/// Retrieve tags
///
/// Retrieve a list of all tags.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAllTagsWithHttpInfo() async {
@@ -193,7 +207,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.read` permission.
/// Retrieve tags
///
/// Retrieve a list of all tags.
Future<List<TagResponseDto>?> getAllTags() async {
final response = await getAllTagsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -212,7 +228,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.read` permission.
/// Retrieve a tag
///
/// Retrieve a specific tag by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -245,7 +263,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.read` permission.
/// Retrieve a tag
///
/// Retrieve a specific tag by its ID.
///
/// Parameters:
///
@@ -265,7 +285,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.asset` permission.
/// Tag assets
///
/// Add a tag to all the specified assets.
///
/// Note: This method returns the HTTP [Response].
///
@@ -300,7 +322,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.asset` permission.
/// Tag assets
///
/// Add a tag to all the specified assets.
///
/// Parameters:
///
@@ -325,7 +349,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.asset` permission.
/// Untag assets
///
/// Remove a tag from all the specified assets.
///
/// Note: This method returns the HTTP [Response].
///
@@ -360,7 +386,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.asset` permission.
/// Untag assets
///
/// Remove a tag from all the specified assets.
///
/// Parameters:
///
@@ -385,7 +413,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.update` permission.
/// Update a tag
///
/// Update an existing tag identified by its ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -420,7 +450,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.update` permission.
/// Update a tag
///
/// Update an existing tag identified by its ID.
///
/// Parameters:
///
@@ -442,7 +474,9 @@ class TagsApi {
return null;
}
/// This endpoint requires the `tag.create` permission.
/// Upsert tags
///
/// Create or update multiple tags in a single request.
///
/// Note: This method returns the HTTP [Response].
///
@@ -474,7 +508,9 @@ class TagsApi {
);
}
/// This endpoint requires the `tag.create` permission.
/// Upsert tags
///
/// Create or update multiple tags in a single request.
///
/// Parameters:
///
+12 -4
View File
@@ -16,7 +16,9 @@ class TimelineApi {
final ApiClient apiClient;
/// This endpoint requires the `asset.read` permission.
/// Get time bucket
///
/// Retrieve a string of all asset ids in a given time bucket.
///
/// Note: This method returns the HTTP [Response].
///
@@ -127,7 +129,9 @@ class TimelineApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Get time bucket
///
/// Retrieve a string of all asset ids in a given time bucket.
///
/// Parameters:
///
@@ -185,7 +189,9 @@ class TimelineApi {
return null;
}
/// This endpoint requires the `asset.read` permission.
/// Get time buckets
///
/// Retrieve a list of all minimal time buckets.
///
/// Note: This method returns the HTTP [Response].
///
@@ -292,7 +298,9 @@ class TimelineApi {
);
}
/// This endpoint requires the `asset.read` permission.
/// Get time buckets
///
/// Retrieve a list of all minimal time buckets.
///
/// Parameters:
///
+18 -6
View File
@@ -16,7 +16,9 @@ class TrashApi {
final ApiClient apiClient;
/// This endpoint requires the `asset.delete` permission.
/// Empty trash
///
/// Permanently delete all items in the trash.
///
/// Note: This method returns the HTTP [Response].
Future<Response> emptyTrashWithHttpInfo() async {
@@ -44,7 +46,9 @@ class TrashApi {
);
}
/// This endpoint requires the `asset.delete` permission.
/// Empty trash
///
/// Permanently delete all items in the trash.
Future<TrashResponseDto?> emptyTrash() async {
final response = await emptyTrashWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -60,7 +64,9 @@ class TrashApi {
return null;
}
/// This endpoint requires the `asset.delete` permission.
/// Restore assets
///
/// Restore specific assets from the trash.
///
/// Note: This method returns the HTTP [Response].
///
@@ -92,7 +98,9 @@ class TrashApi {
);
}
/// This endpoint requires the `asset.delete` permission.
/// Restore assets
///
/// Restore specific assets from the trash.
///
/// Parameters:
///
@@ -112,7 +120,9 @@ class TrashApi {
return null;
}
/// This endpoint requires the `asset.delete` permission.
/// Restore trash
///
/// Restore all items in the trash.
///
/// Note: This method returns the HTTP [Response].
Future<Response> restoreTrashWithHttpInfo() async {
@@ -140,7 +150,9 @@ class TrashApi {
);
}
/// This endpoint requires the `asset.delete` permission.
/// Restore trash
///
/// Restore all items in the trash.
Future<TrashResponseDto?> restoreTrash() async {
final response = await restoreTrashWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
+60 -20
View File
@@ -16,7 +16,9 @@ class UsersAdminApi {
final ApiClient apiClient;
/// This endpoint is an admin-only route, and requires the `adminUser.create` permission.
/// Create a user
///
/// Create a new user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -48,7 +50,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.create` permission.
/// Create a user
///
/// Create a new user.
///
/// Parameters:
///
@@ -68,7 +72,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.delete` permission.
/// Delete a user
///
/// Delete a user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -103,7 +109,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.delete` permission.
/// Delete a user
///
/// Delete a user.
///
/// Parameters:
///
@@ -125,7 +133,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve a user
///
/// Retrieve a specific user by their ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -158,7 +168,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve a user
///
/// Retrieve a specific user by their ID.
///
/// Parameters:
///
@@ -178,7 +190,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve user preferences
///
/// Retrieve the preferences of a specific user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -211,7 +225,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve user preferences
///
/// Retrieve the preferences of a specific user.
///
/// Parameters:
///
@@ -231,7 +247,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminSession.read` permission.
/// Retrieve user sessions
///
/// Retrieve all sessions for a specific user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -264,7 +282,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminSession.read` permission.
/// Retrieve user sessions
///
/// Retrieve all sessions for a specific user.
///
/// Parameters:
///
@@ -287,7 +307,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve user statistics
///
/// Retrieve asset statistics for a specific user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -336,7 +358,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Retrieve user statistics
///
/// Retrieve asset statistics for a specific user.
///
/// Parameters:
///
@@ -362,7 +386,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.delete` permission.
/// Restore a deleted user
///
/// Restore a previously deleted user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -395,7 +421,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.delete` permission.
/// Restore a deleted user
///
/// Restore a previously deleted user.
///
/// Parameters:
///
@@ -415,7 +443,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Search users
///
/// Search for users.
///
/// Note: This method returns the HTTP [Response].
///
@@ -456,7 +486,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.read` permission.
/// Search users
///
/// Search for users.
///
/// Parameters:
///
@@ -481,7 +513,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.update` permission.
/// Update a user
///
/// Update an existing user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -516,7 +550,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.update` permission.
/// Update a user
///
/// Update an existing user.
///
/// Parameters:
///
@@ -538,7 +574,9 @@ class UsersAdminApi {
return null;
}
/// This endpoint is an admin-only route, and requires the `adminUser.update` permission.
/// Update user preferences
///
/// Update the preferences of a specific user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -573,7 +611,9 @@ class UsersAdminApi {
);
}
/// This endpoint is an admin-only route, and requires the `adminUser.update` permission.
/// Update user preferences
///
/// Update the preferences of a specific user.
///
/// Parameters:
///
+90 -30
View File
@@ -16,7 +16,9 @@ class UsersApi {
final ApiClient apiClient;
/// This endpoint requires the `userProfileImage.update` permission.
/// Create user profile image
///
/// Upload and set a new profile image for the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -58,7 +60,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userProfileImage.update` permission.
/// Create user profile image
///
/// Upload and set a new profile image for the current user.
///
/// Parameters:
///
@@ -78,7 +82,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userProfileImage.delete` permission.
/// Delete user profile image
///
/// Delete the profile image of the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> deleteProfileImageWithHttpInfo() async {
@@ -106,7 +112,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userProfileImage.delete` permission.
/// Delete user profile image
///
/// Delete the profile image of the current user.
Future<void> deleteProfileImage() async {
final response = await deleteProfileImageWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -114,7 +122,9 @@ class UsersApi {
}
}
/// This endpoint requires the `userLicense.delete` permission.
/// Delete user product key
///
/// Delete the registered product key for the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> deleteUserLicenseWithHttpInfo() async {
@@ -142,7 +152,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userLicense.delete` permission.
/// Delete user product key
///
/// Delete the registered product key for the current user.
Future<void> deleteUserLicense() async {
final response = await deleteUserLicenseWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -150,7 +162,9 @@ class UsersApi {
}
}
/// This endpoint requires the `userOnboarding.delete` permission.
/// Delete user onboarding
///
/// Delete the onboarding status of the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> deleteUserOnboardingWithHttpInfo() async {
@@ -178,7 +192,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userOnboarding.delete` permission.
/// Delete user onboarding
///
/// Delete the onboarding status of the current user.
Future<void> deleteUserOnboarding() async {
final response = await deleteUserOnboardingWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -186,7 +202,9 @@ class UsersApi {
}
}
/// This endpoint requires the `userPreference.read` permission.
/// Get my preferences
///
/// Retrieve the preferences for the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMyPreferencesWithHttpInfo() async {
@@ -214,7 +232,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userPreference.read` permission.
/// Get my preferences
///
/// Retrieve the preferences for the current user.
Future<UserPreferencesResponseDto?> getMyPreferences() async {
final response = await getMyPreferencesWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -230,7 +250,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `user.read` permission.
/// Get current user
///
/// Retrieve information about the user making the API request.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMyUserWithHttpInfo() async {
@@ -258,7 +280,9 @@ class UsersApi {
);
}
/// This endpoint requires the `user.read` permission.
/// Get current user
///
/// Retrieve information about the user making the API request.
Future<UserAdminResponseDto?> getMyUser() async {
final response = await getMyUserWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -274,7 +298,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userProfileImage.read` permission.
/// Retrieve user profile image
///
/// Retrieve the profile image file for a user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -307,7 +333,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userProfileImage.read` permission.
/// Retrieve user profile image
///
/// Retrieve the profile image file for a user.
///
/// Parameters:
///
@@ -327,7 +355,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `user.read` permission.
/// Retrieve a user
///
/// Retrieve a specific user by their ID.
///
/// Note: This method returns the HTTP [Response].
///
@@ -360,7 +390,9 @@ class UsersApi {
);
}
/// This endpoint requires the `user.read` permission.
/// Retrieve a user
///
/// Retrieve a specific user by their ID.
///
/// Parameters:
///
@@ -380,7 +412,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userLicense.read` permission.
/// Retrieve user product key
///
/// Retrieve information about whether the current user has a registered product key.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getUserLicenseWithHttpInfo() async {
@@ -408,7 +442,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userLicense.read` permission.
/// Retrieve user product key
///
/// Retrieve information about whether the current user has a registered product key.
Future<LicenseResponseDto?> getUserLicense() async {
final response = await getUserLicenseWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -424,7 +460,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userOnboarding.read` permission.
/// Retrieve user onboarding
///
/// Retrieve the onboarding status of the current user.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getUserOnboardingWithHttpInfo() async {
@@ -452,7 +490,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userOnboarding.read` permission.
/// Retrieve user onboarding
///
/// Retrieve the onboarding status of the current user.
Future<OnboardingResponseDto?> getUserOnboarding() async {
final response = await getUserOnboardingWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -468,7 +508,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `user.read` permission.
/// Get all users
///
/// Retrieve a list of all users on the server.
///
/// Note: This method returns the HTTP [Response].
Future<Response> searchUsersWithHttpInfo() async {
@@ -496,7 +538,9 @@ class UsersApi {
);
}
/// This endpoint requires the `user.read` permission.
/// Get all users
///
/// Retrieve a list of all users on the server.
Future<List<UserResponseDto>?> searchUsers() async {
final response = await searchUsersWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -515,7 +559,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userLicense.update` permission.
/// Set user product key
///
/// Register a product key for the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -547,7 +593,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userLicense.update` permission.
/// Set user product key
///
/// Register a product key for the current user.
///
/// Parameters:
///
@@ -567,7 +615,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userOnboarding.update` permission.
/// Update user onboarding
///
/// Update the onboarding status of the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -599,7 +649,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userOnboarding.update` permission.
/// Update user onboarding
///
/// Update the onboarding status of the current user.
///
/// Parameters:
///
@@ -619,7 +671,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `userPreference.update` permission.
/// Update my preferences
///
/// Update the preferences of the current user.
///
/// Note: This method returns the HTTP [Response].
///
@@ -651,7 +705,9 @@ class UsersApi {
);
}
/// This endpoint requires the `userPreference.update` permission.
/// Update my preferences
///
/// Update the preferences of the current user.
///
/// Parameters:
///
@@ -671,7 +727,9 @@ class UsersApi {
return null;
}
/// This endpoint requires the `user.update` permission.
/// Update current user
///
/// Update the current user making teh API request.
///
/// Note: This method returns the HTTP [Response].
///
@@ -703,7 +761,9 @@ class UsersApi {
);
}
/// This endpoint requires the `user.update` permission.
/// Update current user
///
/// Update the current user making teh API request.
///
/// Parameters:
///
@@ -11,12 +11,17 @@
part of openapi.api;
class ViewApi {
ViewApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
class ViewsApi {
ViewsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'GET /view/folder' operation and returns the [Response].
/// Retrieve assets by original path
///
/// Retrieve assets that are children of a specific folder.
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] path (required):
@@ -47,6 +52,10 @@ class ViewApi {
);
}
/// Retrieve assets by original path
///
/// Retrieve assets that are children of a specific folder.
///
/// Parameters:
///
/// * [String] path (required):
@@ -68,7 +77,11 @@ class ViewApi {
return null;
}
/// Performs an HTTP 'GET /view/folder/unique-paths' operation and returns the [Response].
/// Retrieve unique paths
///
/// Retrieve a list of unique folder paths from asset original paths.
///
/// Note: This method returns the HTTP [Response].
Future<Response> getUniqueOriginalPathsWithHttpInfo() async {
// ignore: prefer_const_declarations
final apiPath = r'/view/folder/unique-paths';
@@ -94,6 +107,9 @@ class ViewApi {
);
}
/// Retrieve unique paths
///
/// Retrieve a list of unique folder paths from asset original paths.
Future<List<String>?> getUniqueOriginalPaths() async {
final response = await getUniqueOriginalPathsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {

Some files were not shown because too many files have changed in this diff Show More