Files
telegram-tui/src/message_grouping.rs
Mikhail Kilin 0ca3da54e7 refactor: extract message grouping logic (P3.9)
- 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>
2026-01-31 23:30:41 +03:00

250 lines
8.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Модуль для группировки сообщений по дате и отправителю
//!
//! Предоставляет функции для логической группировки сообщений
//! перед отображением, отделяя логику группировки от рендеринга.
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(&current_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(_)));
}
}