use std::time::Instant;
use crate::database::{
models::todos::Todo,
repository::{NewTodoDraft, Priority},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InputMode {
Normal,
Adding,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FormField {
Title,
Description,
Priority,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Filter {
All,
Active,
Completed,
}
impl Filter {
pub const fn label(self) -> &'static str {
match self {
Filter::All => "All",
Filter::Active => "Active",
Filter::Completed => "Completed",
}
}
pub fn next(self) -> Self {
match self {
Filter::All => Filter::Active,
Filter::Active => Filter::Completed,
Filter::Completed => Filter::All,
}
}
pub fn matches(self, todo: &Todo) -> bool {
match self {
Filter::All => true,
Filter::Active => !todo.completed,
Filter::Completed => todo.completed,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatusKind {
Info,
Success,
Error,
}
#[derive(Clone, Debug)]
pub struct StatusBanner {
pub kind: StatusKind,
pub message: String,
pub created_at: Instant,
}
impl StatusBanner {
pub fn new(kind: StatusKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
created_at: Instant::now(),
}
}
}
pub struct App {
pub todos: Vec<Todo>,
pub selected: usize,
pub filter: Filter,
pub mode: InputMode,
pub active_field: FormField,
pub form: NewTodoDraft,
pub status: Option<StatusBanner>,
pub should_quit: bool,
pub is_loading: bool,
pub terminal_size: (u16, u16),
}
impl Default for App {
fn default() -> Self {
Self {
todos: Vec::new(),
selected: 0,
filter: Filter::All,
mode: InputMode::Normal,
active_field: FormField::Title,
form: NewTodoDraft::default(),
status: None,
should_quit: false,
is_loading: false,
terminal_size: (0, 0),
}
}
}
impl App {
pub fn with_todos(todos: Vec<Todo>) -> Self {
let mut app = Self::default();
app.set_todos(todos);
app
}
pub fn filtered_todos(&self) -> Vec<&Todo> {
self.todos
.iter()
.filter(|t| self.filter.matches(t))
.collect()
}
pub fn filtered_len(&self) -> usize {
self.todos.iter().filter(|t| self.filter.matches(t)).count()
}
pub fn selected_todo(&self) -> Option<&Todo> {
self.todos
.iter()
.filter(|t| self.filter.matches(t))
.nth(self.selected)
}
pub fn selected_todo_id(&self) -> Option<i32> {
self.selected_todo().map(|todo| todo.id)
}
pub fn next(&mut self) {
let len = self.filtered_len();
if len == 0 {
self.selected = 0;
return;
}
self.selected = (self.selected + 1) % len;
}
pub fn previous(&mut self) {
let len = self.filtered_len();
if len == 0 {
self.selected = 0;
return;
}
if self.selected == 0 {
self.selected = len.saturating_sub(1);
} else {
self.selected -= 1;
}
}
pub fn set_todos(&mut self, todos: Vec<Todo>) {
self.todos = todos;
let len = self.filtered_len();
if len == 0 {
self.selected = 0;
} else if self.selected >= len {
self.selected = len - 1;
}
}
pub fn cycle_filter(&mut self) {
self.filter = self.filter.next();
self.selected = 0;
}
pub fn set_status(&mut self, kind: StatusKind, message: impl Into<String>) {
self.status = Some(StatusBanner::new(kind, message));
}
pub fn clear_status(&mut self) {
self.status = None;
}
pub fn set_loading(&mut self, value: bool) {
self.is_loading = value;
}
pub fn enter_add_mode(&mut self) {
if self.mode != InputMode::Adding {
self.reset_form();
}
self.mode = InputMode::Adding;
self.active_field = FormField::Title;
}
pub fn exit_add_mode(&mut self) {
self.mode = InputMode::Normal;
self.active_field = FormField::Title;
}
pub fn toggle_field(&mut self) {
self.active_field = match self.active_field {
FormField::Title => FormField::Description,
FormField::Description => FormField::Priority,
FormField::Priority => FormField::Title,
};
}
fn active_buffer(&mut self) -> &mut String {
match self.active_field {
FormField::Title => &mut self.form.title,
FormField::Description => &mut self.form.description,
FormField::Priority => panic!("Priority field does not have a string buffer"),
}
}
pub fn push_char(&mut self, ch: char) {
self.active_buffer().push(ch);
}
pub fn erase_char(&mut self) {
self.active_buffer().pop();
}
pub fn consume_form(&self) -> Option<NewTodoDraft> {
if self.form.is_valid() {
Some(self.form.clone())
} else {
None
}
}
pub fn reset_form(&mut self) {
self.form.clear();
self.active_field = FormField::Title;
}
pub fn set_priority_high(&mut self) {
self.form.priority = Priority::High;
}
pub fn set_priority_medium(&mut self) {
self.form.priority = Priority::Medium;
}
pub fn set_priority_low(&mut self) {
self.form.priority = Priority::Low;
}
pub fn increase_priority(&mut self) {
self.form.priority = self.form.priority.increase();
}
pub fn decrease_priority(&mut self) {
self.form.priority = self.form.priority.decrease();
}
pub fn set_terminal_size(&mut self, size: (u16, u16)) {
self.terminal_size = size;
}
pub fn get_terminal_size(&self) -> (u16, u16) {
self.terminal_size
}
}