added global event bus. refactored targets screen

This commit is contained in:
Lasan Mahaliyana
2026-06-30 21:21:34 +05:30
parent 7e74fbb3f3
commit ce37bcef6d
11 changed files with 225 additions and 120 deletions
+17 -97
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Shapes
import "."
RowLayout {
id: top
@@ -31,13 +32,23 @@ RowLayout {
transferring = false;
}
}
Connections {
target: EventBus
function onFileSelected(path) {
top.pendingPath = path;
startSharing();
}
function onCancelPendingShareRequested() {
top.cancelPendingShare();
}
}
Component.onCompleted: {
backend.startReceive();
}
Component.onDestruction: {
backend.stopReceive();
backend.stopDiscovery();
}
function cancelPendingShare() {
pendingPath = "";
@@ -54,9 +65,6 @@ RowLayout {
Sidebar {
pendingPath: top.pendingPath
onCancelPendingShareRequested: {
top.cancelPendingShare();
}
}
StackLayout {
id: contentStack
@@ -64,97 +72,9 @@ RowLayout {
Layout.fillHeight: true
currentIndex: top.currentIndex
Drop {
onFileSelected: path => {
top.pendingPath = path;
startSharing();
}
}
Drop {}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: pulseButton
property real pulseSize: Math.min(parent.width, parent.height) * 0.5
width: pulseSize
height: pulseSize
radius: pulseSize / 2
color: "#bde8f9"
anchors.centerIn: parent
SequentialAnimation {
id: pulseAnimation
loops: Animation.Infinite
running: true
ParallelAnimation {
NumberAnimation {
target: pulseButton
property: "scale"
from: 1.0
to: 1.5
duration: 1000
easing.type: Easing.InOutSine
}
NumberAnimation {
target: pulseButton
property: "opacity"
from: 1.0
to: 0
duration: 1000
easing.type: Easing.InOutSine
}
}
ParallelAnimation {
NumberAnimation {
target: pulseButton
property: "scale"
from: 1.5
to: 1.0
duration: 1000
easing.type: Easing.InOutSine
}
NumberAnimation {
target: pulseButton
property: "opacity"
from: 0
to: 1.0
duration: 1000
easing.type: Easing.InOutSine
}
}
}
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width - 64
spacing: 40
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Text {
text: "Searching for devices..."
font.pointSize: 24
font.weight: 600
color: "#377B95"
}
}
Targets {
Layout.alignment: Qt.AlignHCenter
}
}
}
SearchingDevices {}
IncomingShare {
filename: top.filename
+3
View File
@@ -23,10 +23,13 @@ qt_resource_via_qrc(
qrc_file = "resources.qrc",
files = [
"main.qml",
"qmldir",
"AppContent.qml",
"EventBus.qml",
"Drop.qml",
"Sidebar.qml",
"ShareTarget.qml",
"SearchingDevices.qml",
"Targets.qml",
"IncomingShare.qml",
"googlesans_var.ttf",
+2 -3
View File
@@ -4,10 +4,10 @@ import QtQuick.Controls
import QtQuick.Shapes
import Qt.labs.platform as Platform
import QtCore
import "."
Rectangle {
id: dropZone
signal fileSelected(string filePath)
color: "transparent"
Platform.FileDialog {
id: fileDialog
@@ -17,13 +17,12 @@ Rectangle {
nameFilters: ["All files (*)"]
onAccepted: {
fileSelected(fileDialog.file);
EventBus.fileSelected(fileDialog.file);
}
onRejected: {
console.log("File picker canceled");
}
}
//signal fileDropped(string path)
DropArea {
anchors.fill: parent
+9
View File
@@ -0,0 +1,9 @@
pragma Singleton
import QtQuick
QtObject {
signal fileSelected(string filePath)
signal cancelPendingShareRequested()
signal shareTargetSelected(var shareTargetId)
}
+88
View File
@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: pulseButton
property real pulseSize: Math.min(parent.width, parent.height) * 0.5
width: pulseSize
height: pulseSize
radius: pulseSize / 2
color: "#bde8f9"
anchors.centerIn: parent
SequentialAnimation {
id: pulseAnimation
loops: Animation.Infinite
running: true
ParallelAnimation {
NumberAnimation {
target: pulseButton
property: "scale"
from: 0.75
to: 1.5
duration: 600
easing.type: Easing.InOutSine
}
NumberAnimation {
target: pulseButton
property: "opacity"
from: 1.0
to: 0
duration: 600
easing.type: Easing.InOutSine
}
}
ParallelAnimation {
NumberAnimation {
target: pulseButton
property: "scale"
from: 1.5
to: 0.75
duration: 1500
easing.type: Easing.InOutSine
}
NumberAnimation {
target: pulseButton
property: "opacity"
from: 0
to: 1.0
duration: 1500
easing.type: Easing.InOutSine
}
}
}
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width - 64
spacing: 40
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Text {
text: "Searching for devices..."
font.pointSize: 24
font.weight: 600
color: "#377B95"
}
}
Targets {
Layout.alignment: Qt.AlignHCenter
}
}
}
+32 -14
View File
@@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "."
Rectangle {
id: rootItem
@@ -12,18 +13,8 @@ Rectangle {
// --- CUSTOM ARGUMENTS (PROPERTIES) ---
property string deviceName: "Unknown Device"
property string iconSource: "qrc:icons/smartphone.svg"
property string shareTargetId: "1"
signal clickShareTarget()
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
rootItem.clickShareTarget(rootItem.shareTargetId)
}
}
property var shareTargetId: 0
property bool hovered: targetMouseArea.containsMouse
ColumnLayout {
anchors.top: parent.top
@@ -31,6 +22,7 @@ Rectangle {
anchors.right: parent.right
spacing: 5
// --- ICON CONTAINER ---
Item {
id: iconContainer
@@ -38,13 +30,21 @@ Rectangle {
implicitHeight: 60
Layout.alignment: Qt.AlignHCenter
// Icon Circle Background
Rectangle {
id: iconCircle
anchors.fill: parent
color: "#6EA7B6"
color: rootItem.hovered ? "#195871" : "#6EA7B6"
radius: width / 2
Behavior on color {
ColorAnimation {
duration: 80
easing.type: Easing.OutQuad
}
}
Button {
id: iconButton
icon.source: rootItem.iconSource
@@ -59,6 +59,16 @@ Rectangle {
}
}
}
MouseArea {
id: targetMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
EventBus.shareTargetSelected(rootItem.shareTargetId);
}
}
}
// --- TEXT COMPONENT ---
@@ -67,7 +77,15 @@ Rectangle {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
color: "#1A1C1E" // Using the text charcoal color from your palette
color: rootItem.hovered ? "#06384C" : "#1A1C1E"
Behavior on color {
ColorAnimation {
duration: 120
easing.type: Easing.OutQuad
}
}
}
}
}
+2 -3
View File
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Shapes
import "."
Rectangle {
id: sidebar
@@ -12,8 +13,6 @@ Rectangle {
color: "#CBF0FF"
signal cancelPendingShareRequested()
ColumnLayout {
anchors.margins: 20
anchors.fill: parent
@@ -110,7 +109,7 @@ Rectangle {
id: cancelbutton
Layout.fillWidth: true
text: "Cancel"
onClicked: sidebar.cancelPendingShareRequested()
onClicked: EventBus.cancelPendingShareRequested()
contentItem: Text {
text: cancelbutton.text
font.pointSize: 14
+6
View File
@@ -12,22 +12,27 @@ Row {
id: dummyTargets
ListElement {
targetId: 1
deviceName: "Lasan's Pixel"
type: 1
}
ListElement {
targetId: 2
deviceName: "Work Laptop"
type: 2
}
ListElement {
targetId: 3
deviceName: "Kitchen Tablet"
type: 3
}
ListElement {
targetId: 4
deviceName: "A55"
type: 1
}
ListElement {
targetId: 5
deviceName: "Long Device Name For Wrapping"
type: 2
}
@@ -48,6 +53,7 @@ Row {
spacing: 5
ShareTarget {
shareTargetId: shareTargetsRow.useDummyTargets ? model.targetId : model.id
deviceName: model.deviceName
iconSource: {
if (model.type === 2)
+62 -3
View File
@@ -1,3 +1,9 @@
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
@@ -5,6 +11,7 @@
#include <QApplication>
#include <QMetaObject>
#include <QObject>
#include <QSocketNotifier>
#include <QTimer>
#include <QQmlApplicationEngine>
#include <QQmlContext>
@@ -18,6 +25,15 @@ constexpr char kQmlSourceDirEnv[] = "NEARBY_QML_SOURCE_DIR";
constexpr char kDefaultQmlSourceDir[] =
"/home/lasan/Dev/nearby_latest/sharing/linux/app";
int g_signal_pipe[2] = {-1, -1};
void HandleTerminationSignal(int signal) {
const char value = static_cast<char>(signal);
if (g_signal_pipe[1] != -1) {
static_cast<void>(write(g_signal_pipe[1], &value, sizeof(value)));
}
}
bool IsHotReloadEnabled() {
const QByteArray value = qgetenv(kQmlHotReloadEnv);
return value == "1" || value.toLower() == "true";
@@ -65,12 +81,56 @@ bool RequiresFullReload(const QFileInfo& changed_file) {
return changed_file.fileName() == QStringLiteral("main.qml");
}
void InstallTerminationCleanup(QApplication& app, Backend& backend) {
auto cleanup_done = std::make_shared<bool>(false);
const auto cleanup = [&backend, cleanup_done]() {
if (*cleanup_done) {
return;
}
*cleanup_done = true;
backend.stopDiscovery();
backend.stopReceive();
};
QObject::connect(&app, &QCoreApplication::aboutToQuit, &app, cleanup);
if (pipe(g_signal_pipe) != 0) {
return;
}
fcntl(g_signal_pipe[0], F_SETFL,
fcntl(g_signal_pipe[0], F_GETFL, 0) | O_NONBLOCK);
fcntl(g_signal_pipe[1], F_SETFL,
fcntl(g_signal_pipe[1], F_GETFL, 0) | O_NONBLOCK);
auto* notifier = new QSocketNotifier(g_signal_pipe[0],
QSocketNotifier::Read, &app);
QObject::connect(notifier, &QSocketNotifier::activated, &app,
[&app, notifier, cleanup](int socket) {
notifier->setEnabled(false);
char buffer[32];
while (read(socket, buffer, sizeof(buffer)) > 0) {
}
cleanup();
app.quit();
});
struct sigaction action {};
action.sa_handler = HandleTerminationSignal;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
}
} // namespace
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
Backend backend;
InstallTerminationCleanup(app, backend);
const int fontId = QFontDatabase::addApplicationFont(":/googlesans_var.ttf");
if (fontId != -1) {
@@ -83,6 +143,8 @@ int main(int argc, char* argv[]) {
}
if (IsHotReloadEnabled()) {
engine.rootContext()->setContextProperty("backend", &backend);
const QDir qml_source_dir = QmlSourceDir();
const QUrl source_url =
QUrl::fromLocalFile(qml_source_dir.absoluteFilePath("main.qml"));
@@ -132,10 +194,7 @@ int main(int argc, char* argv[]) {
return app.exec();
}
// 1. Create the backend instance in C++
Backend backend;
// 2. Inject it into the QML root context
engine.rootContext()->setContextProperty("backend", &backend);
// Loaded via the qrc scheme since it's compiled into the binary
const QUrl url(QStringLiteral("qrc:/main.qml"));
+1
View File
@@ -0,0 +1 @@
singleton EventBus 1.0 EventBus.qml
+3
View File
@@ -1,10 +1,13 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>main.qml</file>
<file>qmldir</file>
<file>AppContent.qml</file>
<file>EventBus.qml</file>
<file>Sidebar.qml</file>
<file>Drop.qml</file>
<file>ShareTarget.qml</file>
<file>SearchingDevices.qml</file>
<file>IncomingShare.qml</file>
<file>Targets.qml</file>
<file>googlesans_var.ttf</file>