no auto finish

This commit is contained in:
2026-08-24 16:15:41 +08:00
parent 3a0529edf8
commit 7bcb02babe
5 changed files with 219 additions and 13 deletions
+49 -1
View File
@@ -8,6 +8,10 @@
#include <QDebug> #include <QDebug>
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QGuiApplication>
#include <QClipboard>
#include <QDesktopServices>
AppController::AppController(QObject* parent) AppController::AppController(QObject* parent)
: QObject(parent) : QObject(parent)
, m_settings(new LocalSend::Settings(this)) , m_settings(new LocalSend::Settings(this))
@@ -146,6 +150,19 @@ void AppController::setQuickSave(bool enabled)
} }
} }
bool AppController::autoFinish() const
{
return m_settings->autoFinish();
}
void AppController::setAutoFinish(bool enabled)
{
if (m_settings->autoFinish() != enabled) {
m_settings->setAutoFinish(enabled);
emit autoFinishChanged();
}
}
QString AppController::deviceType() const QString AppController::deviceType() const
{ {
return LocalSend::deviceTypeToString(m_settings->deviceType()); return LocalSend::deviceTypeToString(m_settings->deviceType());
@@ -327,6 +344,17 @@ void AppController::onRegisterRequest(const LocalSend::RegisterDto& dto, const Q
emit devicesChanged(); emit devicesChanged();
} }
static bool isMessageRequest(const LocalSend::PrepareUploadRequestDto& dto)
{
// The original LocalSend treats a request with a single "text/plain" file
// that carries a preview as a message/link share instead of a file transfer.
if (dto.files.size() != 1) {
return false;
}
const LocalSend::FileDto& file = dto.files.constBegin().value();
return file.fileType == QStringLiteral("text/plain") && !file.preview.isEmpty();
}
void AppController::onPrepareUploadRequest(const QString& httpSessionId, void AppController::onPrepareUploadRequest(const QString& httpSessionId,
const LocalSend::PrepareUploadRequestDto& dto, const LocalSend::PrepareUploadRequestDto& dto,
const QHostAddress& sender) const QHostAddress& sender)
@@ -356,7 +384,12 @@ void AppController::onPrepareUploadRequest(const QString& httpSessionId,
filesList.append(file); filesList.append(file);
} }
emit receiveRequest(sessionId, dto.info.alias, sender.toString(), filesList); QString message;
if (isMessageRequest(dto)) {
message = dto.files.constBegin().value().preview;
}
emit receiveRequest(sessionId, dto.info.alias, sender.toString(), filesList, message);
} }
void AppController::onUploadRequest(const QString& sessionId, const QString& fileId, void AppController::onUploadRequest(const QString& sessionId, const QString& fileId,
@@ -570,6 +603,21 @@ void AppController::clearPendingFiles()
emit pendingFilesChanged(); emit pendingFilesChanged();
} }
void AppController::copyToClipboard(const QString& text)
{
if (auto* clipboard = QGuiApplication::clipboard()) {
clipboard->setText(text);
}
}
void AppController::openLink(const QString& url)
{
QUrl qUrl(url);
if (qUrl.isValid() && (qUrl.scheme() == QStringLiteral("http") || qUrl.scheme() == QStringLiteral("https"))) {
QDesktopServices::openUrl(qUrl);
}
}
void AppController::sendTo(const QString& deviceFingerprint) void AppController::sendTo(const QString& deviceFingerprint)
{ {
if (m_pendingSendPaths.isEmpty()) { if (m_pendingSendPaths.isEmpty()) {
+8 -1
View File
@@ -19,6 +19,7 @@ class AppController : public QObject
Q_PROPERTY(bool serverRunning READ serverRunning NOTIFY serverRunningChanged) Q_PROPERTY(bool serverRunning READ serverRunning NOTIFY serverRunningChanged)
Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged) Q_PROPERTY(QString downloadPath READ downloadPath WRITE setDownloadPath NOTIFY downloadPathChanged)
Q_PROPERTY(bool quickSave READ quickSave WRITE setQuickSave NOTIFY quickSaveChanged) Q_PROPERTY(bool quickSave READ quickSave WRITE setQuickSave NOTIFY quickSaveChanged)
Q_PROPERTY(bool autoFinish READ autoFinish WRITE setAutoFinish NOTIFY autoFinishChanged)
Q_PROPERTY(bool sending READ sending NOTIFY sendingChanged) Q_PROPERTY(bool sending READ sending NOTIFY sendingChanged)
Q_PROPERTY(double sendProgress READ sendProgress NOTIFY sendProgressChanged) Q_PROPERTY(double sendProgress READ sendProgress NOTIFY sendProgressChanged)
Q_PROPERTY(QString currentSendFileName READ currentSendFileName NOTIFY sendProgressChanged) Q_PROPERTY(QString currentSendFileName READ currentSendFileName NOTIFY sendProgressChanged)
@@ -54,6 +55,9 @@ public:
bool quickSave() const; bool quickSave() const;
void setQuickSave(bool enabled); void setQuickSave(bool enabled);
bool autoFinish() const;
void setAutoFinish(bool enabled);
QVariantList devices() const; QVariantList devices() const;
bool serverRunning() const; bool serverRunning() const;
@@ -96,6 +100,8 @@ public:
Q_INVOKABLE void removePendingFile(int index); Q_INVOKABLE void removePendingFile(int index);
Q_INVOKABLE void clearPendingFiles(); Q_INVOKABLE void clearPendingFiles();
Q_INVOKABLE void retryWithPin(const QString& pin); Q_INVOKABLE void retryWithPin(const QString& pin);
Q_INVOKABLE void copyToClipboard(const QString& text);
Q_INVOKABLE void openLink(const QString& url);
signals: signals:
void aliasChanged(); void aliasChanged();
@@ -104,11 +110,12 @@ signals:
void quickSaveChanged(); void quickSaveChanged();
void devicesChanged(); void devicesChanged();
void serverRunningChanged(); void serverRunningChanged();
void autoFinishChanged();
void sendingChanged(); void sendingChanged();
void sendProgressChanged(); void sendProgressChanged();
void pendingFilesChanged(); void pendingFilesChanged();
void receiveRequest(const QString& sessionId, const QString& senderAlias, void receiveRequest(const QString& sessionId, const QString& senderAlias,
const QString& senderIp, const QVariantList& files); const QString& senderIp, const QVariantList& files, const QString& message);
void receiveProgress(const QString& sessionId, const QString& fileId, double progress); void receiveProgress(const QString& sessionId, const QString& fileId, double progress);
void receiveCompleted(const QString& sessionId); void receiveCompleted(const QString& sessionId);
void receiveError(const QString& sessionId, const QString& error); void receiveError(const QString& sessionId, const QString& error);
+146 -11
View File
@@ -14,6 +14,7 @@ ApplicationWindow {
property var currentFiles: [] property var currentFiles: []
property string currentSenderAlias: "" property string currentSenderAlias: ""
property string currentSenderIp: "" property string currentSenderIp: ""
property string currentMessage: ""
function getDeviceTypeIcon(deviceType) { function getDeviceTypeIcon(deviceType) {
switch (deviceType) { switch (deviceType) {
@@ -173,7 +174,9 @@ ApplicationWindow {
anchors.centerIn: parent anchors.centerIn: parent
modal: true modal: true
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
title: qsTr("Receiving Files") title: succeeded ? qsTr("Receive Complete") : qsTr("Receiving Files")
property bool succeeded: false
ColumnLayout { ColumnLayout {
spacing: 12 spacing: 12
@@ -184,6 +187,7 @@ ApplicationWindow {
} }
Label { Label {
visible: !receiveProgressDialog.succeeded
text: appController.currentReceiveFileName text: appController.currentReceiveFileName
? qsTr("%1 (%2/%3)").arg(appController.currentReceiveFileName) ? qsTr("%1 (%2/%3)").arg(appController.currentReceiveFileName)
.arg(appController.currentReceiveFileIndex) .arg(appController.currentReceiveFileIndex)
@@ -197,17 +201,29 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
from: 0 from: 0
to: 100 to: 100
value: appController.receiveProgress value: receiveProgressDialog.succeeded ? 100 : appController.receiveProgress
} }
Label { Label {
text: qsTr("%1% complete").arg(Math.round(appController.receiveProgress)) text: receiveProgressDialog.succeeded
? qsTr("Files received successfully.")
: qsTr("%1% complete").arg(Math.round(appController.receiveProgress))
color: receiveProgressDialog.succeeded ? palette.mid : palette.mid
}
Label {
visible: receiveProgressDialog.succeeded
text: qsTr("Saved to: %1").arg(appController.downloadPath)
color: palette.mid color: palette.mid
font.pixelSize: 12
wrapMode: Text.WordWrap
Layout.fillWidth: true
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
Button { Button {
visible: !receiveProgressDialog.succeeded
text: qsTr("Cancel") text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
onClicked: { onClicked: {
@@ -215,6 +231,85 @@ ApplicationWindow {
receiveProgressDialog.close() receiveProgressDialog.close()
} }
} }
Button {
visible: receiveProgressDialog.succeeded
text: qsTr("Done")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: receiveProgressDialog.close()
}
}
onClosed: {
succeeded = false
}
}
Dialog {
id: receiveMessageDialog
anchors.centerIn: parent
modal: true
closePolicy: Popup.NoAutoClose
title: qsTr("Message")
ColumnLayout {
spacing: 12
Layout.minimumWidth: 440
Label {
text: qsTr("From: %1 (%2)").arg(currentSenderAlias).arg(currentSenderIp)
font.bold: true
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 200
color: palette.base
border.color: palette.mid
radius: 6
ScrollView {
anchors.fill: parent
anchors.margins: 8
clip: true
TextArea {
id: messageBody
width: ScrollView.availableWidth - 4
text: root.currentMessage
readOnly: true
wrapMode: Text.WordWrap
selectByMouse: true
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
text: qsTr("Copy")
onClicked: {
appController.copyToClipboard(root.currentMessage)
}
}
Button {
visible: isLink(root.currentMessage)
text: qsTr("Open Link")
onClicked: {
appController.openLink(root.currentMessage)
}
}
Item { Layout.fillWidth: true }
Button {
text: qsTr("Close")
onClicked: {
appController.declineReceive(currentSessionId)
currentSessionId = ""
receiveMessageDialog.close()
}
}
}
} }
} }
@@ -223,12 +318,15 @@ ApplicationWindow {
anchors.centerIn: parent anchors.centerIn: parent
modal: true modal: true
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
title: qsTr("Sending Files") title: succeeded ? qsTr("Send Complete") : qsTr("Sending Files")
property bool succeeded: false
ColumnLayout { ColumnLayout {
spacing: 12 spacing: 12
Label { Label {
visible: !sendProgressDialog.succeeded
text: appController.currentSendFileName text: appController.currentSendFileName
? qsTr("%1 (%2/%3)").arg(appController.currentSendFileName) ? qsTr("%1 (%2/%3)").arg(appController.currentSendFileName)
.arg(appController.currentSendFileIndex) .arg(appController.currentSendFileIndex)
@@ -242,17 +340,20 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
from: 0 from: 0
to: 100 to: 100
value: appController.sendProgress value: sendProgressDialog.succeeded ? 100 : appController.sendProgress
} }
Label { Label {
text: qsTr("%1% complete").arg(Math.round(appController.sendProgress)) text: sendProgressDialog.succeeded
? qsTr("Files sent successfully.")
: qsTr("%1% complete").arg(Math.round(appController.sendProgress))
color: palette.mid color: palette.mid
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
Button { Button {
visible: !sendProgressDialog.succeeded
text: qsTr("Cancel") text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
onClicked: { onClicked: {
@@ -260,18 +361,35 @@ ApplicationWindow {
sendProgressDialog.close() sendProgressDialog.close()
} }
} }
Button {
visible: sendProgressDialog.succeeded
text: qsTr("Done")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: sendProgressDialog.close()
}
}
onClosed: {
succeeded = false
} }
} }
Connections { Connections {
target: appController target: appController
function onReceiveRequest(sessionId, senderAlias, senderIp, files) { function onReceiveRequest(sessionId, senderAlias, senderIp, files, message) {
currentSessionId = sessionId currentSessionId = sessionId
currentSenderAlias = senderAlias currentSenderAlias = senderAlias
currentSenderIp = senderIp currentSenderIp = senderIp
currentFiles = files currentFiles = files
if (message !== undefined && message.length > 0) {
// A message (or link) share request: show the message view instead.
currentMessage = message
receiveMessageDialog.open()
return
}
if (appController.quickSave) { if (appController.quickSave) {
appController.acceptReceive(sessionId) appController.acceptReceive(sessionId)
receiveProgressDialog.open() receiveProgressDialog.open()
@@ -281,7 +399,10 @@ ApplicationWindow {
} }
function onReceiveCompleted(sessionId) { function onReceiveCompleted(sessionId) {
receiveProgressDialog.close() receiveProgressDialog.succeeded = true
if (appController.autoFinish || appController.quickSave) {
receiveProgressDialog.close()
}
} }
function onReceiveError(sessionId, error) { function onReceiveError(sessionId, error) {
@@ -297,9 +418,10 @@ ApplicationWindow {
} }
function onSendCompleted(sessionId) { function onSendCompleted(sessionId) {
sendProgressDialog.close() sendProgressDialog.succeeded = true
successDialog.text = qsTr("Files sent successfully!") if (appController.autoFinish) {
successDialog.open() sendProgressDialog.close()
}
} }
function onSendError(error) { function onSendError(error) {
@@ -506,6 +628,12 @@ ApplicationWindow {
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB"
} }
function isLink(text) {
var trimmed = text.trim()
if (trimmed.length === 0 || /\s/.test(trimmed)) return false
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)
}
Component { Component {
id: homePageComponent id: homePageComponent
Page { Page {
@@ -788,6 +916,13 @@ ApplicationWindow {
onCheckedChanged: appController.quickSave = checked onCheckedChanged: appController.quickSave = checked
} }
Label { text: qsTr("Auto Finish:") }
CheckBox {
id: autoFinishCheck
checked: appController.autoFinish
onCheckedChanged: appController.autoFinish = checked
}
Label { text: qsTr("Receive PIN:") } Label { text: qsTr("Receive PIN:") }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
@@ -29,6 +29,9 @@ public:
bool quickSave() const; bool quickSave() const;
void setQuickSave(bool enabled); void setQuickSave(bool enabled);
bool autoFinish() const;
void setAutoFinish(bool enabled);
QString receivePin() const; QString receivePin() const;
void setReceivePin(const QString& pin); void setReceivePin(const QString& pin);
+13
View File
@@ -91,6 +91,19 @@ void Settings::setQuickSave(bool enabled)
} }
} }
bool Settings::autoFinish() const
{
return m_settings.value(QStringLiteral("autoFinish"), false).toBool();
}
void Settings::setAutoFinish(bool enabled)
{
if (autoFinish() != enabled) {
m_settings.setValue(QStringLiteral("autoFinish"), enabled);
m_settings.sync();
}
}
QString Settings::receivePin() const QString Settings::receivePin() const
{ {
return m_settings.value(QStringLiteral("receivePin")).toString(); return m_settings.value(QStringLiteral("receivePin")).toString();