More refactoring; fixes to non-compiling parts.

This commit is contained in:
Werner Kroneman 2023-12-10 16:29:37 +01:00
parent b9639ad85c
commit f8bb6f1fbd
6 changed files with 146 additions and 40 deletions

76
src/configuration.rs Normal file
View File

@ -0,0 +1,76 @@
// Dergchat, a free XMPP client.
// Copyright (C) 2023 Werner Kroneman
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use dioxus::prelude::*;
use keyring::Entry;
use log::error;
use serde_derive::{Deserialize, Serialize};
use crate::widgets::login_screen::LoginAttempt;
/// The configuration struct containing all the configuration options.
#[derive(Serialize, Deserialize, Default)]
pub struct Configuration {
pub stored_username: String,
pub stored_default_nick: String,
}
/// Cache the login attempt into the config and keyring.
///
/// Specifically, we store the username and default nick in the config file, and the password in the keyring.
///
/// Note: we do NOT store the password in the config file, because the latter is stored in plaintext.
///
/// # Arguments
/// * `attempt` - The login attempt to store.
/// * `config` - The current app configuration; UseState should prevent race conditions in writing.
pub fn store_login_details(attempt: LoginAttempt, config: &mut UseRef<Configuration>) {
// Extract the fields from the login attempt.
let LoginAttempt {
username,
default_nick,
password,
} = attempt;
// Open the config state and store the username and default nick.
config.with_mut(|c| {
// Open the keyring and store the password.
let entry = Entry::new("dergchat", &username).expect("Failed to create keyring entry.");
entry.set_password(&password).expect("Failed to set password in keyring.");
// Store the username and default nick in the config.
c.stored_username = username;
c.stored_default_nick = default_nick;
if let Err(e) = confy::store("dergchat", None, c) {
error!("Failed to store the config file: {}", e);
}
});
}
/// Load the configuration from the config file.
///
/// On failure, log the error and return the default configuration.
pub fn load_config() -> Configuration {
match confy::load("dergchat", None) {
Ok(x) => x,
Err(e) => {
error!("Failed to load config file: {}", e);
Configuration::default()
}
}
}

View File

@ -19,6 +19,7 @@
mod types;
mod widgets;
mod xmpp_interface;
mod configuration;
use dioxus::prelude::*;
use dioxus_desktop::Config;

View File

@ -18,6 +18,7 @@ use crate::types::LoginCredentials;
use crate::widgets::login_screen::LoginAttempt;
use crate::widgets::login_screen::{LoginScreen, LoginStatus};
use crate::widgets::room_view::RoomView;
use crate::widgets::no_room_open::NoRoomPlaceholder;
use crate::widgets::sidebar::SideBar;
use crate::xmpp_interface::NetworkCommand;
use dioxus::core::{Element, Scope};
@ -31,7 +32,7 @@ use std::collections::HashMap;
use std::str::FromStr;
use std::string::String;
use keyring::Entry;
use serde_derive::{Deserialize, Serialize};
use crate::configuration::load_config;
use crate::xmpp_interface;
pub mod login_screen;
@ -40,14 +41,10 @@ pub mod room_list;
pub mod room_view;
pub mod send_message;
pub mod sidebar;
#[derive(Serialize, Deserialize, Default)]
struct Configuration {
stored_username: String,
stored_default_nick: String,
}
mod no_room_open;
pub fn App(cx: Scope) -> Element {
let messages = use_ref(cx, || HashMap::new());
let current_room = use_state(cx, || None::<BareJid>);
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
@ -56,13 +53,7 @@ pub fn App(cx: Scope) -> Element {
xmpp_interface::run_xmpp_toplevel(connection_status.to_owned(), messages.to_owned(), rx)
});
let config = use_ref(cx, || match confy::load("dergchat", None) {
Ok(x) => x,
Err(e) => {
error!("Failed to load config file: {}", e);
Configuration::default()
}
});
let config = use_ref(cx, load_config);
{
let config = config.to_owned();
@ -157,6 +148,9 @@ pub fn App(cx: Scope) -> Element {
current_room.set(None);
coroutine.send(NetworkCommand::LeaveRoom { room: x });
},
on_join_room: move |x: BareJid| {
coroutine.send(NetworkCommand::JoinRoom { room: x });
},
}
// The current room.
@ -173,21 +167,11 @@ pub fn App(cx: Scope) -> Element {
}
} else {
// No room selected
rsx! {
NoRoomPlaceholder {}
}
}
}
}
}
fn NoRoomPlaceholder(cx: Scope) -> Element {
render! {
div {
padding: "5mm",
flex_grow: 1,
display: "flex",
background_color: "#166322",
"No room selected. Pick one from the list on the left."
}
}
}

View File

@ -0,0 +1,31 @@
// Dergchat, a free XMPP client.
// Copyright (C) 2023 Werner Kroneman
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use dioxus::core::{Element, Scope};
use dioxus::prelude::*;
/// A placeholder widget for when no room is open.
pub fn NoRoomPlaceholder(cx: Scope) -> Element {
render! {
div {
padding: "5mm",
flex_grow: 1,
display: "flex",
background_color: "#166322",
"No room selected. Pick one from the list on the left."
}
}
}

View File

@ -18,18 +18,25 @@ use dioxus::core::{Element, Scope};
use dioxus::core_macro::Props;
use dioxus::hooks::use_state;
use dioxus::prelude::*;
use jid::BareJid;
use std::str::FromStr;
/// The props for the room join widget, including:
/// * The event handler for when the user clicks the join button, passing in a room name. This name is not validated in any way.
#[derive(Props)]
pub struct RoomJoinProps<'a> {
on_join_room: EventHandler<'a, String>,
on_join_room: EventHandler<'a, BareJid>,
}
/// A simple widget that allows the user to join a room by typing
/// its name into a text field and clicking a button.
///
/// Also validates the room name, and displays an error message if the room name is invalid.
pub fn RoomJoinWidget<'a>(cx: Scope<'a, RoomJoinProps>) -> Element<'a> {
// Store the current room name and error message in state.
let room = use_state(cx, || "".to_owned());
let error = use_state(cx, || "".to_owned());
render! {
div {
@ -39,11 +46,19 @@ pub fn RoomJoinWidget<'a>(cx: Scope<'a, RoomJoinProps>) -> Element<'a> {
flex_grow: 1,
display: "block",
oninput: |evt| {
room.set(evt.value.clone());
room.set(evt.value.to_string());
error.set("".to_owned());
}
}
button {
onclick: move |_| cx.props.on_join_room.call(room.current().to_string()),
onclick: move |_| {
// Validate the room name. If it's a valid Jid, try to join it.
// Otherwise, display an error message.
match BareJid::from_str(room.current().as_str()) {
Ok(jid) => {error.set("".to_string()); cx.props.on_join_room.call(jid);},
Err(e) => {error.set(e.to_string());},
}
},
"Join"
}
}

View File

@ -22,14 +22,15 @@ use crate::widgets::room_list::RoomList;
use crate::widgets::room_join_widget::RoomJoinWidget;
#[derive(Props)]
pub struct SideBarProps {
pub struct SideBarProps<'a> {
pub rooms: Vec<BareJid>,
pub on_room_picked: EventHandler<'static, BareJid>,
pub on_room_left: EventHandler<'static, BareJid>,
pub on_room_picked: EventHandler<'a, BareJid>,
pub on_room_left: EventHandler<'a, BareJid>,
pub on_join_room: EventHandler<'a, BareJid>,
}
/// A widget that combines the RoomList, RoomJoinWidget, and current user info into a sidebar.
pub fn SideBar(cx: Scope<SideBarProps>) -> Element {
pub fn SideBar<'a>(cx: Scope<'a, SideBarProps>) -> Element<'a> {
render! {
div {
padding: "5mm",
@ -46,15 +47,13 @@ pub fn SideBar(cx: Scope<SideBarProps>) -> Element {
// The list of rooms.
RoomList {
rooms: cx.props.rooms.clone(),
on_room_picked: cx.props.on_room_picked.clone(),
on_room_left: cx.props.on_room_left.clone(),
on_room_picked: |e| cx.props.on_room_picked.call(e),
on_room_left: |e| cx.props.on_room_left.call(e),
}
// The widget to join a room.
RoomJoinWidget {
on_join_room: |x: String| {
println!("Joining room: {:?}", x);
},
on_join_room: |x| cx.props.on_join_room.call(x),
}
}
}