Split core and TUI crates
This commit is contained in:
447
crates/tele-core/src/message_grouping.rs
Normal file
447
crates/tele-core/src/message_grouping.rs
Normal file
@@ -0,0 +1,447 @@
|
||||
//! Модуль для группировки сообщений по дате и отправителю
|
||||
//!
|
||||
//! Предоставляет функции для логической группировки сообщений
|
||||
//! перед отображением, отделяя логику группировки от рендеринга.
|
||||
|
||||
use crate::tdlib::MessageInfo;
|
||||
use crate::utils::get_day;
|
||||
|
||||
/// Элемент группированного списка сообщений
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MessageGroup<'a> {
|
||||
/// Разделитель даты (день в формате timestamp)
|
||||
DateSeparator(i32),
|
||||
/// Заголовок отправителя (is_outgoing, sender_name)
|
||||
SenderHeader {
|
||||
is_outgoing: bool,
|
||||
sender_name: String,
|
||||
},
|
||||
/// Сообщение
|
||||
Message(&'a MessageInfo),
|
||||
/// Альбом (группа фото с одинаковым media_album_id)
|
||||
Album(Vec<&'a MessageInfo>),
|
||||
}
|
||||
|
||||
/// Группирует сообщения по дате и отправителю
|
||||
///
|
||||
/// # Аргументы
|
||||
///
|
||||
/// * `messages` - Список сообщений для группировки
|
||||
///
|
||||
/// # Возвращает
|
||||
///
|
||||
/// Вектор `MessageGroup` с разделителями дат, заголовками отправителей и сообщениями
|
||||
///
|
||||
/// # Примеры
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tele_core::message_grouping::{group_messages, MessageGroup};
|
||||
///
|
||||
/// # use tele_core::tdlib::types::MessageBuilder;
|
||||
/// # use tele_core::types::MessageId;
|
||||
/// # let msg = MessageBuilder::new(MessageId::new(1)).sender_name("Alice").text("Hello").build();
|
||||
/// let messages = vec![msg];
|
||||
/// let grouped = group_messages(&messages);
|
||||
///
|
||||
/// for group in grouped {
|
||||
/// match group {
|
||||
/// MessageGroup::DateSeparator(_day) => {
|
||||
/// // Рендерим разделитель даты
|
||||
/// }
|
||||
/// MessageGroup::SenderHeader { is_outgoing, sender_name } => {
|
||||
/// // Рендерим заголовок отправителя
|
||||
/// println!("{}: {}", if is_outgoing { "Outgoing" } else { "Incoming" }, sender_name);
|
||||
/// }
|
||||
/// MessageGroup::Message(msg) => {
|
||||
/// // Рендерим сообщение
|
||||
/// println!("{}", msg.text());
|
||||
/// }
|
||||
/// MessageGroup::Album(messages) => {
|
||||
/// // Рендерим альбом (группу фото)
|
||||
/// println!("Album with {} photos", messages.len());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn group_messages<'a>(messages: &'a [MessageInfo]) -> Vec<MessageGroup<'a>> {
|
||||
let mut result = Vec::new();
|
||||
let mut last_day: Option<i64> = None;
|
||||
let mut last_sender: Option<(bool, String)> = None; // (is_outgoing, sender_name)
|
||||
let mut album_acc: Vec<&MessageInfo> = Vec::new();
|
||||
|
||||
/// Сбрасывает аккумулятор альбома в результат
|
||||
fn flush_album<'a>(acc: &mut Vec<&'a MessageInfo>, result: &mut Vec<MessageGroup<'a>>) {
|
||||
if acc.is_empty() {
|
||||
return;
|
||||
}
|
||||
if acc.len() >= 2 {
|
||||
result.push(MessageGroup::Album(std::mem::take(acc)));
|
||||
} else {
|
||||
// Одно сообщение — не альбом
|
||||
result.push(MessageGroup::Message(acc.remove(0)));
|
||||
}
|
||||
}
|
||||
|
||||
for msg in messages {
|
||||
// Проверяем, нужно ли добавить разделитель даты
|
||||
let msg_day = get_day(msg.date());
|
||||
|
||||
if last_day != Some(msg_day) {
|
||||
// Flush аккумулятор перед разделителем даты
|
||||
flush_album(&mut album_acc, &mut result);
|
||||
// Добавляем разделитель даты
|
||||
result.push(MessageGroup::DateSeparator(msg.date()));
|
||||
last_day = Some(msg_day);
|
||||
last_sender = None; // Сбрасываем отправителя при смене дня
|
||||
}
|
||||
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
let current_sender = (msg.is_outgoing(), sender_name.clone());
|
||||
|
||||
// Проверяем, нужно ли показать заголовок отправителя
|
||||
let show_sender_header = last_sender.as_ref() != Some(¤t_sender);
|
||||
|
||||
if show_sender_header {
|
||||
// Flush аккумулятор перед сменой отправителя
|
||||
flush_album(&mut album_acc, &mut result);
|
||||
result.push(MessageGroup::SenderHeader { is_outgoing: msg.is_outgoing(), sender_name });
|
||||
last_sender = Some(current_sender);
|
||||
}
|
||||
|
||||
// Проверяем, является ли сообщение частью альбома
|
||||
let album_id = msg.media_album_id();
|
||||
if album_id != 0 {
|
||||
// Проверяем, совпадает ли album_id с текущим аккумулятором
|
||||
if let Some(first) = album_acc.first() {
|
||||
if first.media_album_id() == album_id {
|
||||
// Тот же альбом — добавляем
|
||||
album_acc.push(msg);
|
||||
continue;
|
||||
} else {
|
||||
// Другой альбом — flush старый, начинаем новый
|
||||
flush_album(&mut album_acc, &mut result);
|
||||
album_acc.push(msg);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Аккумулятор пуст — начинаем новый альбом
|
||||
album_acc.push(msg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Обычное сообщение (не альбом) — flush аккумулятор
|
||||
flush_album(&mut album_acc, &mut result);
|
||||
result.push(MessageGroup::Message(msg));
|
||||
}
|
||||
|
||||
// Flush оставшийся аккумулятор
|
||||
flush_album(&mut album_acc, &mut result);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tdlib::types::MessageBuilder;
|
||||
use crate::types::MessageId;
|
||||
|
||||
#[test]
|
||||
fn test_group_messages_by_date() {
|
||||
// Создаём сообщения с разными датами
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Message 1")
|
||||
.date(1609459200) // 2021-01-01 00:00:00 UTC
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Alice")
|
||||
.text("Message 2")
|
||||
.date(1609545600) // 2021-01-02 00:00:00 UTC
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// Должно быть: DateSep, SenderHeader, Message, DateSep, SenderHeader, Message
|
||||
assert_eq!(grouped.len(), 6);
|
||||
|
||||
assert!(matches!(grouped[0], MessageGroup::DateSeparator(_)));
|
||||
assert!(matches!(grouped[1], MessageGroup::SenderHeader { .. }));
|
||||
assert!(matches!(grouped[2], MessageGroup::Message(_)));
|
||||
assert!(matches!(grouped[3], MessageGroup::DateSeparator(_)));
|
||||
assert!(matches!(grouped[4], MessageGroup::SenderHeader { .. }));
|
||||
assert!(matches!(grouped[5], MessageGroup::Message(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_group_messages_by_sender() {
|
||||
// Создаём сообщения от разных отправителей
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Message 1")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Alice")
|
||||
.text("Message 2")
|
||||
.date(1609459300) // +100 секунд, тот же день
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let msg3 = MessageBuilder::new(MessageId::new(3))
|
||||
.sender_name("Bob")
|
||||
.text("Message 3")
|
||||
.date(1609459400)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2, msg3];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// Должно быть: DateSep, SenderHeader(Alice), Message, Message, SenderHeader(Bob), Message
|
||||
assert_eq!(grouped.len(), 6);
|
||||
|
||||
assert!(matches!(grouped[0], MessageGroup::DateSeparator(_)));
|
||||
|
||||
if let MessageGroup::SenderHeader { sender_name, .. } = &grouped[1] {
|
||||
assert_eq!(sender_name, "Alice");
|
||||
} else {
|
||||
panic!("Expected SenderHeader");
|
||||
}
|
||||
|
||||
assert!(matches!(grouped[2], MessageGroup::Message(_)));
|
||||
assert!(matches!(grouped[3], MessageGroup::Message(_)));
|
||||
|
||||
if let MessageGroup::SenderHeader { sender_name, .. } = &grouped[4] {
|
||||
assert_eq!(sender_name, "Bob");
|
||||
} else {
|
||||
panic!("Expected SenderHeader");
|
||||
}
|
||||
|
||||
assert!(matches!(grouped[5], MessageGroup::Message(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_group_outgoing_vs_incoming() {
|
||||
// Проверяем группировку исходящих и входящих сообщений
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Hello")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Me")
|
||||
.text("Hi")
|
||||
.date(1609459300)
|
||||
.outgoing()
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// Должно быть: DateSep, SenderHeader(Alice), Message, SenderHeader(Me), Message
|
||||
assert_eq!(grouped.len(), 5);
|
||||
|
||||
if let MessageGroup::SenderHeader { is_outgoing, sender_name } = &grouped[1] {
|
||||
assert!(!*is_outgoing);
|
||||
assert_eq!(sender_name, "Alice");
|
||||
} else {
|
||||
panic!("Expected SenderHeader");
|
||||
}
|
||||
|
||||
if let MessageGroup::SenderHeader { is_outgoing, sender_name } = &grouped[3] {
|
||||
assert!(*is_outgoing);
|
||||
assert_eq!(sender_name, "Вы");
|
||||
} else {
|
||||
panic!("Expected SenderHeader");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_messages() {
|
||||
let messages: Vec<MessageInfo> = vec![];
|
||||
let grouped = group_messages(&messages);
|
||||
assert_eq!(grouped.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_message() {
|
||||
let msg = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Single message")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let messages = vec![msg];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// Должно быть: DateSep, SenderHeader, Message
|
||||
assert_eq!(grouped.len(), 3);
|
||||
assert!(matches!(grouped[0], MessageGroup::DateSeparator(_)));
|
||||
assert!(matches!(grouped[1], MessageGroup::SenderHeader { .. }));
|
||||
assert!(matches!(grouped[2], MessageGroup::Message(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_album_grouping_two_photos() {
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Photo 1")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.media_album_id(12345)
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Alice")
|
||||
.text("Photo 2")
|
||||
.date(1609459201)
|
||||
.incoming()
|
||||
.media_album_id(12345)
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// DateSep, SenderHeader, Album
|
||||
assert_eq!(grouped.len(), 3);
|
||||
assert!(matches!(grouped[0], MessageGroup::DateSeparator(_)));
|
||||
assert!(matches!(grouped[1], MessageGroup::SenderHeader { .. }));
|
||||
if let MessageGroup::Album(album) = &grouped[2] {
|
||||
assert_eq!(album.len(), 2);
|
||||
assert_eq!(album[0].id(), MessageId::new(1));
|
||||
assert_eq!(album[1].id(), MessageId::new(2));
|
||||
} else {
|
||||
panic!("Expected Album, got {:?}", grouped[2]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_album_single_photo_not_album() {
|
||||
// Одно сообщение с album_id → не альбом, обычное сообщение
|
||||
let msg = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Single photo")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.media_album_id(12345)
|
||||
.build();
|
||||
|
||||
let messages = vec![msg];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// DateSep, SenderHeader, Message (не Album)
|
||||
assert_eq!(grouped.len(), 3);
|
||||
assert!(matches!(grouped[2], MessageGroup::Message(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_album_with_regular_messages() {
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Text message")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Alice")
|
||||
.text("Photo 1")
|
||||
.date(1609459201)
|
||||
.incoming()
|
||||
.media_album_id(100)
|
||||
.build();
|
||||
|
||||
let msg3 = MessageBuilder::new(MessageId::new(3))
|
||||
.sender_name("Alice")
|
||||
.text("Photo 2")
|
||||
.date(1609459202)
|
||||
.incoming()
|
||||
.media_album_id(100)
|
||||
.build();
|
||||
|
||||
let msg4 = MessageBuilder::new(MessageId::new(4))
|
||||
.sender_name("Alice")
|
||||
.text("After album")
|
||||
.date(1609459203)
|
||||
.incoming()
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2, msg3, msg4];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// DateSep, SenderHeader, Message, Album, Message
|
||||
assert_eq!(grouped.len(), 5);
|
||||
assert!(matches!(grouped[2], MessageGroup::Message(_)));
|
||||
assert!(matches!(grouped[3], MessageGroup::Album(_)));
|
||||
assert!(matches!(grouped[4], MessageGroup::Message(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_different_albums() {
|
||||
let msg1 = MessageBuilder::new(MessageId::new(1))
|
||||
.sender_name("Alice")
|
||||
.text("Album 1 - Photo 1")
|
||||
.date(1609459200)
|
||||
.incoming()
|
||||
.media_album_id(100)
|
||||
.build();
|
||||
|
||||
let msg2 = MessageBuilder::new(MessageId::new(2))
|
||||
.sender_name("Alice")
|
||||
.text("Album 1 - Photo 2")
|
||||
.date(1609459201)
|
||||
.incoming()
|
||||
.media_album_id(100)
|
||||
.build();
|
||||
|
||||
let msg3 = MessageBuilder::new(MessageId::new(3))
|
||||
.sender_name("Alice")
|
||||
.text("Album 2 - Photo 1")
|
||||
.date(1609459202)
|
||||
.incoming()
|
||||
.media_album_id(200)
|
||||
.build();
|
||||
|
||||
let msg4 = MessageBuilder::new(MessageId::new(4))
|
||||
.sender_name("Alice")
|
||||
.text("Album 2 - Photo 2")
|
||||
.date(1609459203)
|
||||
.incoming()
|
||||
.media_album_id(200)
|
||||
.build();
|
||||
|
||||
let messages = vec![msg1, msg2, msg3, msg4];
|
||||
let grouped = group_messages(&messages);
|
||||
|
||||
// DateSep, SenderHeader, Album(2), Album(2)
|
||||
assert_eq!(grouped.len(), 4);
|
||||
if let MessageGroup::Album(a1) = &grouped[2] {
|
||||
assert_eq!(a1.len(), 2);
|
||||
assert_eq!(a1[0].media_album_id(), 100);
|
||||
} else {
|
||||
panic!("Expected first Album");
|
||||
}
|
||||
if let MessageGroup::Album(a2) = &grouped[3] {
|
||||
assert_eq!(a2.len(), 2);
|
||||
assert_eq!(a2[0].media_album_id(), 200);
|
||||
} else {
|
||||
panic!("Expected second Album");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user