From f093d2a310627aa3ee5a2820339f8a18bd251e81 Mon Sep 17 00:00:00 2001
From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com>
Date: Wed, 14 Jan 2026 05:12:31 +0100
Subject: feat(java): integrate Adoptium API for Java runtime download

Add automatic Java (Temurin) download and installation feature:

- Add Adoptium API v3 integration to fetch latest Java releases
- Support JRE and JDK image types with version selection (8/11/17/21)
- Implement platform detection for macOS, Linux, and Windows
- Add SHA256 checksum verification for downloaded archives
- Add tar.gz extraction support with Unix permission preservation
- Handle macOS-specific Java path structure (Contents/Home/bin)
- Add frontend UI with version selector and download progress
- Register Tauri commands: fetch_adoptium_java, download_adoptium_java,
  fetch_available_java_versions

Dependencies added: sha2, flate2, tar, dirs
---
 ui/src/App.svelte | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 168 insertions(+)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 9b7fe93..02cc173 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -57,6 +57,16 @@
     is_64bit: boolean;
   }
 
+  interface JavaDownloadInfo {
+    version: string;
+    release_name: string;
+    download_url: string;
+    file_name: string;
+    file_size: number;
+    checksum: string | null;
+    image_type: string;
+  }
+
   let versions: Version[] = [];
   let selectedVersion = "";
   let currentAccount: Account | null = null;
@@ -70,6 +80,13 @@
   let javaInstallations: JavaInstallation[] = [];
   let isDetectingJava = false;
 
+  let availableJavaVersions: number[] = [];
+  let selectedJavaVersion = 21;
+  let selectedImageType: "jre" | "jdk" = "jre";
+  let isDownloadingJava = false;
+  let javaDownloadStatus = "";
+  let showJavaDownloadModal = false;
+
   // Login UI State
   let isLoginModalOpen = false;
   let loginMode: "select" | "offline" | "microsoft" = "select";
@@ -144,6 +161,59 @@
     settings.java_path = path;
   }
 
+  async function openJavaDownloadModal() {
+    showJavaDownloadModal = true;
+    javaDownloadStatus = "";
+    try {
+      availableJavaVersions = await invoke("fetch_available_java_versions");
+      // Default selection logic
+      if (availableJavaVersions.includes(21)) {
+        selectedJavaVersion = 21;
+      } else if (availableJavaVersions.includes(17)) {
+        selectedJavaVersion = 17;
+      } else if (availableJavaVersions.length > 0) {
+        selectedJavaVersion = availableJavaVersions[availableJavaVersions.length - 1];
+      }
+    } catch (e) {
+      console.error("Failed to fetch available Java versions:", e);
+      javaDownloadStatus = "Error fetching Java versions: " + e;
+    }
+  }
+
+  function closeJavaDownloadModal() {
+    if (!isDownloadingJava) {
+      showJavaDownloadModal = false;
+    }
+  }
+
+  async function downloadJava() {
+    isDownloadingJava = true;
+    javaDownloadStatus = `Downloading Java ${selectedJavaVersion} ${selectedImageType.toUpperCase()}...`;
+    
+    try {
+      const result: JavaInstallation = await invoke("download_adoptium_java", {
+        majorVersion: selectedJavaVersion,
+        imageType: selectedImageType,
+        customPath: null,
+      });
+      
+      javaDownloadStatus = `Java ${selectedJavaVersion} installed at ${result.path}`;
+      settings.java_path = result.path;
+      
+      await detectJava();
+      
+      setTimeout(() => {
+        showJavaDownloadModal = false;
+        status = `Java ${selectedJavaVersion} is ready to use!`;
+      }, 1500);
+    } catch (e) {
+      console.error("Failed to download Java:", e);
+      javaDownloadStatus = "Download failed: " + e;
+    } finally {
+      isDownloadingJava = false;
+    }
+  }
+
   // --- Auth Functions ---
 
   function openLoginModal() {
@@ -460,6 +530,12 @@
                 >
                   {isDetectingJava ? "Detecting..." : "Auto Detect"}
                 </button>
+                <button
+                  onclick={openJavaDownloadModal}
+                  class="bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 rounded transition-colors whitespace-nowrap"
+                >
+                  Download Java
+                </button>
               </div>
               
               {#if javaInstallations.length > 0}
@@ -782,6 +858,98 @@
     </div>
   {/if}
 
+  {#if showJavaDownloadModal}
+    <div
+      class="fixed inset-0 bg-black/70 flex items-center justify-center z-50 backdrop-blur-sm"
+      onclick={closeJavaDownloadModal}
+    >
+      <div
+        class="bg-zinc-900 border border-zinc-700 rounded-xl p-6 w-full max-w-md shadow-2xl"
+        onclick={(e) => e.stopPropagation()}
+      >
+        <div class="flex justify-between items-center mb-6">
+          <h3 class="text-xl font-bold">Download Java (Adoptium)</h3>
+          {#if !isDownloadingJava}
+            <button
+              onclick={closeJavaDownloadModal}
+              class="text-zinc-500 hover:text-white transition text-xl"
+            >
+              ✕
+            </button>
+          {/if}
+        </div>
+        <div class="space-y-4">
+          <!-- Version Selection -->
+          <div>
+            <label class="block text-sm font-bold text-zinc-400 mb-2">Java Version</label>
+            <select
+              bind:value={selectedJavaVersion}
+              disabled={isDownloadingJava}
+              class="w-full bg-zinc-950 border border-zinc-700 rounded p-3 text-white focus:border-indigo-500 outline-none disabled:opacity-50"
+            >
+              {#each availableJavaVersions as ver}
+                <option value={ver}>
+                  Java {ver} {ver === 21 ? "(Recommended)" : ver === 17 ? "(LTS)" : ver === 8 ? "(Legacy)" : ""}
+                </option>
+              {/each}
+            </select>
+            <p class="text-xs text-zinc-500 mt-1">
+              MC 1.20.5+ requires Java 21, MC 1.17-1.20.4 requires Java 17, older versions require Java 8
+            </p>
+          </div>
+
+          <!-- Image Type Selection -->
+          <div>
+            <label class="block text-sm font-bold text-zinc-400 mb-2">Type</label>
+            <div class="flex gap-3">
+              <button
+                onclick={() => selectedImageType = "jre"}
+                disabled={isDownloadingJava}
+                class="flex-1 p-3 rounded border transition-colors disabled:opacity-50 {selectedImageType === 'jre' ? 'border-indigo-500 bg-indigo-950/30 text-white' : 'border-zinc-700 bg-zinc-950 text-zinc-400 hover:border-zinc-500'}"
+              >
+                <div class="font-bold">JRE</div>
+                <div class="text-xs opacity-70">runtime environment</div>
+              </button>
+              <button
+                onclick={() => selectedImageType = "jdk"}
+                disabled={isDownloadingJava}
+                class="flex-1 p-3 rounded border transition-colors disabled:opacity-50 {selectedImageType === 'jdk' ? 'border-indigo-500 bg-indigo-950/30 text-white' : 'border-zinc-700 bg-zinc-950 text-zinc-400 hover:border-zinc-500'}"
+              >
+                <div class="font-bold">JDK</div>
+                <div class="text-xs opacity-70">development kit</div>
+              </button>
+            </div>
+          </div>
+
+          <!-- Status -->
+          {#if javaDownloadStatus}
+            <div class="p-3 rounded {javaDownloadStatus.startsWith('✓') ? 'bg-green-950/50 border border-green-700 text-green-400' : javaDownloadStatus.includes('failed') || javaDownloadStatus.includes('Failed') ? 'bg-red-950/50 border border-red-700 text-red-400' : 'bg-zinc-800 border border-zinc-700 text-zinc-300'}">
+              <p class="text-sm">{javaDownloadStatus}</p>
+            </div>
+          {/if}
+
+          <!-- Download Button -->
+          <button
+            onclick={downloadJava}
+            disabled={isDownloadingJava || availableJavaVersions.length === 0}
+            class="w-full bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed text-white p-3 rounded font-bold transition-colors flex items-center justify-center gap-2"
+          >
+            {#if isDownloadingJava}
+              <div class="animate-spin rounded-full h-5 w-5 border-2 border-white/30 border-t-white"></div>
+              Downloading...
+            {:else}
+              Download Java {selectedJavaVersion} {selectedImageType.toUpperCase()}
+            {/if}
+          </button>
+
+          <p class="text-xs text-zinc-500 text-center">
+            Provided by <a href="https://adoptium.net" class="text-indigo-400 hover:underline" onclick={(e) => { e.preventDefault(); openLink("https://adoptium.net"); }}>Eclipse Adoptium</a>
+          </p>
+        </div>
+      </div>
+    </div>
+  {/if}
+
   <style>
     @keyframes progress {
       from {
-- 
cgit v1.2.3-70-g09d2


From 1b92b559a37166796d431ade745b31c4f73bc2ed Mon Sep 17 00:00:00 2001
From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com>
Date: Wed, 14 Jan 2026 22:20:41 +0100
Subject: fix(ui): correct styling bugs and code quality issues
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Fix missing h- prefix in VersionsView height class (-40 → h-40)
- Correct typo in SaturnEffect comment (Warn → Warm White)
- Remove double space in App.svelte class attribute
---
 ui/src/App.svelte                     | 2 +-
 ui/src/components/VersionsView.svelte | 2 +-
 ui/src/lib/effects/SaturnEffect.ts    | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 1c465b1..968f6c5 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -140,7 +140,7 @@
   
   {#if uiState.showConsole}
      <!-- Assuming GameConsole handles its own display mode or overlay -->
-    <div class="fixed inset-0 z-[100] bg-black/80  flex items-center justify-center p-10">
+    <div class="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-10">
         <div class="w-full h-full bg-[#1e1e1e] rounded-xl overflow-hidden border border-white/10 shadow-2xl relative">
             <button class="absolute top-4 right-4 text-white hover:text-red-400 z-10" onclick={() => uiState.toggleConsole()}>✕</button>
             <GameConsole />
diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte
index 8f3a568..99cc296 100644
--- a/ui/src/components/VersionsView.svelte
+++ b/ui/src/components/VersionsView.svelte
@@ -158,7 +158,7 @@
              Fetching manifest...
           </div>
         {:else if filteredVersions().length === 0}
-          <div class="flex flex-col items-center justify-center -40 dark:text-white/30 text-black/30 gap-2">
+          <div class="flex flex-col items-center justify-center h-40 dark:text-white/30 text-black/30 gap-2">
              <span class="text-2xl">👻</span>
              <span>No matching versions found</span>
           </div>
diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts
index 8a1c11f..a370936 100644
--- a/ui/src/lib/effects/SaturnEffect.ts
+++ b/ui/src/lib/effects/SaturnEffect.ts
@@ -171,7 +171,7 @@ export class SaturnEffect {
 
             // Optimization: Planet color vs Ring color
             if (type === 0) {
-               // Planet: Warn White
+               // Planet: Warm White
                this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`;
             } else {
                // Ring: Cool White
-- 
cgit v1.2.3-70-g09d2


From 43a3e9c285f3d5d04fef025041a06609a0d1c218 Mon Sep 17 00:00:00 2001
From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com>
Date: Thu, 15 Jan 2026 05:29:58 +0100
Subject: feat(java): Implement Java catalog management and download features

- Added commands to fetch and refresh the Java catalog, cancel downloads, and manage pending downloads.
- Enhanced the Java download modal in the UI to support version selection, download progress, and pending downloads.
- Introduced new types for Java catalog, download progress, and pending downloads.
- Updated settings store to handle Java catalog state, download progress, and pending downloads.
- Improved user experience with loading states, error handling, and status notifications for Java installations.
---
 src-tauri/Cargo.toml                  |   2 +-
 src-tauri/src/core/downloader.rs      | 400 +++++++++++++++++++++++++++++++++-
 src-tauri/src/core/java.rs            | 369 ++++++++++++++++++++++++++++---
 src-tauri/src/main.rs                 |  52 +++++
 ui/src/App.svelte                     |  23 +-
 ui/src/components/SettingsView.svelte | 375 ++++++++++++++++++++++++++-----
 ui/src/stores/settings.svelte.ts      | 294 ++++++++++++++++++++++---
 ui/src/types/index.ts                 |  44 ++++
 8 files changed, 1429 insertions(+), 130 deletions(-)

(limited to 'ui/src/App.svelte')

diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index bc831fb..27a0e0c 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -13,7 +13,7 @@ toml = "0.5"
 log = "0.4"
 env_logger = "0.9"
 tokio = { version = "1.49.0", features = ["full"] }
-reqwest = { version = "0.13.1", features = ["json", "blocking"] }
+reqwest = { version = "0.13.1", features = ["json", "blocking", "stream"] }
 serde_json = "1.0.149"
 tauri = { version = "2.9", features = [] }
 tauri-plugin-shell = "2.3"
diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs
index d33c44d..bf6334f 100644
--- a/src-tauri/src/core/downloader.rs
+++ b/src-tauri/src/core/downloader.rs
@@ -2,10 +2,10 @@ use futures::StreamExt;
 use serde::{Deserialize, Serialize};
 use sha1::Digest as Sha1Digest;
 use std::path::PathBuf;
-use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
 use std::sync::Arc;
-use tauri::{Emitter, Window};
-use tokio::io::AsyncWriteExt;
+use tauri::{AppHandle, Emitter, Manager, Window};
+use tokio::io::{AsyncSeekExt, AsyncWriteExt};
 use tokio::sync::Semaphore;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -18,6 +18,400 @@ pub struct DownloadTask {
     pub sha256: Option<String>,
 }
 
+/// Metadata for resumable downloads stored in .part.meta file
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DownloadMetadata {
+    pub url: String,
+    pub file_name: String,
+    pub total_size: u64,
+    pub downloaded_bytes: u64,
+    pub checksum: Option<String>,
+    pub timestamp: u64,
+    pub segments: Vec<DownloadSegment>,
+}
+
+/// A download segment for multi-segment parallel downloading
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DownloadSegment {
+    pub start: u64,
+    pub end: u64,
+    pub downloaded: u64,
+    pub completed: bool,
+}
+
+/// Progress event for Java download
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaDownloadProgress {
+    pub file_name: String,
+    pub downloaded_bytes: u64,
+    pub total_bytes: u64,
+    pub speed_bytes_per_sec: u64,
+    pub eta_seconds: u64,
+    pub status: String, // "Downloading", "Extracting", "Verifying", "Completed", "Paused", "Error"
+    pub percentage: f32,
+}
+
+/// Pending download task for queue persistence
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct PendingJavaDownload {
+    pub major_version: u32,
+    pub image_type: String,
+    pub download_url: String,
+    pub file_name: String,
+    pub file_size: u64,
+    pub checksum: Option<String>,
+    pub install_path: String,
+    pub created_at: u64,
+}
+
+/// Download queue for persistence
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct DownloadQueue {
+    pub pending_downloads: Vec<PendingJavaDownload>,
+}
+
+impl DownloadQueue {
+    /// Load download queue from file
+    pub fn load(app_handle: &AppHandle) -> Self {
+        let queue_path = app_handle
+            .path()
+            .app_data_dir()
+            .unwrap()
+            .join("download_queue.json");
+        if queue_path.exists() {
+            if let Ok(content) = std::fs::read_to_string(&queue_path) {
+                if let Ok(queue) = serde_json::from_str(&content) {
+                    return queue;
+                }
+            }
+        }
+        Self::default()
+    }
+
+    /// Save download queue to file
+    pub fn save(&self, app_handle: &AppHandle) -> Result<(), String> {
+        let queue_path = app_handle
+            .path()
+            .app_data_dir()
+            .unwrap()
+            .join("download_queue.json");
+        let content = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
+        std::fs::write(&queue_path, content).map_err(|e| e.to_string())?;
+        Ok(())
+    }
+
+    /// Add a pending download
+    pub fn add(&mut self, download: PendingJavaDownload) {
+        // Remove existing download for same version/type
+        self.pending_downloads.retain(|d| {
+            !(d.major_version == download.major_version && d.image_type == download.image_type)
+        });
+        self.pending_downloads.push(download);
+    }
+
+    /// Remove a completed or cancelled download
+    pub fn remove(&mut self, major_version: u32, image_type: &str) {
+        self.pending_downloads.retain(|d| {
+            !(d.major_version == major_version && d.image_type == image_type)
+        });
+    }
+}
+
+/// Global cancel flag for Java downloads
+pub static JAVA_DOWNLOAD_CANCELLED: AtomicBool = AtomicBool::new(false);
+
+/// Reset the cancel flag
+pub fn reset_java_download_cancel() {
+    JAVA_DOWNLOAD_CANCELLED.store(false, Ordering::SeqCst);
+}
+
+/// Cancel the current Java download
+pub fn cancel_java_download() {
+    JAVA_DOWNLOAD_CANCELLED.store(true, Ordering::SeqCst);
+}
+
+/// Check if download is cancelled
+pub fn is_java_download_cancelled() -> bool {
+    JAVA_DOWNLOAD_CANCELLED.load(Ordering::SeqCst)
+}
+
+/// Determine optimal segment count based on file size
+fn get_segment_count(file_size: u64) -> usize {
+    if file_size < 20 * 1024 * 1024 {
+        1 // < 20MB: single segment
+    } else if file_size < 100 * 1024 * 1024 {
+        4 // 20-100MB: 4 segments
+    } else {
+        8 // > 100MB: 8 segments
+    }
+}
+
+/// Download a large file with resume support and progress events
+pub async fn download_with_resume(
+    app_handle: &AppHandle,
+    url: &str,
+    dest_path: &PathBuf,
+    checksum: Option<&str>,
+    total_size: u64,
+) -> Result<(), String> {
+    reset_java_download_cancel();
+
+    let part_path = dest_path.with_extension(
+        dest_path
+            .extension()
+            .map(|e| format!("{}.part", e.to_string_lossy()))
+            .unwrap_or_else(|| "part".to_string()),
+    );
+    let meta_path = PathBuf::from(format!("{}.meta", part_path.display()));
+    let file_name = dest_path
+        .file_name()
+        .unwrap_or_default()
+        .to_string_lossy()
+        .to_string();
+
+    // Load or create metadata
+    let mut metadata = if meta_path.exists() {
+        let content = tokio::fs::read_to_string(&meta_path)
+            .await
+            .map_err(|e| e.to_string())?;
+        serde_json::from_str(&content).unwrap_or_else(|_| create_new_metadata(url, &file_name, total_size, checksum))
+    } else {
+        create_new_metadata(url, &file_name, total_size, checksum)
+    };
+
+    // Create parent directory
+    if let Some(parent) = dest_path.parent() {
+        tokio::fs::create_dir_all(parent)
+            .await
+            .map_err(|e| e.to_string())?;
+    }
+
+    // Open or create part file
+    let file = tokio::fs::OpenOptions::new()
+        .create(true)
+        .write(true)
+        .read(true)
+        .open(&part_path)
+        .await
+        .map_err(|e| format!("Failed to open part file: {}", e))?;
+
+    let file = Arc::new(tokio::sync::Mutex::new(file));
+    let client = reqwest::Client::new();
+    let progress = Arc::new(AtomicU64::new(metadata.downloaded_bytes));
+    let start_time = std::time::Instant::now();
+    let last_progress_bytes = Arc::new(AtomicU64::new(metadata.downloaded_bytes));
+
+    // Download segments concurrently
+    let segment_count = metadata.segments.len();
+    let semaphore = Arc::new(Semaphore::new(segment_count.min(8)));
+    let mut handles = Vec::new();
+
+    for (idx, segment) in metadata.segments.iter().enumerate() {
+        if segment.completed {
+            continue;
+        }
+
+        let client = client.clone();
+        let url = url.to_string();
+        let file = file.clone();
+        let progress = progress.clone();
+        let semaphore = semaphore.clone();
+        let segment_start = segment.start + segment.downloaded;
+        let segment_end = segment.end;
+        let app_handle = app_handle.clone();
+        let file_name = file_name.clone();
+        let total_size = total_size;
+        let last_progress_bytes = last_progress_bytes.clone();
+        let start_time = start_time.clone();
+
+        let handle = tokio::spawn(async move {
+            let _permit = semaphore.acquire().await.unwrap();
+
+            if is_java_download_cancelled() {
+                return Err("Download cancelled".to_string());
+            }
+
+            // Send Range request
+            let range = format!("bytes={}-{}", segment_start, segment_end);
+            let response = client
+                .get(&url)
+                .header("Range", &range)
+                .send()
+                .await
+                .map_err(|e| format!("Request failed: {}", e))?;
+
+            if !response.status().is_success() && response.status() != reqwest::StatusCode::PARTIAL_CONTENT {
+                return Err(format!("Server returned error: {}", response.status()));
+            }
+
+            let mut stream = response.bytes_stream();
+            let mut current_pos = segment_start;
+
+            while let Some(chunk_result) = stream.next().await {
+                if is_java_download_cancelled() {
+                    return Err("Download cancelled".to_string());
+                }
+
+                let chunk = chunk_result.map_err(|e| format!("Stream error: {}", e))?;
+                let chunk_len = chunk.len() as u64;
+
+                // Write to file at correct position
+                {
+                    let mut file_guard = file.lock().await;
+                    file_guard
+                        .seek(std::io::SeekFrom::Start(current_pos))
+                        .await
+                        .map_err(|e| format!("Seek error: {}", e))?;
+                    file_guard
+                        .write_all(&chunk)
+                        .await
+                        .map_err(|e| format!("Write error: {}", e))?;
+                }
+
+                current_pos += chunk_len;
+                let total_downloaded = progress.fetch_add(chunk_len, Ordering::Relaxed) + chunk_len;
+
+                // Emit progress event (throttled)
+                let last_bytes = last_progress_bytes.load(Ordering::Relaxed);
+                if total_downloaded - last_bytes > 100 * 1024 || total_downloaded >= total_size {
+                    last_progress_bytes.store(total_downloaded, Ordering::Relaxed);
+
+                    let elapsed = start_time.elapsed().as_secs_f64();
+                    let speed = if elapsed > 0.0 {
+                        (total_downloaded as f64 / elapsed) as u64
+                    } else {
+                        0
+                    };
+                    let remaining = total_size.saturating_sub(total_downloaded);
+                    let eta = if speed > 0 { remaining / speed } else { 0 };
+                    let percentage = (total_downloaded as f32 / total_size as f32) * 100.0;
+
+                    let _ = app_handle.emit(
+                        "java-download-progress",
+                        JavaDownloadProgress {
+                            file_name: file_name.clone(),
+                            downloaded_bytes: total_downloaded,
+                            total_bytes: total_size,
+                            speed_bytes_per_sec: speed,
+                            eta_seconds: eta,
+                            status: "Downloading".to_string(),
+                            percentage,
+                        },
+                    );
+                }
+            }
+
+            Ok::<usize, String>(idx)
+        });
+
+        handles.push(handle);
+    }
+
+    // Wait for all segments
+    let mut all_success = true;
+    for handle in handles {
+        match handle.await {
+            Ok(Ok(idx)) => {
+                metadata.segments[idx].completed = true;
+            }
+            Ok(Err(e)) => {
+                all_success = false;
+                if e.contains("cancelled") {
+                    // Save progress for resume
+                    metadata.downloaded_bytes = progress.load(Ordering::Relaxed);
+                    let meta_content = serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?;
+                    tokio::fs::write(&meta_path, meta_content).await.ok();
+                    return Err(e);
+                }
+            }
+            Err(e) => {
+                all_success = false;
+                eprintln!("Segment task panicked: {}", e);
+            }
+        }
+    }
+
+    if !all_success {
+        // Save progress
+        metadata.downloaded_bytes = progress.load(Ordering::Relaxed);
+        let meta_content = serde_json::to_string_pretty(&metadata).map_err(|e| e.to_string())?;
+        tokio::fs::write(&meta_path, meta_content).await.ok();
+        return Err("Some segments failed".to_string());
+    }
+
+    // Verify checksum if provided
+    if let Some(expected) = checksum {
+        let _ = app_handle.emit(
+            "java-download-progress",
+            JavaDownloadProgress {
+                file_name: file_name.clone(),
+                downloaded_bytes: total_size,
+                total_bytes: total_size,
+                speed_bytes_per_sec: 0,
+                eta_seconds: 0,
+                status: "Verifying".to_string(),
+                percentage: 100.0,
+            },
+        );
+
+        let data = tokio::fs::read(&part_path)
+            .await
+            .map_err(|e| format!("Failed to read file for verification: {}", e))?;
+        
+        if !verify_checksum(&data, Some(expected), None) {
+            // Checksum failed, delete files and retry
+            tokio::fs::remove_file(&part_path).await.ok();
+            tokio::fs::remove_file(&meta_path).await.ok();
+            return Err("Checksum verification failed".to_string());
+        }
+    }
+
+    // Rename part file to final destination
+    tokio::fs::rename(&part_path, dest_path)
+        .await
+        .map_err(|e| format!("Failed to rename file: {}", e))?;
+
+    // Clean up metadata file
+    tokio::fs::remove_file(&meta_path).await.ok();
+
+    Ok(())
+}
+
+/// Create new download metadata with segments
+fn create_new_metadata(url: &str, file_name: &str, total_size: u64, checksum: Option<&str>) -> DownloadMetadata {
+    let segment_count = get_segment_count(total_size);
+    let segment_size = total_size / segment_count as u64;
+    let mut segments = Vec::new();
+
+    for i in 0..segment_count {
+        let start = i as u64 * segment_size;
+        let end = if i == segment_count - 1 {
+            total_size - 1
+        } else {
+            (i as u64 + 1) * segment_size - 1
+        };
+        segments.push(DownloadSegment {
+            start,
+            end,
+            downloaded: 0,
+            completed: false,
+        });
+    }
+
+    DownloadMetadata {
+        url: url.to_string(),
+        file_name: file_name.to_string(),
+        total_size,
+        downloaded_bytes: 0,
+        checksum: checksum.map(|s| s.to_string()),
+        timestamp: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+        segments,
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ProgressEvent {
     pub file: String,
diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs
index a8fdeea..8341138 100644
--- a/src-tauri/src/core/java.rs
+++ b/src-tauri/src/core/java.rs
@@ -2,11 +2,15 @@ use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
 use std::process::Command;
 use tauri::AppHandle;
+use tauri::Emitter;
 use tauri::Manager;
 
-use crate::core::downloader;
+use crate::core::downloader::{self, JavaDownloadProgress, DownloadQueue, PendingJavaDownload};
 use crate::utils::zip;
 
+const ADOPTIUM_API_BASE: &str = "https://api.adoptium.net/v3";
+const CACHE_DURATION_SECS: u64 = 24 * 60 * 60; // 24 hours
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct JavaInstallation {
     pub path: String,
@@ -37,6 +41,42 @@ impl std::fmt::Display for ImageType {
     }
 }
 
+/// Java release information for UI display
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaReleaseInfo {
+    pub major_version: u32,
+    pub image_type: String,
+    pub version: String,
+    pub release_name: String,
+    pub release_date: Option<String>,
+    pub file_size: u64,
+    pub checksum: Option<String>,
+    pub download_url: String,
+    pub is_lts: bool,
+    pub is_available: bool,
+    pub architecture: String,
+}
+
+/// Java catalog containing all available versions
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JavaCatalog {
+    pub releases: Vec<JavaReleaseInfo>,
+    pub available_major_versions: Vec<u32>,
+    pub lts_versions: Vec<u32>,
+    pub cached_at: u64,
+}
+
+impl Default for JavaCatalog {
+    fn default() -> Self {
+        Self {
+            releases: Vec::new(),
+            available_major_versions: Vec::new(),
+            lts_versions: Vec::new(),
+            cached_at: 0,
+        }
+    }
+}
+
 /// Adoptium `/v3/assets/latest/{version}/hotspot` API response structures
 #[derive(Debug, Clone, Deserialize)]
 pub struct AdoptiumAsset {
@@ -51,6 +91,8 @@ pub struct AdoptiumBinary {
     pub architecture: String,
     pub image_type: String,
     pub package: AdoptiumPackage,
+    #[serde(default)]
+    pub updated_at: Option<String>,
 }
 
 #[derive(Debug, Clone, Deserialize)]
@@ -70,6 +112,15 @@ pub struct AdoptiumVersionData {
     pub openjdk_version: String,
 }
 
+/// Adoptium available releases response
+#[derive(Debug, Clone, Deserialize)]
+pub struct AvailableReleases {
+    pub available_releases: Vec<u32>,
+    pub available_lts_releases: Vec<u32>,
+    pub most_recent_lts: Option<u32>,
+    pub most_recent_feature_release: Option<u32>,
+}
+
 /// Java download information from Adoptium
 #[derive(Debug, Clone, Serialize)]
 pub struct JavaDownloadInfo {
@@ -140,6 +191,170 @@ pub fn get_java_install_dir(app_handle: &AppHandle) -> PathBuf {
     app_handle.path().app_data_dir().unwrap().join("java")
 }
 
+/// Get the cache file path for Java catalog
+fn get_catalog_cache_path(app_handle: &AppHandle) -> PathBuf {
+    app_handle
+        .path()
+        .app_data_dir()
+        .unwrap()
+        .join("java_catalog_cache.json")
+}
+
+/// Load cached Java catalog if not expired
+pub fn load_cached_catalog(app_handle: &AppHandle) -> Option<JavaCatalog> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    if !cache_path.exists() {
+        return None;
+    }
+
+    let content = std::fs::read_to_string(&cache_path).ok()?;
+    let catalog: JavaCatalog = serde_json::from_str(&content).ok()?;
+
+    let now = std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    if now - catalog.cached_at < CACHE_DURATION_SECS {
+        Some(catalog)
+    } else {
+        None
+    }
+}
+
+/// Save Java catalog to cache
+pub fn save_catalog_cache(app_handle: &AppHandle, catalog: &JavaCatalog) -> Result<(), String> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    let content = serde_json::to_string_pretty(catalog).map_err(|e| e.to_string())?;
+    std::fs::write(&cache_path, content).map_err(|e| e.to_string())?;
+    Ok(())
+}
+
+/// Clear Java catalog cache
+pub fn clear_catalog_cache(app_handle: &AppHandle) -> Result<(), String> {
+    let cache_path = get_catalog_cache_path(app_handle);
+    if cache_path.exists() {
+        std::fs::remove_file(&cache_path).map_err(|e| e.to_string())?;
+    }
+    Ok(())
+}
+
+/// Fetch complete Java catalog from Adoptium API with platform availability check
+pub async fn fetch_java_catalog(app_handle: &AppHandle, force_refresh: bool) -> Result<JavaCatalog, String> {
+    // Check cache first unless force refresh
+    if !force_refresh {
+        if let Some(cached) = load_cached_catalog(app_handle) {
+            return Ok(cached);
+        }
+    }
+
+    let os = get_adoptium_os();
+    let arch = get_adoptium_arch();
+    let client = reqwest::Client::new();
+
+    // 1. Fetch available releases
+    let releases_url = format!("{}/info/available_releases", ADOPTIUM_API_BASE);
+    let available: AvailableReleases = client
+        .get(&releases_url)
+        .header("Accept", "application/json")
+        .send()
+        .await
+        .map_err(|e| format!("Failed to fetch available releases: {}", e))?
+        .json()
+        .await
+        .map_err(|e| format!("Failed to parse available releases: {}", e))?;
+
+    let mut releases = Vec::new();
+
+    // 2. Fetch details for each major version
+    for major_version in &available.available_releases {
+        for image_type in &["jre", "jdk"] {
+            let url = format!(
+                "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
+                ADOPTIUM_API_BASE, major_version, os, arch, image_type
+            );
+
+            match client
+                .get(&url)
+                .header("Accept", "application/json")
+                .send()
+                .await
+            {
+                Ok(response) => {
+                    if response.status().is_success() {
+                        if let Ok(assets) = response.json::<Vec<AdoptiumAsset>>().await {
+                            if let Some(asset) = assets.into_iter().next() {
+                                let release_date = asset.binary.updated_at.clone();
+                                releases.push(JavaReleaseInfo {
+                                    major_version: *major_version,
+                                    image_type: image_type.to_string(),
+                                    version: asset.version.semver.clone(),
+                                    release_name: asset.release_name.clone(),
+                                    release_date,
+                                    file_size: asset.binary.package.size,
+                                    checksum: asset.binary.package.checksum,
+                                    download_url: asset.binary.package.link,
+                                    is_lts: available.available_lts_releases.contains(major_version),
+                                    is_available: true,
+                                    architecture: asset.binary.architecture.clone(),
+                                });
+                            }
+                        }
+                    } else {
+                        // Platform not available for this version/type
+                        releases.push(JavaReleaseInfo {
+                            major_version: *major_version,
+                            image_type: image_type.to_string(),
+                            version: format!("{}.x", major_version),
+                            release_name: format!("jdk-{}", major_version),
+                            release_date: None,
+                            file_size: 0,
+                            checksum: None,
+                            download_url: String::new(),
+                            is_lts: available.available_lts_releases.contains(major_version),
+                            is_available: false,
+                            architecture: arch.to_string(),
+                        });
+                    }
+                }
+                Err(_) => {
+                    // Network error, mark as unavailable
+                    releases.push(JavaReleaseInfo {
+                        major_version: *major_version,
+                        image_type: image_type.to_string(),
+                        version: format!("{}.x", major_version),
+                        release_name: format!("jdk-{}", major_version),
+                        release_date: None,
+                        file_size: 0,
+                        checksum: None,
+                        download_url: String::new(),
+                        is_lts: available.available_lts_releases.contains(major_version),
+                        is_available: false,
+                        architecture: arch.to_string(),
+                    });
+                }
+            }
+        }
+    }
+
+    let now = std::time::SystemTime::now()
+        .duration_since(std::time::UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    let catalog = JavaCatalog {
+        releases,
+        available_major_versions: available.available_releases,
+        lts_versions: available.available_lts_releases,
+        cached_at: now,
+    };
+
+    // Save to cache
+    let _ = save_catalog_cache(app_handle, &catalog);
+
+    Ok(catalog)
+}
+
 /// Get Adoptium API download info for a specific Java version and image type
 ///
 /// # Arguments
@@ -157,8 +372,8 @@ pub async fn fetch_java_release(
     let arch = get_adoptium_arch();
 
     let url = format!(
-        "https://api.adoptium.net/v3/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
-        major_version, os, arch, image_type
+        "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}",
+        ADOPTIUM_API_BASE, major_version, os, arch, image_type
     );
 
     let client = reqwest::Client::new();
@@ -199,7 +414,7 @@ pub async fn fetch_java_release(
 
 /// Fetch available Java versions from Adoptium API
 pub async fn fetch_available_versions() -> Result<Vec<u32>, String> {
-    let url = "https://api.adoptium.net/v3/info/available_releases";
+    let url = format!("{}/info/available_releases", ADOPTIUM_API_BASE);
 
     let response = reqwest::get(url)
         .await
@@ -218,7 +433,7 @@ pub async fn fetch_available_versions() -> Result<Vec<u32>, String> {
     Ok(releases.available_releases)
 }
 
-/// Download and install Java
+/// Download and install Java with resume support and progress events
 ///
 /// # Arguments
 /// * `app_handle` - Tauri app handle for accessing app directories
@@ -236,6 +451,7 @@ pub async fn download_and_install_java(
 ) -> Result<JavaInstallation, String> {
     // 1. Fetch download information
     let info = fetch_java_release(major_version, image_type).await?;
+    let file_name = info.file_name.clone();
 
     // 2. Prepare installation directory
     let install_base = custom_path.unwrap_or_else(|| get_java_install_dir(app_handle));
@@ -244,7 +460,24 @@ pub async fn download_and_install_java(
     std::fs::create_dir_all(&install_base)
         .map_err(|e| format!("Failed to create installation directory: {}", e))?;
 
-    // 3. Download the archive
+    // 3. Add to download queue for persistence
+    let mut queue = DownloadQueue::load(app_handle);
+    queue.add(PendingJavaDownload {
+        major_version,
+        image_type: image_type.to_string(),
+        download_url: info.download_url.clone(),
+        file_name: info.file_name.clone(),
+        file_size: info.file_size,
+        checksum: info.checksum.clone(),
+        install_path: install_base.to_string_lossy().to_string(),
+        created_at: std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+    });
+    queue.save(app_handle)?;
+
+    // 4. Download the archive with resume support
     let archive_path = install_base.join(&info.file_name);
 
     // Check if we need to download
@@ -261,30 +494,32 @@ pub async fn download_and_install_java(
     };
 
     if need_download {
-        let client = reqwest::Client::new();
-        let response = client
-            .get(&info.download_url)
-            .send()
-            .await
-            .map_err(|e| format!("Download failed: {}", e))?;
-
-        let bytes = response
-            .bytes()
-            .await
-            .map_err(|e| format!("Failed to read download content: {}", e))?;
-
-        // Verify downloaded file checksum
-        if let Some(expected) = &info.checksum {
-            if !downloader::verify_checksum(&bytes, Some(expected), None) {
-                return Err("Downloaded file verification failed, the file may be corrupted".to_string());
-            }
-        }
-
-        std::fs::write(&archive_path, &bytes)
-            .map_err(|e| format!("Failed to save downloaded file: {}", e))?;
+        // Use resumable download
+        downloader::download_with_resume(
+            app_handle,
+            &info.download_url,
+            &archive_path,
+            info.checksum.as_deref(),
+            info.file_size,
+        )
+        .await?;
     }
 
-    // 4. Extract
+    // 5. Emit extracting status
+    let _ = app_handle.emit(
+        "java-download-progress",
+        JavaDownloadProgress {
+            file_name: file_name.clone(),
+            downloaded_bytes: info.file_size,
+            total_bytes: info.file_size,
+            speed_bytes_per_sec: 0,
+            eta_seconds: 0,
+            status: "Extracting".to_string(),
+            percentage: 100.0,
+        },
+    );
+
+    // 6. Extract
     // If the target directory exists, remove it first
     if version_dir.exists() {
         std::fs::remove_dir_all(&version_dir)
@@ -304,10 +539,10 @@ pub async fn download_and_install_java(
         return Err(format!("Unsupported archive format: {}", info.file_name));
     };
 
-    // 5. Clean up downloaded archive
+    // 7. Clean up downloaded archive
     let _ = std::fs::remove_file(&archive_path);
 
-    // 6. Locate java executable
+    // 8. Locate java executable
     // macOS has a different structure: jdk-xxx/Contents/Home/bin/java
     // Linux/Windows: jdk-xxx/bin/java
     let java_home = version_dir.join(&top_level_dir);
@@ -326,10 +561,28 @@ pub async fn download_and_install_java(
         ));
     }
 
-    // 7. Verify installation
+    // 9. Verify installation
     let installation = check_java_installation(&java_bin)
         .ok_or_else(|| "Failed to verify Java installation".to_string())?;
 
+    // 10. Remove from download queue
+    queue.remove(major_version, &image_type.to_string());
+    queue.save(app_handle)?;
+
+    // 11. Emit completed status
+    let _ = app_handle.emit(
+        "java-download-progress",
+        JavaDownloadProgress {
+            file_name,
+            downloaded_bytes: info.file_size,
+            total_bytes: info.file_size,
+            speed_bytes_per_sec: 0,
+            eta_seconds: 0,
+            status: "Completed".to_string(),
+            percentage: 100.0,
+        },
+    );
+
     Ok(installation)
 }
 
@@ -676,3 +929,57 @@ fn find_java_executable(dir: &PathBuf) -> Option<PathBuf> {
 
     None
 }
+
+/// Resume pending Java downloads from queue
+pub async fn resume_pending_downloads(app_handle: &AppHandle) -> Result<Vec<JavaInstallation>, String> {
+    let queue = DownloadQueue::load(app_handle);
+    let mut installed = Vec::new();
+
+    for pending in queue.pending_downloads.iter() {
+        let image_type = if pending.image_type == "jdk" {
+            ImageType::Jdk
+        } else {
+            ImageType::Jre
+        };
+
+        // Try to resume the download
+        match download_and_install_java(
+            app_handle,
+            pending.major_version,
+            image_type,
+            Some(PathBuf::from(&pending.install_path)),
+        )
+        .await
+        {
+            Ok(installation) => {
+                installed.push(installation);
+            }
+            Err(e) => {
+                eprintln!(
+                    "Failed to resume Java {} {} download: {}",
+                    pending.major_version, pending.image_type, e
+                );
+            }
+        }
+    }
+
+    Ok(installed)
+}
+
+/// Cancel current Java download
+pub fn cancel_current_download() {
+    downloader::cancel_java_download();
+}
+
+/// Get pending downloads from queue
+pub fn get_pending_downloads(app_handle: &AppHandle) -> Vec<PendingJavaDownload> {
+    let queue = DownloadQueue::load(app_handle);
+    queue.pending_downloads
+}
+
+/// Clear a specific pending download
+pub fn clear_pending_download(app_handle: &AppHandle, major_version: u32, image_type: &str) -> Result<(), String> {
+    let mut queue = DownloadQueue::load(app_handle);
+    queue.remove(major_version, image_type);
+    queue.save(app_handle)
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index a2b8c6c..53589d8 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -895,6 +895,45 @@ async fn fetch_available_java_versions() -> Result<Vec<u32>, String> {
     core::java::fetch_available_versions().await
 }
 
+/// Fetch Java catalog with platform availability (uses cache)
+#[tauri::command]
+async fn fetch_java_catalog(
+    app_handle: tauri::AppHandle,
+) -> Result<core::java::JavaCatalog, String> {
+    core::java::fetch_java_catalog(&app_handle, false).await
+}
+
+/// Refresh Java catalog (bypass cache)
+#[tauri::command]
+async fn refresh_java_catalog(
+    app_handle: tauri::AppHandle,
+) -> Result<core::java::JavaCatalog, String> {
+    core::java::fetch_java_catalog(&app_handle, true).await
+}
+
+/// Cancel current Java download
+#[tauri::command]
+async fn cancel_java_download() -> Result<(), String> {
+    core::java::cancel_current_download();
+    Ok(())
+}
+
+/// Get pending Java downloads
+#[tauri::command]
+async fn get_pending_java_downloads(
+    app_handle: tauri::AppHandle,
+) -> Result<Vec<core::downloader::PendingJavaDownload>, String> {
+    Ok(core::java::get_pending_downloads(&app_handle))
+}
+
+/// Resume pending Java downloads
+#[tauri::command]
+async fn resume_java_downloads(
+    app_handle: tauri::AppHandle,
+) -> Result<Vec<core::java::JavaInstallation>, String> {
+    core::java::resume_pending_downloads(&app_handle).await
+}
+
 /// Get Minecraft versions supported by Fabric
 #[tauri::command]
 async fn get_fabric_game_versions() -> Result<Vec<core::fabric::FabricGameVersion>, String> {
@@ -1067,6 +1106,13 @@ fn main() {
                 println!("[Startup] Loaded saved account");
             }
 
+            // Check for pending Java downloads and notify frontend
+            let pending = core::java::get_pending_downloads(&app.app_handle());
+            if !pending.is_empty() {
+                println!("[Startup] Found {} pending Java download(s)", pending.len());
+                let _ = app.emit("pending-java-downloads", pending.len());
+            }
+
             Ok(())
         })
         .invoke_handler(tauri::generate_handler![
@@ -1080,11 +1126,17 @@ fn main() {
             start_microsoft_login,
             complete_microsoft_login,
             refresh_account,
+            // Java commands
             detect_java,
             get_recommended_java,
             fetch_adoptium_java,
             download_adoptium_java,
             fetch_available_java_versions,
+            fetch_java_catalog,
+            refresh_java_catalog,
+            cancel_java_download,
+            get_pending_java_downloads,
+            resume_java_downloads,
             // Fabric commands
             get_fabric_game_versions,
             get_fabric_loader_versions,
diff --git a/ui/src/App.svelte b/ui/src/App.svelte
index 968f6c5..f32a42f 100644
--- a/ui/src/App.svelte
+++ b/ui/src/App.svelte
@@ -1,25 +1,23 @@
 <script lang="ts">
   import { getVersion } from "@tauri-apps/api/app";
-  import { onMount, onDestroy } from "svelte";
   import { convertFileSrc } from "@tauri-apps/api/core";
+  import { onDestroy, onMount } from "svelte";
   import DownloadMonitor from "./lib/DownloadMonitor.svelte";
   import GameConsole from "./lib/GameConsole.svelte";
-  
-  // Components
-  import Sidebar from "./components/Sidebar.svelte";
-  import HomeView from "./components/HomeView.svelte";
-  import VersionsView from "./components/VersionsView.svelte";
-  import SettingsView from "./components/SettingsView.svelte";
+// Components
   import BottomBar from "./components/BottomBar.svelte";
+  import HomeView from "./components/HomeView.svelte";
   import LoginModal from "./components/LoginModal.svelte";
-  import StatusToast from "./components/StatusToast.svelte";
   import ParticleBackground from "./components/ParticleBackground.svelte";
-
-  // Stores
-  import { uiState } from "./stores/ui.svelte";
+  import SettingsView from "./components/SettingsView.svelte";
+  import Sidebar from "./components/Sidebar.svelte";
+  import StatusToast from "./components/StatusToast.svelte";
+  import VersionsView from "./components/VersionsView.svelte";
+// Stores
   import { authState } from "./stores/auth.svelte";
-  import { settingsState } from "./stores/settings.svelte";
   import { gameState } from "./stores/game.svelte";
+  import { settingsState } from "./stores/settings.svelte";
+  import { uiState } from "./stores/ui.svelte";
 
   let mouseX = $state(0);
   let mouseY = $state(0);
@@ -32,6 +30,7 @@
   onMount(async () => {
     authState.checkAccount();
     await settingsState.loadSettings();
+    await settingsState.detectJava();
     gameState.loadVersions();
     getVersion().then((v) => (uiState.appVersion = v));
     window.addEventListener("mousemove", handleMouseMove);
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index d409784..42b1056 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
-  import { settingsState } from "../stores/settings.svelte";
-  import { open } from "@tauri-apps/plugin-dialog";
   import { convertFileSrc } from "@tauri-apps/api/core";
+  import { open } from "@tauri-apps/plugin-dialog";
+  import { settingsState } from "../stores/settings.svelte";
 
   async function selectBackground() {
     try {
@@ -51,9 +51,9 @@
             <!-- Preview -->
             <div class="w-40 h-24 rounded-xl overflow-hidden dark:bg-black/50 bg-gray-200 border dark:border-white/10 border-black/10 relative group shadow-lg">
               {#if settingsState.settings.custom_background_path}
-                <img 
-                  src={convertFileSrc(settingsState.settings.custom_background_path)} 
-                  alt="Background Preview" 
+                <img
+                  src={convertFileSrc(settingsState.settings.custom_background_path)}
+                  alt="Background Preview"
                   class="w-full h-full object-cover"
                 />
               {:else}
@@ -307,68 +307,329 @@
 <!-- Java Download Modal -->
 {#if settingsState.showJavaDownloadModal}
   <div class="fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-black/70">
-    <div class="bg-zinc-900 rounded-2xl border border-white/10 p-8 max-w-md w-full mx-4 shadow-2xl">
-      <h3 class="text-2xl font-bold mb-6 text-white">Download Java</h3>
-      
-      <div class="space-y-6">
-        <!-- Java Version Selection -->
-        <div>
-          <label class="block text-sm font-medium text-white/70 mb-2">Java Version</label>
-          <select
-            bind:value={settingsState.selectedJavaVersion}
-            class="bg-black/40 text-white w-full px-4 py-3 rounded-xl border border-white/10 focus:border-indigo-500/50 outline-none"
+    <div class="bg-zinc-900 rounded-2xl border border-white/10 shadow-2xl w-[900px] max-w-[95vw] h-[600px] max-h-[90vh] flex flex-col overflow-hidden">
+      <!-- Header -->
+      <div class="flex items-center justify-between p-5 border-b border-white/10">
+        <h3 class="text-xl font-bold text-white">Download Java</h3>
+        <button
+          aria-label="Close dialog"
+          onclick={() => settingsState.closeJavaDownloadModal()}
+          disabled={settingsState.isDownloadingJava}
+          class="text-white/40 hover:text-white/80 disabled:opacity-50 transition-colors p-1"
+        >
+          <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+          </svg>
+        </button>
+      </div>
+
+      <!-- Main Content Area -->
+      <div class="flex flex-1 overflow-hidden">
+        <!-- Left Sidebar: Sources -->
+        <div class="w-40 border-r border-white/10 p-3 flex flex-col gap-1">
+          <span class="text-[10px] font-bold uppercase tracking-widest text-white/30 px-2 mb-2">Sources</span>
+          
+          <button
+            disabled
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm opacity-40 cursor-not-allowed text-white/50"
           >
-            {#each settingsState.availableJavaVersions as version}
-              <option value={version}>Java {version}</option>
-            {/each}
-          </select>
+            <div class="w-5 h-5 rounded bg-white/10 flex items-center justify-center text-[10px]">M</div>
+            Mojang
+          </button>
+          
+          <button
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm bg-indigo-500/20 border border-indigo-500/40 text-white"
+          >
+            <div class="w-5 h-5 rounded bg-indigo-500 flex items-center justify-center text-[10px] font-bold">A</div>
+            Adoptium
+          </button>
+          
+          <button
+            disabled
+            class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-left text-sm opacity-40 cursor-not-allowed text-white/50"
+          >
+            <div class="w-5 h-5 rounded bg-white/10 flex items-center justify-center text-[10px]">Z</div>
+            Azul Zulu
+          </button>
         </div>
 
-        <!-- Image Type Selection -->
-        <div>
-          <label class="block text-sm font-medium text-white/70 mb-2">Image Type</label>
-          <div class="grid grid-cols-2 gap-3">
-            <button
-              onclick={() => settingsState.selectedImageType = "jre"}
-              class="px-4 py-3 rounded-xl border transition-all {settingsState.selectedImageType === 'jre' ? 'bg-indigo-500/20 border-indigo-500/50 text-white' : 'bg-black/20 border-white/10 text-white/60 hover:bg-white/5'}"
-            >
-              JRE
-            </button>
+        <!-- Center: Version Selection -->
+        <div class="flex-1 flex flex-col overflow-hidden">
+          <!-- Toolbar -->
+          <div class="flex items-center gap-3 p-4 border-b border-white/5">
+            <!-- Search -->
+            <div class="relative flex-1 max-w-xs">
+              <input
+                type="text"
+                bind:value={settingsState.searchQuery}
+                placeholder="Search versions..."
+                class="w-full bg-black/30 text-white text-sm px-4 py-2 pl-9 rounded-lg border border-white/10 focus:border-indigo-500/50 outline-none"
+              />
+              <svg class="absolute left-3 top-2.5 w-4 h-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
+              </svg>
+            </div>
+            
+            <!-- Recommended Filter -->
+            <label class="flex items-center gap-2 text-sm text-white/60 cursor-pointer select-none">
+              <input
+                type="checkbox"
+                bind:checked={settingsState.showOnlyRecommended}
+                class="w-4 h-4 rounded border-white/20 bg-black/30 text-indigo-500 focus:ring-indigo-500/30"
+              />
+              LTS Only
+            </label>
+
+            <!-- Image Type Toggle -->
+            <div class="flex items-center bg-black/30 rounded-lg p-0.5 border border-white/10">
+              <button
+                onclick={() => settingsState.selectedImageType = "jre"}
+                class="px-3 py-1.5 rounded-md text-xs font-medium transition-all {settingsState.selectedImageType === 'jre' ? 'bg-indigo-500 text-white shadow' : 'text-white/50 hover:text-white/80'}"
+              >
+                JRE
+              </button>
+              <button
+                onclick={() => settingsState.selectedImageType = "jdk"}
+                class="px-3 py-1.5 rounded-md text-xs font-medium transition-all {settingsState.selectedImageType === 'jdk' ? 'bg-indigo-500 text-white shadow' : 'text-white/50 hover:text-white/80'}"
+              >
+                JDK
+              </button>
+            </div>
+          </div>
+
+          <!-- Loading State -->
+          {#if settingsState.isLoadingCatalog}
+            <div class="flex-1 flex items-center justify-center text-white/50">
+              <div class="flex flex-col items-center gap-3">
+                <div class="w-8 h-8 border-2 border-indigo-500/30 border-t-indigo-500 rounded-full animate-spin"></div>
+                <span class="text-sm">Loading Java versions...</span>
+              </div>
+            </div>
+          {:else if settingsState.catalogError}
+            <div class="flex-1 flex items-center justify-center text-red-400">
+              <div class="flex flex-col items-center gap-3 text-center px-8">
+                <svg class="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
+                </svg>
+                <span class="text-sm">{settingsState.catalogError}</span>
+                <button
+                  onclick={() => settingsState.refreshCatalog()}
+                  class="mt-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-sm text-white transition-colors"
+                >
+                  Retry
+                </button>
+              </div>
+            </div>
+          {:else}
+            <!-- Version List -->
+            <div class="flex-1 overflow-auto p-4">
+              <div class="space-y-2">
+                {#each settingsState.availableMajorVersions as version}
+                  {@const isLts = settingsState.javaCatalog?.lts_versions.includes(version)}
+                  {@const isSelected = settingsState.selectedMajorVersion === version}
+                  {@const releaseInfo = settingsState.javaCatalog?.releases.find(r => r.major_version === version && r.image_type === settingsState.selectedImageType)}
+                  {@const isAvailable = releaseInfo?.is_available ?? false}
+                  {@const installStatus = releaseInfo ? settingsState.getInstallStatus(releaseInfo) : 'download'}
+                  
+                  <button
+                    onclick={() => settingsState.selectMajorVersion(version)}
+                    disabled={!isAvailable}
+                    class="w-full flex items-center gap-4 p-3 rounded-xl border transition-all text-left
+                      {isSelected 
+                        ? 'bg-indigo-500/20 border-indigo-500/50 ring-2 ring-indigo-500/30' 
+                        : isAvailable 
+                          ? 'bg-black/20 border-white/10 hover:bg-white/5 hover:border-white/20' 
+                          : 'bg-black/10 border-white/5 opacity-40 cursor-not-allowed'}"
+                  >
+                    <!-- Version Number -->
+                    <div class="w-14 text-center">
+                      <span class="text-xl font-bold {isSelected ? 'text-white' : 'text-white/80'}">{version}</span>
+                    </div>
+                    
+                    <!-- Version Details -->
+                    <div class="flex-1 min-w-0">
+                      <div class="flex items-center gap-2">
+                        <span class="text-sm text-white/70 font-mono truncate">{releaseInfo?.version ?? '--'}</span>
+                        {#if isLts}
+                          <span class="px-1.5 py-0.5 bg-emerald-500/20 text-emerald-400 text-[9px] font-bold rounded uppercase shrink-0">LTS</span>
+                        {/if}
+                      </div>
+                      {#if releaseInfo}
+                        <div class="text-[10px] text-white/40 truncate mt-0.5">
+                          {releaseInfo.release_name} • {settingsState.formatBytes(releaseInfo.file_size)}
+                        </div>
+                      {/if}
+                    </div>
+                    
+                    <!-- Install Status Badge -->
+                    <div class="shrink-0">
+                      {#if installStatus === 'installed'}
+                        <span class="px-2 py-1 bg-emerald-500/20 text-emerald-400 text-[10px] font-bold rounded uppercase">Installed</span>
+                      {:else if isAvailable}
+                        <span class="px-2 py-1 bg-white/10 text-white/50 text-[10px] font-medium rounded">Download</span>
+                      {:else}
+                        <span class="px-2 py-1 bg-red-500/10 text-red-400/60 text-[10px] font-medium rounded">N/A</span>
+                      {/if}
+                    </div>
+                  </button>
+                {/each}
+              </div>
+            </div>
+          {/if}
+        </div>
+
+        <!-- Right Sidebar: Details -->
+        <div class="w-64 border-l border-white/10 flex flex-col">
+          <div class="p-4 border-b border-white/5">
+            <span class="text-[10px] font-bold uppercase tracking-widest text-white/30">Details</span>
+          </div>
+          
+          {#if settingsState.selectedRelease}
+            <div class="flex-1 p-4 space-y-4 overflow-auto">
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Version</div>
+                <div class="text-sm text-white font-mono">{settingsState.selectedRelease.version}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Release Name</div>
+                <div class="text-sm text-white">{settingsState.selectedRelease.release_name}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Release Date</div>
+                <div class="text-sm text-white">{settingsState.formatDate(settingsState.selectedRelease.release_date)}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Size</div>
+                <div class="text-sm text-white">{settingsState.formatBytes(settingsState.selectedRelease.file_size)}</div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Type</div>
+                <div class="flex items-center gap-2">
+                  <span class="text-sm text-white uppercase">{settingsState.selectedRelease.image_type}</span>
+                  {#if settingsState.selectedRelease.is_lts}
+                    <span class="px-1.5 py-0.5 bg-emerald-500/20 text-emerald-400 text-[9px] font-bold rounded">LTS</span>
+                  {/if}
+                </div>
+              </div>
+              
+              <div>
+                <div class="text-[10px] text-white/40 uppercase tracking-wider mb-1">Architecture</div>
+                <div class="text-sm text-white">{settingsState.selectedRelease.architecture}</div>
+              </div>
+              
+              {#if !settingsState.selectedRelease.is_available}
+                <div class="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
+                  <div class="text-xs text-red-400">Not available for your platform</div>
+                </div>
+              {/if}
+            </div>
+          {:else}
+            <div class="flex-1 flex items-center justify-center text-white/30 text-sm p-4 text-center">
+              Select a Java version to view details
+            </div>
+          {/if}
+        </div>
+      </div>
+
+      <!-- Download Progress (MC Style) -->
+      {#if settingsState.isDownloadingJava && settingsState.downloadProgress}
+        <div class="border-t border-white/10 p-4 bg-zinc-900/90">
+          <div class="flex items-center justify-between mb-2">
+            <h3 class="text-white font-bold text-sm">Downloading Java</h3>
+            <span class="text-xs text-zinc-400">{settingsState.downloadProgress.status}</span>
+          </div>
+          
+          <!-- Progress Bar -->
+          <div class="mb-2">
+            <div class="flex justify-between text-[10px] text-zinc-400 mb-1">
+              <span>{settingsState.downloadProgress.file_name}</span>
+              <span>{Math.round(settingsState.downloadProgress.percentage)}%</span>
+            </div>
+            <div class="w-full bg-zinc-800 rounded-full h-2.5 overflow-hidden">
+              <div 
+                class="bg-gradient-to-r from-blue-500 to-cyan-400 h-2.5 rounded-full transition-all duration-200"
+                style="width: {settingsState.downloadProgress.percentage}%"
+              ></div>
+            </div>
+          </div>
+          
+          <!-- Speed & Stats -->
+          <div class="flex justify-between text-[10px] text-zinc-500 font-mono">
+            <span>
+              {settingsState.formatBytes(settingsState.downloadProgress.speed_bytes_per_sec)}/s · 
+              ETA: {settingsState.formatTime(settingsState.downloadProgress.eta_seconds)}
+            </span>
+            <span>
+              {settingsState.formatBytes(settingsState.downloadProgress.downloaded_bytes)} / 
+              {settingsState.formatBytes(settingsState.downloadProgress.total_bytes)}
+            </span>
+          </div>
+        </div>
+      {/if}
+
+      <!-- Pending Downloads Alert -->
+      {#if settingsState.pendingDownloads.length > 0 && !settingsState.isDownloadingJava}
+        <div class="border-t border-amber-500/30 p-4 bg-amber-500/10">
+          <div class="flex items-center justify-between">
+            <div class="flex items-center gap-3">
+              <svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
+              </svg>
+              <span class="text-sm text-amber-200">
+                {settingsState.pendingDownloads.length} pending download(s) can be resumed
+              </span>
+            </div>
             <button
-              onclick={() => settingsState.selectedImageType = "jdk"}
-              class="px-4 py-3 rounded-xl border transition-all {settingsState.selectedImageType === 'jdk' ? 'bg-indigo-500/20 border-indigo-500/50 text-white' : 'bg-black/20 border-white/10 text-white/60 hover:bg-white/5'}"
+              onclick={() => settingsState.resumeDownloads()}
+              class="px-4 py-2 bg-amber-500/20 hover:bg-amber-500/30 text-amber-200 rounded-lg text-sm font-medium transition-colors"
             >
-              JDK
+              Resume All
             </button>
           </div>
-          <p class="text-xs text-white/40 mt-2">
-            JRE: Runtime only (smaller). JDK: Includes development tools.
-          </p>
         </div>
+      {/if}
 
-        <!-- Status -->
-        {#if settingsState.javaDownloadStatus}
-          <div class="bg-black/40 border border-white/10 rounded-xl p-4 text-sm text-white/80">
-            {settingsState.javaDownloadStatus}
-          </div>
-        {/if}
+      <!-- Footer Actions -->
+      <div class="flex items-center justify-between p-4 border-t border-white/10 bg-black/20">
+        <button
+          onclick={() => settingsState.refreshCatalog()}
+          disabled={settingsState.isLoadingCatalog || settingsState.isDownloadingJava}
+          class="flex items-center gap-2 px-4 py-2 bg-white/5 hover:bg-white/10 disabled:opacity-50 text-white/70 rounded-lg text-sm transition-colors"
+        >
+          <svg class="w-4 h-4 {settingsState.isLoadingCatalog ? 'animate-spin' : ''}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
+          </svg>
+          Refresh
+        </button>
 
-        <!-- Actions -->
-        <div class="flex gap-3 pt-2">
-          <button
-            onclick={() => settingsState.closeJavaDownloadModal()}
-            disabled={settingsState.isDownloadingJava}
-            class="flex-1 bg-white/10 hover:bg-white/20 disabled:opacity-50 text-white px-4 py-3 rounded-xl border border-white/5 transition-colors font-medium"
-          >
-            Cancel
-          </button>
-          <button
-            onclick={() => settingsState.downloadJava()}
-            disabled={settingsState.isDownloadingJava}
-            class="flex-1 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white px-4 py-3 rounded-xl transition-colors font-medium"
-          >
-            {settingsState.isDownloadingJava ? "Downloading..." : "Download & Install"}
-          </button>
+        <div class="flex gap-3">
+          {#if settingsState.isDownloadingJava}
+            <button
+              onclick={() => settingsState.cancelDownload()}
+              class="px-5 py-2.5 bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded-lg text-sm font-medium transition-colors"
+            >
+              Cancel Download
+            </button>
+          {:else}
+            {@const isInstalled = settingsState.selectedRelease ? settingsState.getInstallStatus(settingsState.selectedRelease) === 'installed' : false}
+            <button
+              onclick={() => settingsState.closeJavaDownloadModal()}
+              class="px-5 py-2.5 bg-white/10 hover:bg-white/20 text-white rounded-lg text-sm font-medium transition-colors"
+            >
+              Close
+            </button>
+            <button
+              onclick={() => settingsState.downloadJava()}
+              disabled={!settingsState.selectedRelease?.is_available || settingsState.isLoadingCatalog || isInstalled}
+              class="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg text-sm font-medium transition-colors"
+            >
+              {isInstalled ? 'Already Installed' : 'Download & Install'}
+            </button>
+          {/if}
         </div>
       </div>
     </div>
diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts
index 5b87af2..cad466d 100644
--- a/ui/src/stores/settings.svelte.ts
+++ b/ui/src/stores/settings.svelte.ts
@@ -1,5 +1,14 @@
 import { invoke } from "@tauri-apps/api/core";
-import type { JavaInstallation, LauncherConfig } from "../types";
+import { listen, type UnlistenFn } from "@tauri-apps/api/event";
+import type {
+  JavaCatalog,
+  JavaDownloadProgress,
+  JavaDownloadSource,
+  JavaInstallation,
+  JavaReleaseInfo,
+  LauncherConfig,
+  PendingJavaDownload,
+} from "../types";
 import { uiState } from "./ui.svelte";
 
 export class SettingsState {
@@ -17,14 +26,101 @@ export class SettingsState {
   });
   javaInstallations = $state<JavaInstallation[]>([]);
   isDetectingJava = $state(false);
-  
-  // Java download state
+
+  // Java download modal state
   showJavaDownloadModal = $state(false);
-  availableJavaVersions = $state<number[]>([]);
-  selectedJavaVersion = $state(21);
+  selectedDownloadSource = $state<JavaDownloadSource>("adoptium");
+  
+  // Java catalog state
+  javaCatalog = $state<JavaCatalog | null>(null);
+  isLoadingCatalog = $state(false);
+  catalogError = $state("");
+  
+  // Version selection state
+  selectedMajorVersion = $state<number | null>(null);
   selectedImageType = $state<"jre" | "jdk">("jre");
+  showOnlyRecommended = $state(true);
+  searchQuery = $state("");
+  
+  // Download progress state
   isDownloadingJava = $state(false);
+  downloadProgress = $state<JavaDownloadProgress | null>(null);
   javaDownloadStatus = $state("");
+  
+  // Pending downloads
+  pendingDownloads = $state<PendingJavaDownload[]>([]);
+  
+  // Event listener cleanup
+  private progressUnlisten: UnlistenFn | null = null;
+
+  // Computed: filtered releases based on selection
+  get filteredReleases(): JavaReleaseInfo[] {
+    if (!this.javaCatalog) return [];
+    
+    let releases = this.javaCatalog.releases;
+    
+    // Filter by major version if selected
+    if (this.selectedMajorVersion !== null) {
+      releases = releases.filter(r => r.major_version === this.selectedMajorVersion);
+    }
+    
+    // Filter by image type
+    releases = releases.filter(r => r.image_type === this.selectedImageType);
+    
+    // Filter by recommended (LTS) versions
+    if (this.showOnlyRecommended) {
+      releases = releases.filter(r => r.is_lts);
+    }
+    
+    // Filter by search query
+    if (this.searchQuery.trim()) {
+      const query = this.searchQuery.toLowerCase();
+      releases = releases.filter(
+        r =>
+          r.release_name.toLowerCase().includes(query) ||
+          r.version.toLowerCase().includes(query) ||
+          r.major_version.toString().includes(query)
+      );
+    }
+    
+    return releases;
+  }
+
+  // Computed: available major versions for display
+  get availableMajorVersions(): number[] {
+    if (!this.javaCatalog) return [];
+    let versions = [...this.javaCatalog.available_major_versions];
+    
+    // Filter by LTS if showOnlyRecommended is enabled
+    if (this.showOnlyRecommended) {
+      versions = versions.filter(v => this.javaCatalog!.lts_versions.includes(v));
+    }
+    
+    // Sort descending (newest first)
+    return versions.sort((a, b) => b - a);
+  }
+
+  // Get installation status for a release: 'installed' | 'download'
+  getInstallStatus(release: JavaReleaseInfo): 'installed' | 'download' {
+    // Find installed Java that matches the major version and image type (by path pattern)
+    const matchingInstallations = this.javaInstallations.filter(inst => {
+      // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern)
+      const pathLower = inst.path.toLowerCase();
+      const pattern = `temurin-${release.major_version}-${release.image_type}`;
+      return pathLower.includes(pattern);
+    });
+    
+    // If any matching installation exists, it's installed
+    return matchingInstallations.length > 0 ? 'installed' : 'download';
+  }
+
+  // Computed: selected release details
+  get selectedRelease(): JavaReleaseInfo | null {
+    if (!this.javaCatalog || this.selectedMajorVersion === null) return null;
+    return this.javaCatalog.releases.find(
+      r => r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType
+    ) || null;
+  }
 
   async loadSettings() {
     try {
@@ -32,8 +128,8 @@ export class SettingsState {
       this.settings = result;
       // Force dark mode
       if (this.settings.theme !== "dark") {
-          this.settings.theme = "dark";
-          this.saveSettings();
+        this.settings.theme = "dark";
+        this.saveSettings();
       }
     } catch (e) {
       console.error("Failed to load settings:", e);
@@ -74,55 +170,201 @@ export class SettingsState {
   async openJavaDownloadModal() {
     this.showJavaDownloadModal = true;
     this.javaDownloadStatus = "";
+    this.catalogError = "";
+    this.downloadProgress = null;
+    
+    // Setup progress event listener
+    await this.setupProgressListener();
+    
+    // Load catalog
+    await this.loadJavaCatalog(false);
+    
+    // Check for pending downloads
+    await this.loadPendingDownloads();
+  }
+
+  async closeJavaDownloadModal() {
+    if (!this.isDownloadingJava) {
+      this.showJavaDownloadModal = false;
+      // Cleanup listener
+      if (this.progressUnlisten) {
+        this.progressUnlisten();
+        this.progressUnlisten = null;
+      }
+    }
+  }
+
+  private async setupProgressListener() {
+    if (this.progressUnlisten) {
+      this.progressUnlisten();
+    }
+    
+    this.progressUnlisten = await listen<JavaDownloadProgress>(
+      "java-download-progress",
+      (event) => {
+        this.downloadProgress = event.payload;
+        this.javaDownloadStatus = event.payload.status;
+        
+        if (event.payload.status === "Completed") {
+          this.isDownloadingJava = false;
+          setTimeout(async () => {
+            await this.detectJava();
+            uiState.setStatus(`Java installed successfully!`);
+          }, 500);
+        } else if (event.payload.status === "Error") {
+          this.isDownloadingJava = false;
+        }
+      }
+    );
+  }
+
+  async loadJavaCatalog(forceRefresh: boolean) {
+    this.isLoadingCatalog = true;
+    this.catalogError = "";
+    
     try {
-      this.availableJavaVersions = await invoke("fetch_available_java_versions");
-      // Default selection logic
-      if (this.availableJavaVersions.includes(21)) {
-        this.selectedJavaVersion = 21;
-      } else if (this.availableJavaVersions.includes(17)) {
-        this.selectedJavaVersion = 17;
-      } else if (this.availableJavaVersions.length > 0) {
-        this.selectedJavaVersion = this.availableJavaVersions[this.availableJavaVersions.length - 1];
+      const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog";
+      this.javaCatalog = await invoke<JavaCatalog>(command);
+      
+      // Auto-select first LTS version
+      if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) {
+        // Select most recent LTS (21 or highest)
+        const ltsVersions = [...this.javaCatalog.lts_versions].sort((a, b) => b - a);
+        this.selectedMajorVersion = ltsVersions[0];
       }
     } catch (e) {
-      console.error("Failed to fetch available Java versions:", e);
-      this.javaDownloadStatus = "Error fetching Java versions: " + e;
+      console.error("Failed to load Java catalog:", e);
+      this.catalogError = `Failed to load Java catalog: ${e}`;
+    } finally {
+      this.isLoadingCatalog = false;
     }
   }
 
-  closeJavaDownloadModal() {
-    if (!this.isDownloadingJava) {
-      this.showJavaDownloadModal = false;
+  async refreshCatalog() {
+    await this.loadJavaCatalog(true);
+    uiState.setStatus("Java catalog refreshed");
+  }
+
+  async loadPendingDownloads() {
+    try {
+      this.pendingDownloads = await invoke<PendingJavaDownload[]>("get_pending_java_downloads");
+    } catch (e) {
+      console.error("Failed to load pending downloads:", e);
     }
   }
 
+  selectMajorVersion(version: number) {
+    this.selectedMajorVersion = version;
+  }
+
   async downloadJava() {
+    if (!this.selectedRelease || !this.selectedRelease.is_available) {
+      uiState.setStatus("Selected Java version is not available for this platform");
+      return;
+    }
+    
     this.isDownloadingJava = true;
-    this.javaDownloadStatus = `Downloading Java ${this.selectedJavaVersion} ${this.selectedImageType.toUpperCase()}...`;
+    this.javaDownloadStatus = "Starting download...";
+    this.downloadProgress = null;
     
     try {
       const result: JavaInstallation = await invoke("download_adoptium_java", {
-        majorVersion: this.selectedJavaVersion,
+        majorVersion: this.selectedMajorVersion,
         imageType: this.selectedImageType,
         customPath: null,
       });
       
-      this.javaDownloadStatus = `Java ${this.selectedJavaVersion} installed at ${result.path}`;
       this.settings.java_path = result.path;
-      
       await this.detectJava();
       
       setTimeout(() => {
         this.showJavaDownloadModal = false;
-        uiState.setStatus(`Java ${this.selectedJavaVersion} is ready to use!`);
+        uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`);
       }, 1500);
     } catch (e) {
       console.error("Failed to download Java:", e);
-      this.javaDownloadStatus = "Download failed: " + e;
+      this.javaDownloadStatus = `Download failed: ${e}`;
     } finally {
       this.isDownloadingJava = false;
     }
   }
+
+  async cancelDownload() {
+    try {
+      await invoke("cancel_java_download");
+      this.isDownloadingJava = false;
+      this.javaDownloadStatus = "Download cancelled";
+      this.downloadProgress = null;
+      await this.loadPendingDownloads();
+    } catch (e) {
+      console.error("Failed to cancel download:", e);
+    }
+  }
+
+  async resumeDownloads() {
+    if (this.pendingDownloads.length === 0) return;
+    
+    this.isDownloadingJava = true;
+    this.javaDownloadStatus = "Resuming download...";
+    
+    try {
+      const installed = await invoke<JavaInstallation[]>("resume_java_downloads");
+      if (installed.length > 0) {
+        this.settings.java_path = installed[0].path;
+        await this.detectJava();
+        uiState.setStatus(`Resumed and installed ${installed.length} Java version(s)`);
+      }
+      await this.loadPendingDownloads();
+    } catch (e) {
+      console.error("Failed to resume downloads:", e);
+      this.javaDownloadStatus = `Resume failed: ${e}`;
+    } finally {
+      this.isDownloadingJava = false;
+    }
+  }
+
+  // Format bytes to human readable
+  formatBytes(bytes: number): string {
+    if (bytes === 0) return "0 B";
+    const k = 1024;
+    const sizes = ["B", "KB", "MB", "GB"];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
+  }
+
+  // Format seconds to human readable
+  formatTime(seconds: number): string {
+    if (seconds === 0 || !isFinite(seconds)) return "--";
+    if (seconds < 60) return `${Math.round(seconds)}s`;
+    if (seconds < 3600) {
+      const mins = Math.floor(seconds / 60);
+      const secs = Math.round(seconds % 60);
+      return `${mins}m ${secs}s`;
+    }
+    const hours = Math.floor(seconds / 3600);
+    const mins = Math.floor((seconds % 3600) / 60);
+    return `${hours}h ${mins}m`;
+  }
+
+  // Format date string
+  formatDate(dateStr: string | null): string {
+    if (!dateStr) return "--";
+    try {
+      const date = new Date(dateStr);
+      return date.toLocaleDateString("en-US", {
+        year: "2-digit",
+        month: "2-digit",
+        day: "2-digit",
+      });
+    } catch {
+      return "--";
+    }
+  }
+
+  // Legacy compatibility
+  get availableJavaVersions(): number[] {
+    return this.availableMajorVersions;
+  }
 }
 
 export const settingsState = new SettingsState();
diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts
index f92cbf2..163dc92 100644
--- a/ui/src/types/index.ts
+++ b/ui/src/types/index.ts
@@ -53,6 +53,50 @@ export interface JavaDownloadInfo {
   image_type: string;
 }
 
+export interface JavaReleaseInfo {
+  major_version: number;
+  image_type: string;
+  version: string;
+  release_name: string;
+  release_date: string | null;
+  file_size: number;
+  checksum: string | null;
+  download_url: string;
+  is_lts: boolean;
+  is_available: boolean;
+  architecture: string;
+}
+
+export interface JavaCatalog {
+  releases: JavaReleaseInfo[];
+  available_major_versions: number[];
+  lts_versions: number[];
+  cached_at: number;
+}
+
+export interface JavaDownloadProgress {
+  file_name: string;
+  downloaded_bytes: number;
+  total_bytes: number;
+  speed_bytes_per_sec: number;
+  eta_seconds: number;
+  status: string;
+  percentage: number;
+}
+
+export interface PendingJavaDownload {
+  major_version: number;
+  image_type: string;
+  download_url: string;
+  file_name: string;
+  file_size: number;
+  checksum: string | null;
+  install_path: string;
+  created_at: number;
+}
+
+export type JavaDownloadSource = "adoptium" | "mojang" | "azul";
+
 // ==================== Fabric Types ====================
 
 export interface FabricGameVersion {
-- 
cgit v1.2.3-70-g09d2

