import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs ApplicationWindow { id: root visible: true width: 800 height: 600 title: qsTr("LocalSend") property string currentSessionId: "" property var currentFiles: [] property string currentSenderAlias: "" property string currentSenderIp: "" property var receiveProgress: ({}) StackView { id: stackView anchors.fill: parent Component.onCompleted: { push(homePageComponent) } } Dialog { id: receiveDialog anchors.centerIn: parent modal: true closePolicy: Popup.NoAutoClose standardButtons: Dialog.Ok | Dialog.Cancel title: qsTr("Receive Files") onAccepted: { appController.acceptReceive(currentSessionId) currentSessionId = "" } onRejected: { appController.declineReceive(currentSessionId) currentSessionId = "" } ColumnLayout { spacing: 12 Label { text: qsTr("From: %1 (%2)").arg(currentSenderAlias).arg(currentSenderIp) font.bold: true } Label { text: qsTr("Files:") } ListView { Layout.fillWidth: true Layout.preferredHeight: Math.min(200, contentHeight) width: 400 model: currentFiles spacing: 4 delegate: RowLayout { width: ListView.view.width spacing: 8 Label { text: modelData.fileName Layout.fillWidth: true elide: Text.ElideMiddle } Label { text: formatSize(modelData.size) color: "gray" } } } Label { text: qsTr("Save to: %1").arg(appController.downloadPath) color: palette.mid font.pixelSize: 12 } } } Dialog { id: progressDialog anchors.centerIn: parent modal: true closePolicy: Popup.NoAutoClose title: qsTr("Receiving Files") ColumnLayout { spacing: 12 Label { id: progressLabel text: qsTr("Receiving from %1...").arg(currentSenderAlias) } ProgressBar { id: totalProgressBar Layout.fillWidth: true from: 0 to: 100 value: calculateTotalProgress() } Label { text: qsTr("%1% complete").arg(Math.round(totalProgressBar.value)) color: "gray" } } property var progressData: ({}) } Connections { target: appController function onReceiveRequest(sessionId, senderAlias, senderIp, files) { currentSessionId = sessionId currentSenderAlias = senderAlias currentSenderIp = senderIp currentFiles = files if (appController.quickSave) { appController.acceptReceive(sessionId) } else { receiveDialog.open() } } function onReceiveProgress(sessionId, fileId, progress) { if (sessionId === currentSessionId) { receiveProgress[fileId] = progress receiveProgress = Object.assign({}, receiveProgress) progressDialog.progressData = receiveProgress } } function onReceiveCompleted(sessionId) { if (sessionId === currentSessionId) { progressDialog.close() receiveProgress = {} currentSessionId = "" } } function onReceiveError(sessionId, error) { if (sessionId === currentSessionId) { progressDialog.close() errorDialog.text = error errorDialog.open() } } } Dialog { id: errorDialog anchors.centerIn: parent modal: true standardButtons: Dialog.Ok title: qsTr("Error") property alias text: errorLabel.text Label { id: errorLabel } } function formatSize(bytes) { if (bytes < 1024) return bytes + " B" if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + " MB" return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB" } function calculateTotalProgress() { if (!currentFiles || currentFiles.length === 0) return 0 var total = 0 var count = 0 for (var i = 0; i < currentFiles.length; i++) { var fileId = currentFiles[i].id if (receiveProgress[fileId] !== undefined) { total += receiveProgress[fileId] * 100 count++ } } return count > 0 ? total / currentFiles.length : 0 } Component { id: homePageComponent Page { id: homePage signal openSettings() header: ToolBar { RowLayout { anchors.fill: parent Label { text: qsTr("LocalSend") font.bold: true font.pixelSize: 20 Layout.leftMargin: 16 } Item { Layout.fillWidth: true } ToolButton { text: qsTr("Settings") onClicked: homePage.openSettings() } } } ColumnLayout { anchors.fill: parent anchors.margins: 16 spacing: 16 Label { text: qsTr("Nearby Devices") font.bold: true font.pixelSize: 16 } ListView { id: deviceListView Layout.fillWidth: true Layout.fillHeight: true property var devices: appController.devices model: devices spacing: 8 delegate: Pane { width: ListView.view.width padding: 12 background: Rectangle { color: Qt.lighter("gray", 1.8) radius: 8 } RowLayout { anchors.fill: parent Column { Layout.fillWidth: true Label { text: modelData.alias || modelData.ip font.bold: true } Label { text: "%1:%2".arg(modelData.ip).arg(modelData.port) color: "gray" font.pixelSize: 12 } } Button { text: qsTr("Send") onClicked: { // TODO: send to this device } } } } } RowLayout { Layout.fillWidth: true Button { text: qsTr("Refresh") onClicked: appController.refreshDevices() } Item { Layout.fillWidth: true } Label { text: qsTr("Alias: %1").arg(appController.alias) color: palette.mid } } } onOpenSettings: stackView.push(settingsPageComponent) } } Component { id: settingsPageComponent Page { id: settingsPage signal back() header: ToolBar { RowLayout { anchors.fill: parent ToolButton { text: qsTr("Back") onClicked: settingsPage.back() } Label { text: qsTr("Settings") font.bold: true Layout.fillWidth: true Layout.leftMargin: 16 } } } ColumnLayout { anchors.fill: parent anchors.margins: 16 spacing: 16 GridLayout { columns: 2 Layout.fillWidth: true Label { text: qsTr("Device Alias:") } TextField { id: aliasField text: appController.alias onEditingFinished: appController.alias = text Layout.fillWidth: true } Label { text: qsTr("Port:") } SpinBox { id: portField value: appController.port from: 1 to: 65535 onValueChanged: appController.port = value } Label { text: qsTr("Download Path:") } RowLayout { Layout.fillWidth: true TextField { id: downloadPathField text: appController.downloadPath onEditingFinished: appController.downloadPath = text Layout.fillWidth: true } Button { text: qsTr("Browse") onClicked: folderDialog.open() } } Label { text: qsTr("Quick Save:") } CheckBox { id: quickSaveCheck checked: appController.quickSave onCheckedChanged: appController.quickSave = checked } } FolderDialog { id: folderDialog onAccepted: { downloadPathField.text = selectedFolder.toString().replace("file://", "") appController.downloadPath = downloadPathField.text } } Item { Layout.fillHeight: true } Label { text: qsTr("Server Status: %1").arg(appController.serverRunning ? qsTr("Running") : qsTr("Stopped")) color: appController.serverRunning ? "green" : "red" } } onBack: stackView.pop() } } }