xkcd application ; fatjar packaging

This commit is contained in:
2026-05-17 22:10:25 +02:00
parent d07cd476de
commit 19f9f1f3a6
7 changed files with 403 additions and 0 deletions
+29
View File
@@ -16,6 +16,7 @@
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
@@ -24,6 +25,34 @@
<mainClass>com.rdesk.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.rdesk.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
+2
View File
@@ -1,5 +1,6 @@
package com.rdesk;
import com.rdesk.applications.entertainment.comic.Xkcd;
import com.rdesk.applications.productivity.Calculator;
import com.rdesk.applications.productivity.browsing.TabbedBrowser;
import com.rdesk.applications.productivity.code.CodeDesk;
@@ -88,6 +89,7 @@ public class Main {
launcher.addApp(new TabbedBrowser(), "Productivity");
launcher.addApp(new CodeDesk(), "Productivity");
launcher.addApp(new Calculator(), "Productivity");
launcher.addApp(new Xkcd(), "Entertainment");
launcher.addApp(new Exit(), "System");
launcher.addApp(new TaskManager(), "System");
launcher.addApp(new FileExplorerApp(), "System");
@@ -0,0 +1,27 @@
package com.rdesk.applications.entertainment.comic;
public class ComicData {
private int num;
private String title;
private String img;
private String alt;
// Gson requires default constructor
public ComicData() {}
public int getNum() {
return num;
}
public String getTitle() {
return title;
}
public String getImg() {
return img;
}
public String getAlt() {
return alt;
}
}
@@ -0,0 +1,58 @@
package com.rdesk.applications.entertainment.comic;
import com.google.gson.Gson;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ComicFetcher {
private static final String BASE_URL = "https://xkcd.com/";
private static final String INFO_URL = BASE_URL + "info.0.json";
private static final String COMIC_URL_TEMPLATE = BASE_URL + "%d/info.0.json";
private final HttpClient httpClient;
private final Gson gson;
public ComicFetcher() {
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
public int fetchLatestComicNumber() throws IOException, InterruptedException {
String json = fetchJson(INFO_URL);
LatestInfo info = gson.fromJson(json, LatestInfo.class);
if (info == null || info.num == 0) {
throw new IOException("api response error");
}
return info.num;
}
public ComicData fetchComicData(int num) throws IOException, InterruptedException {
String url = String.format(COMIC_URL_TEMPLATE, num);
String json = fetchJson(url);
ComicData comic = gson.fromJson(json, ComicData.class);
if (comic == null || comic.getImg() == null) {
throw new IOException("error , idk bro #" + num);
}
return comic;
}
private String fetchJson(String urlString) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(urlString))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("HTTP " + response.statusCode() + " for " + urlString);
}
return response.body();
}
private static class LatestInfo {
int num;
}
}
@@ -0,0 +1,54 @@
package com.rdesk.applications.entertainment.comic;
import com.rdesk.kernel.DesktopApp;
import com.rdesk.kernel.Kernel;
import javax.swing.*;
import java.awt.*;
public class Xkcd implements DesktopApp {
@Override
public String getId() {
return "xkcd";
}
@Override
public String getName() {
return "XKCD";
}
@Override
public JInternalFrame createWindow(Kernel kernel) {
JInternalFrame frame = new JInternalFrame(
"XKCD",
true, true, true, true
);
frame.setSize(800, 700);
frame.setLayout(new BorderLayout(5, 5));
JPanel topPanel = new JPanel(new BorderLayout(5, 0));
JButton refreshButton = new JButton("Random Comic");
JLabel titleLabel = new JLabel(" ", SwingConstants.CENTER);
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
topPanel.add(refreshButton, BorderLayout.WEST);
topPanel.add(titleLabel, BorderLayout.CENTER);
frame.add(topPanel, BorderLayout.NORTH);
ZoomableImagePanel imagePanel = new ZoomableImagePanel();
frame.add(imagePanel, BorderLayout.CENTER);
JTextArea altTextArea = new JTextArea(4, 40);
altTextArea.setEditable(false);
altTextArea.setLineWrap(true);
altTextArea.setWrapStyleWord(true);
altTextArea.setFont(new Font("SansSerif", Font.PLAIN, 12));
JScrollPane altScrollPane = new JScrollPane(altTextArea);
frame.add(altScrollPane, BorderLayout.SOUTH);
new XkcdController(refreshButton, titleLabel, altTextArea, imagePanel).loadRandomComic();
frame.setVisible(true);
return frame;
}
}
@@ -0,0 +1,97 @@
package com.rdesk.applications.entertainment.comic;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.concurrent.ExecutionException;
public class XkcdController {
private final JButton refreshButton;
private final JLabel titleLabel;
private final JTextArea altTextArea;
private final ZoomableImagePanel imagePanel;
private final ComicFetcher fetcher;
public XkcdController(JButton refreshButton, JLabel titleLabel,
JTextArea altTextArea, ZoomableImagePanel imagePanel) {
this.refreshButton = refreshButton;
this.titleLabel = titleLabel;
this.altTextArea = altTextArea;
this.imagePanel = imagePanel;
this.fetcher = new ComicFetcher();
refreshButton.addActionListener(e -> loadRandomComic());
}
public void loadRandomComic() {
setLoadingState(true);
SwingWorker<ComicData, Void> worker = new SwingWorker<>() {
@Override
protected ComicData doInBackground() throws Exception {
int latest = fetcher.fetchLatestComicNumber();
int randomNum = (int) (Math.random() * latest) + 1;
return fetcher.fetchComicData(randomNum);
}
@Override
protected void done() {
setLoadingState(false);
try {
ComicData comic = get();
if (comic != null) {
displayComic(comic);
} else {
showError("cant load comic");
}
} catch (InterruptedException | ExecutionException e) {
showError("Error: " + e.getCause().getMessage());
}
}
};
worker.execute();
}
private void displayComic(ComicData comic) {
titleLabel.setText(String.format("#%d: %s", comic.getNum(), comic.getTitle()));
altTextArea.setText(comic.getAlt());
SwingWorker<BufferedImage, Void> imageWorker = new SwingWorker<>() {
@Override
protected BufferedImage doInBackground() throws Exception {
URL url = new URL(comic.getImg());
return javax.imageio.ImageIO.read(url);
}
@Override
protected void done() {
try {
BufferedImage img = get();
imagePanel.setImage(img);
} catch (Exception e) {
showError("failed to load image: " + e.getMessage());
imagePanel.clearImage();
}
}
};
imageWorker.execute();
}
private void setLoadingState(boolean loading) {
SwingUtilities.invokeLater(() -> {
refreshButton.setEnabled(!loading);
if (loading) {
titleLabel.setText("fetching image");
altTextArea.setText("");
imagePanel.clearImage();
}
});
}
private void showError(String message) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
titleLabel.setText(" ");
altTextArea.setText("");
});
}
}
@@ -0,0 +1,136 @@
package com.rdesk.applications.entertainment.comic;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
public class ZoomableImagePanel extends JPanel {
private BufferedImage originalImage;
private double zoom = 1.0;
private Point dragStart;
private Point viewportStart;
private final JViewport viewport;
private static final double ZOOM_STEP = 0.1;
private static final double MIN_ZOOM = 0.2;
private static final double MAX_ZOOM = 3.0;
public ZoomableImagePanel() {
setLayout(new BorderLayout());
JLabel imageLabel = new JLabel("", SwingConstants.CENTER);
JScrollPane scrollPane = new JScrollPane(imageLabel);
add(scrollPane, BorderLayout.CENTER);
this.viewport = scrollPane.getViewport();
imageLabel.addMouseWheelListener(this::onMouseWheel);
DragHandler dragHandler = new DragHandler(imageLabel);
imageLabel.addMouseListener(dragHandler);
imageLabel.addMouseMotionListener(dragHandler);
}
public void setImage(BufferedImage image) {
this.originalImage = image;
this.zoom = 1.0;
updateDisplay();
SwingUtilities.invokeLater(() -> viewport.setViewPosition(new Point(0, 0)));
}
public void clearImage() {
this.originalImage = null;
removeImageFromLabel();
}
private void updateDisplay() {
if (originalImage == null) {
removeImageFromLabel();
return;
}
JLabel label = getImageLabel();
int newWidth = (int) (originalImage.getWidth() * zoom);
int newHeight = (int) (originalImage.getHeight() * zoom);
Image scaled = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
label.setIcon(new ImageIcon(scaled));
label.setText("");
label.setPreferredSize(new Dimension(newWidth, newHeight));
revalidate();
repaint();
}
private void onMouseWheel(MouseWheelEvent e) {
if (originalImage == null) return;
double oldZoom = zoom;
double newZoom = zoom + (e.getWheelRotation() < 0 ? ZOOM_STEP : -ZOOM_STEP);
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
if (newZoom == zoom) return;
Point mouseOnScreen = e.getPoint();
Point viewPos = viewport.getViewPosition();
double xRatio = (viewPos.x + mouseOnScreen.x) / (double) viewport.getExtentSize().width;
double yRatio = (viewPos.y + mouseOnScreen.y) / (double) viewport.getExtentSize().height;
zoom = newZoom;
updateDisplay();
Dimension contentSize = getImageLabel().getPreferredSize();
Dimension viewSize = viewport.getExtentSize();
int newX = (int) (contentSize.width * xRatio - viewSize.width / 2.0);
int newY = (int) (contentSize.height * yRatio - viewSize.height / 2.0);
newX = Math.max(0, Math.min(newX, contentSize.width - viewSize.width));
newY = Math.max(0, Math.min(newY, contentSize.height - viewSize.height));
viewport.setViewPosition(new Point(newX, newY));
}
private JLabel getImageLabel() {
return (JLabel) ((JScrollPane) getComponent(0)).getViewport().getView();
}
private void removeImageFromLabel() {
JLabel label = getImageLabel();
label.setIcon(null);
label.setText("Loading");
label.setPreferredSize(null);
revalidate();
}
private class DragHandler extends MouseAdapter {
private final JLabel label;
DragHandler(JLabel label) {
this.label = label;
}
@Override
public void mousePressed(MouseEvent e) {
if (!SwingUtilities.isLeftMouseButton(e)) return;
dragStart = e.getPoint();
viewportStart = viewport.getViewPosition();
label.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
@Override
public void mouseReleased(MouseEvent e) {
if (!SwingUtilities.isLeftMouseButton(e)) return;
dragStart = null;
viewportStart = null;
label.setCursor(Cursor.getDefaultCursor());
}
@Override
public void mouseDragged(MouseEvent e) {
if (dragStart == null || viewportStart == null) return;
int deltaX = dragStart.x - e.getX();
int deltaY = dragStart.y - e.getY();
Point newPos = new Point(viewportStart.x + deltaX, viewportStart.y + deltaY);
Dimension contentSize = label.getPreferredSize();
Dimension viewSize = viewport.getExtentSize();
newPos.x = Math.max(0, Math.min(newPos.x, contentSize.width - viewSize.width));
newPos.y = Math.max(0, Math.min(newPos.y, contentSize.height - viewSize.height));
viewport.setViewPosition(newPos);
}
}
}