Files
LockScreenWallpaper/plugins/android/src/main/java/WallpaperPlugin.kt
T
Christoph a5f71b576b feat(immich): add prefetch and mobile data controls
Adds Immich image prefetch and a mobile data option to the UI.
Android now ships ImmichPrefetchWorker to fetch originals in the background.
Frontend and desktop code are updated to expose and persist new settings.
Translations cover the new labels and hints in multiple languages.

- Introduce ImmichPrefetchWorker for background prefetch
- Add allowMobileData and prefetchImmich UI controls
- Wire changes to desktop state and translations
2026-08-22 23:18:08 +02:00

207 lines
8.3 KiB
Kotlin

package de.wechselbild.wallpaper
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.result.ActivityResult
import app.tauri.annotation.ActivityCallback
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import java.io.File
import java.util.UUID
import java.util.concurrent.Executors
@InvokeArg
class SettingArgs { lateinit var name: String; var value: Boolean = false }
@InvokeArg
class IntervalArgs { var minutes: Int = 0 }
@InvokeArg
class GalleryArgs { var offset: Int = 0; var limit: Int = 48 }
@InvokeArg
class DeleteImageArgs { lateinit var id: String }
@InvokeArg
class ApplyWallpaperArgs { lateinit var id: String }
@InvokeArg
class DeleteImagesArgs { var ids: Array<String> = emptyArray() }
@InvokeArg
class ImageCropArgs {
lateinit var id: String
lateinit var mode: String
var zoom: Double = 1.0
var positionX: Double = 0.5
var positionY: Double = 0.5
var rotation: Int = 0
}
@InvokeArg
class ImmichConnectArgs { lateinit var serverUrl: String; lateinit var apiKey: String }
@InvokeArg
class ImmichAssetsArgs { var albumId: String? = null; var page: Int = 1; var size: Int = 30 }
@InvokeArg
class ImmichImportArgs { var ids: Array<String> = emptyArray() }
@TauriPlugin
class WallpaperPlugin(private val activity: Activity) : Plugin(activity) {
private val io = Executors.newSingleThreadExecutor()
@Command fun getState(invoke: Invoke) {
io.execute { invoke.resolve(WallpaperStore.state(activity)) }
}
@Command fun getGallery(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(GalleryArgs::class.java)
invoke.resolve(WallpaperStore.gallery(activity, args.offset, args.limit))
} catch (error: Exception) { invoke.reject(error.message ?: "Galerie konnte nicht geladen werden") }
}
@Command fun selectImages(invoke: Invoke) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
}
startActivityForResult(invoke, intent, "selectedImages")
}
@Command fun deleteImage(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(DeleteImageArgs::class.java)
if (!WallpaperStore.delete(activity, args.id)) throw IllegalArgumentException("Bild wurde nicht gefunden")
invoke.resolve(WallpaperStore.state(activity))
} catch (error: Exception) { invoke.reject(error.message ?: "Bild konnte nicht gelöscht werden") }
}
@Command fun getImageIds(invoke: Invoke) = io.execute {
val ids = JSArray().apply { WallpaperStore.imageIds(activity).forEach { put(it) } }
invoke.resolve(JSObject().apply { put("ids", ids) })
}
@Command fun deleteImages(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(DeleteImagesArgs::class.java)
if (WallpaperStore.deleteMany(activity, args.ids.toList()) == 0) throw IllegalArgumentException("Keine Bilder gefunden")
invoke.resolve(WallpaperStore.state(activity))
} catch (error: Exception) { invoke.reject(error.message ?: "Bilder konnten nicht gelöscht werden") }
}
@Command fun setImageCrop(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(ImageCropArgs::class.java)
val image = WallpaperStore.setCrop(activity, args.id, args.mode, args.zoom, args.positionX, args.positionY, args.rotation)
?: throw IllegalArgumentException("Bild wurde nicht gefunden")
invoke.resolve(image)
} catch (error: Exception) { invoke.reject(error.message ?: "Bildausschnitt konnte nicht gespeichert werden") }
}
@ActivityCallback
fun selectedImages(invoke: Invoke, result: ActivityResult) {
if (result.resultCode != Activity.RESULT_OK) { invoke.reject("Bildauswahl abgebrochen"); return }
val uris = mutableListOf<Uri>()
result.data?.data?.let(uris::add)
result.data?.clipData?.let { clip -> for (i in 0 until clip.itemCount) uris.add(clip.getItemAt(i).uri) }
if (uris.isEmpty()) { invoke.reject("Keine Bilder ausgewählt"); return }
io.execute {
try {
uris.forEach { uri ->
val type = activity.contentResolver.getType(uri)
val extension = when (type) { "image/png" -> "png"; "image/webp" -> "webp"; else -> "jpg" }
val target = File(WallpaperStore.directory(activity), "${System.currentTimeMillis()}-${UUID.randomUUID()}.$extension")
activity.contentResolver.openInputStream(uri).use { input -> requireNotNull(input) { "Bild konnte nicht geöffnet werden" }; target.outputStream().use(input::copyTo) }
}
invoke.resolve(WallpaperStore.state(activity))
} catch (error: Exception) { invoke.reject(error.message ?: "Bilder konnten nicht importiert werden") }
}
}
@Command fun setSetting(invoke: Invoke) {
try {
val args = invoke.parseArgs(SettingArgs::class.java)
WallpaperStore.set(activity, args.name, args.value)
invoke.resolve(WallpaperStore.state(activity))
} catch (error: Exception) { invoke.reject(error.message ?: "Einstellung konnte nicht gespeichert werden") }
}
@Command fun setInterval(invoke: Invoke) {
try {
val args = invoke.parseArgs(IntervalArgs::class.java)
WallpaperStore.setInterval(activity, args.minutes)
WallpaperScheduler.cancel(activity)
ScreenOnRotationService.stop(activity)
if (args.minutes == -1) ScreenOnRotationService.start(activity)
else if (args.minutes > 0) WallpaperScheduler.scheduleNext(activity)
invoke.resolve(WallpaperStore.state(activity))
} catch (error: Exception) { invoke.reject(error.message ?: "Wechselintervall konnte nicht gespeichert werden") }
}
@Command fun nextWallpaper(invoke: Invoke) = io.execute {
try {
if (WallpaperStore.applyNext(activity)) invoke.resolve(WallpaperStore.state(activity))
else invoke.reject("Kein verfügbares Bild konnte angewendet werden")
} catch (error: Exception) { invoke.reject(error.message ?: "Das Hintergrundbild konnte nicht geändert werden") }
}
@Command fun applyWallpaper(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(ApplyWallpaperArgs::class.java)
if (WallpaperStore.apply(activity, args.id)) invoke.resolve(WallpaperStore.state(activity))
else invoke.reject("Bild konnte nicht angewendet werden")
} catch (error: Exception) { invoke.reject(error.message ?: "Das Hintergrundbild konnte nicht geändert werden") }
}
@Command fun getImmichConnection(invoke: Invoke) = io.execute {
invoke.resolve(ImmichClient.connection(activity))
}
@Command fun connectImmich(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(ImmichConnectArgs::class.java)
val result = ImmichClient.connect(activity, args.serverUrl, args.apiKey)
if (WallpaperStore.prefetchImmich(activity)) ImmichPrefetchWorker.enqueue(activity)
invoke.resolve(result)
} catch (error: Exception) { invoke.reject(error.message ?: "Immich-Verbindung fehlgeschlagen") }
}
@Command fun disconnectImmich(invoke: Invoke) = io.execute {
ImmichPrefetchWorker.cancel(activity)
invoke.resolve(ImmichClient.disconnect(activity))
}
@Command fun getImmichAlbums(invoke: Invoke) = io.execute {
try { invoke.resolve(ImmichClient.albums(activity)) }
catch (error: Exception) { invoke.reject(error.message ?: "Immich-Alben konnten nicht geladen werden") }
}
@Command fun getImmichAssets(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(ImmichAssetsArgs::class.java)
invoke.resolve(ImmichClient.assets(activity, args.albumId, args.page, args.size))
} catch (error: Exception) { invoke.reject(error.message ?: "Immich-Bilder konnten nicht geladen werden") }
}
@Command fun importImmichAssets(invoke: Invoke) = io.execute {
try {
val args = invoke.parseArgs(ImmichImportArgs::class.java)
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())
}
}