Add relay view

Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
kernelkind 2024-04-17 13:36:25 -04:00 committed by William Casarin
parent 805e18261c
commit 349e3baa99
7 changed files with 227 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -17,6 +17,8 @@ mod key_parsing;
pub mod login_manager; pub mod login_manager;
mod notecache; mod notecache;
mod profile; mod profile;
pub mod relay_pool_manager;
pub mod relay_view;
mod result; mod result;
mod time; mod time;
mod timecache; mod timecache;

54
src/relay_pool_manager.rs Normal file
View File

@ -0,0 +1,54 @@
use enostr::RelayPool;
pub use enostr::RelayStatus;
/// The interface to a RelayPool for UI components.
/// Represents all user-facing operations that can be performed for a user's relays
pub struct RelayPoolManager<'a> {
pub pool: &'a mut RelayPool,
}
pub struct RelayInfo<'a> {
pub relay_url: &'a str,
pub status: &'a RelayStatus,
}
impl<'a> RelayPoolManager<'a> {
pub fn new(pool: &'a mut RelayPool) -> Self {
RelayPoolManager { pool }
}
pub fn get_relay_infos(&self) -> Vec<RelayInfo> {
self.pool
.relays
.iter()
.map(|relay| RelayInfo {
relay_url: &relay.relay.url,
status: &relay.relay.status,
})
.collect()
}
/// index of the Vec<RelayInfo> from get_relay_infos
pub fn remove_relay(&mut self, index: usize) {
if index < self.pool.relays.len() {
self.pool.relays.remove(index);
}
}
/// removes all specified relay indicies shown in get_relay_infos
pub fn remove_relays(&mut self, mut indices: Vec<usize>) {
indices.sort_unstable_by(|a, b| b.cmp(a));
indices.iter().for_each(|index| self.remove_relay(*index));
}
pub fn add_relay(&mut self, ctx: &egui::Context, relay_url: String) {
let _ = self.pool.add_url(relay_url, create_wakeup(ctx));
}
}
fn create_wakeup(ctx: &egui::Context) -> impl Fn() + Send + Sync + Clone + 'static {
let ctx = ctx.clone();
move || {
ctx.request_repaint();
}
}

171
src/relay_view.rs Normal file
View File

@ -0,0 +1,171 @@
use crate::relay_pool_manager::{RelayPoolManager, RelayStatus};
use egui::{Align, Button, Frame, Layout, Margin, Rgba, RichText, Rounding, Ui, Vec2};
use crate::app_style::NotedeckTextStyle;
pub struct RelayView<'a> {
ctx: &'a egui::Context,
manager: RelayPoolManager<'a>,
}
impl<'a> RelayView<'a> {
pub fn new(ctx: &'a egui::Context, manager: RelayPoolManager<'a>) -> Self {
RelayView { ctx, manager }
}
pub fn panel(&'a mut self) {
let mut indices_to_remove: Option<Vec<usize>> = None;
egui::CentralPanel::default().show(self.ctx, |ui| {
ui.add_space(24.0);
ui.horizontal(|ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
ui.label(
RichText::new("Relays")
.text_style(NotedeckTextStyle::Heading2.text_style()),
);
});
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if ui.add(add_relay_button()).clicked() {
// TODO: navigate to 'add relay view'
};
});
});
ui.add_space(8.0);
egui::ScrollArea::vertical()
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
.auto_shrink([false; 2])
.show(ui, |ui| {
indices_to_remove = self.show_relays(ui);
});
});
if let Some(indices) = indices_to_remove {
self.manager.remove_relays(indices);
}
}
/// Show the current relays, and returns the indices of relays the user requested to delete
fn show_relays(&'a self, ui: &mut Ui) -> Option<Vec<usize>> {
let mut indices_to_remove: Option<Vec<usize>> = None;
for (index, relay_info) in self.manager.get_relay_infos().iter().enumerate() {
ui.add_space(8.0);
ui.vertical_centered_justified(|ui| {
relay_frame(ui).show(ui, |ui| {
ui.horizontal(|ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
Frame::none()
// This frame is needed to add margin because the label will be added to the outer frame first and centered vertically before the connection status is added so the vertical centering isn't accurate.
// TODO: remove this hack and actually center the url & status at the same time
.inner_margin(Margin::symmetric(0.0, 4.0))
.show(ui, |ui| {
egui::ScrollArea::horizontal()
.id_source(index)
.max_width(
ui.max_rect().width()
- get_right_side_width(relay_info.status),
) // TODO: refactor to dynamically check the size of the 'right to left' portion and set the max width to be the screen width minus padding minus 'right to left' width
.show(ui, |ui| {
ui.label(
RichText::new(relay_info.relay_url)
.text_style(
NotedeckTextStyle::Monospace.text_style(),
)
.color(
ui.style()
.visuals
.noninteractive()
.fg_stroke
.color,
),
);
});
});
});
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if ui.add(delete_button(ui.visuals().dark_mode)).clicked() {
indices_to_remove.get_or_insert_with(Vec::new).push(index);
};
show_connection_status(ui, relay_info.status);
});
});
});
});
}
indices_to_remove
}
}
fn get_right_side_width(status: &RelayStatus) -> f32 {
match status {
RelayStatus::Connected => 150.0,
RelayStatus::Connecting => 160.0,
RelayStatus::Disconnected => 175.0,
}
}
fn add_relay_button() -> egui::Button<'static> {
Button::new("+ Add relay").min_size(Vec2::new(0.0, 32.0))
}
fn delete_button(dark_mode: bool) -> egui::Button<'static> {
let img_data = if dark_mode {
egui::include_image!("../assets/icons/delete_icon_4x.png")
} else {
// TODO: use light delete icon
egui::include_image!("../assets/icons/delete_icon_4x.png")
};
egui::Button::image(egui::Image::new(img_data).max_width(10.0)).frame(false)
}
fn relay_frame(ui: &mut Ui) -> Frame {
Frame::none()
.inner_margin(Margin::same(8.0))
.rounding(ui.style().noninteractive().rounding)
.stroke(ui.style().visuals.noninteractive().bg_stroke)
}
fn show_connection_status(ui: &mut Ui, status: &RelayStatus) {
let fg_color = match status {
RelayStatus::Connected => ui.visuals().selection.bg_fill,
RelayStatus::Connecting => ui.visuals().warn_fg_color,
RelayStatus::Disconnected => ui.visuals().error_fg_color,
};
let bg_color = egui::lerp(Rgba::from(fg_color)..=Rgba::BLACK, 0.8).into();
let label_text = match status {
RelayStatus::Connected => "Connected",
RelayStatus::Connecting => "Connecting...",
RelayStatus::Disconnected => "Not Connected",
};
let frame = Frame::none()
.rounding(Rounding::same(100.0))
.fill(bg_color)
.inner_margin(Margin::symmetric(12.0, 4.0));
frame.show(ui, |ui| {
ui.label(RichText::new(label_text).color(fg_color));
ui.add(get_connection_icon(status));
});
}
fn get_connection_icon(status: &RelayStatus) -> egui::Image<'static> {
let img_data = match status {
RelayStatus::Connected => egui::include_image!("../assets/icons/connected_icon_4x.png"),
RelayStatus::Connecting => egui::include_image!("../assets/icons/connecting_icon_4x.png"),
RelayStatus::Disconnected => {
egui::include_image!("../assets/icons/disconnected_icon_4x.png")
}
};
egui::Image::new(img_data)
}