use crate::messages::{Msg, Sender};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pinned {
pub id: i64,
pub sender: Sender,
pub outgoing: bool,
pub date: i32,
pub snippet: String,
}
impl Pinned {
pub fn new(id: i64, msg: &Msg) -> Self {
Self {
id,
sender: msg.sender,
outgoing: msg.outgoing,
date: msg.date,
snippet: msg.snippet(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PinChoice {
Pin,
ForBoth,
ForMe,
Notify,
Quietly,
}
impl PinChoice {
pub fn label(self, with: &str, channel: bool) -> String {
match self {
PinChoice::Pin => "Pin".into(),
PinChoice::ForBoth => format!("Pin for me and {with}"),
PinChoice::ForMe => "Pin just for me".into(),
PinChoice::Notify if channel => "Pin and notify subscribers".into(),
PinChoice::Notify => "Pin and notify members".into(),
PinChoice::Quietly => "Pin without notifying".into(),
}
}
pub fn flags(self) -> (bool, bool) {
match self {
PinChoice::Pin | PinChoice::ForBoth | PinChoice::Notify => (false, false),
PinChoice::ForMe => (false, true),
PinChoice::Quietly => (true, false),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Place {
Saved,
Private,
Group,
Channel,
}
pub struct PinMenu {
pub message_id: i64,
pub snippet: String,
pub place: Place,
pub with: String,
pub choices: Vec<PinChoice>,
pub selected: usize,
}
impl PinMenu {
pub fn new(message_id: i64, snippet: String, place: Place, with: String) -> Self {
Self {
message_id,
snippet,
place,
with,
choices: Vec::new(),
selected: 0,
}
}
pub fn allow(&mut self) {
let (choices, selected) = match self.place {
Place::Saved => (vec![PinChoice::Pin], 0),
Place::Private => (vec![PinChoice::ForBoth, PinChoice::ForMe], 0),
Place::Group | Place::Channel => (vec![PinChoice::Notify, PinChoice::Quietly], 1),
};
self.choices = choices;
self.selected = selected;
}
pub fn label(&self, choice: PinChoice) -> String {
choice.label(&self.with, self.place == Place::Channel)
}
}
pub struct PinnedMenu {
message_id: i64,
row: usize,
}
impl PinnedMenu {
pub fn new(pinned: &[Pinned]) -> Option<Self> {
let first = pinned.first()?;
Some(Self {
message_id: first.id,
row: 0,
})
}
pub fn row(&self, pinned: &[Pinned]) -> Option<usize> {
pinned.iter().position(|p| p.id == self.message_id)
}
pub fn current<'a>(&self, pinned: &'a [Pinned]) -> Option<&'a Pinned> {
pinned.get(self.row(pinned)?)
}
pub fn move_by(&mut self, pinned: &[Pinned], delta: isize) {
let from = self.row(pinned).unwrap_or(self.row);
let row = from
.saturating_add_signed(delta)
.min(pinned.len().saturating_sub(1));
if let Some(p) = pinned.get(row) {
(self.message_id, self.row) = (p.id, row);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pinned(ids: &[i64]) -> Vec<Pinned> {
ids.iter()
.map(|&id| Pinned {
id,
sender: Sender::User(1),
outgoing: false,
date: 0,
snippet: format!("message {id}"),
})
.collect()
}
#[test]
fn the_list_cursor_stays_on_its_message_as_pins_come_and_go() {
let list = pinned(&[9, 7, 5]);
let mut menu = PinnedMenu::new(&list).unwrap();
menu.move_by(&list, 1);
assert_eq!(menu.current(&list).unwrap().id, 7);
let list = pinned(&[12, 9, 7, 5]);
assert_eq!(menu.current(&list).unwrap().id, 7, "not 9, now in its row");
let list = pinned(&[12, 9, 5]);
assert_eq!(menu.current(&list), None);
assert_eq!(menu.row(&list), None);
menu.move_by(&list, 1);
assert_eq!(
menu.current(&list).unwrap().id,
5,
"down from the row it was in"
);
assert!(PinnedMenu::new(&[]).is_none());
}
#[test]
fn the_choices_fit_the_chat_and_start_on_the_gentle_one() {
let mut menu = PinMenu::new(1, "hi".into(), Place::Private, "Maya".into());
assert!(menu.choices.is_empty(), "until TDLib says it can be pinned");
menu.allow();
let labels: Vec<String> = menu.choices.iter().map(|&c| menu.label(c)).collect();
assert_eq!(labels, ["Pin for me and Maya", "Pin just for me"]);
assert_eq!(menu.choices[menu.selected], PinChoice::ForBoth);
assert_eq!(PinChoice::ForMe.flags(), (false, true));
let mut menu = PinMenu::new(1, "hi".into(), Place::Channel, String::new());
menu.allow();
assert_eq!(menu.label(menu.choices[0]), "Pin and notify subscribers");
assert_eq!(menu.choices[menu.selected], PinChoice::Quietly);
assert_eq!(PinChoice::Quietly.flags(), (true, false));
}
}