refactor: add server list to sidebar

This commit is contained in:
MeexReay 2025-09-03 20:14:33 +03:00
parent 02c4862178
commit 431736e967
4 changed files with 369 additions and 199 deletions

View File

@ -3,6 +3,8 @@ use serde_default::DefaultFromSerde;
use serde_yml; use serde_yml;
use std::{error::Error, fs, path::PathBuf}; use std::{error::Error, fs, path::PathBuf};
use super::SERVER_LIST;
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
fn default_true() -> bool { fn default_true() -> bool {
@ -30,6 +32,10 @@ pub fn default_message_format() -> String {
MESSAGE_FORMAT.to_string() MESSAGE_FORMAT.to_string()
} }
pub fn default_servers() -> Vec<String> {
SERVER_LIST.to_vec()
}
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)] #[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
pub struct Config { pub struct Config {
#[serde(default = "default_host")] #[serde(default = "default_host")]
@ -70,6 +76,8 @@ pub struct Config {
pub debug_logs: bool, pub debug_logs: bool,
#[serde(default)] #[serde(default)]
pub avatar: Option<String>, pub avatar: Option<String>,
#[serde(default = "default_servers")]
pub servers: Vec<String>,
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -183,6 +191,7 @@ pub struct Args {
pub avatar: Option<String>, pub avatar: Option<String>,
#[arg(long)] #[arg(long)]
pub debug_logs: bool, pub debug_logs: bool,
// TODO: add servers
} }
impl Args { impl Args {

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -12,40 +12,32 @@ use std::time::{Duration, SystemTime};
use clap::crate_version; use clap::crate_version;
use libadwaita::gdk::Texture;
use libadwaita::gtk::gdk_pixbuf::InterpType;
use libadwaita::gtk::{Button, Label};
use libadwaita::{
self as adw, Avatar, Breakpoint, BreakpointCondition, OverlaySplitView
};
use adw::gdk::Display; use adw::gdk::Display;
use adw::gio::{ActionEntry, ApplicationFlags, Menu}; use adw::gio::{ActionEntry, ApplicationFlags, Menu};
use adw::glib::clone; use adw::glib::clone;
use adw::glib::{ use adw::glib::{self, source::timeout_add_local_once, timeout_add_once};
self, source::timeout_add_local_once, timeout_add_once,
};
use adw::prelude::*; use adw::prelude::*;
use adw::{Application, ApplicationWindow}; use adw::{Application, ApplicationWindow};
use libadwaita::gdk::Texture;
use libadwaita::gtk::gdk_pixbuf::InterpType;
use libadwaita::gtk::{Button, Entry, Label, Picture};
use libadwaita::{self as adw, Avatar, Breakpoint, BreakpointCondition, Dialog, OverlaySplitView};
use adw::gtk; use adw::gtk;
use gtk::gdk_pixbuf::{Pixbuf, PixbufLoader}; use gtk::gdk_pixbuf::{Pixbuf, PixbufLoader};
use gtk::{ use gtk::{Box as GtkBox, CssProvider, Orientation, ScrolledWindow, Settings};
Box as GtkBox,
CssProvider,
Orientation, ScrolledWindow, Settings,
};
use crate::chat::grab_avatar; use crate::chat::grab_avatar;
use super::config::get_config_path;
use super::{ use super::{
config::{ config::{save_config, Config},
save_config, Config, ctx::Context,
}, print_message, recv_tick, sanitize_message,
ctx::Context, print_message, recv_tick, sanitize_message,
}; };
mod preferences;
mod page; mod page;
mod preferences;
mod widgets; mod widgets;
use page::*; use page::*;
@ -53,7 +45,7 @@ use preferences::*;
pub fn try_save_config(path: PathBuf, config: &Config) { pub fn try_save_config(path: PathBuf, config: &Config) {
match save_config(path, config) { match save_config(path, config) {
Ok(_) => {}, Ok(_) => {}
Err(e) => { Err(e) => {
println!("save config error: {e}") println!("save config error: {e}")
} }
@ -71,7 +63,7 @@ struct UiModel {
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))] #[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc<RwLock<Vec<String>>>, notifications: Arc<RwLock<Vec<String>>>,
avatars: Arc<Mutex<HashMap<u64, Vec<Avatar>>>>, avatars: Arc<Mutex<HashMap<u64, Vec<Avatar>>>>,
latest_sign: Arc<AtomicU64> latest_sign: Arc<AtomicU64>,
} }
thread_local!( thread_local!(
@ -163,9 +155,175 @@ fn build_menu(ctx: Arc<Context>, app: &Application) -> Menu {
menu menu
} }
fn build_sidebar(_ctx: Arc<Context>, _app: &Application) -> GtkBox { fn build_sidebar_button(
let sidebar = GtkBox::new(Orientation::Vertical, 0); ctx: Arc<Context>,
sidebar.append(&Label::new(Some("hello worlding"))); split_view: &OverlaySplitView,
server: String,
servers_list: &GtkBox,
) -> GtkBox {
let hbox = GtkBox::new(Orientation::Horizontal, 5);
let button = Button::builder().label(&server).hexpand(true).build();
button.connect_clicked(clone!(
#[weak]
split_view,
#[weak]
ctx,
#[strong]
server,
move |_| {
let mut config = ctx.config.read().unwrap().clone();
config.host = server.clone();
ctx.set_config(&config);
try_save_config(get_config_path(), &config);
update_window_title(ctx.clone());
if split_view.is_collapsed() {
split_view.set_show_sidebar(false);
}
}
));
hbox.append(&button);
let delete_button = Button::from_icon_name("user-trash-symbolic");
delete_button.connect_clicked(clone!(
#[weak]
ctx,
#[weak]
hbox,
#[weak]
servers_list,
#[strong]
server,
move |_| {
servers_list.remove(&hbox);
let mut config = ctx.config.read().unwrap().clone();
let index = config.servers.iter().position(|x| *x == server).unwrap();
config.servers.remove(index);
ctx.set_config(&config);
try_save_config(get_config_path(), &config);
}
));
hbox.append(&delete_button);
hbox
}
fn build_sidebar(ctx: Arc<Context>, app: &Application, split_view: &OverlaySplitView) -> GtkBox {
let sidebar = GtkBox::new(Orientation::Vertical, 5);
sidebar.append(
&Picture::builder()
.paintable(&Texture::for_pixbuf(
&load_pixbuf(include_bytes!("images/servers.png")).unwrap(),
))
.build(),
);
let servers_list = GtkBox::new(Orientation::Vertical, 5);
for server in ctx.config(|o| o.servers.clone()) {
servers_list.append(&build_sidebar_button(
ctx.clone(),
&split_view,
server,
&servers_list,
));
}
sidebar.append(&servers_list);
let add_server = Button::builder()
.label("Add Server")
// .start_icon_name("list-add-symbolic")
.margin_top(10)
.build();
add_server.connect_clicked(clone!(
#[weak]
app,
#[weak]
servers_list,
#[weak]
ctx,
#[weak]
split_view,
move |_| {
let dialog = Dialog::new();
let vbox = GtkBox::new(Orientation::Vertical, 5);
vbox.set_margin_bottom(20);
vbox.set_margin_top(20);
vbox.set_margin_end(20);
vbox.set_margin_start(20);
vbox.append(&Label::builder().label("Add server").build());
let entry = Entry::builder().placeholder_text("Server host").build();
vbox.append(&entry);
let hbox = GtkBox::new(Orientation::Horizontal, 5);
let confirm = Button::builder().label("Confirm").build();
confirm.connect_clicked(clone!(
#[weak]
dialog,
#[weak]
servers_list,
#[weak]
ctx,
#[weak]
split_view,
#[weak]
entry,
move |_| {
let server: String = entry.text().into();
let mut config = ctx.config.read().unwrap().clone();
config.servers.push(server.clone());
ctx.set_config(&config);
try_save_config(get_config_path(), &config);
servers_list.append(&build_sidebar_button(
ctx.clone(),
&split_view,
server,
&servers_list,
));
dialog.close();
}
));
hbox.append(&confirm);
let cancel = Button::builder().label("Cancel").build();
cancel.connect_clicked(clone!(
#[weak]
dialog,
move |_| {
dialog.close();
}
));
hbox.append(&cancel);
vbox.append(&hbox);
dialog.set_child(Some(&vbox));
dialog.present(app.active_window().as_ref());
}
));
sidebar.append(&add_server);
sidebar sidebar
} }
@ -182,43 +340,43 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let is_dark_theme = true; let is_dark_theme = true;
let main_box = GtkBox::new(Orientation::Vertical, 0);
let title = format!( let main_box = GtkBox::new(Orientation::Vertical, 0);
"bRAC - Connected to {} as {}",
ctx.config(|o| o.host.clone()),
&ctx.name()
);
let (header, page, chat_box, chat_scrolled) = build_page(ctx.clone(), app); let (header, page, chat_box, chat_scrolled) = build_page(ctx.clone(), app);
let sidebar = build_sidebar(ctx.clone(), &app);
let split_view = OverlaySplitView::builder() let split_view = OverlaySplitView::builder()
.sidebar(&sidebar)
.content(&page) .content(&page)
.enable_hide_gesture(true) .enable_hide_gesture(true)
.enable_show_gesture(true) .enable_show_gesture(true)
.collapsed(true) .collapsed(true)
.build(); .build();
let sidebar = build_sidebar(ctx.clone(), &app, &split_view);
split_view.set_sidebar(Some(&sidebar));
main_box.append(&split_view); main_box.append(&split_view);
let toggle_button = Button::from_icon_name("go-previous-symbolic"); let toggle_button = Button::from_icon_name("go-previous-symbolic");
toggle_button.connect_clicked(clone!( toggle_button.connect_clicked(clone!(
#[weak] split_view, #[weak]
split_view,
move |_| { move |_| {
split_view.set_show_sidebar(!split_view.shows_sidebar()); split_view.set_show_sidebar(!split_view.shows_sidebar());
} }
)); ));
header.pack_start(&toggle_button); header.pack_start(&toggle_button);
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
.title(&title) .title(&format!(
"bRAC - Connected to {} as {}",
ctx.config(|o| o.host.clone()),
&ctx.name()
))
.default_width(500) .default_width(500)
.default_height(500) .default_height(500)
.resizable(true) .resizable(true)
@ -226,17 +384,15 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
.content(&main_box) .content(&main_box)
.build(); .build();
let breakpoint = Breakpoint::new( let breakpoint = Breakpoint::new(BreakpointCondition::new_length(
BreakpointCondition::new_length( libadwaita::BreakpointConditionLengthType::MinWidth,
libadwaita::BreakpointConditionLengthType::MinWidth, 700.0,
700.0, libadwaita::LengthUnit::Px,
libadwaita::LengthUnit::Px ));
)
);
breakpoint.add_setter(&split_view, "collapsed", Some(&false.into())); breakpoint.add_setter(&split_view, "collapsed", Some(&false.into()));
breakpoint.add_setter(&toggle_button, "visible", Some(&false.into())); breakpoint.add_setter(&toggle_button, "visible", Some(&false.into()));
window.add_breakpoint(breakpoint); window.add_breakpoint(breakpoint);
window.present(); window.present();
@ -252,7 +408,7 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))] #[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc::new(RwLock::new(Vec::<String>::new())), notifications: Arc::new(RwLock::new(Vec::<String>::new())),
avatars: Arc::new(Mutex::new(HashMap::new())), avatars: Arc::new(Mutex::new(HashMap::new())),
latest_sign: Arc::new(AtomicU64::new(0)) latest_sign: Arc::new(AtomicU64::new(0)),
} }
} }
@ -329,22 +485,41 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
if ctx.config(|o| !o.new_ui_enabled) { if ctx.config(|o| !o.new_ui_enabled) {
return; return;
} }
thread::spawn(move || { thread::spawn(move || {
for message in messages.iter() { for message in messages.iter() {
let Some(avatar_url) = grab_avatar(message) else { continue }; let Some(avatar_url) = grab_avatar(message) else {
continue;
};
let avatar_id = get_avatar_id(&avatar_url); let avatar_id = get_avatar_id(&avatar_url);
let Some(avatar) = load_avatar(&avatar_url, ctx.config(|o| o.proxy.clone()), ctx.config(|o| o.max_avatar_size as usize)) else { println!("cant load avatar: {avatar_url} request error"); continue }; let Some(avatar) = load_avatar(
let Ok(pixbuf) = load_pixbuf(&avatar) else { println!("cant load avatar: {avatar_url} pixbuf error"); continue; }; &avatar_url,
let Some(pixbuf) = pixbuf.scale_simple(32, 32, InterpType::Bilinear) else { println!("cant load avatar: {avatar_url} scale image error"); continue }; ctx.config(|o| o.proxy.clone()),
ctx.config(|o| o.max_avatar_size as usize),
) else {
println!("cant load avatar: {avatar_url} request error");
continue;
};
let Ok(pixbuf) = load_pixbuf(&avatar) else {
println!("cant load avatar: {avatar_url} pixbuf error");
continue;
};
let Some(pixbuf) =
pixbuf.scale_simple(32, 32, InterpType::Bilinear)
else {
println!("cant load avatar: {avatar_url} scale image error");
continue;
};
let texture = Texture::for_pixbuf(&pixbuf); let texture = Texture::for_pixbuf(&pixbuf);
timeout_add_once(Duration::ZERO, { timeout_add_once(Duration::ZERO, {
move || { move || {
GLOBAL.with(|global| { GLOBAL.with(|global| {
if let Some(ui) = &*global.borrow() { if let Some(ui) = &*global.borrow() {
if let Some(pics) = ui.avatars.lock().unwrap().remove(&avatar_id) { if let Some(pics) =
ui.avatars.lock().unwrap().remove(&avatar_id)
{
for pic in pics { for pic in pics {
pic.set_custom_image(Some(&texture)); pic.set_custom_image(Some(&texture));
} }
@ -454,42 +629,42 @@ fn load_avatar(url: &str, proxy: Option<String>, response_limit: usize) -> Optio
} else { } else {
format!("socks5://{proxy}") format!("socks5://{proxy}")
}; };
reqwest::blocking::Client::builder() reqwest::blocking::Client::builder()
.proxy(reqwest::Proxy::all(&proxy).ok()?) .proxy(reqwest::Proxy::all(&proxy).ok()?)
.build().ok()? .build()
.ok()?
} else { } else {
reqwest::blocking::Client::new() reqwest::blocking::Client::new()
}; };
client.get(url).send().ok()
.and_then(|mut resp| {
let mut data = Vec::new();
let mut length = 0;
loop {
if length >= response_limit {
break;
}
let mut buf = vec![0; (response_limit - length).min(1024)];
let now_len = resp.read(&mut buf).ok()?;
if now_len == 0 {
break;
}
buf.truncate(now_len);
length += now_len;
data.append(&mut buf);
}
Some(data) client.get(url).send().ok().and_then(|mut resp| {
}) let mut data = Vec::new();
let mut length = 0;
loop {
if length >= response_limit {
break;
}
let mut buf = vec![0; (response_limit - length).min(1024)];
let now_len = resp.read(&mut buf).ok()?;
if now_len == 0 {
break;
}
buf.truncate(now_len);
length += now_len;
data.append(&mut buf);
}
Some(data)
})
} }
// creates sign that expires in 0-20 minutes // creates sign that expires in 0-20 minutes
fn get_message_sign(name: &str, date: &str) -> u64 { fn get_message_sign(name: &str, date: &str) -> u64 {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(name.as_bytes()); hasher.write(name.as_bytes());
hasher.write(date[..date.len()-2].as_bytes()); hasher.write(date[..date.len() - 2].as_bytes());
hasher.finish() hasher.finish()
} }
@ -512,9 +687,21 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
} }
if ctx.config(|o| o.new_ui_enabled) { if ctx.config(|o| o.new_ui_enabled) {
ui.chat_box.append(&get_new_message_box(ctx.clone(), ui, message, notify, formatting_enabled)); ui.chat_box.append(&get_new_message_box(
ctx.clone(),
ui,
message,
notify,
formatting_enabled,
));
} else { } else {
ui.chat_box.append(&get_message_box(ctx.clone(), ui, message, notify, formatting_enabled)); ui.chat_box.append(&get_message_box(
ctx.clone(),
ui,
message,
notify,
formatting_enabled,
));
}; };
timeout_add_local_once(Duration::from_millis(1000), move || { timeout_add_local_once(Duration::from_millis(1000), move || {

View File

@ -1,35 +1,29 @@
use std::sync::Arc; use std::sync::Arc;
use libadwaita::gtk::Adjustment;
use libadwaita::{
self as adw, ActionRow, ButtonRow, EntryRow, PreferencesDialog, PreferencesGroup, PreferencesPage, SpinRow, SwitchRow
};
use adw::gdk::Display; use adw::gdk::Display;
use adw::glib::clone; use adw::glib::clone;
use adw::glib::{ use adw::glib::{self};
self,
};
use adw::prelude::*; use adw::prelude::*;
use adw::Application; use adw::Application;
use libadwaita::gtk::Adjustment;
use libadwaita::{
self as adw, ActionRow, ButtonRow, EntryRow, PreferencesDialog, PreferencesGroup,
PreferencesPage, SpinRow, SwitchRow,
};
use adw::gtk; use adw::gtk;
use gtk::Button; use gtk::Button;
use crate::chat::{ use crate::chat::{
config::{ config::{get_config_path, Config},
get_config_path, Config,
},
ctx::Context, ctx::Context,
}; };
use super::{try_save_config, update_window_title}; use super::{try_save_config, update_window_title};
pub fn open_settings(ctx: Arc<Context>, app: &Application) { pub fn open_settings(ctx: Arc<Context>, app: &Application) {
let dialog = PreferencesDialog::builder().build(); let dialog = PreferencesDialog::builder().build();
let page = PreferencesPage::builder() let page = PreferencesPage::builder()
.title("General") .title("General")
.icon_name("avatar-default-symbolic") .icon_name("avatar-default-symbolic")
@ -40,7 +34,6 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
.description("Profile preferences") .description("Profile preferences")
.build(); .build();
// Name preference // Name preference
let name = EntryRow::builder() let name = EntryRow::builder()
@ -50,27 +43,22 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&name); group.add(&name);
// Avatar preference // Avatar preference
let avatar = EntryRow::builder() let avatar = EntryRow::builder()
.title("Avatar Link") .title("Avatar Link")
.text(ctx.config(|o| o.avatar.clone()).unwrap_or_default()) .text(ctx.config(|o| o.avatar.clone()).unwrap_or_default())
.build(); .build();
group.add(&avatar); group.add(&avatar);
page.add(&group); page.add(&group);
let group = PreferencesGroup::builder() let group = PreferencesGroup::builder()
.title("Server") .title("Server")
.description("Connection preferences") .description("Connection preferences")
.build(); .build();
// Host preference // Host preference
let host = EntryRow::builder() let host = EntryRow::builder()
@ -80,60 +68,61 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&host); group.add(&host);
// Messages limit preference // Messages limit preference
let messages_limit = SpinRow::builder() let messages_limit = SpinRow::builder()
.title("Messages limit") .title("Messages limit")
.adjustment(&Adjustment::builder() .adjustment(
.lower(1.0) &Adjustment::builder()
.upper(1048576.0) .lower(1.0)
.page_increment(10.0) .upper(1048576.0)
.step_increment(10.0) .page_increment(10.0)
.value(ctx.config(|o| o.max_messages) as f64) .step_increment(10.0)
.build()) .value(ctx.config(|o| o.max_messages) as f64)
.build(),
)
.build(); .build();
group.add(&messages_limit); group.add(&messages_limit);
// Update interval preference // Update interval preference
let update_interval = SpinRow::builder() let update_interval = SpinRow::builder()
.title("Update interval") .title("Update interval")
.subtitle("In milliseconds") .subtitle("In milliseconds")
.adjustment(&Adjustment::builder() .adjustment(
.lower(10.0) &Adjustment::builder()
.upper(1048576.0) .lower(10.0)
.page_increment(10.0) .upper(1048576.0)
.step_increment(10.0) .page_increment(10.0)
.value(ctx.config(|o| o.update_time) as f64) .step_increment(10.0)
.build()) .value(ctx.config(|o| o.update_time) as f64)
.build(),
)
.build(); .build();
group.add(&update_interval); group.add(&update_interval);
// Update interval OOF preference // Update interval OOF preference
let update_interval_oof = SpinRow::builder() let update_interval_oof = SpinRow::builder()
.title("Update interval when unfocused") .title("Update interval when unfocused")
.subtitle("In milliseconds") .subtitle("In milliseconds")
.adjustment(&Adjustment::builder() .adjustment(
.lower(10.0) &Adjustment::builder()
.upper(1048576.0) .lower(10.0)
.page_increment(10.0) .upper(1048576.0)
.step_increment(10.0) .page_increment(10.0)
.value(ctx.config(|o| o.oof_update_time) as f64) .step_increment(10.0)
.build()) .value(ctx.config(|o| o.oof_update_time) as f64)
.build(),
)
.build(); .build();
group.add(&update_interval_oof); group.add(&update_interval_oof);
page.add(&group); page.add(&group);
let group = PreferencesGroup::builder() let group = PreferencesGroup::builder()
.title("Config") .title("Config")
.description("Configuration tools") .description("Configuration tools")
@ -154,7 +143,8 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
config_path_copy.set_margin_top(10); config_path_copy.set_margin_top(10);
config_path_copy.set_margin_bottom(10); config_path_copy.set_margin_bottom(10);
config_path_copy.connect_clicked(clone!( config_path_copy.connect_clicked(clone!(
#[weak] clipboard, #[weak]
clipboard,
move |_| { move |_| {
if let Some(text) = get_config_path().to_str() { if let Some(text) = get_config_path().to_str() {
clipboard.set_text(text); clipboard.set_text(text);
@ -164,19 +154,20 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
config_path.add_suffix(&config_path_copy); config_path.add_suffix(&config_path_copy);
config_path.set_activatable(false); config_path.set_activatable(false);
group.add(&config_path); group.add(&config_path);
// Reset button // Reset button
let reset_button = ButtonRow::builder() let reset_button = ButtonRow::builder().title("Reset all").build();
.title("Reset all")
.build();
reset_button.connect_activated(clone!( reset_button.connect_activated(clone!(
#[weak] ctx, #[weak]
#[weak] app, ctx,
#[weak] dialog, #[weak]
app,
#[weak]
dialog,
move |_| { move |_| {
dialog.close(); dialog.close();
let config = Config::default(); let config = Config::default();
@ -185,15 +176,13 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
open_settings(ctx, &app); open_settings(ctx, &app);
} }
)); ));
group.add(&reset_button); group.add(&reset_button);
page.add(&group); page.add(&group);
dialog.add(&page); dialog.add(&page);
let page = PreferencesPage::builder() let page = PreferencesPage::builder()
.title("Protocol") .title("Protocol")
.icon_name("network-wired-symbolic") .icon_name("network-wired-symbolic")
@ -204,7 +193,6 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
.description("Network preferences") .description("Network preferences")
.build(); .build();
// Proxy preference // Proxy preference
let proxy = EntryRow::builder() let proxy = EntryRow::builder()
@ -214,35 +202,33 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&proxy); group.add(&proxy);
// Max avatar size preference // Max avatar size preference
let max_avatar_size = SpinRow::builder() let max_avatar_size = SpinRow::builder()
.title("Max avatar size") .title("Max avatar size")
.subtitle("Maximum avatar size in bytes") .subtitle("Maximum avatar size in bytes")
.adjustment(&Adjustment::builder() .adjustment(
.lower(0.0) &Adjustment::builder()
.upper(1074790400.0) .lower(0.0)
.page_increment(1024.0) .upper(1074790400.0)
.step_increment(1024.0) .page_increment(1024.0)
.value(ctx.config(|o| o.max_avatar_size) as f64) .step_increment(1024.0)
.build()) .value(ctx.config(|o| o.max_avatar_size) as f64)
.build(),
)
.build(); .build();
group.add(&max_avatar_size); group.add(&max_avatar_size);
page.add(&group); page.add(&group);
let group = PreferencesGroup::builder() let group = PreferencesGroup::builder()
.title("Protocol") .title("Protocol")
.description("Rac protocol preferences") .description("Rac protocol preferences")
.build(); .build();
// Message format preference // Message format preference
let message_format = EntryRow::builder() let message_format = EntryRow::builder()
.title("Message format") .title("Message format")
.text(ctx.config(|o| o.message_format.clone())) .text(ctx.config(|o| o.message_format.clone()))
@ -252,9 +238,8 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
page.add(&group); page.add(&group);
// Hide IP preference // Hide IP preference
let hide_my_ip = SwitchRow::builder() let hide_my_ip = SwitchRow::builder()
.title("Hide IP") .title("Hide IP")
.subtitle("Hides only for clRAC and other dummy clients") .subtitle("Hides only for clRAC and other dummy clients")
@ -263,9 +248,8 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&hide_my_ip); group.add(&hide_my_ip);
// Chunked reading preference // Chunked reading preference
let chunked_reading = SwitchRow::builder() let chunked_reading = SwitchRow::builder()
.title("Chunked reading") .title("Chunked reading")
.subtitle("Read messages in chunks (less traffic usage, less compatibility)") .subtitle("Read messages in chunks (less traffic usage, less compatibility)")
@ -274,9 +258,8 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&chunked_reading); group.add(&chunked_reading);
// Enable commands preference // Enable commands preference
let enable_commands = SwitchRow::builder() let enable_commands = SwitchRow::builder()
.title("Enable commands") .title("Enable commands")
.subtitle("Enable slash commands (eg. /login) on client-side") .subtitle("Enable slash commands (eg. /login) on client-side")
@ -285,11 +268,9 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
group.add(&enable_commands); group.add(&enable_commands);
page.add(&group); page.add(&group);
dialog.add(&page);
dialog.add(&page);
let page = PreferencesPage::builder() let page = PreferencesPage::builder()
.title("Interface") .title("Interface")
@ -301,102 +282,96 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
.description("Messages render preferences") .description("Messages render preferences")
.build(); .build();
// Debug logs preference // Debug logs preference
let debug_logs = SwitchRow::builder() let debug_logs = SwitchRow::builder()
.title("Debug logs") .title("Debug logs")
.subtitle("Print debug logs to the chat") .subtitle("Print debug logs to the chat")
.active(ctx.config(|o| o.debug_logs)) .active(ctx.config(|o| o.debug_logs))
.build(); .build();
group.add(&debug_logs); group.add(&debug_logs);
// Show IPs preference // Show IPs preference
let show_ips = SwitchRow::builder() let show_ips = SwitchRow::builder()
.title("Show IPs") .title("Show IPs")
.subtitle("Show authors IP addresses if possible") .subtitle("Show authors IP addresses if possible")
.active(ctx.config(|o| o.show_other_ip)) .active(ctx.config(|o| o.show_other_ip))
.build(); .build();
group.add(&show_ips); group.add(&show_ips);
// Format messages preference // Format messages preference
let format_messages = SwitchRow::builder() let format_messages = SwitchRow::builder()
.title("Format messages") .title("Format messages")
.subtitle("Disable to see raw messages") .subtitle("Disable to see raw messages")
.active(ctx.config(|o| o.formatting_enabled)) .active(ctx.config(|o| o.formatting_enabled))
.build(); .build();
group.add(&format_messages); group.add(&format_messages);
// Show avatars preference // Show avatars preference
let show_avatars = SwitchRow::builder() let show_avatars = SwitchRow::builder()
.title("Show avatars") .title("Show avatars")
.subtitle("Enables new messages UI") .subtitle("Enables new messages UI")
.active(ctx.config(|o| o.new_ui_enabled)) .active(ctx.config(|o| o.new_ui_enabled))
.build(); .build();
group.add(&show_avatars); group.add(&show_avatars);
page.add(&group); page.add(&group);
let group = PreferencesGroup::builder() let group = PreferencesGroup::builder()
.title("Interface") .title("Interface")
.description("General interface preferences (restart after changing)") .description("General interface preferences (restart after changing)")
.build(); .build();
// Remove GUI shit preference // Remove GUI shit preference
let remove_gui_shit = SwitchRow::builder() let remove_gui_shit = SwitchRow::builder()
.title("Remove GUI shit") .title("Remove GUI shit")
.subtitle("Removes calendar, konata and clock") .subtitle("Removes calendar, konata and clock")
.active(ctx.config(|o| o.remove_gui_shit)) .active(ctx.config(|o| o.remove_gui_shit))
.build(); .build();
group.add(&remove_gui_shit); group.add(&remove_gui_shit);
// Konata size preference // Konata size preference
let konata_size = SpinRow::builder() let konata_size = SpinRow::builder()
.title("Konata size") .title("Konata size")
.subtitle("Set konata size percent") .subtitle("Set konata size percent")
.adjustment(&Adjustment::builder() .adjustment(
.lower(0.0) &Adjustment::builder()
.upper(200.0) .lower(0.0)
.page_increment(10.0) .upper(200.0)
.step_increment(10.0) .page_increment(10.0)
.value(ctx.config(|o| o.konata_size) as f64) .step_increment(10.0)
.build()) .value(ctx.config(|o| o.konata_size) as f64)
.build(),
)
.build(); .build();
group.add(&konata_size); group.add(&konata_size);
// Enable notifications preference // Enable notifications preference
let enable_notifications = SwitchRow::builder() let enable_notifications = SwitchRow::builder()
.title("Enable notifications") .title("Enable notifications")
.subtitle("Send notifications on chat and system messages") .subtitle("Send notifications on chat and system messages")
.active(ctx.config(|o| o.notifications_enabled)) .active(ctx.config(|o| o.notifications_enabled))
.build(); .build();
group.add(&enable_notifications); group.add(&enable_notifications);
page.add(&group); page.add(&group);
dialog.add(&page); dialog.add(&page);
dialog.connect_closed(move |_| { dialog.connect_closed(move |_| {
let old_config = ctx.config.read().unwrap().clone();
let config = Config { let config = Config {
host: host.text().to_string(), host: host.text().to_string(),
name: { name: {
@ -441,6 +416,7 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
Some(proxy) Some(proxy)
} }
}, },
servers: old_config.servers,
}; };
ctx.set_config(&config); ctx.set_config(&config);
try_save_config(get_config_path(), &config); try_save_config(get_config_path(), &config);
@ -449,5 +425,3 @@ pub fn open_settings(ctx: Arc<Context>, app: &Application) {
dialog.present(app.active_window().as_ref()); dialog.present(app.active_window().as_ref());
} }