Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: unread #62

Merged
merged 5 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions data/src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use tokio::fs;
use tokio::time::Instant;

pub use self::manager::{Manager, Resource};
use crate::time::Posix;
use crate::user::Nick;
use crate::{compression, environment, message, server, Message};

Expand Down Expand Up @@ -120,29 +121,27 @@ pub enum History {
kind: Kind,
messages: Vec<Message>,
last_received_at: Option<Instant>,
unread_message_count: usize,
opened_at: Posix,
},
Full {
server: server::Server,
kind: Kind,
messages: Vec<Message>,
last_received_at: Option<Instant>,
opened_at: Posix,
},
}

impl History {
fn partial(server: server::Server, kind: Kind) -> Self {
fn partial(server: server::Server, kind: Kind, opened_at: Posix) -> Self {
Self::Partial {
server,
kind,
messages: vec![],
last_received_at: None,
}
}

fn messages(&self) -> &[Message] {
match self {
History::Partial { messages, .. } => messages,
History::Full { messages, .. } => messages,
unread_message_count: 0,
opened_at,
}
}

Expand All @@ -151,8 +150,13 @@ impl History {
History::Partial {
messages,
last_received_at,
unread_message_count,
..
} => {
if message.triggers_unread() {
*unread_message_count += 1;
}

messages.push(message);
*last_received_at = Some(Instant::now());
}
Expand All @@ -174,6 +178,7 @@ impl History {
kind,
messages,
last_received_at,
..
} => {
if let Some(last_received) = *last_received_at {
let since = now.duration_since(last_received);
Expand All @@ -195,6 +200,7 @@ impl History {
kind,
messages,
last_received_at,
..
} => {
if let Some(last_received) = *last_received_at {
let since = now.duration_since(last_received);
Expand Down Expand Up @@ -234,7 +240,7 @@ impl History {
let kind = kind.clone();
let messages = std::mem::take(messages);

*self = Self::partial(server.clone(), kind.clone());
*self = Self::partial(server.clone(), kind.clone(), Posix::now());

Some(async move { overwrite(&server, &kind, &messages).await })
}
Expand All @@ -258,6 +264,14 @@ impl History {
}
}
}

#[derive(Debug)]
pub struct View<'a> {
pub total: usize,
pub old_messages: Vec<&'a Message>,
pub new_messages: Vec<&'a Message>,
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("can't resolve data directory")]
Expand Down
85 changes: 55 additions & 30 deletions data/src/history/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use tokio::time::Instant;

use crate::history::{self, History};
use crate::message::{self, Limit};
use crate::time::Posix;
use crate::user::Nick;
use crate::{server, Server};

Expand Down Expand Up @@ -133,46 +134,28 @@ impl Manager {
server: &Server,
channel: &str,
limit: Option<Limit>,
) -> (usize, Vec<&crate::Message>) {
) -> Option<history::View<'_>> {
self.data
.messages(server, &history::Kind::Channel(channel.to_string()))
.map(|messages| {
let total = messages.len();

(total, with_limit(limit, messages.iter()))
})
.unwrap_or_else(|| (0, vec![]))
.history_view(server, &history::Kind::Channel(channel.to_string()), limit)
}

pub fn get_server_messages(
&self,
server: &Server,
limit: Option<Limit>,
) -> (usize, Vec<&crate::Message>) {
) -> Option<history::View<'_>> {
self.data
.messages(server, &history::Kind::Server)
.map(|messages| {
let total = messages.len();

(total, with_limit(limit, messages.iter()))
})
.unwrap_or_else(|| (0, vec![]))
.history_view(server, &history::Kind::Server, limit)
}

pub fn get_query_messages(
&self,
server: &Server,
nick: &Nick,
limit: Option<Limit>,
) -> (usize, Vec<&crate::Message>) {
) -> Option<history::View<'_>> {
self.data
.messages(server, &history::Kind::Query(nick.clone()))
.map(|messages| {
let total = messages.len();

(total, with_limit(limit, messages.iter()))
})
.unwrap_or_else(|| (0, vec![]))
.history_view(server, &history::Kind::Query(nick.clone()), limit)
}

pub fn get_unique_queries(&self, server: &Server) -> Vec<&Nick> {
Expand All @@ -192,6 +175,23 @@ impl Manager {
queries
}

pub fn has_unread(&self, server: &Server, kind: &history::Kind) -> bool {
self.data
.map
.get(server)
.and_then(|map| map.get(kind))
.map(|history| {
matches!(
history,
History::Partial {
unread_message_count,
..
} if *unread_message_count > 0
)
})
.unwrap_or_default()
}

pub fn broadcast(&mut self, server: &Server, broadcast: Broadcast) {
let Some(map) = self.data.map.get(server) else {
return;
Expand Down Expand Up @@ -275,15 +275,18 @@ impl Data {
History::Partial {
messages: new_messages,
last_received_at,
opened_at,
..
} => {
let last_received_at = *last_received_at;
let opened_at = *opened_at;
messages.extend(std::mem::take(new_messages));
entry.insert(History::Full {
server,
kind,
messages,
last_received_at,
opened_at,
});
}
_ => {
Expand All @@ -292,6 +295,7 @@ impl Data {
kind,
messages,
last_received_at: None,
opened_at: Posix::now(),
});
}
},
Expand All @@ -301,16 +305,37 @@ impl Data {
kind,
messages,
last_received_at: None,
opened_at: Posix::now(),
});
}
}
}

fn messages(&self, server: &server::Server, kind: &history::Kind) -> Option<&[crate::Message]> {
self.map
.get(server)
.and_then(|map| map.get(kind))
.map(History::messages)
fn history_view(
&self,
server: &server::Server,
kind: &history::Kind,
limit: Option<Limit>,
) -> Option<history::View> {
let History::Full { messages, opened_at, .. } = self.map.get(server)?.get(kind)? else {
return None;
};

let total = messages.len();
let limited = with_limit(limit, messages.iter());

let split_at = limited
.iter()
.position(|message| message.received_at >= *opened_at)
.unwrap_or(limited.len());

let (old, new) = limited.split_at(split_at);

Some(history::View {
total,
old_messages: old.to_vec(),
new_messages: new.to_vec(),
})
}

fn add_message(
Expand All @@ -323,7 +348,7 @@ impl Data {
.entry(server.clone())
.or_default()
.entry(kind.clone())
.or_insert_with(|| History::partial(server, kind))
.or_insert_with(|| History::partial(server, kind, message.received_at))
.add_message(message)
}

Expand Down
15 changes: 15 additions & 0 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ pub enum Source {
Query(Nick, Sender),
}

impl Source {
pub fn sender(&self) -> Option<&Sender> {
match self {
Source::Server => None,
Source::Channel(_, sender) => Some(sender),
Source::Query(_, sender) => Some(sender),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Sender {
User(User),
Expand Down Expand Up @@ -92,6 +102,11 @@ impl Message {
}
}

pub fn triggers_unread(&self) -> bool {
matches!(self.direction, Direction::Received)
&& matches!(self.source.sender(), Some(Sender::User(_) | Sender::Action))
}

pub fn received(encoded: Encoded, our_nick: &Nick) -> Option<Message> {
let server_time = server_time(&encoded);
let text = text(&encoded, our_nick)?;
Expand Down
75 changes: 50 additions & 25 deletions src/buffer/scroll_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use data::message::Limit;
use data::server::Server;
use data::user::Nick;
use data::{history, time};
use iced::widget::scrollable;
use iced::widget::{column, container, horizontal_rule, scrollable};
use iced::{Command, Length};

use crate::widget::{Column, Element};
use crate::theme;
use crate::widget::Element;

#[derive(Debug, Clone)]
pub enum Message {
Expand All @@ -31,42 +32,66 @@ pub fn view<'a>(
history: &'a history::Manager,
format: impl Fn(&'a data::Message) -> Option<Element<'a, Message>> + 'a,
) -> Element<'a, Message> {
let (total, messages) = match kind {
let Some(history::View {
total,
old_messages,
new_messages,
}) = (match kind {
Kind::Server(server) => history.get_server_messages(server, Some(state.limit)),
Kind::Channel(server, channel) => {
history.get_channel_messages(server, channel, Some(state.limit))
}
Kind::Query(server, user) => history.get_query_messages(server, user, Some(state.limit)),
}) else {
return column![].into();
};

let count = messages.len();
let count = old_messages.len() + new_messages.len();
let remaining = count < total;
let oldest = messages
.first()
let oldest = old_messages
.iter()
.chain(&new_messages)
.next()
.map(|message| message.received_at)
.unwrap_or_else(time::Posix::now);
let status = state.status;

scrollable(
Column::with_children(messages.into_iter().filter_map(format).collect())
let old = old_messages
.into_iter()
.filter_map(&format)
.collect::<Vec<_>>();
let new = new_messages
.into_iter()
.filter_map(format)
.collect::<Vec<_>>();

let show_divider = !new.is_empty() || matches!(status, Status::Idle(Anchor::Bottom));

let content = if show_divider {
let divider = container(horizontal_rule(1).style(theme::Rule::Unread))
.width(Length::Fill)
.padding([0, 8]),
)
.vertical_scroll(
scrollable::Properties::default()
.alignment(status.alignment())
.width(5)
.scroller_width(5),
)
.on_scroll(move |viewport| Message::Scrolled {
count,
remaining,
oldest,
status,
viewport,
})
.id(state.scrollable.clone())
.into()
.padding(5);
column![column(old), divider, column(new)]
} else {
column![column(old), column(new)]
};
Comment on lines +70 to +77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean way of doing this, nice!


scrollable(container(content).width(Length::Fill).padding([0, 8]))
.vertical_scroll(
scrollable::Properties::default()
.alignment(status.alignment())
.width(5)
.scroller_width(5),
)
.on_scroll(move |viewport| Message::Scrolled {
count,
remaining,
oldest,
status,
viewport,
})
.id(state.scrollable.clone())
.into()
}

#[derive(Debug, Clone)]
Expand Down
Loading