refactor: restructure MessageInfo with logical field grouping (P2.6)
Сгруппированы 16 плоских полей MessageInfo в 4 логические структуры для улучшения организации кода и maintainability. Новые структуры: - MessageMetadata: id, sender_name, date, edit_date - MessageContent: text, entities - MessageState: is_outgoing, is_read, can_be_edited, can_be_deleted_* - MessageInteractions: reply_to, forward_from, reactions Изменения: - Добавлены 4 новые структуры в tdlib/types.rs - Обновлена MessageInfo для использования новых структур - Добавлен конструктор MessageInfo::new() для удобного создания - Добавлены getter методы (id(), text(), sender_name() и др.) для удобного доступа - Обновлены все места создания MessageInfo (convert_message) - Обновлены все места использования (~200+ обращений): * ui/messages.rs: рендеринг сообщений * app/mod.rs: логика приложения * input/main_input.rs: обработка ввода и копирование * tdlib/client.rs: обработка updates * Все тестовые файлы (14 файлов) Преимущества: - Логическая группировка данных - Проще понимать структуру сообщения - Легче добавлять новые поля в будущем - Улучшенная читаемость кода Статус: Priority 2 теперь 80% (4/5 задач) - ✅ Error enum - ✅ Config validation - ✅ Newtype для ID - ✅ MessageInfo реструктуризация - ⏳ MessageBuilder pattern Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -420,13 +420,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Pinned bar (если есть закреплённое сообщение)
|
||||
if let Some(pinned_msg) = &app.td_client.current_pinned_message() {
|
||||
let pinned_preview: String = pinned_msg.content.chars().take(40).collect();
|
||||
let ellipsis = if pinned_msg.content.chars().count() > 40 {
|
||||
let pinned_preview: String = pinned_msg.text().chars().take(40).collect();
|
||||
let ellipsis = if pinned_msg.text().chars().count() > 40 {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let pinned_datetime = crate::utils::format_datetime(pinned_msg.date);
|
||||
let pinned_datetime = crate::utils::format_datetime(pinned_msg.date());
|
||||
let pinned_text = format!("📌 {} {}{}", pinned_datetime, pinned_preview, ellipsis);
|
||||
let pinned_hint = "Ctrl+P";
|
||||
|
||||
@@ -454,26 +454,26 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let mut last_sender: Option<(bool, String)> = None; // (is_outgoing, sender_name)
|
||||
|
||||
// ID выбранного сообщения для подсветки
|
||||
let selected_msg_id = app.get_selected_message().map(|m| m.id);
|
||||
let selected_msg_id = app.get_selected_message().map(|m| m.id());
|
||||
// Номер строки, где начинается выбранное сообщение (для автоскролла)
|
||||
let mut selected_msg_line: Option<usize> = None;
|
||||
|
||||
for msg in app.td_client.current_chat_messages() {
|
||||
// Проверяем, выбрано ли это сообщение
|
||||
let is_selected = selected_msg_id == Some(msg.id);
|
||||
let is_selected = selected_msg_id == Some(msg.id());
|
||||
|
||||
// Запоминаем строку начала выбранного сообщения
|
||||
if is_selected {
|
||||
selected_msg_line = Some(lines.len());
|
||||
}
|
||||
// Проверяем, нужно ли добавить разделитель даты
|
||||
let msg_day = get_day(msg.date);
|
||||
let msg_day = get_day(msg.date());
|
||||
if last_day != Some(msg_day) {
|
||||
if last_day.is_some() {
|
||||
lines.push(Line::from("")); // Пустая строка перед разделителем
|
||||
}
|
||||
// Добавляем разделитель даты по центру
|
||||
let date_str = format_date(msg.date);
|
||||
let date_str = format_date(msg.date());
|
||||
let date_line = format!("──────── {} ────────", date_str);
|
||||
let padding = content_width.saturating_sub(date_line.chars().count()) / 2;
|
||||
lines.push(Line::from(vec![
|
||||
@@ -485,13 +485,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
last_sender = None; // Сбрасываем отправителя при смене дня
|
||||
}
|
||||
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name.clone()
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
let current_sender = (msg.is_outgoing, sender_name.clone());
|
||||
let current_sender = (msg.is_outgoing(), sender_name.clone());
|
||||
|
||||
// Проверяем, нужно ли показать заголовок отправителя
|
||||
let show_sender_header = last_sender.as_ref() != Some(¤t_sender);
|
||||
@@ -502,7 +502,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
lines.push(Line::from(""));
|
||||
}
|
||||
|
||||
let sender_style = if msg.is_outgoing {
|
||||
let sender_style = if msg.is_outgoing() {
|
||||
Style::default()
|
||||
.fg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
@@ -512,7 +512,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD)
|
||||
};
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Заголовок "Вы" справа
|
||||
let header_text = format!("{} ────────────────", sender_name);
|
||||
let header_len = header_text.chars().count();
|
||||
@@ -534,12 +534,12 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Форматируем время (HH:MM) с учётом timezone из config
|
||||
let time = format_timestamp_with_tz(msg.date, &app.config.general.timezone);
|
||||
let time = format_timestamp_with_tz(msg.date(), &app.config.general.timezone);
|
||||
|
||||
// Цвет сообщения (из config или жёлтый если выбрано)
|
||||
let msg_color = if is_selected {
|
||||
app.config.parse_color(&app.config.colors.selected_message)
|
||||
} else if msg.is_outgoing {
|
||||
} else if msg.is_outgoing() {
|
||||
app.config.parse_color(&app.config.colors.outgoing_message)
|
||||
} else {
|
||||
app.config.parse_color(&app.config.colors.incoming_message)
|
||||
@@ -550,11 +550,11 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let marker_len = selection_marker.chars().count();
|
||||
|
||||
// Отображаем forward если есть
|
||||
if let Some(forward) = &msg.forward_from {
|
||||
if let Some(forward) = msg.forward_from() {
|
||||
let forward_line = format!("↪ Переслано от {}", forward.sender_name);
|
||||
let forward_len = forward_line.chars().count();
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Forward справа для исходящих
|
||||
let padding = content_width.saturating_sub(forward_len + 1);
|
||||
lines.push(Line::from(vec![
|
||||
@@ -571,7 +571,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Отображаем reply если есть
|
||||
if let Some(reply) = &msg.reply_to {
|
||||
if let Some(reply) = msg.reply_to() {
|
||||
let reply_text: String = reply.text.chars().take(40).collect();
|
||||
let ellipsis = if reply.text.chars().count() > 40 {
|
||||
"..."
|
||||
@@ -581,7 +581,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let reply_line = format!("┌ {}: {}{}", reply.sender_name, reply_text, ellipsis);
|
||||
let reply_len = reply_line.chars().count();
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Reply справа для исходящих
|
||||
let padding = content_width.saturating_sub(reply_len + 1);
|
||||
lines.push(Line::from(vec![
|
||||
@@ -597,17 +597,17 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
}
|
||||
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Исходящие: справа, формат "текст (HH:MM ✎ ✓✓)"
|
||||
let read_mark = if msg.is_read { "✓✓" } else { "✓" };
|
||||
let edit_mark = if msg.edit_date > 0 { "✎ " } else { "" };
|
||||
let read_mark = if msg.is_read() { "✓✓" } else { "✓" };
|
||||
let edit_mark = if msg.is_edited() { "✎ " } else { "" };
|
||||
let time_mark = format!("({} {}{})", time, edit_mark, read_mark);
|
||||
let time_mark_len = time_mark.chars().count() + 1; // +1 для пробела
|
||||
|
||||
// Максимальная ширина для текста сообщения (оставляем место для time_mark и маркера)
|
||||
let max_msg_width = content_width.saturating_sub(time_mark_len + marker_len + 2);
|
||||
|
||||
let wrapped_lines = wrap_text_with_offsets(&msg.content, max_msg_width);
|
||||
let wrapped_lines = wrap_text_with_offsets(msg.text(), max_msg_width);
|
||||
let total_wrapped = wrapped_lines.len();
|
||||
|
||||
for (i, wrapped) in wrapped_lines.into_iter().enumerate() {
|
||||
@@ -616,7 +616,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Получаем entities для этой строки
|
||||
let line_entities = adjust_entities_for_substring(
|
||||
&msg.entities,
|
||||
msg.entities(),
|
||||
wrapped.start_offset,
|
||||
line_len,
|
||||
);
|
||||
@@ -662,21 +662,21 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
} else {
|
||||
// Входящие: слева, формат "(HH:MM ✎) текст"
|
||||
let edit_mark = if msg.edit_date > 0 { " ✎" } else { "" };
|
||||
let edit_mark = if msg.is_edited() { " ✎" } else { "" };
|
||||
let time_str = format!("({}{})", time, edit_mark);
|
||||
let time_prefix_len = time_str.chars().count() + 2; // " (HH:MM) "
|
||||
|
||||
// Максимальная ширина для текста
|
||||
let max_msg_width = content_width.saturating_sub(time_prefix_len + 1);
|
||||
|
||||
let wrapped_lines = wrap_text_with_offsets(&msg.content, max_msg_width);
|
||||
let wrapped_lines = wrap_text_with_offsets(msg.text(), max_msg_width);
|
||||
|
||||
for (i, wrapped) in wrapped_lines.into_iter().enumerate() {
|
||||
let line_len = wrapped.text.chars().count();
|
||||
|
||||
// Получаем entities для этой строки
|
||||
let line_entities = adjust_entities_for_substring(
|
||||
&msg.entities,
|
||||
msg.entities(),
|
||||
wrapped.start_offset,
|
||||
line_len,
|
||||
);
|
||||
@@ -714,10 +714,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Отображаем реакции под сообщением
|
||||
if !msg.reactions.is_empty() {
|
||||
if !msg.reactions().is_empty() {
|
||||
let mut reaction_spans = vec![];
|
||||
|
||||
for reaction in &msg.reactions {
|
||||
for reaction in &msg.reactions() {
|
||||
if !reaction_spans.is_empty() {
|
||||
reaction_spans.push(Span::raw(" "));
|
||||
}
|
||||
@@ -749,7 +749,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Выравниваем реакции в зависимости от типа сообщения
|
||||
if msg.is_outgoing {
|
||||
if msg.is_outgoing() {
|
||||
// Реакции справа для исходящих
|
||||
let reactions_text: String = reaction_spans
|
||||
.iter()
|
||||
@@ -815,8 +815,8 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let forward_preview = app
|
||||
.get_forwarding_message()
|
||||
.map(|m| {
|
||||
let text_preview: String = m.content.chars().take(40).collect();
|
||||
let ellipsis = if m.content.chars().count() > 40 {
|
||||
let text_preview: String = m.text().chars().take(40).collect();
|
||||
let ellipsis = if m.text().chars().count() > 40 {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
@@ -875,10 +875,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
let sender = if m.is_outgoing {
|
||||
"Вы"
|
||||
} else {
|
||||
&m.sender_name
|
||||
m.sender_name()
|
||||
};
|
||||
let text_preview: String = m.content.chars().take(30).collect();
|
||||
let ellipsis = if m.content.chars().count() > 30 {
|
||||
let text_preview: String = m.text().chars().take(30).collect();
|
||||
let ellipsis = if m.text().chars().count() > 30 {
|
||||
"..."
|
||||
} else {
|
||||
""
|
||||
@@ -1056,15 +1056,15 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Маркер выбора, имя и дата
|
||||
let marker = if is_selected { "▶ " } else { " " };
|
||||
let sender_color = if msg.is_outgoing {
|
||||
let sender_color = if msg.is_outgoing() {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::Cyan
|
||||
};
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name.clone()
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
lines.push(Line::from(vec![
|
||||
@@ -1081,7 +1081,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(
|
||||
format!("({})", crate::utils::format_datetime(msg.date)),
|
||||
format!("({})", crate::utils::format_datetime(msg.date())),
|
||||
Style::default().fg(Color::Gray),
|
||||
),
|
||||
]));
|
||||
@@ -1093,7 +1093,7 @@ fn render_search_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
Color::White
|
||||
};
|
||||
let max_width = content_width.saturating_sub(4);
|
||||
let wrapped = wrap_text_with_offsets(&msg.content, max_width);
|
||||
let wrapped = wrap_text_with_offsets(msg.text(), max_width);
|
||||
let wrapped_count = wrapped.len();
|
||||
|
||||
for wrapped_line in wrapped.into_iter().take(2) {
|
||||
@@ -1222,15 +1222,15 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
// Маркер выбора и имя отправителя
|
||||
let marker = if is_selected { "▶ " } else { " " };
|
||||
let sender_color = if msg.is_outgoing {
|
||||
let sender_color = if msg.is_outgoing() {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::Cyan
|
||||
};
|
||||
let sender_name = if msg.is_outgoing {
|
||||
let sender_name = if msg.is_outgoing() {
|
||||
"Вы".to_string()
|
||||
} else {
|
||||
msg.sender_name.clone()
|
||||
msg.sender_name().to_string()
|
||||
};
|
||||
|
||||
lines.push(Line::from(vec![
|
||||
@@ -1247,7 +1247,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(
|
||||
format!("({})", crate::utils::format_datetime(msg.date)),
|
||||
format!("({})", crate::utils::format_datetime(msg.date())),
|
||||
Style::default().fg(Color::Gray),
|
||||
),
|
||||
]));
|
||||
@@ -1259,7 +1259,7 @@ fn render_pinned_mode(f: &mut Frame, area: Rect, app: &App) {
|
||||
Color::White
|
||||
};
|
||||
let max_width = content_width.saturating_sub(4);
|
||||
let wrapped = wrap_text_with_offsets(&msg.content, max_width);
|
||||
let wrapped = wrap_text_with_offsets(msg.text(), max_width);
|
||||
let wrapped_count = wrapped.len();
|
||||
|
||||
for wrapped_line in wrapped.into_iter().take(3) {
|
||||
|
||||
Reference in New Issue
Block a user