aboutsummaryrefslogtreecommitdiffstats
path: root/src/message_view.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/message_view.rs367
1 files changed, 261 insertions, 106 deletions
diff --git a/src/message_view.rs b/src/message_view.rs
index 16e8ac1..742aac0 100644
--- a/src/message_view.rs
+++ b/src/message_view.rs
@@ -1,29 +1,34 @@
-use std::borrow::Cow;
+use std::{path::PathBuf, time::Duration};
-use chrono::NaiveDate;
+use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
+use filamento::chat::Delivery;
+use iced::advanced::Overlay;
+use iced::alignment::Vertical;
+use iced::widget::{horizontal_space, mouse_area, text_editor, vertical_space, Container};
use iced::{
- alignment::Horizontal::{self, Right},
border::Radius,
- color,
- theme::Palette,
- widget::{
- button, column, container, horizontal_space, row, scrollable, text, text_editor,
- text_editor::Content, text_input, Column,
- },
- Border, Color, Element,
+ font::{Style, Weight},
+ widget::{button, column, container, image, row, scrollable, text, text_editor::Content},
+ Border, Color, Element, Font,
Length::{Fill, Shrink},
Theme,
};
+use iced::{color, overlay, Length, Padding};
use indexmap::IndexMap;
use jid::JID;
-use luz::chat::Message as ChatMessage;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
+use crate::icons;
+use crate::{icons::Icon, MacawChat, MacawMessage};
+
pub struct MessageView {
+ pub file_root: PathBuf,
+ // references chats, users
+ pub chat: MacawChat,
+ // references users, messages
+ pub messages: IndexMap<Uuid, (MacawMessage, bool)>,
pub config: Config,
- pub jid: JID,
- pub message_history: IndexMap<Uuid, ChatMessage>,
pub new_message: Content,
pub shift_pressed: bool,
}
@@ -43,8 +48,11 @@ impl Default for Config {
#[derive(Debug, Clone)]
pub enum Message {
- MessageHistory(Vec<ChatMessage>),
- Message(ChatMessage),
+ MessageHistory(Vec<MacawMessage>),
+ Message(MacawMessage),
+ MessageHovered(Uuid),
+ MessageUnhovered(Uuid),
+ MessageRightClicked(Uuid),
MessageCompose(text_editor::Action),
SendMessage(String),
}
@@ -55,42 +63,22 @@ pub enum Action {
}
impl MessageView {
- pub fn new(jid: JID, config: &super::Config) -> Self {
+ pub fn new(chat: MacawChat, config: &super::Config, file_root: PathBuf) -> Self {
Self {
- jid,
+ chat,
// TODO: save position in message history
- message_history: IndexMap::new(),
+ messages: IndexMap::new(),
// TODO: save draft (as part of chat struct?)
new_message: Content::new(),
config: config.message_view_config.clone(),
// TODO: have centralised modifier state location?
shift_pressed: false,
+ file_root,
}
}
pub fn update(&mut self, message: Message) -> Action {
match message {
- Message::MessageHistory(messages) => {
- if self.message_history.is_empty() {
- self.message_history = messages
- .into_iter()
- .map(|message| (message.id.clone(), message))
- .collect();
- }
- Action::None
- }
- Message::Message(message) => {
- let i = self
- .message_history
- .iter()
- .position(|(_id, m)| m.timestamp > message.timestamp);
- if let Some(i) = i {
- self.message_history.insert_before(i, message.id, message);
- } else {
- self.message_history.insert(message.id, message);
- }
- Action::None
- }
Message::MessageCompose(a) => {
match &a {
text_editor::Action::Edit(edit) => match edit {
@@ -120,30 +108,109 @@ impl MessageView {
self.new_message = Content::new();
Action::SendMessage(m)
}
+ Message::MessageHistory(macaw_messages) => {
+ if self.messages.is_empty() {
+ self.messages = macaw_messages
+ .into_iter()
+ .map(|message| (message.id, (message, false)))
+ .collect()
+ } else {
+ for message in macaw_messages {
+ let index = match self
+ .messages
+ .binary_search_by(|_, value| value.0.timestamp.cmp(&message.timestamp))
+ {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ self.messages
+ .insert_before(index, message.id, (message, false));
+ }
+ }
+ Action::None
+ }
+ Message::Message(macaw_message) => {
+ if let Some((_, last)) = self.messages.last() {
+ if last.0.timestamp < macaw_message.timestamp {
+ self.messages
+ .insert(macaw_message.id, (macaw_message, false));
+ } else {
+ let index = match self.messages.binary_search_by(|_, value| {
+ value.0.timestamp.cmp(&macaw_message.timestamp)
+ }) {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ self.messages.insert_before(
+ index,
+ macaw_message.id,
+ (macaw_message, false),
+ );
+ }
+ } else {
+ self.messages
+ .insert(macaw_message.id, (macaw_message, false));
+ }
+ Action::None
+ }
+ Message::MessageHovered(uuid) => {
+ if let Some(message) = self.messages.get_mut(&uuid) {
+ message.1 = true;
+ }
+ Action::None
+ }
+ Message::MessageUnhovered(uuid) => {
+ if let Some(message) = self.messages.get_mut(&uuid) {
+ message.1 = false;
+ }
+ Action::None
+ }
+ Message::MessageRightClicked(uuid) => todo!(),
}
}
pub fn view(&self) -> Element<Message> {
- let mut messages_view = column![].spacing(8).padding(8);
- let mut latest_date = NaiveDate::MIN;
- for (_id, message) in &self.message_history {
- let message_date = message.timestamp.naive_local().date();
- if message_date > latest_date {
- latest_date = message_date;
- messages_view = messages_view.push(date(latest_date));
+ let mut messages_view = column![];
+ let mut last_timestamp = NaiveDateTime::MIN;
+ let mut last_user: Option<JID> = None;
+ for (_id, message) in &self.messages {
+ let message_timestamp = message.0.timestamp.naive_local();
+ if message_timestamp.date() > last_timestamp.date() {
+ messages_view = messages_view.push(date(message_timestamp.date()));
+ }
+ if last_user.as_ref() != Some(&message.0.from.jid)
+ || message_timestamp - last_timestamp > TimeDelta::minutes(3)
+ {
+ messages_view = messages_view.push(self.message(&message.0, message.1, true));
+ } else {
+ messages_view = messages_view.push(self.message(&message.0, message.1, false));
}
- messages_view = messages_view.push(self.message(message));
+ last_user = Some(message.0.from.jid.clone());
+ last_timestamp = message_timestamp;
}
let text_editor = text_editor(&self.new_message)
.placeholder("new message")
.on_action(Message::MessageCompose)
- .wrapping(text::Wrapping::WordOrGlyph);
+ .wrapping(text::Wrapping::WordOrGlyph)
+ .style(|theme, status| text_editor::Style {
+ background: color!(0xdcdcdc).into(),
+ border: Border {
+ color: Color::BLACK,
+ width: 0.0,
+ radius: 0.into(),
+ },
+ icon: color!(0x00000000),
+ placeholder: color!(0xacacac),
+ value: color!(0x000000),
+ selection: color!(0xffce07),
+ });
let message_send_input = row![
text_editor,
- button("send").on_press(Message::SendMessage(self.new_message.text()))
+ // button(Icon::NewBubble24).on_press(Message::SendMessage(self.new_message.text()))
]
.padding(8);
column![
+ self.header(),
scrollable(messages_view)
.height(Fill)
.width(Fill)
@@ -154,69 +221,157 @@ impl MessageView {
.into()
}
- pub fn message<'a>(&'a self, message: &'a ChatMessage) -> Element<'a, Message> {
+ pub fn header(&self) -> Element<'_, Message> {
+ // TODO: contact stored here for name
+ let mut bold = Font::with_name("K2D");
+ bold.weight = Weight::Bold;
+ let mut sweet = Font::with_name("Diolce");
+ sweet.style = Style::Italic;
+ let mut name_and_jid = column![];
+ if let Some(nick) = &self.chat.user.nick {
+ name_and_jid = name_and_jid.push(text(nick).font(bold).size(20));
+ }
+ let jid = self.chat.user.jid.as_bare().to_string();
+ name_and_jid = name_and_jid.push(text(jid).font(sweet));
+ let mut header = row![];
+ if let Some(avatar) = &self.chat.user.avatar {
+ let mut path = self.file_root.join(avatar);
+ path.set_extension("jpg");
+ header = header.push(container(image(path).width(48).height(48)));
+ }
+ header = header.push(name_and_jid);
+ container(
+ container(header.spacing(8).padding(8))
+ .style(|theme: &Theme| {
+ container::Style::default()
+ .background(theme.extended_palette().background.strong.color)
+ })
+ .width(Fill),
+ )
+ .padding(8)
+ .width(Fill)
+ .into()
+ }
+
+ pub fn message<'a>(
+ &'a self,
+ message: &'a MacawMessage,
+ hovered: bool,
+ major: bool,
+ // next_read: bool,
+ ) -> Element<'a, Message> {
let timestamp = message.timestamp.naive_local();
let timestamp = timestamp.time().format("%H:%M").to_string();
- if self.jid == message.from.as_bare() {
+ let container: Container<Message> = if major {
+ let nick: String = if let Some(nick) = &message.from.nick {
+ nick.to_string()
+ } else {
+ message.from.jid.as_bare().to_string()
+ };
+ let mut bold = Font::with_name("K2D");
+ bold.weight = Weight::Bold;
+ let mut thin = Font::with_name("K2D");
+ thin.weight = Weight::Thin;
+ let timestamp = text(timestamp).size(12).font(thin);
+ let header = row![text(nick).font(bold), timestamp]
+ .align_y(Vertical::Bottom)
+ .spacing(8);
+ let message_right = column![header, text(&message.body.body)].spacing(8);
+ let avatar = if let Some(avatar) = &message.from.avatar {
+ let mut path = self.file_root.join(avatar);
+ path.set_extension("jpg");
+ // info!("got avatar: {:?}", path);
+ container(image(path).width(48).height(48))
+ } else {
+ container("").width(48)
+ };
+ let show_delivery = match message.delivery {
+ Some(Delivery::Sending) => true,
+ Some(Delivery::Failed) => true,
+ // TODO: queued icon
+ Some(Delivery::Queued) => true,
+ _ => hovered,
+ };
+ let delivery = if show_delivery {
+ message
+ .delivery
+ .map(|delivery| icons::delivery_to_icon_svg(delivery))
+ .unwrap_or_default()
+ } else {
+ None
+ };
+ let delivery: Container<Message> = if let Some(delivery) = delivery {
+ container(delivery)
+ } else {
+ container("")
+ }
+ .width(16);
+ let major_message = row![avatar, message_right, horizontal_space(), delivery];
+ container(major_message.spacing(8))
+ } else {
+ let timestamp = if hovered {
+ let mut thin = Font::with_name("K2D");
+ thin.weight = Weight::Thin;
+ let timestamp = text(timestamp).size(10).font(thin);
+ container(timestamp).align_right(48)
+ } else {
+ container("").width(48)
+ };
+ let show_delivery = match message.delivery {
+ Some(Delivery::Sending) => true,
+ Some(Delivery::Failed) => true,
+ // TODO: queued icon
+ Some(Delivery::Queued) => true,
+ _ => hovered,
+ };
+ let delivery = if show_delivery {
+ message
+ .delivery
+ .map(|delivery| icons::delivery_to_icon_svg(delivery))
+ .unwrap_or_default()
+ } else {
+ None
+ };
+ let delivery: Container<Message> = if let Some(delivery) = delivery {
+ container(delivery)
+ } else {
+ container("")
+ }
+ .width(16);
container(
- container(
- column![
- text(message.body.body.as_str()).wrapping(text::Wrapping::WordOrGlyph),
- container(text(timestamp).wrapping(text::Wrapping::None).size(12)) // .align_right(Fill)
- ]
- .width(Shrink)
- .max_width(500),
- )
- .padding(16)
- .style(|theme: &Theme| {
- let palette = theme.extended_palette();
- container::Style::default()
- .background(palette.primary.weak.color)
- .border(Border {
- color: Color::BLACK,
- width: 0.,
- // width: 4.,
- radius: Radius::new(16),
- })
- }),
+ row![
+ timestamp,
+ text(&message.body.body),
+ horizontal_space(),
+ delivery
+ ]
+ .align_y(Vertical::Top)
+ .spacing(8),
)
- .align_left(Fill)
- .into()
- } else {
- let element: Element<Message> = container(
- container(
- column![
- text(message.body.body.as_str()).wrapping(text::Wrapping::WordOrGlyph),
- container(text(timestamp).wrapping(text::Wrapping::None).size(12))
- .align_right(Fill) // row![
- // // horizontal_space(),
- // // horizontal_space(),
- // text(timestamp).wrapping(text::Wrapping::None).size(12)
- // ] // container(text(timestamp).wrapping(text::Wrapping::None).size(12))
- // .align_right(Fill)
- ]
- .width(Shrink)
- .max_width(500),
- )
- .padding(16)
- .style(|theme: &Theme| {
- let palette = theme.extended_palette();
- container::Style::default()
- .background(palette.primary.base.color)
- .border(Border {
- color: Color::BLACK,
- width: 0.,
- // width: 4.,
- radius: Radius::new(16),
- })
+ };
+ // let overlay = overlay::Element::new(Box::new(Overlay {}));
+ mouse_area(
+ container
+ .width(Length::Fill)
+ .style(move |theme| {
+ if hovered {
+ container::Style::default()
+ .background(theme.extended_palette().background.weak.color)
+ } else {
+ container::Style::default()
+ }
+ })
+ .padding(Padding {
+ top: 4.,
+ right: 8.,
+ bottom: 4.,
+ left: 8.,
}),
- )
- .align_right(Fill)
- .into();
- // element.explain(Color::BLACK)
- element
- }
+ )
+ .on_enter(Message::MessageHovered(message.id))
+ .on_exit(Message::MessageUnhovered(message.id))
+ .into()
}
}