Add Compose Desktop GUI scaffold and README docs - #2193
Open
soloturn wants to merge 6 commits into
Open
Conversation
Adds a Kotlin + Compose Multiplatform Desktop toolchain alongside the
existing Java/Swing app, with a single minimal screen (URL input,
Rip/Stop/Panic, status line, progress bar, scrolling log) wired to the
existing AbstractRipper/RipStatusHandler business logic rather than a
reimplementation.
- build.gradle.kts: Kotlin 2.4.10 + Compose 1.11.1 plugins/deps, google()
repo, Kotlin targets JVM 21, shadowJar opts into zip64 (Compose's
bundled natives push the fat jar past the 65535-entry zip limit).
- gradle.properties: relaxes JVM-target validation from error to the
benign warning it's meant to be (Java release 25 vs Kotlin JVM 21).
- App.java: new -Dripme.gui=compose system property selects the Compose
window; default (no property) remains Swing's MainWindow, unchanged.
- src/main/kotlin/.../ui/compose/{ComposeMain,RipController,MainScreen}.kt:
the new screen. RipController implements RipStatusHandler and mirrors
MainWindow's handleEvent/ripAlbum logic (URL normalize -> getRipper ->
setup -> setObserver -> Thread(ripper).start(); stop/panic; status-color
mapping; log capped at 2000 lines), marshaling ripper callbacks onto the
EDT via SwingUtilities.invokeLater since Compose Desktop composes there.
Deliberately deferred (scaffold scope only): decompose navigation,
queue/history/config panels, tray icon, i18n, native installers/jpackage,
a --compose CLI flag, and Compose UI tests.
Verified: ./gradlew clean build green (full Java test suite passes),
shadowJar produces a launchable fat jar, both launch paths (default
Swing, -Dripme.gui=compose) smoke-tested from the shaded jar.
Brings the Compose Desktop GUI (scaffold from the previous commit) up to
feature parity with the Swing MainWindow, so it's a real alternative UI
rather than a single-screen demo. Launch/default behavior unchanged
(-Dripme.gui=compose, defaulting to swing).
Queue (ui/compose/queue/): QueueController owns RipController rather than
duplicating its start/stop/panic/update logic. Ports MainWindow's
RipButtonHandler/ripNextAlbum/canRip 1:1: multi-URL queueing,
{start-end} range expansion, duplicate rejection, stop/panic re-enqueue,
persistence to config, load-on-startup.
History (ui/compose/history/): HistoryStore is a thin observable bridge
over the existing Java History/HistoryEntry classes (shared file format
with the CLI, not forked) - load/save/remove/clear(confirm)/re-rip,
written on every RIP_COMPLETE via QueueController.
Configuration (ui/compose/config/): every MainWindow setting, read/written
through the same Utils.getConfig*/setConfig* statics - no parallel store.
Save-dir/URL-list file pickers and a cookie dialog (CookieDialog.kt).
Tray (ui/compose/tray/): Compose Desktop's Tray API - hide/show, About,
clipboard-autorip checkbox (shared state with the config panel), Exit.
Navigation: a plain enum Panel + NavController (mutableStateOf-based),
not a navigation library. Evaluated and rejected decompose/Navigation-3:
ripme's actual need is 5 mutually-exclusive panel states with no back
stack and no process-death restoration, desktop-only - that machinery
solves problems this app doesn't have. Confirmed decompose 3.5.0 does
resolve/compile/run cleanly against Compose Multiplatform 1.11.1 if ever
needed for real multiplatform navigation later.
i18n: Strings.kt's tr(key) + a manual localeVersion recomposition trigger,
since Utils.getLocalizedString reads a static, non-observable resource
bundle - the declarative equivalent of MainWindow's changeLocale().
Shared Java changes (both backward-compatible, Swing behavior unchanged):
- ClipboardUtils: MainWindow.ripAlbumStatic call replaced with a settable
Consumer<String> dispatch handler so Compose's QueueController can
receive clipboard-autorip'd URLs instead.
- RipStatusComplete: added getCount() (was package-private, needed by
HistoryStore in a different package).
Also:
- App.java: logs which GUI launched, and warns (rather than silently
falling back) on an unrecognized -Dripme.gui value.
- build.gradle.kts: compose.runtime/foundation/material3/ui DSL aliases
(deprecated by the Compose Gradle plugin) replaced with direct artifact
coordinates; material3 pins independently at 1.9.0 (verified via
`gradlew dependencies`, doesn't track the 1.11.1 Compose release train).
- ConfigScreen.kt: deprecated ClickableText replaced with Text +
Modifier.clickable.
- TrayIntegration.kt: deprecated painterResource(String) replaced with
ImageIO.read + BitmapPainter, matching how MainWindow.setupTrayIcon
already loads the same icon.png off the classpath.
Known gaps vs strict MainWindow parity: no live "<host> album detected"
preview while typing, no history table column sorting, tray icon visual
rendering unconfirmed (no real display in the build/test environment).
Verified: ./gradlew build -x test green, shadowJar launches cleanly under
both -Dripme.gui=compose and the default Swing path. Per explicit
instruction, the live-network-dependent ripper unit test suite was not
run as part of this work.
…ource The Compose Desktop GUI (com.rarchives.ripme.ui.compose) is opt-in via the -Dripme.gui=compose system property; Swing remains the default. Add a README section covering both invocation from a downloaded release jar and from a locally built one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018v7wmgDn3VKQRo8hbFCR6S
ConfigScreen laid out every setting as its own full-width row, making the panel run roughly 2x taller than MainWindow's equivalent GridBagLayout grid. Rebuilt it as a two-column grid (SettingsRow) that mirrors MainWindow's addItemToConfigGridBagConstraints(...) calls row-for-row - checkboxes pack two to a line, buttons get compact content padding instead of Material3's default. Also gives the Log/History/Queue/Configuration nav buttons the same icons (comment.png/time.png/list.png/gear.png) MainWindow's JButtons use, loaded off the classpath via the same ImageIO -> ComposeImageBitmap path tray/TrayIntegration.kt already uses for the tray icon (new ResourceIcons.kt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
soloturn
force-pushed
the
compose-gui-scaffold
branch
from
August 15, 2026 11:57
f1b679b to
7041122
Compare
The two-column SettingsRow grid from the previous commit nested a row's label and its OutlinedTextField together inside the left column only, leaving the right column empty - squeezing both into half the row's width instead of giving each its own column like MainWindow's GridBagLayout does (col0 = label, col1 = field). Shrinking field width made this worse, not better. Split every field row into left = label, right = field, and swap Material3's OutlinedTextField (~56.dp tall by default, floating-label chrome) for a compact bordered BasicTextField sized like a Swing JTextField. Verified live: launched the built jar (-Dripme.gui=compose) and visually confirmed the settings panel end to end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rip and Stop reuse icon.png/stop.png, the same assets MainWindow's ripButton/stopButton use. MainWindow's panicButton has no icon of its own; Panic does an immediate hard-abort (ripper.stop() + panic(), no graceful finish-current-item like Stop) so it gets a warning-triangle vector icon from material-icons-core (new dependency) rather than an unrelated resource PNG - a first attempt reused the otherwise-unused wrench.png, but a wrench reads as "settings/tool", not "abort". Verified live against the built jar (-Dripme.gui=compose). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces an opt-in JetBrains Compose Multiplatform Desktop GUI scaffold that runs alongside the existing Swing MainWindow, selectable via -Dripme.gui=compose, and documents how to launch it from both release jars and local builds.
Changes:
- Adds a new Compose Desktop UI implementation (main screen, navigation, log/history/queue/config panels, tray integration, AWT interop).
- Adds controller/state bridges to reuse existing ripping, history, config, and clipboard autorip logic.
- Updates build + docs to include Compose dependencies and describe launching the Compose GUI.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/kotlin/com/rarchives/ripme/ui/compose/tray/TrayIntegration.kt | Compose Desktop tray menu + About dialog integration. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/Strings.kt | Compose-localized string helper with manual recomposition trigger. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/RipController.kt | Compose-facing rip lifecycle/state adapter over existing ripper logic. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/ResourceIcons.kt | Loads classpath PNG icons for Compose UI. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/queue/QueueScreen.kt | Compose queue panel UI (list + remove actions). |
| src/main/kotlin/com/rarchives/ripme/ui/compose/queue/QueueController.kt | Queue draining/persistence + clipboard handler wiring. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/queue/QueueComponent.kt | Minimal component wrapper for queue screen. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/nav/Panel.kt | Panel enum + simple nav controller for panel toggling. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/MainScreen.kt | Compose main screen scaffold (URL entry, actions, panels). |
| src/main/kotlin/com/rarchives/ripme/ui/compose/log/LogScreen.kt | Compose log panel UI. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/log/LogComponent.kt | Minimal component wrapper for log screen. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/history/HistoryStore.kt | Compose-observable wrapper over shared Java History persistence. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/history/HistoryScreen.kt | Compose history UI (table-like list + actions). |
| src/main/kotlin/com/rarchives/ripme/ui/compose/history/HistoryComponent.kt | Minimal component wrapper for history screen. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/config/CookieDialog.kt | Compose cookie settings dialog (SiteCookieStorage-backed). |
| src/main/kotlin/com/rarchives/ripme/ui/compose/config/ConfigScreen.kt | Compose configuration panel UI. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/config/ConfigController.kt | Compose config state + persistence and update-check wiring. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/config/ConfigComponent.kt | Minimal component wrapper for config screen. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/ComposeMain.kt | Compose GUI entry point + lifecycle wiring + window persistence. |
| src/main/kotlin/com/rarchives/ripme/ui/compose/AwtInterop.kt | Swing/AWT interop helpers for file pickers and Desktop actions. |
| src/main/java/com/rarchives/ripme/ui/RipStatusComplete.java | Exposes count via getter for non-Swing consumers. |
| src/main/java/com/rarchives/ripme/ui/ClipboardUtils.java | Makes ClipboardUtils public + adds configurable autorip dispatch hook. |
| src/main/java/com/rarchives/ripme/App.java | Adds ripme.gui property switch to launch Swing vs Compose GUI. |
| README.md | Documents how to launch the new Compose GUI (release jar + source). |
| gradle.properties | Adjusts Kotlin JVM target validation mode for mixed targets. |
| build.gradle.kts | Adds Kotlin + Compose plugins/dependencies and enables Zip64 for shadow jar. |
Suppressed comments (1)
src/main/kotlin/com/rarchives/ripme/ui/compose/AwtInterop.kt:42
- JFileChooser is Swing UI and should be created/shown on the EDT. Showing it from a background thread is not Swing-thread-safe; marshal the dialog display via SwingUtilities.invokeAndWait and then invoke the callback on the EDT.
fun chooseFile(startDir: String, onChosen: (File) -> Unit) {
thread(name = "AwtInterop-chooseFile") {
UIManager.put("FileChooser.useSystemExtensionHiding", false)
val jfc = JFileChooser(startDir)
jfc.fileSelectionMode = JFileChooser.FILES_ONLY
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+22
to
+35
| fun chooseDirectory(startDir: String, onChosen: (File) -> Unit) { | ||
| thread(name = "AwtInterop-chooseDirectory") { | ||
| UIManager.put("FileChooser.useSystemExtensionHiding", false) | ||
| val jfc = JFileChooser(startDir) | ||
| jfc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY | ||
| val result = jfc.showDialog(null, "select directory") | ||
| if (result == JFileChooser.APPROVE_OPTION) { | ||
| val chosen = jfc.selectedFile | ||
| if (chosen != null) { | ||
| onChosen(chosen) | ||
| } | ||
| } | ||
| } | ||
| } |
Comment on lines
+55
to
+58
| val painter = remember { | ||
| val stream = Thread.currentThread().contextClassLoader.getResourceAsStream("icon.png") | ||
| BitmapPainter(ImageIO.read(stream).toComposeImageBitmap()) | ||
| } |
| } | ||
| Text("Do you want to visit the project homepage on GitHub?") | ||
| Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { | ||
| Button(onClick = { AwtInterop.browse("http://github.com/ripmeapp/ripme") }) { Text("Yes") } |
Comment on lines
+28
to
+32
| static Consumer<String> ripHandler = MainWindow::ripAlbumStatic; | ||
|
|
||
| public static void setRipHandler(Consumer<String> handler) { | ||
| ripHandler = handler; | ||
| } |
| rippedURLs.add(url); | ||
| // TODO Queue rip instead of just starting it | ||
| MainWindow.ripAlbumStatic(url); | ||
| ClipboardUtils.ripHandler.accept(url); |
| progress = 0f | ||
| setStatus("Starting rip...", Color.Black) | ||
|
|
||
| Thread(newRipper).start() |
Comment on lines
+41
to
+42
| ./gradlew assemble | ||
| java -Dripme.gui=compose -jar build/libs/ripme-<version>.jar |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MainWindow, with full feature parity (ripping, config, history, log, queue, system tray) — see Future/Roadmap: New GUI - compose-jb #2082.-Dripme.gui=compose(Swing stays the default;-Dripme.gui=swingis equivalent to today's behavior).Test plan
./gradlew compileKotlin compileJava— compiles clean (rebased onto latestmain)../gradlew assemble && java -Dripme.gui=compose -jar build/libs/ripme-<version>.jarlaunches the Compose GUI; default launch (no property) still opens Swing.🤖 Generated with Claude Code