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 = 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 = 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() 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()) } }