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
234 lines
8.4 KiB
TypeScript
234 lines
8.4 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
export type WallpaperState = {
|
|
imageCount: number;
|
|
enabled: boolean;
|
|
intervalMinutes: number;
|
|
shuffle: boolean;
|
|
lockScreenOnly: boolean;
|
|
allowMobileData: boolean;
|
|
prefetchImmich: boolean;
|
|
currentIndex: number;
|
|
currentId: string | null;
|
|
imageIds: string[];
|
|
imageUrls: string[];
|
|
};
|
|
|
|
export type GalleryImage = {
|
|
id: string;
|
|
url: string;
|
|
selected: boolean;
|
|
cropMode: "cover" | "contain";
|
|
cropZoom: number;
|
|
cropPositionX: number;
|
|
cropPositionY: number;
|
|
cropRotation: number;
|
|
};
|
|
|
|
export type GalleryPage = {
|
|
total: number;
|
|
items: GalleryImage[];
|
|
};
|
|
|
|
export type ImmichConnection = {
|
|
configured: boolean;
|
|
serverUrl: string;
|
|
userName: string;
|
|
};
|
|
|
|
export type ImmichAlbum = {
|
|
id: string;
|
|
name: string;
|
|
assetCount: number;
|
|
thumbnailUrl: string;
|
|
};
|
|
|
|
export type ImmichAsset = {
|
|
id: string;
|
|
fileName: string;
|
|
thumbnailUrl: string;
|
|
takenAt: string;
|
|
};
|
|
|
|
export type ImmichAssetsPage = {
|
|
items: ImmichAsset[];
|
|
page: number;
|
|
hasMore: boolean;
|
|
};
|
|
|
|
export type ImmichImportProgress = {
|
|
active: boolean;
|
|
completed: number;
|
|
total: number;
|
|
bytesDownloaded: number;
|
|
bytesTotal: number;
|
|
};
|
|
|
|
const demoState: WallpaperState = {
|
|
imageCount: 3,
|
|
enabled: true,
|
|
intervalMinutes: 30,
|
|
shuffle: true,
|
|
lockScreenOnly: true,
|
|
allowMobileData: false,
|
|
prefetchImmich: true,
|
|
currentIndex: 0,
|
|
currentId: "demo-0",
|
|
imageIds: ["demo-0", "demo-1", "demo-2"],
|
|
imageUrls: ["/wallpapers/alpine.png", "/wallpapers/waterfall.png", "/wallpapers/coast.png"],
|
|
};
|
|
|
|
const inTauri = () => "__TAURI_INTERNALS__" in window;
|
|
const demoCrops = new Map<string, Pick<GalleryImage, "cropMode" | "cropZoom" | "cropPositionX" | "cropPositionY" | "cropRotation">>();
|
|
const defaultCrop = { cropMode: "cover" as const, cropZoom: 1, cropPositionX: 0.5, cropPositionY: 0.5, cropRotation: 0 };
|
|
let demoImmichConnection: ImmichConnection = { configured: false, serverUrl: "", userName: "" };
|
|
|
|
export async function getState(): Promise<WallpaperState> {
|
|
return inTauri() ? invoke<WallpaperState>("plugin:wallpaper|get_state") : demoState;
|
|
}
|
|
|
|
export async function getGallery(offset = 0, limit = 48): Promise<GalleryPage> {
|
|
if (inTauri()) return invoke<GalleryPage>("plugin:wallpaper|get_gallery", { offset, limit });
|
|
return {
|
|
total: demoState.imageUrls.length,
|
|
items: demoState.imageUrls.slice(offset, offset + limit).map((url, index) => {
|
|
const id = `demo-${offset + index}`;
|
|
return { id, url, selected: offset + index === demoState.currentIndex, ...(demoCrops.get(id) ?? defaultCrop) };
|
|
}),
|
|
};
|
|
}
|
|
|
|
export async function selectImages(): Promise<WallpaperState> {
|
|
if (!inTauri()) return demoState;
|
|
return invoke<WallpaperState>("plugin:wallpaper|select_images");
|
|
}
|
|
|
|
export async function deleteImage(id: string): Promise<WallpaperState> {
|
|
if (inTauri()) return invoke<WallpaperState>("plugin:wallpaper|delete_image", { id });
|
|
const index = Number(id.replace("demo-", ""));
|
|
if (Number.isInteger(index) && index >= 0 && index < demoState.imageUrls.length) {
|
|
demoState.imageUrls.splice(index, 1);
|
|
demoState.imageCount = demoState.imageUrls.length;
|
|
demoState.currentIndex = Math.min(demoState.currentIndex, Math.max(0, demoState.imageCount - 1));
|
|
demoState.imageIds = demoState.imageUrls.map((_, itemIndex) => `demo-${itemIndex}`);
|
|
demoState.currentId = demoState.imageIds[demoState.currentIndex] ?? null;
|
|
}
|
|
return { ...demoState, imageUrls: [...demoState.imageUrls] };
|
|
}
|
|
|
|
export async function getImageIds(): Promise<string[]> {
|
|
if (inTauri()) return invoke<string[]>("plugin:wallpaper|get_image_ids");
|
|
return demoState.imageUrls.map((_, index) => `demo-${index}`);
|
|
}
|
|
|
|
export async function deleteImages(ids: string[]): Promise<WallpaperState> {
|
|
if (inTauri()) return invoke<WallpaperState>("plugin:wallpaper|delete_images", { ids });
|
|
const indexes = new Set(ids.map(id => Number(id.replace("demo-", ""))).filter(Number.isInteger));
|
|
demoState.imageUrls = demoState.imageUrls.filter((_, index) => !indexes.has(index));
|
|
demoState.imageCount = demoState.imageUrls.length;
|
|
demoState.currentIndex = Math.min(demoState.currentIndex, Math.max(0, demoState.imageCount - 1));
|
|
demoState.imageIds = demoState.imageUrls.map((_, index) => `demo-${index}`);
|
|
demoState.currentId = demoState.imageIds[demoState.currentIndex] ?? null;
|
|
return { ...demoState, imageUrls: [...demoState.imageUrls] };
|
|
}
|
|
|
|
export async function setImageCrop(image: GalleryImage): Promise<GalleryImage> {
|
|
const payload = {
|
|
id: image.id,
|
|
mode: image.cropMode,
|
|
zoom: image.cropZoom,
|
|
positionX: image.cropPositionX,
|
|
positionY: image.cropPositionY,
|
|
rotation: image.cropRotation,
|
|
};
|
|
if (inTauri()) return invoke<GalleryImage>("plugin:wallpaper|set_image_crop", payload);
|
|
demoCrops.set(image.id, {
|
|
cropMode: image.cropMode,
|
|
cropZoom: image.cropZoom,
|
|
cropPositionX: image.cropPositionX,
|
|
cropPositionY: image.cropPositionY,
|
|
cropRotation: image.cropRotation,
|
|
});
|
|
return { ...image };
|
|
}
|
|
|
|
export async function setSetting(name: "shuffle" | "lockScreenOnly" | "allowMobileData" | "prefetchImmich", value: boolean) {
|
|
if (!inTauri()) return { ...demoState, [name]: value };
|
|
return invoke<WallpaperState>("plugin:wallpaper|set_setting", { name, value });
|
|
}
|
|
|
|
export async function setIntervalMinutes(value: number) {
|
|
if (!inTauri()) {
|
|
demoState.intervalMinutes = value;
|
|
demoState.enabled = value > 0;
|
|
return { ...demoState };
|
|
}
|
|
return invoke<WallpaperState>("plugin:wallpaper|set_interval", { minutes: value });
|
|
}
|
|
|
|
export async function nextWallpaper(): Promise<WallpaperState> {
|
|
if (!inTauri()) {
|
|
if (demoState.imageUrls.length) demoState.currentIndex = (demoState.currentIndex + 1) % demoState.imageUrls.length;
|
|
demoState.currentId = demoState.imageIds[demoState.currentIndex] ?? null;
|
|
return { ...demoState };
|
|
}
|
|
return invoke<WallpaperState>("plugin:wallpaper|next_wallpaper");
|
|
}
|
|
|
|
export async function applyWallpaper(id: string): Promise<WallpaperState> {
|
|
if (!inTauri()) {
|
|
const index = demoState.imageIds.indexOf(id);
|
|
if (index < 0) throw new Error("Image not found");
|
|
demoState.currentIndex = index;
|
|
demoState.currentId = id;
|
|
return { ...demoState };
|
|
}
|
|
return invoke<WallpaperState>("plugin:wallpaper|apply_wallpaper", { id });
|
|
}
|
|
|
|
export async function getImmichConnection(): Promise<ImmichConnection> {
|
|
return inTauri() ? invoke<ImmichConnection>("plugin:wallpaper|get_immich_connection") : demoImmichConnection;
|
|
}
|
|
|
|
export async function connectImmich(serverUrl: string, apiKey: string): Promise<ImmichConnection> {
|
|
if (inTauri()) return invoke<ImmichConnection>("plugin:wallpaper|connect_immich", { serverUrl, apiKey });
|
|
if (!serverUrl.trim() || !apiKey.trim()) throw new Error("Missing Immich credentials");
|
|
demoImmichConnection = { configured: true, serverUrl: serverUrl.trim(), userName: "Demo User" };
|
|
return demoImmichConnection;
|
|
}
|
|
|
|
export async function disconnectImmich(): Promise<ImmichConnection> {
|
|
if (inTauri()) return invoke<ImmichConnection>("plugin:wallpaper|disconnect_immich");
|
|
demoImmichConnection = { configured: false, serverUrl: "", userName: "" };
|
|
return demoImmichConnection;
|
|
}
|
|
|
|
export async function getImmichAlbums(): Promise<ImmichAlbum[]> {
|
|
if (inTauri()) return (await invoke<{ albums: ImmichAlbum[] }>("plugin:wallpaper|get_immich_albums")).albums;
|
|
return [{ id: "demo-album", name: "Nature", assetCount: 3, thumbnailUrl: "/wallpapers/alpine.png" }];
|
|
}
|
|
|
|
export async function getImmichAssets(albumId: string | null, page = 1, size = 30): Promise<ImmichAssetsPage> {
|
|
if (inTauri()) return invoke<ImmichAssetsPage>("plugin:wallpaper|get_immich_assets", { albumId, page, size });
|
|
return {
|
|
items: demoState.imageUrls.map((thumbnailUrl, index) => ({
|
|
id: `00000000-0000-4000-8000-00000000000${index}`,
|
|
fileName: `wallpaper-${index + 1}.jpg`,
|
|
thumbnailUrl,
|
|
takenAt: "2026-08-21T12:00:00Z",
|
|
})),
|
|
page,
|
|
hasMore: false,
|
|
};
|
|
}
|
|
|
|
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 };
|
|
}
|