Add Qt6 desktop uploader

A native Qt6/C++ desktop client for the gallery's POST /upload API, in
desktop/ with its own flake (kept separate from the Go server module the
same way android/ is). Single-photo parity with the Android app:

- multipart upload with preemptive HTTP Basic auth and retry-aware outcome
  classification (uploadclient)
- JPEG/PNG metadata stripped byte-for-byte to drop embedded GPS, EXIF
  orientation baked in, and a quality/scale valve for the 20M nginx cap;
  HEIC decodes via the qtimageformats plugin (imageprep)
- base URL / username in QSettings, password in the system keychain via
  qtkeychain; https required and a trailing /upload stripped (settings)
- settings and post screens in a QStackedWidget (mainwindow)

Native Wayland or xcb is selected at runtime. Bulk upload, EXIF
capture-date prefill, and moving image prep off the UI thread are left as
follow-ups.
This commit is contained in:
2026-08-26 00:28:35 +02:00
parent d4fec5b0ff
commit d07e3eb0dc
13 changed files with 1066 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <QMainWindow>
#include "settings.h"
#include "uploadclient.h"
class QDateEdit;
class QLabel;
class QLineEdit;
class QPushButton;
class QStackedWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
private:
QWidget *buildSettingsPage();
QWidget *buildPostPage();
void showSettings();
void showPost();
void saveAndTest();
void chooseImage();
void submit();
Settings m_settings;
QStackedWidget *m_stack = nullptr;
// Settings page
QLineEdit *m_baseUrl = nullptr;
QLineEdit *m_username = nullptr;
QLineEdit *m_password = nullptr;
QPushButton *m_saveButton = nullptr;
QPushButton *m_settingsBack = nullptr;
QLabel *m_settingsStatus = nullptr;
// Post page
QLabel *m_preview = nullptr;
QLineEdit *m_caption = nullptr;
QDateEdit *m_date = nullptr;
QPushButton *m_chooseButton = nullptr;
QPushButton *m_postButton = nullptr;
QLabel *m_postStatus = nullptr;
QLabel *m_lastPost = nullptr;
QString m_imagePath; // currently selected source image
};