iamb 0.0.8

A Matrix chat client that uses Vim keybindings
use std::ops::{Deref, DerefMut};
use std::time::{Duration, Instant};

use matrix_sdk::{
    room::Room as MatrixRoom,
    ruma::{OwnedRoomId, RoomId},
};

use modalkit::tui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style},
    text::{Span, Spans, Text},
    widgets::StatefulWidget,
};

use modalkit::{
    widgets::list::{List, ListState},
    widgets::{TermOffset, TerminalCursor, WindowOps},
};

use crate::base::{IambBufferId, IambInfo, ProgramStore, RoomFocus};

use crate::windows::RoomItem;

const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5);

pub struct SpaceState {
    room_id: OwnedRoomId,
    room: MatrixRoom,
    list: ListState<RoomItem, IambInfo>,
    last_fetch: Option<Instant>,
}

impl SpaceState {
    pub fn new(room: MatrixRoom) -> Self {
        let room_id = room.room_id().to_owned();
        let content = IambBufferId::Room(room_id.clone(), RoomFocus::Scrollback);
        let list = ListState::new(content, vec![]);
        let last_fetch = None;

        SpaceState { room_id, room, list, last_fetch }
    }

    pub fn refresh_room(&mut self, store: &mut ProgramStore) {
        if let Some(room) = store.application.worker.client.get_room(self.id()) {
            self.room = room;
        }
    }

    pub fn room(&self) -> &MatrixRoom {
        &self.room
    }

    pub fn id(&self) -> &RoomId {
        &self.room_id
    }

    pub fn dup(&self, store: &mut ProgramStore) -> Self {
        SpaceState {
            room_id: self.room_id.clone(),
            room: self.room.clone(),
            list: self.list.dup(store),
            last_fetch: self.last_fetch,
        }
    }
}

impl TerminalCursor for SpaceState {
    fn get_term_cursor(&self) -> Option<TermOffset> {
        self.list.get_term_cursor()
    }
}

impl Deref for SpaceState {
    type Target = ListState<RoomItem, IambInfo>;

    fn deref(&self) -> &Self::Target {
        &self.list
    }
}

impl DerefMut for SpaceState {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.list
    }
}

pub struct Space<'a> {
    focused: bool,
    store: &'a mut ProgramStore,
}

impl<'a> Space<'a> {
    pub fn new(store: &'a mut ProgramStore) -> Self {
        Space { focused: false, store }
    }

    pub fn focus(mut self, focused: bool) -> Self {
        self.focused = focused;
        self
    }
}

impl<'a> StatefulWidget for Space<'a> {
    type State = SpaceState;

    fn render(self, area: Rect, buffer: &mut Buffer, state: &mut Self::State) {
        let mut empty_message = None;
        let need_fetch = match state.last_fetch {
            Some(i) => i.elapsed() >= SPACE_HIERARCHY_DEBOUNCE,
            None => true,
        };

        if need_fetch {
            let res = self.store.application.worker.space_members(state.room_id.clone());

            match res {
                Ok(members) => {
                    let items = members
                        .into_iter()
                        .filter_map(|id| {
                            let (room, _, tags) =
                                self.store.application.worker.get_room(id.clone()).ok()?;
                            let room_info = std::sync::Arc::new((room, tags));

                            if id != state.room_id {
                                Some(RoomItem::new(room_info, self.store))
                            } else {
                                None
                            }
                        })
                        .collect();

                    state.list.set(items);
                    state.last_fetch = Some(Instant::now());
                },
                Err(e) => {
                    let lines = vec![
                        Spans::from("Unable to fetch space room hierarchy:"),
                        Span::styled(e.to_string(), Style::default().fg(Color::Red)).into(),
                    ];

                    empty_message = Text { lines }.into();
                },
            }
        }

        let mut list = List::new(self.store).focus(self.focused);

        if let Some(text) = empty_message {
            list = list.empty_message(text);
        }

        list.render(area, buffer, &mut state.list)
    }
}