This commit is contained in:
Mikhail Kilin
2026-01-18 14:49:31 +03:00
parent d701464fde
commit b6d9291864
29 changed files with 3920 additions and 833 deletions

View File

@@ -1,170 +1,17 @@
use crate::app::App;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, Paragraph},
Frame,
};
mod loading;
mod auth;
mod main_screen;
mod chat_list;
mod messages;
mod footer;
pub fn draw(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
])
.split(f.area());
use ratatui::Frame;
use crate::app::{App, AppScreen};
draw_tabs(f, app, chunks[0]);
let main_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
.split(chunks[1]);
draw_chat_list(f, app, main_chunks[0]);
draw_messages(f, app, main_chunks[1]);
draw_status_bar(f, app, chunks[2]);
}
fn draw_tabs(f: &mut Frame, app: &App, area: Rect) {
let tabs: Vec<Span> = app
.tabs
.iter()
.enumerate()
.map(|(i, t)| {
let style = if i == app.selected_tab {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::White)
};
Span::styled(format!(" {}:{} ", i + 1, t), style)
})
.collect();
let tabs_line = Line::from(tabs);
let tabs_paragraph = Paragraph::new(tabs_line).block(
Block::default()
.borders(Borders::ALL)
.title("Telegram TUI"),
);
f.render_widget(tabs_paragraph, area);
}
fn draw_chat_list(f: &mut Frame, app: &App, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(0)])
.split(area);
let search = Paragraph::new(format!("🔍 {}", app.search_query))
.block(Block::default().borders(Borders::ALL));
f.render_widget(search, chunks[0]);
let items: Vec<ListItem> = app
.chats
.iter()
.enumerate()
.map(|(i, chat)| {
let pin_icon = if chat.is_pinned { "📌 " } else { " " };
let unread_badge = if chat.unread_count > 0 {
format!(" ({})", chat.unread_count)
} else {
String::new()
};
let content = format!("{}{}{}", pin_icon, chat.name, unread_badge);
let style = if Some(i) == app.selected_chat {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::REVERSED)
} else if chat.unread_count > 0 {
Style::default().fg(Color::Cyan)
} else {
Style::default()
};
ListItem::new(content).style(style)
})
.collect();
let list = List::new(items).block(Block::default().borders(Borders::ALL));
f.render_widget(list, chunks[1]);
}
fn draw_messages(f: &mut Frame, app: &App, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
])
.split(area);
let header = Paragraph::new(app.get_current_chat_name()).block(
Block::default()
.borders(Borders::ALL)
.style(Style::default().fg(Color::White)),
);
f.render_widget(header, chunks[0]);
let mut message_lines: Vec<Line> = vec![];
for msg in &app.messages {
message_lines.push(Line::from(""));
let time_and_name = if msg.is_outgoing {
let status = match msg.read_status {
2 => "✓✓",
1 => "",
_ => "",
};
format!("{} ────────────────────────────────────── {} {}",
msg.sender, msg.time, status)
} else {
format!("{} ──────────────────────────────────────── {}",
msg.sender, msg.time)
};
let style = if msg.is_outgoing {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::Cyan)
};
message_lines.push(Line::from(Span::styled(time_and_name, style)));
message_lines.push(Line::from(msg.text.clone()));
pub fn render(f: &mut Frame, app: &mut App) {
match app.screen {
AppScreen::Loading => loading::render(f, app),
AppScreen::Auth => auth::render(f, app),
AppScreen::Main => main_screen::render(f, app),
}
let messages = Paragraph::new(message_lines)
.block(Block::default().borders(Borders::ALL))
.style(Style::default().fg(Color::White));
f.render_widget(messages, chunks[1]);
let input = Paragraph::new(format!("> {}_", app.input))
.block(Block::default().borders(Borders::ALL));
f.render_widget(input, chunks[2]);
}
fn draw_status_bar(f: &mut Frame, _app: &App, area: Rect) {
let status_text = " Esc: Back | Enter: Open | ^R: Reply | ^E: Edit | ^D: Delete";
let status = Paragraph::new(status_text)
.style(Style::default().fg(Color::Gray))
.block(
Block::default()
.borders(Borders::TOP)
.title("[User: Online]"),
);
f.render_widget(status, area);
}