diff --git a/plugins/android/src/main/java/BootReceiver.kt b/plugins/android/src/main/java/BootReceiver.kt index 0e96d5b..3f3bf08 100644 --- a/plugins/android/src/main/java/BootReceiver.kt +++ b/plugins/android/src/main/java/BootReceiver.kt @@ -11,5 +11,6 @@ class BootReceiver : BroadcastReceiver() { WallpaperStore.screenOnEnabled(context) -> runCatching { ScreenOnRotationService.start(context) } WallpaperStore.timedEnabled(context) -> WallpaperScheduler.scheduleNext(context) } + if (ImmichClient.configured(context) && WallpaperStore.prefetchImmich(context)) ImmichPrefetchWorker.enqueue(context) } } diff --git a/plugins/android/src/main/java/ImmichClient.kt b/plugins/android/src/main/java/ImmichClient.kt index 2cb334c..cd7c775 100644 --- a/plugins/android/src/main/java/ImmichClient.kt +++ b/plugins/android/src/main/java/ImmichClient.kt @@ -119,6 +119,11 @@ object ImmichClient { fun connection(context: Context) = connectionObject(context) + fun configured(context: Context): Boolean { + val preferences = prefs(context) + return preferences.getString(KEY_SERVER_URL, "").orEmpty().isNotBlank() && !decryptApiKey(context).isNullOrBlank() + } + fun connect(context: Context, serverUrl: String, apiKey: String): JSObject { val normalized = normalizeServerUrl(serverUrl) require(apiKey.isNotBlank()) { "Bitte gib deinen Immich API-Key ein." } @@ -314,10 +319,10 @@ object ImmichClient { return bounds.outWidth > 0 && bounds.outHeight > 0 } - fun automaticDownloadsAllowed(context: Context): Boolean { + fun automaticDownloadsAllowed(context: Context, allowMetered: Boolean): Boolean { val connectivity = context.getSystemService(ConnectivityManager::class.java) @Suppress("DEPRECATION") - return connectivity.activeNetworkInfo?.isConnected == true && !connectivity.isActiveNetworkMetered + return connectivity.activeNetworkInfo?.isConnected == true && (allowMetered || !connectivity.isActiveNetworkMetered) } @Synchronized diff --git a/plugins/android/src/main/java/ImmichPrefetchWorker.kt b/plugins/android/src/main/java/ImmichPrefetchWorker.kt new file mode 100644 index 0000000..88091ae --- /dev/null +++ b/plugins/android/src/main/java/ImmichPrefetchWorker.kt @@ -0,0 +1,37 @@ +package de.wechselbild.wallpaper + +import android.content.Context +import androidx.work.Constraints +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters +import java.util.concurrent.TimeUnit + +class ImmichPrefetchWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters) { + override fun doWork(): Result { + WallpaperStore.prefetchImmichOriginals(applicationContext) + return Result.success() + } + + companion object { + private const val WORK_NAME = "immich-wifi-prefetch" + + fun enqueue(context: Context) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + .build() + val request = PeriodicWorkRequestBuilder(6, TimeUnit.HOURS) + .setConstraints(constraints) + .build() + WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, request) + } + + fun cancel(context: Context) { + WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME) + } + } +} diff --git a/plugins/android/src/main/java/WallpaperPlugin.kt b/plugins/android/src/main/java/WallpaperPlugin.kt index 54252d3..9b9223f 100644 --- a/plugins/android/src/main/java/WallpaperPlugin.kt +++ b/plugins/android/src/main/java/WallpaperPlugin.kt @@ -170,11 +170,14 @@ class WallpaperPlugin(private val activity: Activity) : Plugin(activity) { @Command fun connectImmich(invoke: Invoke) = io.execute { try { val args = invoke.parseArgs(ImmichConnectArgs::class.java) - invoke.resolve(ImmichClient.connect(activity, args.serverUrl, args.apiKey)) + 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)) } diff --git a/plugins/android/src/main/java/WallpaperStore.kt b/plugins/android/src/main/java/WallpaperStore.kt index 47ce657..7357d8a 100644 --- a/plugins/android/src/main/java/WallpaperStore.kt +++ b/plugins/android/src/main/java/WallpaperStore.kt @@ -102,10 +102,16 @@ object WallpaperStore { fun screenOnEnabled(context: Context) = intervalMinutes(context) == -1 fun shuffle(context: Context) = prefs(context).getBoolean("shuffle", true) fun lockOnly(context: Context) = prefs(context).getBoolean("lockScreenOnly", true) + fun allowMobileData(context: Context) = prefs(context).getBoolean("allowMobileData", false) + fun prefetchImmich(context: Context) = prefs(context).getBoolean("prefetchImmich", true) fun set(context: Context, name: String, value: Boolean) { - require(name in setOf("shuffle", "lockScreenOnly")) { "Unbekannte Einstellung" } + require(name in setOf("shuffle", "lockScreenOnly", "allowMobileData", "prefetchImmich")) { "Unbekannte Einstellung" } prefs(context).edit().putBoolean(name, value).apply() + if (name == "prefetchImmich") { + if (value && ImmichClient.configured(context)) ImmichPrefetchWorker.enqueue(context) + else if (!value) ImmichPrefetchWorker.cancel(context) + } } fun setInterval(context: Context, minutes: Int) { @@ -125,6 +131,8 @@ object WallpaperStore { put("intervalMinutes", intervalMinutes(context)) put("shuffle", shuffle(context)) put("lockScreenOnly", lockOnly(context)) + put("allowMobileData", allowMobileData(context)) + put("prefetchImmich", prefetchImmich(context)) put("currentIndex", previewIndex) put("currentId", items.getOrNull(index)?.id ?: JSONObject.NULL) val ids = JSArray() @@ -152,6 +160,16 @@ object WallpaperStore { @Synchronized fun imageIds(context: Context) = entries(context).map { it.id } + fun prefetchImmichOriginals(context: Context) { + val unavailableServers = mutableSetOf() + entries(context).filterIsInstance().forEach { entry -> + if (entry.serverUrl in unavailableServers) return@forEach + if (ImmichClient.cachedOriginal(context, entry.serverUrl, entry.assetId, allowNetwork = true) == null) { + unavailableServers.add(entry.serverUrl) + } + } + } + private fun cropKey(id: String) = CROP_PREFIX + id private fun crop(context: Context, entry: Entry): CropSettings { val raw = prefs(context).getString(cropKey(entry.id), null) ?: return CropSettings() @@ -401,7 +419,7 @@ object WallpaperStore { val sourceFile = when (entry) { is Entry.Local -> entry.file is Entry.Immich -> { - val allowNetwork = entry.serverUrl !in unavailableServers && (!automatic || ImmichClient.automaticDownloadsAllowed(context)) + val allowNetwork = entry.serverUrl !in unavailableServers && (!automatic || ImmichClient.automaticDownloadsAllowed(context, allowMobileData(context))) val source = ImmichClient.cachedOriginal(context, entry.serverUrl, entry.assetId, allowNetwork) if (source == null) { if (allowNetwork) unavailableServers.add(entry.serverUrl) diff --git a/plugins/src/desktop.rs b/plugins/src/desktop.rs index 4ca0982..291fa13 100644 --- a/plugins/src/desktop.rs +++ b/plugins/src/desktop.rs @@ -21,6 +21,8 @@ impl Wallpaper { interval_minutes: 30, shuffle: true, lock_screen_only: true, + allow_mobile_data: false, + prefetch_immich: true, current_index: 0, current_id: Some("demo-0".into()), image_ids: vec!["demo-0".into(), "demo-1".into(), "demo-2".into()], @@ -89,6 +91,8 @@ impl Wallpaper { "enabled" => state.enabled = payload.value, "shuffle" => state.shuffle = payload.value, "lockScreenOnly" => state.lock_screen_only = payload.value, + "allowMobileData" => state.allow_mobile_data = payload.value, + "prefetchImmich" => state.prefetch_immich = payload.value, _ => {} }; Ok(state) diff --git a/plugins/src/models.rs b/plugins/src/models.rs index 014d6d3..40290a9 100644 --- a/plugins/src/models.rs +++ b/plugins/src/models.rs @@ -8,6 +8,8 @@ pub struct WallpaperState { pub interval_minutes: i32, pub shuffle: bool, pub lock_screen_only: bool, + pub allow_mobile_data: bool, + pub prefetch_immich: bool, pub current_index: usize, pub current_id: Option, pub image_ids: Vec, diff --git a/src/App.tsx b/src/App.tsx index fdebc94..0c273ca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,10 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; -import { ArrowLeft, Check, ChevronRight, Cloud, CloudDownload, Crop, Home, Images, Languages, Link2Off, LockKeyhole, Plus, RotateCcw, RotateCw, Save, Server, Settings, Shuffle, Smartphone, Sparkles, Trash2 } from "lucide-react"; +import { ArrowLeft, Check, ChevronRight, Cloud, CloudDownload, Crop, Download, Home, Images, Languages, Link2Off, LockKeyhole, Plus, RotateCcw, RotateCw, Save, Server, Settings, Shuffle, Smartphone, Sparkles, Trash2, Wifi } from "lucide-react"; import { applyWallpaper, 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"; -const initial: WallpaperState = { imageCount: 0, enabled: false, intervalMinutes: 0, shuffle: true, lockScreenOnly: true, currentIndex: 0, currentId: null, imageIds: [], imageUrls: [] }; +const initial: WallpaperState = { imageCount: 0, enabled: false, intervalMinutes: 0, shuffle: true, lockScreenOnly: true, allowMobileData: false, prefetchImmich: true, currentIndex: 0, currentId: null, imageIds: [], imageUrls: [] }; function Switch({ checked, onChange, label }: { checked: boolean; onChange: (value: boolean) => void; label: string }) { return ; @@ -107,13 +107,17 @@ export default function App() { finally { setGalleryLoading(false); } } - async function update(name: "shuffle" | "lockScreenOnly", value: boolean) { + async function update(name: "shuffle" | "lockScreenOnly" | "allowMobileData" | "prefetchImmich", value: boolean) { + const previous = state[name]; setState(prev => ({ ...prev, [name]: value })); try { const saved = await setSetting(name, value); setState(saved); setNotice(t.settingSaved); - } catch { setNotice(t.androidOnly); } + } catch (error) { + setState(prev => ({ ...prev, [name]: previous })); + setNotice(String(error).replace(/^Error:\s*/, "") || t.androidOnly); + } } async function updateInterval(minutes: number) { @@ -425,6 +429,10 @@ export default function App() {

{it.immich}

{it.immichIntro}

{immichConnection.configured ? <>
{it.connectedAs} {immichConnection.userName}{immichConnection.serverUrl}
+ } label={it.prefetchImmich} value={state.prefetchImmich} onChange={v => update("prefetchImmich", v)} /> +

{it.prefetchImmichHint}

+ } label={it.allowMobileData} value={state.allowMobileData} onChange={v => update("allowMobileData", v)} /> +

{it.allowMobileDataHint}

: <> diff --git a/src/immich-i18n.ts b/src/immich-i18n.ts index 905c7f2..2760d40 100644 --- a/src/immich-i18n.ts +++ b/src/immich-i18n.ts @@ -22,6 +22,10 @@ export type ImmichText = { importSuccess: string; immichLoadFailed: string; noImmichPhotos: string; + allowMobileData: string; + allowMobileDataHint: string; + prefetchImmich: string; + prefetchImmichHint: string; }; export const immichTranslations: Record = { @@ -47,6 +51,10 @@ export const immichTranslations: Record = { importSuccess: "Immich-Bilder wurden zur Sammlung hinzugefügt", immichLoadFailed: "Immich-Bilder konnten nicht geladen werden", noImmichPhotos: "Keine Bilder gefunden", + allowMobileData: "Auch über mobile Daten laden", + allowMobileDataHint: "Ist das ausgeschaltet, lädt der automatische Wechsel Immich-Bilder nur über WLAN und überspringt sie sonst. Manuelles Wechseln lädt immer.", + prefetchImmich: "Immich-Bilder immer vorladen", + prefetchImmichHint: "Lädt neue Immich-Bilder automatisch im Hintergrund herunter, sobald WLAN verfügbar ist – nie über mobile Daten. Ausgeschaltet werden Bilder nur bei Bedarf geladen.", }, en: { immich: "Immich", immichIntro: "Import images from your own Immich server.", serverUrl: "Server URL", apiKey: "API key", @@ -56,6 +64,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP is unencrypted. Use HTTPS outside your home network.", allPhotos: "All photos", selectImmichPhotos: "Select images to import", importSelected: "Import selection", importing: "Importing images…", importSuccess: "Immich images imported", immichLoadFailed: "Could not load Immich images", noImmichPhotos: "No images found", + allowMobileData: "Allow downloads over mobile data", + allowMobileDataHint: "When off, automatic rotation only downloads Immich images over Wi-Fi and skips them otherwise. Manually changing the wallpaper always downloads.", + prefetchImmich: "Always prefetch Immich images", + prefetchImmichHint: "Downloads new Immich images in the background as soon as Wi-Fi is available – never over mobile data. When off, images are only downloaded when needed.", }, fr: { immich: "Immich", immichIntro: "Importez des images depuis votre propre serveur Immich.", serverUrl: "URL du serveur", apiKey: "Clé API", @@ -65,6 +77,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP n’est pas chiffré. Utilisez HTTPS hors de votre réseau domestique.", allPhotos: "Toutes les photos", selectImmichPhotos: "Sélectionnez les images à importer", importSelected: "Importer la sélection", importing: "Importation des images…", importSuccess: "Images Immich importées", immichLoadFailed: "Impossible de charger les images Immich", noImmichPhotos: "Aucune image trouvée", + allowMobileData: "Autoriser les données mobiles", + allowMobileDataHint: "Désactivé, la rotation automatique ne télécharge les images Immich que via le Wi-Fi et les ignore sinon. Un changement manuel télécharge toujours.", + prefetchImmich: "Toujours précharger les images Immich", + prefetchImmichHint: "Télécharge les nouvelles images Immich en arrière-plan dès que le Wi-Fi est disponible – jamais via les données mobiles. Désactivé, les images ne sont téléchargées qu’en cas de besoin.", }, es: { immich: "Immich", immichIntro: "Importa imágenes desde tu propio servidor Immich.", serverUrl: "URL del servidor", apiKey: "Clave API", @@ -74,6 +90,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP no está cifrado. Usa HTTPS fuera de tu red doméstica.", allPhotos: "Todas las fotos", selectImmichPhotos: "Selecciona imágenes para importar", importSelected: "Importar selección", importing: "Importando imágenes…", importSuccess: "Imágenes de Immich importadas", immichLoadFailed: "No se pudieron cargar las imágenes de Immich", noImmichPhotos: "No se encontraron imágenes", + allowMobileData: "Permitir datos móviles", + allowMobileDataHint: "Si está desactivado, la rotación automática solo descarga imágenes de Immich por Wi-Fi y las omite si no hay Wi-Fi. Cambiar manualmente siempre descarga.", + prefetchImmich: "Precargar siempre las imágenes de Immich", + prefetchImmichHint: "Descarga las nuevas imágenes de Immich en segundo plano en cuanto haya Wi-Fi disponible, nunca con datos móviles. Si está desactivado, las imágenes solo se descargan cuando se necesitan.", }, it: { immich: "Immich", immichIntro: "Importa immagini dal tuo server Immich.", serverUrl: "URL del server", apiKey: "Chiave API", @@ -83,6 +103,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP non è crittografato. Usa HTTPS fuori dalla rete domestica.", allPhotos: "Tutte le foto", selectImmichPhotos: "Seleziona le immagini da importare", importSelected: "Importa selezione", importing: "Importazione immagini…", importSuccess: "Immagini Immich importate", immichLoadFailed: "Impossibile caricare le immagini Immich", noImmichPhotos: "Nessuna immagine trovata", + allowMobileData: "Consenti dati mobili", + allowMobileDataHint: "Se disattivato, la rotazione automatica scarica le immagini Immich solo via Wi-Fi e le salta altrimenti. Il cambio manuale scarica sempre.", + prefetchImmich: "Precarica sempre le immagini Immich", + prefetchImmichHint: "Scarica le nuove immagini Immich in background non appena il Wi-Fi è disponibile, mai con i dati mobili. Se disattivato, le immagini vengono scaricate solo quando servono.", }, nl: { immich: "Immich", immichIntro: "Importeer afbeeldingen van je eigen Immich-server.", serverUrl: "Server-URL", apiKey: "API-sleutel", @@ -92,6 +116,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP is niet versleuteld. Gebruik HTTPS buiten je thuisnetwerk.", allPhotos: "Alle foto’s", selectImmichPhotos: "Selecteer afbeeldingen om te importeren", importSelected: "Selectie importeren", importing: "Afbeeldingen importeren…", importSuccess: "Immich-afbeeldingen geïmporteerd", immichLoadFailed: "Immich-afbeeldingen konden niet worden geladen", noImmichPhotos: "Geen afbeeldingen gevonden", + allowMobileData: "Mobiele data toestaan", + allowMobileDataHint: "Indien uit, worden Immich-afbeeldingen bij automatisch wisselen alleen via wifi gedownload en anders overgeslagen. Handmatig wisselen downloadt altijd.", + prefetchImmich: "Immich-afbeeldingen altijd vooraf laden", + prefetchImmichHint: "Downloadt nieuwe Immich-afbeeldingen op de achtergrond zodra wifi beschikbaar is – nooit via mobiele data. Indien uit, worden afbeeldingen alleen bij gebruik gedownload.", }, pl: { immich: "Immich", immichIntro: "Importuj obrazy z własnego serwera Immich.", serverUrl: "Adres serwera", apiKey: "Klucz API", @@ -101,6 +129,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP nie jest szyfrowany. Poza siecią domową używaj HTTPS.", allPhotos: "Wszystkie zdjęcia", selectImmichPhotos: "Wybierz obrazy do importu", importSelected: "Importuj wybrane", importing: "Importowanie obrazów…", importSuccess: "Zaimportowano obrazy Immich", immichLoadFailed: "Nie udało się wczytać obrazów Immich", noImmichPhotos: "Nie znaleziono obrazów", + allowMobileData: "Zezwól na dane mobilne", + allowMobileDataHint: "Gdy wyłączone, automatyczna zmiana pobiera obrazy Immich tylko przez Wi-Fi, w przeciwnym razie je pomija. Ręczna zmiana zawsze pobiera.", + prefetchImmich: "Zawsze pobieraj obrazy Immich z wyprzedzeniem", + prefetchImmichHint: "Pobiera nowe obrazy Immich w tle, gdy tylko dostępne jest Wi-Fi – nigdy przez dane mobilne. Gdy wyłączone, obrazy są pobierane tylko w razie potrzeby.", }, pt: { immich: "Immich", immichIntro: "Importe imagens do seu próprio servidor Immich.", serverUrl: "URL do servidor", apiKey: "Chave de API", @@ -110,6 +142,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP não é encriptado. Use HTTPS fora da sua rede doméstica.", allPhotos: "Todas as fotos", selectImmichPhotos: "Selecione imagens para importar", importSelected: "Importar seleção", importing: "A importar imagens…", importSuccess: "Imagens do Immich importadas", immichLoadFailed: "Não foi possível carregar imagens do Immich", noImmichPhotos: "Nenhuma imagem encontrada", + allowMobileData: "Permitir dados móveis", + allowMobileDataHint: "Quando desativado, a rotação automática só transfere imagens do Immich por Wi-Fi e ignora-as caso contrário. A troca manual transfere sempre.", + prefetchImmich: "Pré-carregar sempre imagens do Immich", + prefetchImmichHint: "Transfere novas imagens do Immich em segundo plano assim que o Wi-Fi estiver disponível – nunca com dados móveis. Quando desativado, as imagens só são transferidas quando necessário.", }, ja: { immich: "Immich", immichIntro: "自分の Immich サーバーから画像を取り込みます。", serverUrl: "サーバー URL", apiKey: "API キー", @@ -119,6 +155,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP は暗号化されません。自宅ネットワーク外では HTTPS を使用してください。", allPhotos: "すべての写真", selectImmichPhotos: "取り込む画像を選択", importSelected: "選択項目を取り込む", importing: "画像を取り込み中…", importSuccess: "Immich の画像を取り込みました", immichLoadFailed: "Immich の画像を読み込めません", noImmichPhotos: "画像が見つかりません", + allowMobileData: "モバイルデータでのダウンロードを許可", + allowMobileDataHint: "オフの場合、自動切り替えは Wi-Fi 接続時のみ Immich の画像をダウンロードし、それ以外はスキップします。手動での切り替えは常にダウンロードします。", + prefetchImmich: "Immich画像を常に先読みする", + prefetchImmichHint: "Wi-Fi が利用可能になり次第、新しい Immich 画像をバックグラウンドでダウンロードします(モバイルデータは使用しません)。オフの場合、画像は必要なときのみダウンロードされます。", }, ko: { immich: "Immich", immichIntro: "내 Immich 서버에서 이미지를 가져옵니다.", serverUrl: "서버 URL", apiKey: "API 키", @@ -128,6 +168,10 @@ export const immichTranslations: Record = { httpWarning: "HTTP는 암호화되지 않습니다. 홈 네트워크 밖에서는 HTTPS를 사용하세요.", allPhotos: "모든 사진", selectImmichPhotos: "가져올 이미지를 선택하세요", importSelected: "선택 항목 가져오기", importing: "이미지 가져오는 중…", importSuccess: "Immich 이미지를 가져왔습니다", immichLoadFailed: "Immich 이미지를 불러올 수 없습니다", noImmichPhotos: "이미지를 찾을 수 없습니다", + allowMobileData: "모바일 데이터 다운로드 허용", + allowMobileDataHint: "꺼져 있으면 자동 변경 시 Wi-Fi에서만 Immich 이미지를 다운로드하고 그렇지 않으면 건너뜁니다. 수동 변경은 항상 다운로드합니다.", + prefetchImmich: "Immich 이미지 항상 미리 다운로드", + prefetchImmichHint: "Wi-Fi를 사용할 수 있게 되면 새 Immich 이미지를 백그라운드에서 다운로드합니다(모바일 데이터는 사용하지 않음). 꺼져 있으면 필요할 때만 이미지를 다운로드합니다.", }, "zh-CN": { immich: "Immich", immichIntro: "从你自己的 Immich 服务器导入图片。", serverUrl: "服务器 URL", apiKey: "API 密钥", @@ -137,5 +181,9 @@ export const immichTranslations: Record = { httpWarning: "HTTP 未加密。在家庭网络之外请使用 HTTPS。", allPhotos: "所有照片", selectImmichPhotos: "选择要导入的图片", importSelected: "导入所选图片", importing: "正在导入图片…", importSuccess: "已导入 Immich 图片", immichLoadFailed: "无法加载 Immich 图片", noImmichPhotos: "未找到图片", + allowMobileData: "允许使用移动数据下载", + allowMobileDataHint: "关闭时,自动更换仅在 Wi-Fi 下下载 Immich 图片,否则将跳过;手动更换始终会下载。", + prefetchImmich: "始终预下载 Immich 图片", + prefetchImmichHint: "只要有 Wi-Fi 可用,就会在后台下载新的 Immich 图片——绝不使用移动数据。关闭时,图片仅在需要时才会下载。", }, }; diff --git a/src/native.ts b/src/native.ts index 10ecf1c..38f0f44 100644 --- a/src/native.ts +++ b/src/native.ts @@ -6,6 +6,8 @@ export type WallpaperState = { intervalMinutes: number; shuffle: boolean; lockScreenOnly: boolean; + allowMobileData: boolean; + prefetchImmich: boolean; currentIndex: number; currentId: string | null; imageIds: string[]; @@ -68,6 +70,8 @@ const demoState: WallpaperState = { intervalMinutes: 30, shuffle: true, lockScreenOnly: true, + allowMobileData: false, + prefetchImmich: true, currentIndex: 0, currentId: "demo-0", imageIds: ["demo-0", "demo-1", "demo-2"], @@ -148,7 +152,7 @@ export async function setImageCrop(image: GalleryImage): Promise { return { ...image }; } -export async function setSetting(name: "shuffle" | "lockScreenOnly", value: boolean) { +export async function setSetting(name: "shuffle" | "lockScreenOnly" | "allowMobileData" | "prefetchImmich", value: boolean) { if (!inTauri()) return { ...demoState, [name]: value }; return invoke("plugin:wallpaper|set_setting", { name, value }); }