synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;

use crate::notifications::push_rules::persist_room_notification_level;
use crate::state::app_state::{AppState, RightPanelView};
use crate::state::room_state::NotificationLevel;

/// Right panel for configuring per-room notification settings.
#[component]
pub fn NotificationPanel() -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let mut saving = use_signal(|| false);
    let mut save_error = use_signal(|| Option::<String>::None);

    let state_read = state.read();
    let current_level = state_read
        .active_room()
        .map(|r| r.notification_level.clone())
        .unwrap_or(NotificationLevel::Default);

    let close_panel = move |_| {
        state.write().right_panel = RightPanelView::Closed;
    };

    let back_to_info = move |_| {
        state.write().right_panel = RightPanelView::RoomInfo;
    };

    let mut set_level = move |level: NotificationLevel| {
        let (client, room_id, previous_level) = {
            let state_read = state.read();
            (
                state_read.client.clone(),
                state_read.active_room_id.clone(),
                state_read
                    .active_room()
                    .map(|room| room.notification_level.clone())
                    .unwrap_or(NotificationLevel::Default),
            )
        };

        let Some(client) = client else {
            save_error.set(Some("Not logged in".to_string()));
            return;
        };
        let Some(room_id) = room_id else {
            return;
        };

        if let Some(room) = state.write().rooms.get_mut(&room_id) {
            room.notification_level = level.clone();
        }

        saving.set(true);
        save_error.set(None);

        spawn(async move {
            match persist_room_notification_level(&client, &room_id, level.clone()).await {
                Ok(()) => {}
                Err(e) => {
                    if let Some(room) = state.write().rooms.get_mut(&room_id) {
                        room.notification_level = previous_level;
                    }
                    save_error.set(Some(e));
                }
            }
            saving.set(false);
        });
    };

    rsx! {
        div {
            class: "notification-panel",

            // Header
            header {
                class: "notification-panel__header",
                button {
                    class: "notification-panel__back-btn",
                    title: "Back to room info",
                    onclick: back_to_info,
                    ""
                }
                h3 {
                    class: "notification-panel__title",
                    "Notifications"
                }
                button {
                    class: "notification-panel__close-btn",
                    title: "Close panel",
                    onclick: close_panel,
                    ""
                }
            }

            // Description
            div {
                class: "notification-panel__description",
                p {
                    "Choose how you want to be notified about activity in this room."
                }
                if *saving.read() {
                    p { "Saving..." }
                }
                if let Some(err) = save_error.read().as_ref() {
                    p { "{err}" }
                }
            }

            // Notification options
            div {
                class: "notification-panel__options",

                NotificationOption {
                    label: "Default",
                    description: "Use your global notification settings.",
                    is_selected: matches!(current_level, NotificationLevel::Default),
                    disabled: *saving.read(),
                    on_select: move |_| set_level(NotificationLevel::Default),
                }

                NotificationOption {
                    label: "All Messages",
                    description: "Get notified for every message in this room.",
                    is_selected: matches!(current_level, NotificationLevel::AllMessages),
                    disabled: *saving.read(),
                    on_select: move |_| set_level(NotificationLevel::AllMessages),
                }

                NotificationOption {
                    label: "Mentions & Keywords",
                    description: "Only get notified when you are mentioned or a keyword is used.",
                    is_selected: matches!(current_level, NotificationLevel::MentionsAndKeywords),
                    disabled: *saving.read(),
                    on_select: move |_| set_level(NotificationLevel::MentionsAndKeywords),
                }

                NotificationOption {
                    label: "Mute",
                    description: "You will not receive any notifications from this room.",
                    is_selected: matches!(current_level, NotificationLevel::Mute),
                    disabled: *saving.read(),
                    on_select: move |_| set_level(NotificationLevel::Mute),
                }
            }
        }
    }
}

/// A single selectable notification option.
#[component]
fn NotificationOption(
    label: &'static str,
    description: &'static str,
    is_selected: bool,
    disabled: bool,
    on_select: EventHandler<MouseEvent>,
) -> Element {
    let class = if is_selected {
        "notification-option notification-option--selected"
    } else {
        "notification-option"
    };

    rsx! {
        button {
            class: "{class}",
            disabled: disabled,
            onclick: move |evt| on_select.call(evt),

            div {
                class: "notification-option__radio",
                if is_selected {
                    span { class: "notification-option__radio-dot" }
                }
            }

            div {
                class: "notification-option__text",
                span {
                    class: "notification-option__label",
                    "{label}"
                }
                span {
                    class: "notification-option__description",
                    "{description}"
                }
            }
        }
    }
}