Some checks failed
ci/woodpecker/pr/check Pipeline was successful
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
- Add #[allow(unused_imports)] on pub re-exports used only by lib/tests - Add #[allow(dead_code)] on public API items unused in binary target - Fix collapsible_if, redundant_closure, unnecessary_map_or in main.rs - Prefix unused test variables with underscore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
725 lines
22 KiB
Rust
725 lines
22 KiB
Rust
use tdlib_rs::enums::TextEntityType;
|
||
use tdlib_rs::types::TextEntity;
|
||
|
||
use crate::types::{ChatId, MessageId};
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct ChatInfo {
|
||
pub id: ChatId,
|
||
pub title: String,
|
||
pub username: Option<String>,
|
||
pub last_message: String,
|
||
pub last_message_date: i32,
|
||
pub unread_count: i32,
|
||
/// Количество непрочитанных упоминаний (@)
|
||
pub unread_mention_count: i32,
|
||
pub is_pinned: bool,
|
||
pub order: i64,
|
||
/// ID последнего прочитанного исходящего сообщения (для галочек)
|
||
pub last_read_outbox_message_id: MessageId,
|
||
/// ID папок, в которых находится чат
|
||
pub folder_ids: Vec<i32>,
|
||
/// Чат замьючен (уведомления отключены)
|
||
pub is_muted: bool,
|
||
/// Черновик сообщения
|
||
pub draft_text: Option<String>,
|
||
}
|
||
|
||
/// Информация о сообщении, на которое отвечают
|
||
#[derive(Debug, Clone)]
|
||
pub struct ReplyInfo {
|
||
/// ID сообщения, на которое отвечают
|
||
pub message_id: MessageId,
|
||
/// Имя отправителя оригинального сообщения
|
||
pub sender_name: String,
|
||
/// Текст оригинального сообщения (превью)
|
||
pub text: String,
|
||
}
|
||
|
||
/// Информация о пересланном сообщении
|
||
#[derive(Debug, Clone)]
|
||
pub struct ForwardInfo {
|
||
/// Имя оригинального отправителя
|
||
pub sender_name: String,
|
||
}
|
||
|
||
/// Информация о реакции на сообщение
|
||
#[derive(Debug, Clone)]
|
||
pub struct ReactionInfo {
|
||
/// Эмодзи реакции (например, "👍")
|
||
pub emoji: String,
|
||
/// Количество людей, поставивших эту реакцию
|
||
pub count: i32,
|
||
/// Поставил ли текущий пользователь эту реакцию
|
||
pub is_chosen: bool,
|
||
}
|
||
|
||
/// Информация о медиа-контенте сообщения
|
||
#[derive(Debug, Clone)]
|
||
pub enum MediaInfo {
|
||
Photo(PhotoInfo),
|
||
Voice(VoiceInfo),
|
||
}
|
||
|
||
/// Информация о фотографии в сообщении
|
||
#[derive(Debug, Clone)]
|
||
pub struct PhotoInfo {
|
||
pub file_id: i32,
|
||
pub width: i32,
|
||
pub height: i32,
|
||
pub download_state: PhotoDownloadState,
|
||
}
|
||
|
||
/// Состояние загрузки фотографии
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone)]
|
||
pub enum PhotoDownloadState {
|
||
NotDownloaded,
|
||
Downloading,
|
||
Downloaded(String),
|
||
Error(String),
|
||
}
|
||
|
||
/// Информация о голосовом сообщении
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone)]
|
||
pub struct VoiceInfo {
|
||
pub file_id: i32,
|
||
pub duration: i32, // seconds
|
||
pub mime_type: String,
|
||
/// Waveform данные для визуализации (base64-encoded строка амплитуд)
|
||
pub waveform: String,
|
||
pub download_state: VoiceDownloadState,
|
||
}
|
||
|
||
/// Состояние загрузки голосового сообщения
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone)]
|
||
pub enum VoiceDownloadState {
|
||
NotDownloaded,
|
||
Downloading,
|
||
Downloaded(String), // path to cached OGG file
|
||
Error(String),
|
||
}
|
||
|
||
/// Метаданные сообщения (ID, отправитель, время)
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageMetadata {
|
||
pub id: MessageId,
|
||
pub sender_name: String,
|
||
pub date: i32,
|
||
/// Дата редактирования (0 если не редактировалось)
|
||
pub edit_date: i32,
|
||
/// ID медиа-альбома (0 если не часть альбома)
|
||
pub media_album_id: i64,
|
||
}
|
||
|
||
/// Контент сообщения (текст и форматирование)
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageContent {
|
||
pub text: String,
|
||
/// Сущности форматирования (bold, italic, code и т.д.)
|
||
pub entities: Vec<TextEntity>,
|
||
/// Медиа-контент (фото, видео и т.д.)
|
||
pub media: Option<MediaInfo>,
|
||
}
|
||
|
||
/// Состояние и права доступа к сообщению
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageState {
|
||
pub is_outgoing: bool,
|
||
pub is_read: bool,
|
||
/// Можно ли редактировать сообщение
|
||
pub can_be_edited: bool,
|
||
/// Можно ли удалить только для себя
|
||
pub can_be_deleted_only_for_self: bool,
|
||
/// Можно ли удалить для всех
|
||
pub can_be_deleted_for_all_users: bool,
|
||
}
|
||
|
||
/// Взаимодействия с сообщением (reply, forward, reactions)
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageInteractions {
|
||
/// Информация о reply (если это ответ на сообщение)
|
||
pub reply_to: Option<ReplyInfo>,
|
||
/// Информация о forward (если сообщение переслано)
|
||
pub forward_from: Option<ForwardInfo>,
|
||
/// Реакции на сообщение
|
||
pub reactions: Vec<ReactionInfo>,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageInfo {
|
||
pub metadata: MessageMetadata,
|
||
pub content: MessageContent,
|
||
pub state: MessageState,
|
||
pub interactions: MessageInteractions,
|
||
}
|
||
|
||
impl MessageInfo {
|
||
/// Создать новое сообщение
|
||
#[allow(clippy::too_many_arguments)]
|
||
pub fn new(
|
||
id: MessageId,
|
||
sender_name: String,
|
||
is_outgoing: bool,
|
||
content: String,
|
||
entities: Vec<TextEntity>,
|
||
date: i32,
|
||
edit_date: i32,
|
||
is_read: bool,
|
||
can_be_edited: bool,
|
||
can_be_deleted_only_for_self: bool,
|
||
can_be_deleted_for_all_users: bool,
|
||
reply_to: Option<ReplyInfo>,
|
||
forward_from: Option<ForwardInfo>,
|
||
reactions: Vec<ReactionInfo>,
|
||
) -> Self {
|
||
Self {
|
||
metadata: MessageMetadata {
|
||
id,
|
||
sender_name,
|
||
date,
|
||
edit_date,
|
||
media_album_id: 0,
|
||
},
|
||
content: MessageContent { text: content, entities, media: None },
|
||
state: MessageState {
|
||
is_outgoing,
|
||
is_read,
|
||
can_be_edited,
|
||
can_be_deleted_only_for_self,
|
||
can_be_deleted_for_all_users,
|
||
},
|
||
interactions: MessageInteractions { reply_to, forward_from, reactions },
|
||
}
|
||
}
|
||
|
||
// Удобные getter'ы для частых операций
|
||
pub fn id(&self) -> MessageId {
|
||
self.metadata.id
|
||
}
|
||
|
||
pub fn sender_name(&self) -> &str {
|
||
&self.metadata.sender_name
|
||
}
|
||
|
||
pub fn date(&self) -> i32 {
|
||
self.metadata.date
|
||
}
|
||
|
||
pub fn is_edited(&self) -> bool {
|
||
self.metadata.edit_date > 0
|
||
}
|
||
|
||
pub fn media_album_id(&self) -> i64 {
|
||
self.metadata.media_album_id
|
||
}
|
||
|
||
pub fn text(&self) -> &str {
|
||
&self.content.text
|
||
}
|
||
|
||
pub fn entities(&self) -> &[TextEntity] {
|
||
&self.content.entities
|
||
}
|
||
|
||
pub fn is_outgoing(&self) -> bool {
|
||
self.state.is_outgoing
|
||
}
|
||
|
||
pub fn is_read(&self) -> bool {
|
||
self.state.is_read
|
||
}
|
||
|
||
pub fn can_be_edited(&self) -> bool {
|
||
self.state.can_be_edited
|
||
}
|
||
|
||
pub fn can_be_deleted_only_for_self(&self) -> bool {
|
||
self.state.can_be_deleted_only_for_self
|
||
}
|
||
|
||
pub fn can_be_deleted_for_all_users(&self) -> bool {
|
||
self.state.can_be_deleted_for_all_users
|
||
}
|
||
|
||
/// Checks if the message contains a mention (@username or user mention)
|
||
pub fn has_mention(&self) -> bool {
|
||
self.content.entities.iter().any(|entity| {
|
||
matches!(entity.r#type, TextEntityType::Mention | TextEntityType::MentionName(_))
|
||
})
|
||
}
|
||
|
||
/// Проверяет, содержит ли сообщение фото
|
||
pub fn has_photo(&self) -> bool {
|
||
matches!(self.content.media, Some(MediaInfo::Photo(_)))
|
||
}
|
||
|
||
/// Возвращает ссылку на PhotoInfo (если есть)
|
||
pub fn photo_info(&self) -> Option<&PhotoInfo> {
|
||
match &self.content.media {
|
||
Some(MediaInfo::Photo(info)) => Some(info),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Возвращает мутабельную ссылку на PhotoInfo (если есть)
|
||
pub fn photo_info_mut(&mut self) -> Option<&mut PhotoInfo> {
|
||
match &mut self.content.media {
|
||
Some(MediaInfo::Photo(info)) => Some(info),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Проверяет, содержит ли сообщение голосовое
|
||
pub fn has_voice(&self) -> bool {
|
||
matches!(self.content.media, Some(MediaInfo::Voice(_)))
|
||
}
|
||
|
||
/// Возвращает ссылку на VoiceInfo (если есть)
|
||
pub fn voice_info(&self) -> Option<&VoiceInfo> {
|
||
match &self.content.media {
|
||
Some(MediaInfo::Voice(info)) => Some(info),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Возвращает мутабельную ссылку на VoiceInfo (если есть)
|
||
#[allow(dead_code)]
|
||
pub fn voice_info_mut(&mut self) -> Option<&mut VoiceInfo> {
|
||
match &mut self.content.media {
|
||
Some(MediaInfo::Voice(info)) => Some(info),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
pub fn reply_to(&self) -> Option<&ReplyInfo> {
|
||
self.interactions.reply_to.as_ref()
|
||
}
|
||
|
||
pub fn forward_from(&self) -> Option<&ForwardInfo> {
|
||
self.interactions.forward_from.as_ref()
|
||
}
|
||
|
||
pub fn reactions(&self) -> &[ReactionInfo] {
|
||
&self.interactions.reactions
|
||
}
|
||
}
|
||
|
||
/// Builder для удобного создания MessageInfo с fluent API
|
||
///
|
||
/// # Примеры
|
||
///
|
||
/// ```
|
||
/// use tele_tui::tdlib::MessageBuilder;
|
||
/// use tele_tui::types::MessageId;
|
||
///
|
||
/// let message = MessageBuilder::new(MessageId::new(123))
|
||
/// .sender_name("Alice")
|
||
/// .text("Hello, world!")
|
||
/// .outgoing()
|
||
/// .date(1640000000)
|
||
/// .build();
|
||
/// ```
|
||
pub struct MessageBuilder {
|
||
id: MessageId,
|
||
sender_name: String,
|
||
is_outgoing: bool,
|
||
text: String,
|
||
entities: Vec<TextEntity>,
|
||
date: i32,
|
||
edit_date: i32,
|
||
is_read: bool,
|
||
can_be_edited: bool,
|
||
can_be_deleted_only_for_self: bool,
|
||
can_be_deleted_for_all_users: bool,
|
||
reply_to: Option<ReplyInfo>,
|
||
forward_from: Option<ForwardInfo>,
|
||
reactions: Vec<ReactionInfo>,
|
||
media: Option<MediaInfo>,
|
||
media_album_id: i64,
|
||
}
|
||
|
||
impl MessageBuilder {
|
||
/// Создать новый builder с обязательным ID сообщения
|
||
pub fn new(id: MessageId) -> Self {
|
||
Self {
|
||
id,
|
||
sender_name: String::new(),
|
||
is_outgoing: false,
|
||
text: String::new(),
|
||
entities: Vec::new(),
|
||
date: 0,
|
||
edit_date: 0,
|
||
is_read: false,
|
||
can_be_edited: false,
|
||
can_be_deleted_only_for_self: true,
|
||
can_be_deleted_for_all_users: false,
|
||
reply_to: None,
|
||
forward_from: None,
|
||
reactions: Vec::new(),
|
||
media: None,
|
||
media_album_id: 0,
|
||
}
|
||
}
|
||
|
||
/// Установить имя отправителя
|
||
pub fn sender_name(mut self, name: impl Into<String>) -> Self {
|
||
self.sender_name = name.into();
|
||
self
|
||
}
|
||
|
||
/// Пометить сообщение как исходящее
|
||
pub fn outgoing(mut self) -> Self {
|
||
self.is_outgoing = true;
|
||
self.can_be_edited = true;
|
||
self.can_be_deleted_for_all_users = true;
|
||
self
|
||
}
|
||
|
||
/// Пометить сообщение как входящее
|
||
pub fn incoming(mut self) -> Self {
|
||
self.is_outgoing = false;
|
||
self.can_be_edited = false;
|
||
self.can_be_deleted_for_all_users = false;
|
||
self
|
||
}
|
||
|
||
/// Установить текст сообщения
|
||
pub fn text(mut self, text: impl Into<String>) -> Self {
|
||
self.text = text.into();
|
||
self
|
||
}
|
||
|
||
/// Установить entities для форматирования
|
||
pub fn entities(mut self, entities: Vec<TextEntity>) -> Self {
|
||
self.entities = entities;
|
||
self
|
||
}
|
||
|
||
/// Установить дату сообщения (unix timestamp)
|
||
pub fn date(mut self, date: i32) -> Self {
|
||
self.date = date;
|
||
self
|
||
}
|
||
|
||
/// Установить дату редактирования (unix timestamp)
|
||
pub fn edit_date(mut self, edit_date: i32) -> Self {
|
||
self.edit_date = edit_date;
|
||
self
|
||
}
|
||
|
||
/// Пометить сообщение как прочитанное
|
||
pub fn read(mut self) -> Self {
|
||
self.is_read = true;
|
||
self
|
||
}
|
||
|
||
/// Пометить сообщение как непрочитанное
|
||
pub fn unread(mut self) -> Self {
|
||
self.is_read = false;
|
||
self
|
||
}
|
||
|
||
/// Разрешить редактирование
|
||
pub fn editable(mut self) -> Self {
|
||
self.can_be_edited = true;
|
||
self
|
||
}
|
||
|
||
/// Разрешить удаление только для себя
|
||
pub fn deletable_for_self(mut self) -> Self {
|
||
self.can_be_deleted_only_for_self = true;
|
||
self
|
||
}
|
||
|
||
/// Разрешить удаление для всех
|
||
pub fn deletable_for_all(mut self) -> Self {
|
||
self.can_be_deleted_for_all_users = true;
|
||
self
|
||
}
|
||
|
||
/// Установить информацию об ответе
|
||
pub fn reply_to(mut self, reply: ReplyInfo) -> Self {
|
||
self.reply_to = Some(reply);
|
||
self
|
||
}
|
||
|
||
/// Установить информацию о пересылке
|
||
pub fn forward_from(mut self, forward: ForwardInfo) -> Self {
|
||
self.forward_from = Some(forward);
|
||
self
|
||
}
|
||
|
||
/// Установить реакции
|
||
pub fn reactions(mut self, reactions: Vec<ReactionInfo>) -> Self {
|
||
self.reactions = reactions;
|
||
self
|
||
}
|
||
|
||
/// Установить медиа-контент
|
||
pub fn media(mut self, media: MediaInfo) -> Self {
|
||
self.media = Some(media);
|
||
self
|
||
}
|
||
|
||
/// Установить ID медиа-альбома
|
||
pub fn media_album_id(mut self, id: i64) -> Self {
|
||
self.media_album_id = id;
|
||
self
|
||
}
|
||
|
||
/// Построить MessageInfo из данных builder'а
|
||
pub fn build(self) -> MessageInfo {
|
||
let mut msg = MessageInfo::new(
|
||
self.id,
|
||
self.sender_name,
|
||
self.is_outgoing,
|
||
self.text,
|
||
self.entities,
|
||
self.date,
|
||
self.edit_date,
|
||
self.is_read,
|
||
self.can_be_edited,
|
||
self.can_be_deleted_only_for_self,
|
||
self.can_be_deleted_for_all_users,
|
||
self.reply_to,
|
||
self.forward_from,
|
||
self.reactions,
|
||
);
|
||
msg.content.media = self.media;
|
||
msg.metadata.media_album_id = self.media_album_id;
|
||
msg
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use crate::types::MessageId;
|
||
|
||
#[test]
|
||
fn test_message_builder_basic() {
|
||
let message = MessageBuilder::new(MessageId::new(123))
|
||
.sender_name("Alice")
|
||
.text("Hello, world!")
|
||
.date(1640000000)
|
||
.build();
|
||
|
||
assert_eq!(message.id(), MessageId::new(123));
|
||
assert_eq!(message.sender_name(), "Alice");
|
||
assert_eq!(message.text(), "Hello, world!");
|
||
assert_eq!(message.date(), 1640000000);
|
||
assert!(!message.is_outgoing());
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_builder_outgoing() {
|
||
let message = MessageBuilder::new(MessageId::new(456))
|
||
.sender_name("Me")
|
||
.text("Test message")
|
||
.outgoing()
|
||
.read()
|
||
.build();
|
||
|
||
assert!(message.is_outgoing());
|
||
assert!(message.is_read());
|
||
assert!(message.can_be_edited());
|
||
assert!(message.can_be_deleted_for_all_users());
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_builder_edited() {
|
||
let message = MessageBuilder::new(MessageId::new(789))
|
||
.text("Original text")
|
||
.date(1640000000)
|
||
.edit_date(1640000060)
|
||
.build();
|
||
|
||
assert!(message.is_edited());
|
||
assert_eq!(message.metadata.edit_date, 1640000060);
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_builder_with_reply() {
|
||
let reply = ReplyInfo {
|
||
message_id: MessageId::new(100),
|
||
sender_name: "Bob".to_string(),
|
||
text: "Original message".to_string(),
|
||
};
|
||
|
||
let message = MessageBuilder::new(MessageId::new(200))
|
||
.text("Reply text")
|
||
.reply_to(reply)
|
||
.build();
|
||
|
||
assert!(message.reply_to().is_some());
|
||
assert_eq!(message.reply_to().unwrap().sender_name, "Bob");
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_builder_with_reactions() {
|
||
let reaction = ReactionInfo {
|
||
emoji: "👍".to_string(), count: 5, is_chosen: true
|
||
};
|
||
|
||
let message = MessageBuilder::new(MessageId::new(300))
|
||
.text("Cool message")
|
||
.reactions(vec![reaction.clone()])
|
||
.build();
|
||
|
||
assert_eq!(message.reactions().len(), 1);
|
||
assert_eq!(message.reactions()[0].emoji, "👍");
|
||
assert_eq!(message.reactions()[0].count, 5);
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_builder_fluent_api() {
|
||
let message = MessageBuilder::new(MessageId::new(999))
|
||
.sender_name("Charlie")
|
||
.text("Complex message")
|
||
.date(1640000000)
|
||
.outgoing()
|
||
.read()
|
||
.editable()
|
||
.deletable_for_all()
|
||
.build();
|
||
|
||
assert_eq!(message.sender_name(), "Charlie");
|
||
assert_eq!(message.text(), "Complex message");
|
||
assert!(message.is_outgoing());
|
||
assert!(message.is_read());
|
||
assert!(message.can_be_edited());
|
||
assert!(message.can_be_deleted_for_all_users());
|
||
}
|
||
|
||
#[test]
|
||
fn test_message_has_mention() {
|
||
// Message without mentions
|
||
let message = MessageBuilder::new(MessageId::new(1))
|
||
.text("Hello world")
|
||
.build();
|
||
assert!(!message.has_mention());
|
||
|
||
// Message with @mention
|
||
let message_with_mention = MessageBuilder::new(MessageId::new(2))
|
||
.text("Hello @user")
|
||
.entities(vec![TextEntity {
|
||
offset: 6,
|
||
length: 5,
|
||
r#type: TextEntityType::Mention,
|
||
}])
|
||
.build();
|
||
assert!(message_with_mention.has_mention());
|
||
|
||
// Message with MentionName
|
||
let message_with_mention_name = MessageBuilder::new(MessageId::new(3))
|
||
.text("Hello John")
|
||
.entities(vec![TextEntity {
|
||
offset: 6,
|
||
length: 4,
|
||
r#type: TextEntityType::MentionName(tdlib_rs::types::TextEntityTypeMentionName {
|
||
user_id: 123,
|
||
}),
|
||
}])
|
||
.build();
|
||
assert!(message_with_mention_name.has_mention());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct FolderInfo {
|
||
pub id: i32,
|
||
pub name: String,
|
||
}
|
||
|
||
/// Информация о профиле чата/пользователя
|
||
#[derive(Debug, Clone)]
|
||
pub struct ProfileInfo {
|
||
pub chat_id: ChatId,
|
||
pub title: String,
|
||
pub username: Option<String>,
|
||
pub bio: Option<String>,
|
||
pub phone_number: Option<String>,
|
||
pub chat_type: String, // "Личный чат", "Группа", "Канал"
|
||
pub member_count: Option<i32>,
|
||
pub description: Option<String>,
|
||
pub invite_link: Option<String>,
|
||
pub is_group: bool,
|
||
pub online_status: Option<String>,
|
||
}
|
||
|
||
/// Состояние сетевого соединения
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub enum NetworkState {
|
||
/// Ожидание подключения к сети
|
||
WaitingForNetwork,
|
||
/// Подключение к прокси
|
||
ConnectingToProxy,
|
||
/// Подключение к серверам Telegram
|
||
Connecting,
|
||
/// Обновление данных
|
||
Updating,
|
||
/// Подключено
|
||
Ready,
|
||
}
|
||
|
||
/// Онлайн-статус пользователя
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub enum UserOnlineStatus {
|
||
/// Онлайн
|
||
Online,
|
||
/// Был недавно (менее часа назад)
|
||
Recently,
|
||
/// Был на этой неделе
|
||
LastWeek,
|
||
/// Был в этом месяце
|
||
LastMonth,
|
||
/// Давно не был
|
||
LongTimeAgo,
|
||
/// Оффлайн с указанием времени (unix timestamp)
|
||
Offline(i32),
|
||
}
|
||
|
||
/// Состояние модального окна для просмотра изображения
|
||
#[cfg(feature = "images")]
|
||
#[derive(Debug, Clone)]
|
||
pub struct ImageModalState {
|
||
/// ID сообщения с фото
|
||
pub message_id: MessageId,
|
||
/// Путь к файлу изображения
|
||
pub photo_path: String,
|
||
/// Ширина оригинального изображения
|
||
pub photo_width: i32,
|
||
/// Высота оригинального изображения
|
||
pub photo_height: i32,
|
||
}
|
||
|
||
/// Состояние воспроизведения голосового сообщения
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone)]
|
||
pub struct PlaybackState {
|
||
/// ID сообщения, которое воспроизводится
|
||
pub message_id: MessageId,
|
||
/// Статус воспроизведения
|
||
pub status: PlaybackStatus,
|
||
/// Текущая позиция (секунды)
|
||
pub position: f32,
|
||
/// Общая длительность (секунды)
|
||
pub duration: f32,
|
||
/// Громкость (0.0 - 1.0)
|
||
pub volume: f32,
|
||
}
|
||
|
||
/// Статус воспроизведения
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub enum PlaybackStatus {
|
||
Playing,
|
||
Paused,
|
||
Stopped,
|
||
Loading,
|
||
Error(String),
|
||
}
|