feat(import-progress): track and expose Immich import progress
This change introduces a cross-language import progress API for Immich imports and exposes a new query command to report live progress. A new ImportProgress model captures active state, totals, and bytes downloaded, allowing consuming code to display import status to users. - Adds ImportProgress data type and progress tracking during import - Exposes get_immich_import_progress command in Android and Rust - Updates permissions and schemas to include allow-get-immich-import-progress
This commit is contained in:
@@ -17,6 +17,7 @@ import java.nio.charset.StandardCharsets
|
||||
import java.security.KeyStore
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
@@ -33,8 +34,16 @@ object ImmichClient {
|
||||
private const val KEY_ALIAS = "wallpaperflow_immich_api_key"
|
||||
private val assetIdPattern = Regex("^[0-9a-fA-F-]{36}$")
|
||||
private val thumbnailPool = Executors.newFixedThreadPool(4)
|
||||
private val importProgress = AtomicReference(ImportProgress())
|
||||
|
||||
private data class Credentials(val serverUrl: String, val apiKey: String)
|
||||
private data class ImportProgress(
|
||||
val active: Boolean = false,
|
||||
val completed: Int = 0,
|
||||
val total: Int = 0,
|
||||
val bytesDownloaded: Long = 0,
|
||||
val bytesTotal: Long = 0,
|
||||
)
|
||||
|
||||
private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
|
||||
@@ -188,7 +197,21 @@ object ImmichClient {
|
||||
val connection = openConnection(serverUrl, apiKey, path)
|
||||
try {
|
||||
if (connection.responseCode !in 200..299) throw IOException(errorMessage(connection))
|
||||
connection.inputStream.use { input -> target.outputStream().use { output -> input.copyTo(output) } }
|
||||
val total = connection.contentLengthLong.coerceAtLeast(0)
|
||||
var downloaded = 0L
|
||||
importProgress.updateAndGet { it.copy(bytesDownloaded = 0, bytesTotal = total) }
|
||||
connection.inputStream.use { input ->
|
||||
target.outputStream().use { output ->
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
while (true) {
|
||||
val count = input.read(buffer)
|
||||
if (count < 0) break
|
||||
output.write(buffer, 0, count)
|
||||
downloaded += count
|
||||
importProgress.updateAndGet { it.copy(bytesDownloaded = downloaded, bytesTotal = total) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return connection.contentType.orEmpty().substringBefore(';')
|
||||
} catch (error: Exception) {
|
||||
target.delete()
|
||||
@@ -285,7 +308,26 @@ object ImmichClient {
|
||||
require(ids.isNotEmpty()) { "Wähle mindestens ein Immich-Bild aus." }
|
||||
require(ids.size <= 100) { "Bitte importiere höchstens 100 Bilder auf einmal." }
|
||||
val credentials = credentials(context)
|
||||
ids.distinct().forEach { importOne(context, credentials, it) }
|
||||
return WallpaperStore.state(context)
|
||||
val uniqueIds = ids.distinct()
|
||||
importProgress.set(ImportProgress(active = true, total = uniqueIds.size))
|
||||
try {
|
||||
uniqueIds.forEachIndexed { index, id ->
|
||||
importOne(context, credentials, id)
|
||||
importProgress.updateAndGet { it.copy(completed = index + 1, bytesDownloaded = 0, bytesTotal = 0) }
|
||||
}
|
||||
return WallpaperStore.state(context)
|
||||
} finally {
|
||||
importProgress.updateAndGet { it.copy(active = false) }
|
||||
}
|
||||
}
|
||||
|
||||
fun importProgress(): JSObject = importProgress.get().let { progress ->
|
||||
JSObject().apply {
|
||||
put("active", progress.active)
|
||||
put("completed", progress.completed)
|
||||
put("total", progress.total)
|
||||
put("bytesDownloaded", progress.bytesDownloaded)
|
||||
put("bytesTotal", progress.bytesTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,4 +179,8 @@ class WallpaperPlugin(private val activity: Activity) : Plugin(activity) {
|
||||
invoke.resolve(ImmichClient.importAssets(activity, args.ids.toList()))
|
||||
} catch (error: Exception) { invoke.reject(error.message ?: "Immich-Bilder konnten nicht importiert werden") }
|
||||
}
|
||||
|
||||
@Command fun getImmichImportProgress(invoke: Invoke) {
|
||||
invoke.resolve(ImmichClient.importProgress())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const COMMANDS: &[&str] = &[
|
||||
"get_immich_albums",
|
||||
"get_immich_assets",
|
||||
"import_immich_assets",
|
||||
"get_immich_import_progress",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-immich-import-progress"
|
||||
description = "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
commands.allow = ["get_immich_import_progress"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-immich-import-progress"
|
||||
description = "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
commands.deny = ["get_immich_import_progress"]
|
||||
@@ -20,6 +20,7 @@ Allow the LockScreenWallpaper app to manage its wallpaper collection
|
||||
- `allow-get-immich-albums`
|
||||
- `allow-get-immich-assets`
|
||||
- `allow-import-immich-assets`
|
||||
- `allow-get-immich-import-progress`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -267,6 +268,32 @@ Denies the get_immich_connection command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`wallpaper:allow-get-immich-import-progress`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_immich_import_progress command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`wallpaper:deny-get-immich-import-progress`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_immich_import_progress command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`wallpaper:allow-get-state`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[default]
|
||||
description = "Allow the LockScreenWallpaper app to manage its wallpaper collection"
|
||||
permissions = ["allow-get-state", "allow-get-gallery", "allow-select-images", "allow-delete-image", "allow-get-image-ids", "allow-delete-images", "allow-set-image-crop", "allow-set-setting", "allow-set-interval", "allow-next-wallpaper", "allow-get-immich-connection", "allow-connect-immich", "allow-disconnect-immich", "allow-get-immich-albums", "allow-get-immich-assets", "allow-import-immich-assets"]
|
||||
permissions = ["allow-get-state", "allow-get-gallery", "allow-select-images", "allow-delete-image", "allow-get-image-ids", "allow-delete-images", "allow-set-image-crop", "allow-set-setting", "allow-set-interval", "allow-next-wallpaper", "allow-get-immich-connection", "allow-connect-immich", "allow-disconnect-immich", "allow-get-immich-albums", "allow-get-immich-assets", "allow-import-immich-assets", "allow-get-immich-import-progress"]
|
||||
|
||||
@@ -402,6 +402,18 @@
|
||||
"const": "deny-get-immich-connection",
|
||||
"markdownDescription": "Denies the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-immich-import-progress",
|
||||
"markdownDescription": "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-immich-import-progress",
|
||||
"markdownDescription": "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -487,10 +499,10 @@
|
||||
"markdownDescription": "Denies the set_setting command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`",
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`"
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -111,3 +111,8 @@ pub(crate) async fn import_immich_assets<R: Runtime>(
|
||||
) -> Result<WallpaperState> {
|
||||
app.wallpaper().import_immich_assets(ImmichImportRequest { ids })
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn get_immich_import_progress<R: Runtime>(app: AppHandle<R>) -> Result<ImmichImportProgress> {
|
||||
app.wallpaper().get_immich_import_progress()
|
||||
}
|
||||
|
||||
@@ -143,4 +143,7 @@ impl<R: Runtime> Wallpaper<R> {
|
||||
pub fn import_immich_assets(&self, _payload: ImmichImportRequest) -> crate::Result<WallpaperState> {
|
||||
Ok(Self::demo())
|
||||
}
|
||||
pub fn get_immich_import_progress(&self) -> crate::Result<ImmichImportProgress> {
|
||||
Ok(ImmichImportProgress::default())
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -51,7 +51,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::disconnect_immich,
|
||||
commands::get_immich_albums,
|
||||
commands::get_immich_assets,
|
||||
commands::import_immich_assets
|
||||
commands::import_immich_assets,
|
||||
commands::get_immich_import_progress
|
||||
])
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
|
||||
@@ -88,4 +88,7 @@ impl<R: Runtime> Wallpaper<R> {
|
||||
pub fn import_immich_assets(&self, payload: ImmichImportRequest) -> crate::Result<WallpaperState> {
|
||||
self.0.run_mobile_plugin("importImmichAssets", payload).map_err(Into::into)
|
||||
}
|
||||
pub fn get_immich_import_progress(&self) -> crate::Result<ImmichImportProgress> {
|
||||
self.0.run_mobile_plugin("getImmichImportProgress", ()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,3 +139,13 @@ pub struct ImmichAssetsPage {
|
||||
pub struct ImmichImportRequest {
|
||||
pub ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImmichImportProgress {
|
||||
pub active: bool,
|
||||
pub completed: usize,
|
||||
pub total: usize,
|
||||
pub bytes_downloaded: u64,
|
||||
pub bytes_total: u64,
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2193,10 +2193,10 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`",
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`",
|
||||
"type": "string",
|
||||
"const": "wallpaper:default",
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`"
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect_immich command without any pre-configured scope.",
|
||||
@@ -2252,6 +2252,12 @@
|
||||
"const": "wallpaper:allow-get-immich-connection",
|
||||
"markdownDescription": "Enables the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:allow-get-immich-import-progress",
|
||||
"markdownDescription": "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2348,6 +2354,12 @@
|
||||
"const": "wallpaper:deny-get-immich-connection",
|
||||
"markdownDescription": "Denies the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:deny-get-immich-import-progress",
|
||||
"markdownDescription": "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -2193,10 +2193,10 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`",
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`",
|
||||
"type": "string",
|
||||
"const": "wallpaper:default",
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`"
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect_immich command without any pre-configured scope.",
|
||||
@@ -2252,6 +2252,12 @@
|
||||
"const": "wallpaper:allow-get-immich-connection",
|
||||
"markdownDescription": "Enables the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:allow-get-immich-import-progress",
|
||||
"markdownDescription": "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2348,6 +2354,12 @@
|
||||
"const": "wallpaper:deny-get-immich-connection",
|
||||
"markdownDescription": "Denies the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:deny-get-immich-import-progress",
|
||||
"markdownDescription": "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -2193,10 +2193,10 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`",
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`",
|
||||
"type": "string",
|
||||
"const": "wallpaper:default",
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`"
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect_immich command without any pre-configured scope.",
|
||||
@@ -2252,6 +2252,12 @@
|
||||
"const": "wallpaper:allow-get-immich-connection",
|
||||
"markdownDescription": "Enables the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:allow-get-immich-import-progress",
|
||||
"markdownDescription": "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2348,6 +2354,12 @@
|
||||
"const": "wallpaper:deny-get-immich-connection",
|
||||
"markdownDescription": "Denies the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:deny-get-immich-import-progress",
|
||||
"markdownDescription": "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -2193,10 +2193,10 @@
|
||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`",
|
||||
"description": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`",
|
||||
"type": "string",
|
||||
"const": "wallpaper:default",
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`"
|
||||
"markdownDescription": "Allow the LockScreenWallpaper app to manage its wallpaper collection\n#### This default permission set includes:\n\n- `allow-get-state`\n- `allow-get-gallery`\n- `allow-select-images`\n- `allow-delete-image`\n- `allow-get-image-ids`\n- `allow-delete-images`\n- `allow-set-image-crop`\n- `allow-set-setting`\n- `allow-set-interval`\n- `allow-next-wallpaper`\n- `allow-get-immich-connection`\n- `allow-connect-immich`\n- `allow-disconnect-immich`\n- `allow-get-immich-albums`\n- `allow-get-immich-assets`\n- `allow-import-immich-assets`\n- `allow-get-immich-import-progress`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect_immich command without any pre-configured scope.",
|
||||
@@ -2252,6 +2252,12 @@
|
||||
"const": "wallpaper:allow-get-immich-connection",
|
||||
"markdownDescription": "Enables the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:allow-get-immich-import-progress",
|
||||
"markdownDescription": "Enables the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2348,6 +2354,12 @@
|
||||
"const": "wallpaper:deny-get-immich-connection",
|
||||
"markdownDescription": "Denies the get_immich_connection command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_immich_import_progress command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "wallpaper:deny-get-immich-import-progress",
|
||||
"markdownDescription": "Denies the get_immich_import_progress command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_state command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
+12
-3
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { ArrowLeft, Check, ChevronRight, Cloud, CloudDownload, Crop, Home, Images, Languages, Link2Off, LockKeyhole, Plus, RotateCcw, Save, Server, Settings, Shuffle, Smartphone, Sparkles, Trash2 } from "lucide-react";
|
||||
import { connectImmich, deleteImages, disconnectImmich, getGallery, getImageIds, getImmichAlbums, getImmichAssets, getImmichConnection, getState, importImmichAssets, nextWallpaper, selectImages, setImageCrop, setIntervalMinutes, setSetting, type GalleryImage, type ImmichAlbum, type ImmichAsset, type ImmichConnection, type WallpaperState } from "./native";
|
||||
import { connectImmich, deleteImages, disconnectImmich, getGallery, getImageIds, getImmichAlbums, getImmichAssets, getImmichConnection, getImmichImportProgress, getState, importImmichAssets, nextWallpaper, selectImages, setImageCrop, setIntervalMinutes, setSetting, type GalleryImage, type ImmichAlbum, type ImmichAsset, type ImmichConnection, type ImmichImportProgress, type WallpaperState } from "./native";
|
||||
import { initialLanguage, languageNames, languages, translations, type Language } from "./i18n-local";
|
||||
import { immichTranslations } from "./immich-i18n";
|
||||
|
||||
@@ -42,6 +42,7 @@ export default function App() {
|
||||
const [immichPage, setImmichPage] = useState(1);
|
||||
const [immichHasMore, setImmichHasMore] = useState(false);
|
||||
const [immichLoading, setImmichLoading] = useState(false);
|
||||
const [immichImportProgress, setImmichImportProgress] = useState<ImmichImportProgress | null>(null);
|
||||
const phonePreviewRef = useRef<HTMLDivElement>(null);
|
||||
const phoneImageRef = useRef<HTMLImageElement>(null);
|
||||
const previewPointers = useRef(new Map<number, { x: number; y: number }>());
|
||||
@@ -228,14 +229,22 @@ export default function App() {
|
||||
async function importFromImmich() {
|
||||
if (!immichSelected.size) return;
|
||||
setBusy(true);
|
||||
setImmichImportProgress({ active: true, completed: 0, total: immichSelected.size, bytesDownloaded: 0, bytesTotal: 0 });
|
||||
setNotice(it.importing);
|
||||
const poll = window.setInterval(() => {
|
||||
void getImmichImportProgress().then(setImmichImportProgress).catch(() => undefined);
|
||||
}, 250);
|
||||
try {
|
||||
setState(await importImmichAssets([...immichSelected]));
|
||||
setImmichSelected(new Set());
|
||||
setNotice(it.importSuccess);
|
||||
setTab("home");
|
||||
} catch (error) { setNotice(String(error).replace(/^Error:\s*/, "") || it.immichLoadFailed); }
|
||||
finally { setBusy(false); }
|
||||
finally {
|
||||
window.clearInterval(poll);
|
||||
setBusy(false);
|
||||
setImmichImportProgress(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCrop() {
|
||||
@@ -389,7 +398,7 @@ export default function App() {
|
||||
{immichAssets.length ? <div className="gallery-grid immich-grid">{immichAssets.map((asset, index) => <article className={immichSelected.has(asset.id) ? "chosen" : ""} key={asset.id}><button className="gallery-image-button" aria-label={`${immichSelected.has(asset.id) ? t.deselectImage : t.selectImage} ${index + 1}`} aria-pressed={immichSelected.has(asset.id)} onClick={() => toggleImmichAsset(asset.id)}>{asset.thumbnailUrl ? <img src={asset.thumbnailUrl} alt={asset.fileName} /> : <span className="immich-placeholder"><Images /></span>}</button><span className="selection-check"><Check /></span></article>)}</div> : !immichLoading && <div className="gallery-empty"><Cloud /><h2>{it.noImmichPhotos}</h2></div>}
|
||||
{immichLoading && <p className="gallery-status">{t.galleryLoading}</p>}
|
||||
{!immichLoading && immichHasMore && <button className="load-more" onClick={() => loadImmichPhotos(immichAlbumId, immichPage + 1, true)}>{t.loadMore}</button>}
|
||||
{!!immichSelected.size && <button className="primary immich-import" onClick={importFromImmich} disabled={busy}><CloudDownload /> {busy ? it.importing : `${it.importSelected} (${immichSelected.size})`}</button>}
|
||||
{!!immichSelected.size && (busy && immichImportProgress ? <div className="immich-import-progress" role="status" aria-live="polite"><div><span>{it.importing}</span><strong>{immichImportProgress.completed} / {immichImportProgress.total}</strong></div><progress max={immichImportProgress.bytesTotal || immichImportProgress.total || 1} value={immichImportProgress.bytesTotal ? immichImportProgress.bytesDownloaded : immichImportProgress.completed} />{immichImportProgress.bytesTotal > 0 && <div><span>{Math.round(immichImportProgress.bytesDownloaded / immichImportProgress.bytesTotal * 100)}%</span><span>{(immichImportProgress.bytesDownloaded / 1024 / 1024).toFixed(1)} / {(immichImportProgress.bytesTotal / 1024 / 1024).toFixed(1)} MB</span></div>}</div> : <button className="primary immich-import" onClick={importFromImmich} disabled={busy}><CloudDownload /> {`${it.importSelected} (${immichSelected.size})`}</button>)}
|
||||
</section> : tab === "gallery" ?
|
||||
<section className="gallery-page" aria-label={t.myImages}>
|
||||
{!!galleryTotal && <div className="selection-toolbar"><span>{selectedIds.size ? `${selectedIds.size} ${t.selected}` : t.selectImagesToDelete}</span><button onClick={selectAll} disabled={busy}>{selectedIds.size === galleryTotal ? t.clearSelection : t.selectAll}</button></div>}
|
||||
|
||||
@@ -51,6 +51,14 @@ export type ImmichAssetsPage = {
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
export type ImmichImportProgress = {
|
||||
active: boolean;
|
||||
completed: number;
|
||||
total: number;
|
||||
bytesDownloaded: number;
|
||||
bytesTotal: number;
|
||||
};
|
||||
|
||||
const demoState: WallpaperState = {
|
||||
imageCount: 3,
|
||||
enabled: true,
|
||||
@@ -191,3 +199,8 @@ export async function importImmichAssets(ids: string[]): Promise<WallpaperState>
|
||||
if (inTauri()) return invoke<WallpaperState>("plugin:wallpaper|import_immich_assets", { ids });
|
||||
return { ...demoState, imageUrls: [...demoState.imageUrls] };
|
||||
}
|
||||
|
||||
export async function getImmichImportProgress(): Promise<ImmichImportProgress> {
|
||||
if (inTauri()) return invoke<ImmichImportProgress>("plugin:wallpaper|get_immich_import_progress");
|
||||
return { active: false, completed: 0, total: 0, bytesDownloaded: 0, bytesTotal: 0 };
|
||||
}
|
||||
|
||||
+8
-2
@@ -2,7 +2,7 @@
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-width: 320px; min-height: 100vh; }
|
||||
button { font: inherit; }
|
||||
.app-shell { width: min(100%, 480px); min-height: 100dvh; margin: 0 auto; background: #f8faf7; position: relative; padding-bottom: 94px; box-shadow: 0 0 50px rgba(32, 50, 37, .12); overflow-anchor: none; }
|
||||
.app-shell { width: min(100%, 480px); min-height: 100dvh; margin: 0 auto; background: #f8faf7; position: relative; padding-bottom: calc(94px + env(safe-area-inset-bottom)); box-shadow: 0 0 50px rgba(32, 50, 37, .12); overflow-anchor: none; }
|
||||
header { padding: max(24px, env(safe-area-inset-top)) 24px 18px; display: flex; align-items: center; justify-content: space-between; }
|
||||
h1 { font-size: 34px; letter-spacing: -1.7px; line-height: 1.05; margin: 0 0 6px; font-weight: 800; }
|
||||
header p { margin: 0; font-size: 13px; color: #657068; font-weight: 500; }
|
||||
@@ -110,11 +110,17 @@ h2 { margin: 0; font-size: 21px; letter-spacing: -.6px; }
|
||||
.immich-placeholder { width: 100%; height: 100%; display: grid; place-items: center; color: #78907d; background: var(--sage); }
|
||||
.immich-placeholder svg { width: 28px; }
|
||||
.immich-import { position: sticky; z-index: 4; bottom: max(16px, env(safe-area-inset-bottom)); margin-bottom: 0; box-shadow: 0 10px 30px rgba(20,93,50,.3); }
|
||||
.immich-import-progress { position: sticky; z-index: 4; bottom: max(16px, env(safe-area-inset-bottom)); margin-top: 18px; padding: 13px 15px; border-radius: 17px; color: white; background: var(--green); box-shadow: 0 10px 30px rgba(20,93,50,.3); }
|
||||
.immich-import-progress > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 12px; font-weight: 750; }
|
||||
.immich-import-progress progress { width: 100%; height: 7px; margin-top: 9px; border: 0; border-radius: 99px; overflow: hidden; background: rgba(255,255,255,.28); accent-color: white; }
|
||||
.immich-import-progress progress::-webkit-progress-bar { background: rgba(255,255,255,.28); }
|
||||
.immich-import-progress progress::-webkit-progress-value { border-radius: 99px; background: white; }
|
||||
.immich-import-progress progress::-moz-progress-bar { border-radius: 99px; background: white; }
|
||||
nav { position: fixed; z-index: 10; bottom: 0; left: 50%; transform: translateX(-50%); width: min(100%, 480px); height: calc(76px + env(safe-area-inset-bottom)); padding: 8px 34px env(safe-area-inset-bottom); background: rgba(248,250,247,.96); backdrop-filter: blur(16px); border-top: 1px solid var(--line); display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
nav button { border: 0; color: #657068; background: transparent; border-radius: 18px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 3px; font-size: 11px; font-weight: 650; }
|
||||
nav button.active { color: var(--green); background: var(--sage); }
|
||||
nav svg { width: 21px; }
|
||||
.snackbar { position: fixed; z-index: 20; left: 50%; transform: translateX(-50%); bottom: 92px; max-width: calc(100% - 40px); background: #26312a; color: white; border: 0; border-radius: 12px; padding: 13px 18px; font-size: 12px; box-shadow: 0 8px 26px rgba(0,0,0,.22); }
|
||||
.snackbar { position: fixed; z-index: 20; left: 50%; transform: translateX(-50%); bottom: calc(92px + env(safe-area-inset-bottom)); max-width: calc(100% - 40px); background: #26312a; color: white; border: 0; border-radius: 12px; padding: 13px 18px; font-size: 12px; box-shadow: 0 8px 26px rgba(0,0,0,.22); }
|
||||
.gallery-page { padding-bottom: max(24px, env(safe-area-inset-bottom)); }
|
||||
.selection-toolbar { min-height: 44px; margin: 0 0 11px; padding: 0 4px; display: flex; align-items: center; justify-content: space-between; gap: 12px; color: #6c766e; font-size: 12px; }
|
||||
.selection-toolbar button { min-height: 36px; padding: 0 12px; border: 0; border-radius: 11px; color: var(--green); background: var(--sage); font-size: 12px; font-weight: 800; }
|
||||
|
||||
Reference in New Issue
Block a user