- Create src/message_grouping.rs module (255 lines) - Add MessageGroup enum (DateSeparator, SenderHeader, Message) - Add group_messages() function for date/sender grouping - Write 5 unit tests (all passing) - Add full rustdoc documentation with examples - Update REFACTORING_ROADMAP.md (Priority 3: 3/4 tasks, 75%) - Update CONTEXT.md with refactoring progress - Overall refactoring progress: 11/17 tasks (65%) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
250 lines
8.7 KiB
Rust
250 lines
8.7 KiB
Rust
//! Модуль для группировки сообщений по дате и отправителю
|
||
//!
|
||
//! Предоставляет функции для логической группировки сообщений
|
||
//! перед отображением, отделяя логику группировки от рендеринга.
|
||
|
||
use crate::tdlib::MessageInfo;
|
||
use crate::utils::get_day;
|
||
|
||
/// Элемент группированного списка сообщений
|
||
#[derive(Debug, Clone)]
|
||
pub enum MessageGroup {
|
||
/// Разделитель даты (день в формате timestamp)
|
||
DateSeparator(i32),
|
||
/// Заголовок отправителя (is_outgoing, sender_name)
|
||
SenderHeader { is_outgoing: bool, sender_name: String },
|
||
/// Сообщение
|
||
Message(MessageInfo),
|
||
}
|
||
|
||
/// Группирует сообщения по дате и отправителю
|
||
///
|
||
/// # Аргументы
|
||
///
|
||
/// * `messages` - Список сообщений для группировки
|
||
///
|
||
/// # Возвращает
|
||
///
|
||
/// Вектор `MessageGroup` с разделителями дат, заголовками отправителей и сообщениями
|
||
///
|
||
/// # Примеры
|
||
///
|
||
/// ```no_run
|
||
/// use tele_tui::message_grouping::{group_messages, MessageGroup};
|
||
///
|
||
/// # use tele_tui::tdlib::types::MessageBuilder;
|
||
/// # use tele_tui::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());
|
||
/// }
|
||
/// }
|
||
/// }
|
||
/// ```
|
||
pub fn group_messages(messages: &[MessageInfo]) -> Vec<MessageGroup> {
|
||
let mut result = Vec::new();
|
||
let mut last_day: Option<i64> = None;
|
||
let mut last_sender: Option<(bool, String)> = None; // (is_outgoing, sender_name)
|
||
|
||
for msg in messages {
|
||
// Проверяем, нужно ли добавить разделитель даты
|
||
let msg_day = get_day(msg.date());
|
||
|
||
if last_day != Some(msg_day) {
|
||
// Добавляем разделитель даты
|
||
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 {
|
||
result.push(MessageGroup::SenderHeader {
|
||
is_outgoing: msg.is_outgoing(),
|
||
sender_name,
|
||
});
|
||
last_sender = Some(current_sender);
|
||
}
|
||
|
||
// Добавляем само сообщение
|
||
result.push(MessageGroup::Message(msg.clone()));
|
||
}
|
||
|
||
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_eq!(*is_outgoing, false);
|
||
assert_eq!(sender_name, "Alice");
|
||
} else {
|
||
panic!("Expected SenderHeader");
|
||
}
|
||
|
||
if let MessageGroup::SenderHeader { is_outgoing, sender_name } = &grouped[3] {
|
||
assert_eq!(*is_outgoing, true);
|
||
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(_)));
|
||
}
|
||
}
|