use std::collections::HashSet;
use crate::discord::ids::marker::{GuildMarker, UserMarker};
use crate::discord::{
AppCommand, AppEvent, ForumPostArchiveState, MentionInfo, MessageSnapshotInfo,
};
mod channel_tree;
mod channels;
mod composer;
mod dashboard;
mod diagnostics;
mod discord_ui;
mod emoji;
mod events;
mod guilds;
mod layout_cache;
mod local_upload_preview;
mod member_grouping;
mod message_history_refresh;
mod message_layout;
mod message_render;
mod message_viewport;
mod model;
mod navigation;
mod options;
mod pane_filter;
mod pending_messages;
mod popups;
mod presentation;
mod request_tracking;
mod runtime_state;
mod scroll;
mod subscriptions;
mod text_completion;
mod toast;
mod user;
mod voice_actions;
use composer::ComposerUiState;
use discord_ui::DiscordUiState;
use layout_cache::{LayoutCacheState, MessageRowContentMetrics, MessageRowContentMetricsCacheKey};
use message_history_refresh::MessageHistoryRefreshState;
use message_render::{add_literal_mention_highlights, normalize_text_highlights};
use message_viewport::{MessageViewportState, ThreadReturnTarget};
use navigation::{ActiveGuildScope, FolderKey, FolderSettingsState, NavigationState};
use options::SettingsState;
use pane_filter::PaneFilterState;
use pending_messages::PendingMessageUiState;
use popups::PopupUiState;
use request_tracking::RequestTrackingState;
use runtime_state::{
MediaPlaybackPreparingUiState, RuntimeUiState, ToastMessage, VoiceConnectionUiState,
};
use scroll::clamp_selected_index;
pub(in crate::tui) const MINIMUM_ESTABLISHED_DM_MESSAGES: usize = 5;
pub use composer::{
CommandPickerEntry, ComposerLock, EmojiPickerEntry, MAX_MENTION_PICKER_VISIBLE,
MentionPickerEntry, MentionPickerTarget,
};
pub use dashboard::DashboardState;
pub use member_grouping::{MemberEntry, MemberGroup};
pub use message_viewport::MessagePaneSource;
#[cfg(test)]
pub(crate) use model::ActionAvailability;
pub use model::{
ActionItem, AppliedForumTag, AttachmentDownloadProgressView, AttachmentViewerItem,
ChannelActionItem, ChannelPaneEntry, ChannelSearchSuggestionItem, ChannelSwitcherItem,
ChannelThreadItem, EmojiReactionItem, FocusPane, ForumPostComposerAttachmentView,
ForumPostComposerField, ForumPostComposerTagView, ForumPostComposerView, GuildActionItem,
GuildPaneEntry, LocalUploadPreviewView, MemberActionItem, MemberSearchResultItem,
MessageActionItem, MessageActionKind, MessageSearchResultItem, MuteActionDurationItem,
PollVotePickerItem, SearchFieldView, SearchPopupMode, SearchPopupView, SearchResultItem,
SearchSuggestionItem, ThreadActionItem, ThreadEditField, ThreadEditTagView, ThreadEditView,
ThreadMessagePreview, ThreadNotificationItem, ThreadSummary,
};
pub use model::{
ChannelActionKind, GuildActionKind, MemberActionKind, MessageUrlItem, ThreadActionKind,
};
pub use options::DisplayOptionItem;
pub(in crate::tui) use popups::{
ActiveModalPopupKind, ConfirmationButton, MessageConfirmationKind,
};
pub use popups::{
AttachmentViewerZoom, EmojiReactionPickerState, MessageActionMenuState, MessageUrlPickerState,
NotificationInboxChannelLoad, NotificationInboxItem, NotificationInboxLoad,
NotificationInboxMessage, NotificationInboxTab, NotificationInboxUnreadItem,
PollVotePickerState, ReactionUsersEntry, ReactionUsersPopupState, UserProfileSettingsField,
UserProfileSettingsTab,
};
pub use presentation::{
apply_discord_foreground, discord_role_mention_background, folder_style, normal_text_style,
presence_marker, presence_style,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ToastKind {
Info,
Success,
Error,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ToastView<'a> {
pub text: &'a str,
pub kind: ToastKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct DesktopNotification {
pub title: String,
pub body: String,
}
fn message_notification_body(
content: Option<&str>,
sticker_count: usize,
attachment_count: usize,
embed_count: usize,
) -> String {
let content = content.unwrap_or_default().trim();
if !content.is_empty() {
let single_line = content.split_whitespace().collect::<Vec<_>>().join(" ");
return truncate_notification_text(&single_line, 200);
}
if attachment_count > 0 {
return format!("sent {attachment_count} attachment(s)");
}
if sticker_count > 0 {
return format!("sent {sticker_count} sticker(s)");
}
if embed_count > 0 {
return format!("sent {embed_count} embed(s)");
}
"sent a message".to_owned()
}
fn truncate_notification_text(value: &str, max_chars: usize) -> String {
let mut chars = value.chars();
let truncated: String = chars.by_ref().take(max_chars).collect();
if chars.next().is_some() {
format!("{truncated}...")
} else {
truncated
}
}
impl DashboardState {
#[cfg(test)]
pub fn push_event(&mut self, event: AppEvent) {
self.push_event_inner(event, true);
}
pub fn push_effect(&mut self, event: AppEvent) {
if let AppEvent::ChannelUpsert(channel) = &event {
self.record_thread_channel_upserted(channel);
return;
}
self.push_event_inner(event, false);
}
}
#[cfg(test)]
mod tests;