mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
more ui stuff
This commit is contained in:
@@ -5,20 +5,15 @@ import QtQuick.Shapes
|
||||
import "."
|
||||
|
||||
RowLayout {
|
||||
id: top
|
||||
id: topLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
property string pendingPath: ""
|
||||
property bool pendingTransfer: false
|
||||
property bool incomingShare: false
|
||||
property int currentIndex: 1
|
||||
|
||||
property string filename: "VacationPhoto_2026.jpg"
|
||||
property string targetname: "Lasan's A55"
|
||||
property real progressValue: 0.64
|
||||
property string statusText: "Receiving 1 of 1 items"
|
||||
property bool transferring: true
|
||||
property int currentIndex: 0
|
||||
property var selectedTransferId: 0
|
||||
|
||||
Connections {
|
||||
target: backend
|
||||
@@ -28,8 +23,14 @@ RowLayout {
|
||||
}
|
||||
function onIncomingTransfer(share_target_id, device_name, status) {
|
||||
console.log("Getting incoming share");
|
||||
console.log("selected transfer: " + share_target_id)
|
||||
selectedTransferId = share_target_id;
|
||||
currentIndex = 2;
|
||||
transferring = false;
|
||||
}
|
||||
function onTransferUpdate(share_target_id, device_name, status, progress) {
|
||||
if (selectedTransferId === share_target_id) {
|
||||
currentIndex = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +38,22 @@ RowLayout {
|
||||
target: EventBus
|
||||
|
||||
function onFileSelected(path) {
|
||||
top.pendingPath = path;
|
||||
topLayout.pendingPath = path;
|
||||
startSharing();
|
||||
}
|
||||
|
||||
function onShareTargetSelected(shareTargetId) {
|
||||
if (topLayout.pendingPath.length === 0) {
|
||||
return;
|
||||
}
|
||||
topLayout.selectedTransferId = shareTargetId;
|
||||
backend.prepareOutgoingTransfer(shareTargetId, topLayout.pendingPath);
|
||||
backend.sendFile(shareTargetId, topLayout.pendingPath);
|
||||
topLayout.currentIndex = 2;
|
||||
}
|
||||
|
||||
function onCancelPendingShareRequested() {
|
||||
top.cancelPendingShare();
|
||||
topLayout.cancelPendingShare();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +63,7 @@ RowLayout {
|
||||
|
||||
function cancelPendingShare() {
|
||||
pendingPath = "";
|
||||
selectedTransferId = 0;
|
||||
currentIndex = 0;
|
||||
backend.stopDiscovery();
|
||||
backend.startReceive();
|
||||
@@ -64,24 +76,65 @@ RowLayout {
|
||||
}
|
||||
|
||||
Sidebar {
|
||||
pendingPath: top.pendingPath
|
||||
pendingPath: topLayout.pendingPath
|
||||
}
|
||||
StackLayout {
|
||||
id: contentStack
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: top.currentIndex
|
||||
currentIndex: topLayout.currentIndex
|
||||
|
||||
Drop {}
|
||||
|
||||
SearchingDevices {}
|
||||
|
||||
IncomingShare {
|
||||
filename: top.filename
|
||||
targetname: top.targetname
|
||||
progressValue: top.progressValue
|
||||
statusText: top.statusText
|
||||
transferring: top.transferring
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Repeater {
|
||||
id: transferRepeater
|
||||
model: backend.transfers
|
||||
|
||||
IncomingShare {
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("transferId: " + model.transferId)
|
||||
console.log("selectedTransferId: " + topLayout.selectedTransferId)
|
||||
console.log(model.transferId == topLayout.selectedTransferId)
|
||||
|
||||
}
|
||||
|
||||
visible: topLayout.selectedTransferId == 0 || model.transferId == topLayout.selectedTransferId
|
||||
shareTargetId: model.transferId
|
||||
direction: model.direction
|
||||
filename: model.localPath
|
||||
targetname: model.deviceName
|
||||
progressValue: model.progress
|
||||
status: model.status
|
||||
totalBytes: model.totalBytes
|
||||
transferredBytes: model.transferredBytes
|
||||
totalAttachmentsCount: model.totalAttachmentsCount
|
||||
transferredAttachmentsCount: model.transferredAttachmentsCount
|
||||
isFinalStatus: model.isFinalStatus
|
||||
awaitingLocalConfirmation: model.awaitingLocalConfirmation
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#DCF5FF"
|
||||
visible: transferRepeater.count === 0
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Preparing transfer..."
|
||||
color: "#377B95"
|
||||
font.pointSize: 18
|
||||
font.weight: 600
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ Rectangle {
|
||||
drop.acceptProposedAction();
|
||||
if (drop.hasUrls) {
|
||||
console.log(drop.urls[0].toString());
|
||||
EventBus.fileSelected(drop.urls[0].toString());
|
||||
for (let i of drop.formats) {
|
||||
console.log(i);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,87 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
property var shareTargetId: 0
|
||||
property string direction: "receive"
|
||||
property string filename: "ThisIsAnImage.jpg"
|
||||
property string targetname: "Lasan's A55"
|
||||
property bool transferring: true
|
||||
property bool transferring: !awaitingLocalConfirmation
|
||||
property real progressValue: 0.64
|
||||
property string statusText: "Receiving file"
|
||||
property string status: "kUnknown"
|
||||
property bool isFinalStatus: false
|
||||
property bool awaitingLocalConfirmation: false
|
||||
property int totalAttachmentsCount: 0
|
||||
property int transferredAttachmentsCount: 0
|
||||
property var totalBytes: 0
|
||||
property var transferredBytes: 0
|
||||
|
||||
function baseName(path) {
|
||||
if (!path || path.length === 0) {
|
||||
return direction === "send" ? "Selected file" : transferSummary()
|
||||
}
|
||||
const normalized = decodeURIComponent(path).replace("file://", "")
|
||||
const parts = normalized.split("/")
|
||||
return parts.length === 0 ? normalized : parts[parts.length - 1]
|
||||
}
|
||||
|
||||
function transferSummary() {
|
||||
if (totalAttachmentsCount > 0) {
|
||||
return totalAttachmentsCount === 1 ? "1 item" : totalAttachmentsCount + " items"
|
||||
}
|
||||
if (totalBytes > 0) {
|
||||
return formatBytes(totalBytes)
|
||||
}
|
||||
return "Shared items"
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
let value = Number(bytes)
|
||||
if (!isFinite(value) || value <= 0) {
|
||||
return ""
|
||||
}
|
||||
const units = ["B", "KB", "MB", "GB", "TB"]
|
||||
let unitIndex = 0
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024
|
||||
unitIndex += 1
|
||||
}
|
||||
return (unitIndex === 0 ? Math.round(value) : value.toFixed(value >= 10 ? 1 : 2)) + " " + units[unitIndex]
|
||||
}
|
||||
|
||||
function statusText() {
|
||||
if (status === "kAwaitingLocalConfirmation") {
|
||||
return "Incoming share request"
|
||||
}
|
||||
if (status === "kConnecting") {
|
||||
return direction === "send" ? "Connecting" : "Preparing transfer"
|
||||
}
|
||||
if (status === "kAwaitingRemoteAcceptance") {
|
||||
return "Waiting for receiver"
|
||||
}
|
||||
if (status === "kInProgress") {
|
||||
const verb = direction === "send" ? "Sending" : "Receiving"
|
||||
if (totalAttachmentsCount > 0) {
|
||||
return verb + " " + Math.min(transferredAttachmentsCount + 1, totalAttachmentsCount) + " of " + totalAttachmentsCount + " items"
|
||||
}
|
||||
return verb
|
||||
}
|
||||
if (status === "kComplete") {
|
||||
return direction === "send" ? "Sent" : "Received"
|
||||
}
|
||||
if (status === "kReject") {
|
||||
return "Rejected"
|
||||
}
|
||||
if (status === "kCancelled") {
|
||||
return "Cancelled"
|
||||
}
|
||||
if (status === "kTimedOut") {
|
||||
return "Timed out"
|
||||
}
|
||||
if (isFinalStatus) {
|
||||
return "Transfer failed"
|
||||
}
|
||||
return direction === "send" ? "Starting send" : "Preparing transfer"
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: root.transferring
|
||||
@@ -68,7 +144,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.filename
|
||||
text: root.baseName(root.filename)
|
||||
color: "#57707A"
|
||||
font.pointSize: 12
|
||||
elide: Text.ElideMiddle
|
||||
@@ -102,7 +178,7 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: root.statusText
|
||||
text: root.statusText()
|
||||
color: "#57707A"
|
||||
font.pointSize: 12
|
||||
Layout.fillWidth: true
|
||||
@@ -127,6 +203,8 @@ Rectangle {
|
||||
Button {
|
||||
id: cancelButton
|
||||
text: "Cancel"
|
||||
visible: !root.isFinalStatus
|
||||
onClicked: backend.cancel(root.shareTargetId)
|
||||
|
||||
contentItem: Text {
|
||||
text: cancelButton.text
|
||||
@@ -174,7 +252,7 @@ Rectangle {
|
||||
font.pointSize: 18
|
||||
font.weight: 700
|
||||
color: "#377B95"
|
||||
text: "Incoming share"
|
||||
text: root.direction === "send" ? "Outgoing share" : "Incoming share"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
@@ -194,7 +272,7 @@ Rectangle {
|
||||
color: "#57707A"
|
||||
font.pointSize: 11
|
||||
wrapMode: Text.WrapAnywhere
|
||||
text: root.filename
|
||||
text: root.baseName(root.filename)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideMiddle
|
||||
@@ -216,7 +294,8 @@ Rectangle {
|
||||
Button {
|
||||
id: cancelbutton
|
||||
Layout.fillWidth: true
|
||||
text: "Cancel"
|
||||
text: "Reject"
|
||||
onClicked: backend.reject(root.shareTargetId)
|
||||
|
||||
contentItem: Text {
|
||||
text: cancelbutton.text
|
||||
@@ -240,6 +319,7 @@ Rectangle {
|
||||
id: acceptButton
|
||||
Layout.fillWidth: true
|
||||
text: "Accept"
|
||||
onClicked: backend.accept(root.shareTargetId)
|
||||
|
||||
contentItem: Text {
|
||||
text: acceptButton.text
|
||||
|
||||
@@ -6,7 +6,7 @@ Row {
|
||||
id: shareTargetsRow
|
||||
spacing: 10
|
||||
|
||||
property bool useDummyTargets: true
|
||||
property bool useDummyTargets: false
|
||||
|
||||
ListModel {
|
||||
id: dummyTargets
|
||||
@@ -53,7 +53,7 @@ Row {
|
||||
spacing: 5
|
||||
|
||||
ShareTarget {
|
||||
shareTargetId: shareTargetsRow.useDummyTargets ? model.targetId : model.id
|
||||
shareTargetId: model.targetId
|
||||
deviceName: model.deviceName
|
||||
iconSource: {
|
||||
if (model.type === 2)
|
||||
|
||||
+263
-13
@@ -1,9 +1,13 @@
|
||||
#include "sharing/linux/app/backend.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QPointer>
|
||||
#include <QUrl>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
namespace {
|
||||
@@ -12,6 +16,35 @@ QString ToQString(const std::string& value) {
|
||||
return QString::fromStdString(value);
|
||||
}
|
||||
|
||||
QString NormalizeLocalPath(const QString& path) {
|
||||
const QUrl url(path);
|
||||
if (url.isValid() && url.isLocalFile()) {
|
||||
return url.toLocalFile();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
double ProgressFraction(const std::optional<nearby::sharing::linux::app::Transfer>& transfer) {
|
||||
if (!transfer.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
return std::clamp(transfer->progress / 100.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
bool IsAwaitingLocalConfirmation(
|
||||
const std::optional<nearby::sharing::linux::app::Transfer>& transfer) {
|
||||
return transfer.has_value() &&
|
||||
transfer->status == "kAwaitingLocalConfirmation";
|
||||
}
|
||||
|
||||
QString DeviceNameFor(
|
||||
const std::optional<nearby::sharing::linux::app::ShareTarget>& target) {
|
||||
if (!target.has_value() || target->device_name.empty()) {
|
||||
return QStringLiteral("Unknown device");
|
||||
}
|
||||
return ToQString(target->device_name);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ShareTargetModel::ShareTargetModel(QObject* parent)
|
||||
@@ -57,7 +90,7 @@ QVariant ShareTargetModel::data(const QModelIndex& index, int role) const {
|
||||
|
||||
QHash<int, QByteArray> ShareTargetModel::roleNames() const {
|
||||
return {
|
||||
{IdRole, "id"},
|
||||
{IdRole, "targetId"},
|
||||
{DeviceNameRole, "deviceName"},
|
||||
{TypeRole, "type"},
|
||||
{IsIncomingRole, "isIncoming"},
|
||||
@@ -104,6 +137,15 @@ void ShareTargetModel::ResetTargets(const std::vector<ShareTarget>& targets) {
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
std::optional<ShareTargetModel::ShareTarget> ShareTargetModel::FindTarget(
|
||||
int64_t target_id) const {
|
||||
const int row = IndexOf(target_id);
|
||||
if (row < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return targets_[row];
|
||||
}
|
||||
|
||||
int ShareTargetModel::IndexOf(int64_t target_id) const {
|
||||
for (int i = 0; i < static_cast<int>(targets_.size()); ++i) {
|
||||
if (targets_[i].id == target_id) {
|
||||
@@ -113,7 +155,178 @@ int ShareTargetModel::IndexOf(int64_t target_id) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Backend::Backend(QObject* parent) : QObject(parent), targets_(this) {
|
||||
ShareTransferModel::ShareTransferModel(QObject* parent)
|
||||
: QAbstractListModel(parent) {}
|
||||
|
||||
int ShareTransferModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(transfers_.size());
|
||||
}
|
||||
|
||||
QVariant ShareTransferModel::data(const QModelIndex& index, int role) const {
|
||||
if (!index.isValid() || index.row() < 0 ||
|
||||
index.row() >= static_cast<int>(transfers_.size())) {
|
||||
return {};
|
||||
}
|
||||
return DataForRow(transfers_[index.row()], role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ShareTransferModel::roleNames() const {
|
||||
return {
|
||||
{IdRole, "transferId"},
|
||||
{DirectionRole, "direction"},
|
||||
{DeviceNameRole, "deviceName"},
|
||||
{TypeRole, "type"},
|
||||
{StatusRole, "status"},
|
||||
{ProgressRole, "progress"},
|
||||
{TransferredBytesRole, "transferredBytes"},
|
||||
{TotalBytesRole, "totalBytes"},
|
||||
{TransferSpeedRole, "transferSpeed"},
|
||||
{EstimatedTimeRemainingRole, "estimatedTimeRemaining"},
|
||||
{TotalAttachmentsCountRole, "totalAttachmentsCount"},
|
||||
{TransferredAttachmentsCountRole, "transferredAttachmentsCount"},
|
||||
{IsFinalStatusRole, "isFinalStatus"},
|
||||
{HasTargetRole, "hasTarget"},
|
||||
{HasTransferRole, "hasTransfer"},
|
||||
{AwaitingLocalConfirmationRole, "awaitingLocalConfirmation"},
|
||||
{LocalPathRole, "localPath"},
|
||||
};
|
||||
}
|
||||
|
||||
void ShareTransferModel::ApplyTarget(const ShareTarget& target) {
|
||||
const int row = IndexOf(target.id);
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
transfers_[row].target = target;
|
||||
EmitRowChanged(row);
|
||||
}
|
||||
|
||||
void ShareTransferModel::ApplyTransfer(const QString& direction,
|
||||
const ShareTarget& target,
|
||||
const Transfer& transfer) {
|
||||
int row = IndexOf(target.id);
|
||||
if (row < 0) {
|
||||
row = static_cast<int>(transfers_.size());
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
transfers_.push_back(Row{target.id, direction, target, transfer, {}});
|
||||
endInsertRows();
|
||||
return;
|
||||
}
|
||||
|
||||
transfers_[row].direction = direction;
|
||||
transfers_[row].target = target;
|
||||
transfers_[row].transfer = transfer;
|
||||
EmitRowChanged(row);
|
||||
}
|
||||
|
||||
void ShareTransferModel::PrepareOutgoingTransfer(
|
||||
int64_t target_id, const QString& local_path,
|
||||
const std::optional<ShareTarget>& target) {
|
||||
int row = IndexOf(target_id);
|
||||
if (row < 0) {
|
||||
row = static_cast<int>(transfers_.size());
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
Row transfer;
|
||||
transfer.id = target_id;
|
||||
transfer.direction = QStringLiteral("send");
|
||||
transfer.target = target;
|
||||
transfer.local_path = local_path;
|
||||
transfers_.push_back(std::move(transfer));
|
||||
endInsertRows();
|
||||
return;
|
||||
}
|
||||
|
||||
transfers_[row].direction = QStringLiteral("send");
|
||||
transfers_[row].target = target;
|
||||
transfers_[row].local_path = local_path;
|
||||
EmitRowChanged(row);
|
||||
}
|
||||
|
||||
void ShareTransferModel::RemoveTransfer(int64_t target_id) {
|
||||
const int row = IndexOf(target_id);
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
transfers_.erase(transfers_.begin() + row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
int ShareTransferModel::IndexOf(int64_t target_id) const {
|
||||
for (int i = 0; i < static_cast<int>(transfers_.size()); ++i) {
|
||||
if (transfers_[i].id == target_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVariant ShareTransferModel::DataForRow(const Row& row, int role) const {
|
||||
const Transfer empty_transfer;
|
||||
const Transfer& transfer = row.transfer.value_or(empty_transfer);
|
||||
|
||||
switch (role) {
|
||||
case IdRole:
|
||||
return QVariant::fromValue<qint64>(row.id);
|
||||
case DirectionRole:
|
||||
return row.direction;
|
||||
case DeviceNameRole:
|
||||
return DeviceNameFor(row.target);
|
||||
case TypeRole:
|
||||
return row.target.has_value() ? row.target->type : 0;
|
||||
case StatusRole:
|
||||
if (row.transfer.has_value()) {
|
||||
return ToQString(transfer.status);
|
||||
}
|
||||
return row.direction == QStringLiteral("send")
|
||||
? QStringLiteral("kConnecting")
|
||||
: QStringLiteral("kUnknown");
|
||||
case ProgressRole:
|
||||
return ProgressFraction(row.transfer);
|
||||
case TransferredBytesRole:
|
||||
return QVariant::fromValue<qint64>(transfer.transferred_bytes);
|
||||
case TotalBytesRole:
|
||||
return QVariant::fromValue<qint64>(transfer.total_bytes);
|
||||
case TransferSpeedRole:
|
||||
return QVariant::fromValue<qint64>(transfer.transfer_speed);
|
||||
case EstimatedTimeRemainingRole:
|
||||
return QVariant::fromValue<qint64>(transfer.estimated_time_remaining);
|
||||
case TotalAttachmentsCountRole:
|
||||
return transfer.total_attachments_count;
|
||||
case TransferredAttachmentsCountRole:
|
||||
return transfer.transferred_attachments_count;
|
||||
case IsFinalStatusRole:
|
||||
return transfer.is_final_status;
|
||||
case HasTargetRole:
|
||||
return row.target.has_value();
|
||||
case HasTransferRole:
|
||||
return row.transfer.has_value();
|
||||
case AwaitingLocalConfirmationRole:
|
||||
return IsAwaitingLocalConfirmation(row.transfer);
|
||||
case LocalPathRole:
|
||||
return row.local_path;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void ShareTransferModel::EmitRowChanged(int row) {
|
||||
const QModelIndex changed_index = index(row);
|
||||
emit dataChanged(changed_index, changed_index,
|
||||
{IdRole, DirectionRole, DeviceNameRole, TypeRole,
|
||||
StatusRole, ProgressRole, TransferredBytesRole,
|
||||
TotalBytesRole, TransferSpeedRole,
|
||||
EstimatedTimeRemainingRole, TotalAttachmentsCountRole,
|
||||
TransferredAttachmentsCountRole, IsFinalStatusRole,
|
||||
HasTargetRole, HasTransferRole,
|
||||
AwaitingLocalConfirmationRole, LocalPathRole});
|
||||
}
|
||||
|
||||
Backend::Backend(QObject* parent)
|
||||
: QObject(parent), targets_(this), transfers_(this) {
|
||||
try {
|
||||
client_ = std::make_unique<Client>(this);
|
||||
SetStatusText(QStringLiteral("Connected to nearby sharing daemon"));
|
||||
@@ -146,11 +359,18 @@ void Backend::stopDiscovery() {
|
||||
}
|
||||
|
||||
void Backend::sendFile(qint64 share_target_id, const QString& path) {
|
||||
RunCommand(QStringLiteral("Send file"), [this, share_target_id, path]() {
|
||||
return client_->SendFile(share_target_id, path.toStdString());
|
||||
const QString local_path = NormalizeLocalPath(path);
|
||||
RunCommand(QStringLiteral("Send file"), [this, share_target_id, local_path]() {
|
||||
return client_->SendFile(share_target_id, local_path.toStdString());
|
||||
});
|
||||
}
|
||||
|
||||
void Backend::prepareOutgoingTransfer(qint64 share_target_id,
|
||||
const QString& path) {
|
||||
transfers_.PrepareOutgoingTransfer(share_target_id, NormalizeLocalPath(path),
|
||||
targets_.FindTarget(share_target_id));
|
||||
}
|
||||
|
||||
void Backend::accept(qint64 share_target_id) {
|
||||
RunCommand(QStringLiteral("Accept transfer"), [this, share_target_id]() {
|
||||
return client_->Accept(share_target_id);
|
||||
@@ -189,8 +409,9 @@ void Backend::OnIncomingTransfer(const std::string& direction,
|
||||
const Transfer& transfer) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, target, transfer]() {
|
||||
[this, direction, target, transfer]() {
|
||||
ApplyTarget(target);
|
||||
transfers_.ApplyTransfer(ToQString(direction), target, transfer);
|
||||
emit incomingTransfer(target.id, ToQString(target.device_name),
|
||||
ToQString(transfer.status));
|
||||
},
|
||||
@@ -202,8 +423,9 @@ void Backend::OnTransferUpdate(const std::string& direction,
|
||||
const Transfer& transfer) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, target, transfer]() {
|
||||
[this, direction, target, transfer]() {
|
||||
ApplyTarget(target);
|
||||
transfers_.ApplyTransfer(ToQString(direction), target, transfer);
|
||||
emit transferUpdate(target.id, ToQString(target.device_name),
|
||||
ToQString(transfer.status), transfer.progress);
|
||||
},
|
||||
@@ -217,6 +439,7 @@ void Backend::OnStatusChanged(const Status& status) {
|
||||
|
||||
void Backend::ApplyTarget(const ShareTarget& target) {
|
||||
targets_.ApplyTarget(target);
|
||||
transfers_.ApplyTarget(target);
|
||||
}
|
||||
|
||||
void Backend::RemoveTarget(const ShareTarget& target) {
|
||||
@@ -246,13 +469,40 @@ void Backend::RunCommand(
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ApplyCommandResult(command, operation());
|
||||
} catch (const sdbus::Error& error) {
|
||||
SetStatusText(
|
||||
QStringLiteral("%1 failed: %2")
|
||||
.arg(command, QString::fromStdString(error.getMessage())));
|
||||
}
|
||||
QPointer<Backend> backend(this);
|
||||
std::thread([backend, command, operation]() {
|
||||
try {
|
||||
auto result = operation();
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(
|
||||
backend,
|
||||
[backend, command, result = std::move(result)]() {
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
backend->ApplyCommandResult(command, result);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
} catch (const sdbus::Error& error) {
|
||||
const QString message = QStringLiteral("%1 failed: %2")
|
||||
.arg(command, QString::fromStdString(
|
||||
error.getMessage()));
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(
|
||||
backend,
|
||||
[backend, message]() {
|
||||
if (!backend) {
|
||||
return;
|
||||
}
|
||||
backend->SetStatusText(message);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
@@ -42,6 +43,7 @@ class ShareTargetModel : public QAbstractListModel {
|
||||
void ApplyTarget(const ShareTarget& target);
|
||||
void RemoveTarget(int64_t target_id);
|
||||
void ResetTargets(const std::vector<ShareTarget>& targets);
|
||||
std::optional<ShareTarget> FindTarget(int64_t target_id) const;
|
||||
|
||||
private:
|
||||
int IndexOf(int64_t target_id) const;
|
||||
@@ -49,6 +51,62 @@ class ShareTargetModel : public QAbstractListModel {
|
||||
std::vector<ShareTarget> targets_;
|
||||
};
|
||||
|
||||
class ShareTransferModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using ShareTarget = nearby::sharing::linux::app::ShareTarget;
|
||||
using Transfer = nearby::sharing::linux::app::Transfer;
|
||||
|
||||
enum Role {
|
||||
IdRole = Qt::UserRole + 1,
|
||||
DirectionRole,
|
||||
DeviceNameRole,
|
||||
TypeRole,
|
||||
StatusRole,
|
||||
ProgressRole,
|
||||
TransferredBytesRole,
|
||||
TotalBytesRole,
|
||||
TransferSpeedRole,
|
||||
EstimatedTimeRemainingRole,
|
||||
TotalAttachmentsCountRole,
|
||||
TransferredAttachmentsCountRole,
|
||||
IsFinalStatusRole,
|
||||
HasTargetRole,
|
||||
HasTransferRole,
|
||||
AwaitingLocalConfirmationRole,
|
||||
LocalPathRole,
|
||||
};
|
||||
|
||||
explicit ShareTransferModel(QObject* parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
void ApplyTarget(const ShareTarget& target);
|
||||
void ApplyTransfer(const QString& direction, const ShareTarget& target,
|
||||
const Transfer& transfer);
|
||||
void PrepareOutgoingTransfer(int64_t target_id, const QString& local_path,
|
||||
const std::optional<ShareTarget>& target);
|
||||
void RemoveTransfer(int64_t target_id);
|
||||
|
||||
private:
|
||||
struct Row {
|
||||
int64_t id = 0;
|
||||
QString direction;
|
||||
std::optional<ShareTarget> target;
|
||||
std::optional<Transfer> transfer;
|
||||
QString local_path;
|
||||
};
|
||||
|
||||
int IndexOf(int64_t target_id) const;
|
||||
QVariant DataForRow(const Row& row, int role) const;
|
||||
void EmitRowChanged(int row);
|
||||
|
||||
std::vector<Row> transfers_;
|
||||
};
|
||||
|
||||
class Backend
|
||||
: public QObject,
|
||||
public nearby::sharing::linux::app::NearbySharingDbusClient::Observer {
|
||||
@@ -61,6 +119,7 @@ class Backend
|
||||
Q_PROPERTY(bool scanning READ scanning NOTIFY statusChanged)
|
||||
Q_PROPERTY(bool transferring READ transferring NOTIFY statusChanged)
|
||||
Q_PROPERTY(QAbstractListModel* targets READ targets CONSTANT)
|
||||
Q_PROPERTY(QAbstractListModel* transfers READ transfers CONSTANT)
|
||||
|
||||
public:
|
||||
explicit Backend(QObject* parent = nullptr);
|
||||
@@ -72,12 +131,15 @@ class Backend
|
||||
bool scanning() const { return status_.is_scanning; }
|
||||
bool transferring() const { return status_.is_transferring; }
|
||||
QAbstractListModel* targets() { return &targets_; }
|
||||
QAbstractListModel* transfers() { return &transfers_; }
|
||||
|
||||
Q_INVOKABLE void startReceive();
|
||||
Q_INVOKABLE void stopReceive();
|
||||
Q_INVOKABLE void startDiscovery();
|
||||
Q_INVOKABLE void stopDiscovery();
|
||||
Q_INVOKABLE void sendFile(qint64 share_target_id, const QString& path);
|
||||
Q_INVOKABLE void prepareOutgoingTransfer(qint64 share_target_id,
|
||||
const QString& path);
|
||||
Q_INVOKABLE void accept(qint64 share_target_id);
|
||||
Q_INVOKABLE void reject(qint64 share_target_id);
|
||||
Q_INVOKABLE void cancel(qint64 share_target_id);
|
||||
@@ -119,6 +181,7 @@ class Backend
|
||||
QString status_text_;
|
||||
Status status_;
|
||||
ShareTargetModel targets_;
|
||||
ShareTransferModel transfers_;
|
||||
std::unique_ptr<Client> client_;
|
||||
bool is_incoming_transfer_ = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user