mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
added IPC cilent for tui
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_cc//cc:cc_test.bzl", "cc_test")
|
||||
|
||||
load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands")
|
||||
|
||||
@@ -51,6 +52,31 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ipc_client",
|
||||
srcs = [
|
||||
"ipc_client.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ipc_client.h",
|
||||
],
|
||||
deps = [
|
||||
"@nlohmann_json//:json",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ipc_client_test",
|
||||
srcs = [
|
||||
"ipc_client_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":ipc_client",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@nlohmann_json//:json",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "file_picker",
|
||||
srcs = [
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "sharing/linux/tui/ui/home_screen.h"
|
||||
|
||||
@@ -33,6 +35,12 @@ int TuiApp::Run() {
|
||||
selected_file_ = path;
|
||||
current_page_ = Page::Sharing;
|
||||
},
|
||||
.incoming_share_device_name = incoming_share_device_name_,
|
||||
.incoming_share_device_type = incoming_share_device_type_,
|
||||
.on_incoming_share_accept =
|
||||
[this]() { current_page_ = Page::FilePicker; },
|
||||
.on_incoming_share_decline =
|
||||
[this]() { current_page_ = Page::FilePicker; },
|
||||
});
|
||||
|
||||
auto app =
|
||||
@@ -48,10 +56,16 @@ bool TuiApp::HandleEvent(Event event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event == Event::Backspace && current_page_ == Page::Sharing) {
|
||||
current_page_ = Page::FilePicker;
|
||||
selected_file_.clear();
|
||||
return true;
|
||||
if (event == Event::Backspace) {
|
||||
if (current_page_ == Page::Sharing) {
|
||||
current_page_ = Page::FilePicker;
|
||||
selected_file_.clear();
|
||||
return true;
|
||||
}
|
||||
if (current_page_ == Page::IncomingShare) {
|
||||
current_page_ = Page::FilePicker;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ftxui/component/screen_interactive.hpp"
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
#include "sharing/linux/tui/file_picker.h"
|
||||
#include "sharing/linux/tui/page.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
using namespace ftxui;
|
||||
|
||||
@@ -23,6 +24,8 @@ class TuiApp {
|
||||
Page current_page_ = Page::FilePicker;
|
||||
std::string hostname_;
|
||||
std::string selected_file_;
|
||||
std::string incoming_share_device_name_ = "Lasan's A55";
|
||||
ShareTargetType incoming_share_device_type_ = ShareTargetType::kPhone;
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
|
||||
@@ -35,6 +35,22 @@ cc_library(
|
||||
":icons",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "incoming_share_card",
|
||||
srcs = [
|
||||
"incoming_share_card.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"incoming_share_card.h",
|
||||
],
|
||||
deps = [
|
||||
":share_target",
|
||||
"//sharing/linux/tui:palette",
|
||||
"@ftxui//:ftxui",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "file_picker_card",
|
||||
srcs = [
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "sharing/linux/tui/components/incoming_share_card.h"
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "ftxui/dom/elements.hpp"
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
#include "sharing/linux/tui/palette.h"
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
using namespace ftxui;
|
||||
|
||||
Component IncomingShareCard(IncomingShareCardOptions options) {
|
||||
auto accept_button = Button("Accept", [options] {
|
||||
if (options.on_accept) {
|
||||
options.on_accept();
|
||||
}
|
||||
});
|
||||
auto decline_button = Button("Decline", [options] {
|
||||
if (options.on_decline) {
|
||||
options.on_decline();
|
||||
}
|
||||
});
|
||||
auto controls = Container::Horizontal({accept_button, decline_button});
|
||||
|
||||
return Renderer(controls, [accept_button, decline_button, options] {
|
||||
return vbox({
|
||||
window(text(" Incoming share ") | center,
|
||||
vbox({
|
||||
paragraph("Do you want to accept this share?") |
|
||||
bold | center,
|
||||
separator(),
|
||||
ShareTarget(options.device_name, options.device_type),
|
||||
separator(),
|
||||
hbox({
|
||||
accept_button->Render() | flex,
|
||||
separator(),
|
||||
decline_button->Render() | flex,
|
||||
}),
|
||||
})),
|
||||
}) |
|
||||
borderStyled(Palette::border) | bgcolor(Palette::surface) |
|
||||
size(WIDTH, GREATER_THAN, 40);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
using namespace ftxui;
|
||||
|
||||
struct IncomingShareCardOptions {
|
||||
std::string device_name;
|
||||
ShareTargetType device_type = ShareTargetType::kUnknown;
|
||||
std::function<void()> on_accept;
|
||||
std::function<void()> on_decline;
|
||||
};
|
||||
|
||||
Component IncomingShareCard(IncomingShareCardOptions options);
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
@@ -25,8 +25,8 @@ Element ShareTarget(std::string device_name, ShareTargetType device_type) {
|
||||
{icon, separatorLight() | dim,
|
||||
paragraph(device_name) | color(Palette::secondary) | bold | center
|
||||
|
||||
}) |
|
||||
size(WIDTH, EQUAL, 14) | border;
|
||||
}) |bgcolor(Palette::surface)|
|
||||
size(WIDTH, EQUAL, 14) | borderStyled(Palette::border);
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "sharing/linux/tui/ipc_client.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
|
||||
IpcClient::IpcClient(std::string socket_path)
|
||||
: socket_path_(std::move(socket_path)) {}
|
||||
|
||||
IpcClient::~IpcClient() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool IpcClient::Start(EventHandler event_handler) {
|
||||
Stop();
|
||||
event_handler_ = std::move(event_handler);
|
||||
|
||||
socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (socket_fd_ < 0) {
|
||||
Emit({{"event", "ipc_disconnected"},
|
||||
{"message", "failed to create socket"}});
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, socket_path_.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(socket_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) <
|
||||
0) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
Emit({{"event", "ipc_disconnected"},
|
||||
{"message", "failed to connect to daemon"}});
|
||||
return false;
|
||||
}
|
||||
|
||||
running_.store(true);
|
||||
connected_.store(true);
|
||||
read_thread_ = std::thread([this] { ReadLoop(); });
|
||||
Emit({{"event", "ipc_connected"}});
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpcClient::Stop() {
|
||||
running_.store(false);
|
||||
connected_.store(false);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(write_mutex_);
|
||||
if (socket_fd_ >= 0) {
|
||||
shutdown(socket_fd_, SHUT_RDWR);
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (read_thread_.joinable()) {
|
||||
read_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool IpcClient::Send(const nlohmann::json& message) {
|
||||
return SendLine(message.dump());
|
||||
}
|
||||
|
||||
void IpcClient::ReadLoop() {
|
||||
std::string buffer;
|
||||
char chunk[1024]{};
|
||||
|
||||
while (running_.load()) {
|
||||
ssize_t received = recv(socket_fd_, chunk, sizeof(chunk), 0);
|
||||
if (received > 0) {
|
||||
buffer.append(chunk, static_cast<size_t>(received));
|
||||
size_t newline = std::string::npos;
|
||||
while ((newline = buffer.find('\n')) != std::string::npos) {
|
||||
std::string line = buffer.substr(0, newline);
|
||||
buffer.erase(0, newline + 1);
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Emit(nlohmann::json::parse(line));
|
||||
} catch (const nlohmann::json::exception& error) {
|
||||
Emit({{"event", "ipc_error"},
|
||||
{"message",
|
||||
std::string("malformed daemon JSON: ") + error.what()}});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (received < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
connected_.store(false);
|
||||
if (running_.load()) {
|
||||
Emit({{"event", "ipc_disconnected"}, {"message", "daemon disconnected"}});
|
||||
}
|
||||
}
|
||||
|
||||
void IpcClient::Emit(nlohmann::json event) {
|
||||
if (event_handler_) {
|
||||
event_handler_(event);
|
||||
}
|
||||
}
|
||||
|
||||
bool IpcClient::SendLine(std::string_view line) {
|
||||
std::lock_guard<std::mutex> lock(write_mutex_);
|
||||
if (socket_fd_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string data(line);
|
||||
if (data.empty() || data.back() != '\n') {
|
||||
data.push_back('\n');
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
while (total_sent < data.size()) {
|
||||
ssize_t sent = send(socket_fd_, data.data() + total_sent,
|
||||
data.size() - total_sent, MSG_NOSIGNAL);
|
||||
if (sent > 0) {
|
||||
total_sent += static_cast<size_t>(sent);
|
||||
continue;
|
||||
}
|
||||
if (sent < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
|
||||
class IpcClient {
|
||||
public:
|
||||
using EventHandler = std::function<void(const nlohmann::json& event)>;
|
||||
|
||||
explicit IpcClient(std::string socket_path = "/tmp/nearby_sharing_sock");
|
||||
~IpcClient();
|
||||
|
||||
IpcClient(const IpcClient&) = delete;
|
||||
IpcClient& operator=(const IpcClient&) = delete;
|
||||
|
||||
bool Start(EventHandler event_handler);
|
||||
void Stop();
|
||||
bool Send(const nlohmann::json& message);
|
||||
bool connected() const { return connected_.load(); }
|
||||
|
||||
private:
|
||||
void ReadLoop();
|
||||
void Emit(nlohmann::json event);
|
||||
bool SendLine(std::string_view line);
|
||||
|
||||
std::string socket_path_;
|
||||
EventHandler event_handler_;
|
||||
std::thread read_thread_;
|
||||
mutable std::mutex write_mutex_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> connected_{false};
|
||||
int socket_fd_ = -1;
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
@@ -0,0 +1,135 @@
|
||||
#include "sharing/linux/tui/ipc_client.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
namespace {
|
||||
|
||||
std::string TestSocketPath() {
|
||||
return "/tmp/nearby_tui_ipc_client_test_" + std::to_string(getpid());
|
||||
}
|
||||
|
||||
int CreateServerSocket(const std::string& socket_path) {
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
|
||||
unlink(socket_path.c_str());
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
EXPECT_EQ(bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0);
|
||||
EXPECT_EQ(listen(fd, 1), 0);
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::string ReadLine(int fd) {
|
||||
std::string line;
|
||||
char byte = '\0';
|
||||
while (recv(fd, &byte, 1, 0) == 1) {
|
||||
if (byte == '\n') {
|
||||
return line;
|
||||
}
|
||||
line.push_back(byte);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
void SendAll(int fd, const std::string& data) {
|
||||
size_t sent_total = 0;
|
||||
while (sent_total < data.size()) {
|
||||
ssize_t sent = send(fd, data.data() + sent_total, data.size() - sent_total,
|
||||
MSG_NOSIGNAL);
|
||||
ASSERT_GT(sent, 0);
|
||||
sent_total += static_cast<size_t>(sent);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(IpcClientTest, SendsJsonCommandWithNewline) {
|
||||
std::string socket_path = TestSocketPath();
|
||||
int server_fd = CreateServerSocket(socket_path);
|
||||
std::string received;
|
||||
|
||||
std::thread server_thread([&] {
|
||||
int client_fd = accept(server_fd, nullptr, nullptr);
|
||||
ASSERT_GE(client_fd, 0);
|
||||
received = ReadLine(client_fd);
|
||||
close(client_fd);
|
||||
});
|
||||
|
||||
IpcClient client(socket_path);
|
||||
ASSERT_TRUE(client.Start([](const nlohmann::json&) {}));
|
||||
EXPECT_TRUE(client.Send({{"command", "start_receive"}}));
|
||||
client.Stop();
|
||||
|
||||
server_thread.join();
|
||||
close(server_fd);
|
||||
unlink(socket_path.c_str());
|
||||
|
||||
nlohmann::json command = nlohmann::json::parse(received);
|
||||
EXPECT_EQ(command["command"], "start_receive");
|
||||
}
|
||||
|
||||
TEST(IpcClientTest, ParsesSplitJsonEvents) {
|
||||
std::string socket_path = TestSocketPath();
|
||||
int server_fd = CreateServerSocket(socket_path);
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::vector<nlohmann::json> events;
|
||||
|
||||
std::thread server_thread([&] {
|
||||
int client_fd = accept(server_fd, nullptr, nullptr);
|
||||
ASSERT_GE(client_fd, 0);
|
||||
SendAll(client_fd,
|
||||
R"({"event":"target_discovered","share_target":{"id":1}})"
|
||||
"\n"
|
||||
R"({"event":"incoming)");
|
||||
SendAll(client_fd, R"(_transfer","share_target":{"id":2},"transfer":{}})"
|
||||
"\n");
|
||||
close(client_fd);
|
||||
});
|
||||
|
||||
IpcClient client(socket_path);
|
||||
ASSERT_TRUE(client.Start([&](const nlohmann::json& event) {
|
||||
if (event.value("event", std::string()).rfind("ipc_", 0) == 0) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
events.push_back(event);
|
||||
cv.notify_one();
|
||||
}));
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cv.wait_for(lock, std::chrono::seconds(2),
|
||||
[&] { return events.size() >= 2; });
|
||||
}
|
||||
client.Stop();
|
||||
|
||||
server_thread.join();
|
||||
close(server_fd);
|
||||
unlink(socket_path.c_str());
|
||||
|
||||
ASSERT_GE(events.size(), 2u);
|
||||
EXPECT_EQ(events[0]["event"], "target_discovered");
|
||||
EXPECT_EQ(events[1]["event"], "incoming_transfer");
|
||||
}
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
@@ -5,6 +5,7 @@ namespace nearby::sharing::linux_tui {
|
||||
enum class Page {
|
||||
FilePicker,
|
||||
Sharing,
|
||||
IncomingShare,
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
|
||||
+31
-10
@@ -6,20 +6,41 @@ namespace nearby::sharing::linux_tui {
|
||||
using namespace ftxui;
|
||||
|
||||
struct Palette {
|
||||
inline static const Color primary =
|
||||
Color::RGB(0xF1, 0xF9, 0xF6); // Off-white mint tint
|
||||
// === Core Typography & Brand ===
|
||||
// Primary light text for high-contrast readability against dark backgrounds
|
||||
inline static const Color primary = Color::RGB(0xF1, 0xF9, 0xF6); // Off-white mint tint
|
||||
|
||||
inline static const Color secondary =
|
||||
Color::RGB(0x8A, 0xAF, 0xA4); // Muted sage gray
|
||||
// Secondary text, inactive states, or subtle accents
|
||||
inline static const Color secondary = Color::RGB(0x8A, 0xAF, 0xA4); // Muted sage gray
|
||||
|
||||
inline static const Color accent =
|
||||
Color::RGB(0xA3, 0xD9, 0xC9); // Original Mint Leaf
|
||||
// Main brand accent, focused element backgrounds, or prominent icons (Unchanged)
|
||||
inline static const Color accent = Color::RGB(0xA3, 0xD9, 0xC9); // Original Mint Leaf
|
||||
|
||||
inline static const Color active =
|
||||
Color::RGB(0x23, 0x3D, 0x34); // Deep forest green tint
|
||||
// === Surface & Structure ===
|
||||
// Active menu item highlights, selection cards, or focused component backgrounds
|
||||
inline static const Color active = Color::RGB(0x23, 0x3D, 0x34); // Deep forest green tint
|
||||
|
||||
inline static const Color base =
|
||||
Color::RGB(0x12, 0x1D, 0x1A); // Near-black deep mint/charcoal
|
||||
// Base application window background
|
||||
inline static const Color base = Color::RGB(0x12, 0x1D, 0x1A); // Near-black deep mint/charcoal
|
||||
|
||||
// Explicit component surface background (e.g., sidebars, modals, or inactive cards)
|
||||
inline static const Color surface = Color::RGB(0x19, 0x2A, 0x25); // Mid-tone dark green
|
||||
|
||||
// Standard UI borders, dividers, and subtle grid lines
|
||||
inline static const Color border = Color::RGB(0x32, 0x52, 0x47); // Defined slate green
|
||||
|
||||
// Extremely muted text, placeholders, or disabled options
|
||||
inline static const Color disabled = Color::RGB(0x56, 0x73, 0x6B); // Ghostly sage green
|
||||
|
||||
// === Functional / Status Elements ===
|
||||
// Critical errors, destructive actions, or alerts (toned down for dark mode)
|
||||
inline static const Color error = Color::RGB(0xE0, 0x7A, 0x7A); // Soft desaturated coral/red
|
||||
|
||||
// Warnings, pending indicators, or high-priority notifications
|
||||
inline static const Color warning = Color::RGB(0xE6, 0xC2, 0x80); // Soft amber gold
|
||||
|
||||
// Success messages, online badges, or completed operations
|
||||
inline static const Color success = Color::RGB(0x81, 0xC7, 0x9D); // Vibrant spring mint
|
||||
};
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
|
||||
@@ -56,6 +56,7 @@ cc_library(
|
||||
":file_selected_screen",
|
||||
":home_header",
|
||||
":sidebar",
|
||||
"//sharing/linux/tui/components:incoming_share_card",
|
||||
"//sharing/linux/tui:file_picker",
|
||||
"//sharing/linux/tui:page",
|
||||
"//sharing/linux/tui:palette",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "sharing/linux/tui/ui/file_selected_screen.h"
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
#include "sharing/linux/tui/palette.h"
|
||||
|
||||
namespace nearby::sharing::linux_tui {
|
||||
@@ -10,20 +10,28 @@ Element FileSelectedScreen(const std::string& selected_file) {
|
||||
FlexboxConfig config;
|
||||
config.direction = FlexboxConfig::Direction::Row;
|
||||
config.wrap = FlexboxConfig::Wrap::Wrap;
|
||||
config.gap_x = 2;
|
||||
config.gap_x = 1;
|
||||
config.gap_y = 1;
|
||||
return vbox({
|
||||
flexbox({
|
||||
ShareTarget("Lasan's A55", ShareTargetType::kPhone),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("Lasan's S9+", ShareTargetType::kTablet),
|
||||
ShareTarget("Lasan's S9+", ShareTargetType::kTablet),
|
||||
hbox(
|
||||
{filler(),
|
||||
flexbox(
|
||||
{
|
||||
ShareTarget("Lasan's A55", ShareTargetType::kPhone),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("lasan-laptop", ShareTargetType::kLaptop),
|
||||
ShareTarget("Lasan's S9+", ShareTargetType::kTablet),
|
||||
ShareTarget("Lasan's S9+", ShareTargetType::kTablet),
|
||||
|
||||
}, config) | flex
|
||||
},
|
||||
config) |
|
||||
flex
|
||||
|
||||
}),
|
||||
filler(),
|
||||
}) |
|
||||
borderStyled(Palette::primary) | flex;
|
||||
flex;
|
||||
}
|
||||
|
||||
} // namespace nearby::sharing::linux_tui
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ftxui/dom/elements.hpp"
|
||||
#include "sharing/linux/tui/components/file_picker_card.h"
|
||||
#include "sharing/linux/tui/components/incoming_share_card.h"
|
||||
#include "sharing/linux/tui/palette.h"
|
||||
#include "sharing/linux/tui/ui/file_selected_screen.h"
|
||||
#include "sharing/linux/tui/ui/home_header.h"
|
||||
@@ -14,17 +15,32 @@ Component HomeScreen(HomeScreenOptions options) {
|
||||
auto file_picker_card =
|
||||
FilePickerCard({.file_picker = options.file_picker,
|
||||
.on_file_selected = options.on_file_selected});
|
||||
auto incoming_share_card =
|
||||
IncomingShareCard({.device_name = options.incoming_share_device_name,
|
||||
.device_type = options.incoming_share_device_type,
|
||||
.on_accept = options.on_incoming_share_accept,
|
||||
.on_decline = options.on_incoming_share_decline});
|
||||
auto content = Container::Vertical({file_picker_card, incoming_share_card});
|
||||
|
||||
return Renderer(file_picker_card, [file_picker_card, options] {
|
||||
return Renderer(content, [file_picker_card, incoming_share_card, options] {
|
||||
const std::string selected_file =
|
||||
options.selected_file == nullptr ? "" : *options.selected_file;
|
||||
const Page current_page = options.current_page == nullptr
|
||||
? Page::FilePicker
|
||||
: *options.current_page;
|
||||
|
||||
auto main_panel = current_page == Page::FilePicker
|
||||
? file_picker_card->Render()
|
||||
: FileSelectedScreen(selected_file);
|
||||
Element main_panel;
|
||||
switch (current_page) {
|
||||
case Page::FilePicker:
|
||||
main_panel = file_picker_card->Render();
|
||||
break;
|
||||
case Page::IncomingShare:
|
||||
main_panel = incoming_share_card->Render();
|
||||
break;
|
||||
case Page::Sharing:
|
||||
main_panel = FileSelectedScreen(selected_file);
|
||||
break;
|
||||
}
|
||||
|
||||
return vbox({
|
||||
HomeHeader() | color(Palette::accent),
|
||||
@@ -32,6 +48,7 @@ Component HomeScreen(HomeScreenOptions options) {
|
||||
hbox({
|
||||
Sidebar({.hostname = options.hostname,
|
||||
.selected_file = selected_file}),
|
||||
separator() | color(Palette::border),
|
||||
main_panel | flex,
|
||||
}) | flex,
|
||||
}) |
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "ftxui/component/component.hpp"
|
||||
#include "sharing/linux/tui/components/share_target.h"
|
||||
#include "sharing/linux/tui/file_picker.h"
|
||||
#include "sharing/linux/tui/page.h"
|
||||
|
||||
@@ -16,6 +17,10 @@ struct HomeScreenOptions {
|
||||
const std::string* selected_file = nullptr;
|
||||
FilePicker* file_picker = nullptr;
|
||||
std::function<void(std::string)> on_file_selected;
|
||||
std::string incoming_share_device_name;
|
||||
ShareTargetType incoming_share_device_type = ShareTargetType::kUnknown;
|
||||
std::function<void()> on_incoming_share_accept;
|
||||
std::function<void()> on_incoming_share_decline;
|
||||
};
|
||||
|
||||
Component HomeScreen(HomeScreenOptions options);
|
||||
|
||||
Reference in New Issue
Block a user