2023-05-28 22:18:30 -07:00
|
|
|
|
2023-08-07 22:27:27 -07:00
|
|
|
use std::sync::Mutex;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
use crossterm::event::{Event, self, KeyCode};
|
2023-08-07 22:27:27 -07:00
|
|
|
use tokio;
|
2023-06-01 20:36:56 -07:00
|
|
|
use ratatui::widgets::ListState;
|
|
|
|
|
2023-08-09 22:39:00 -07:00
|
|
|
use crossbeam_channel::Receiver;
|
|
|
|
|
2023-08-07 22:27:27 -07:00
|
|
|
use log::warn;
|
|
|
|
|
|
|
|
use crate::db::DB;
|
2023-06-01 20:36:56 -07:00
|
|
|
use crate::uis::history::HistoryState;
|
2023-05-31 22:27:26 -07:00
|
|
|
use crate::uis::new_transaction::NewTransactionTabState;
|
2023-06-01 20:36:56 -07:00
|
|
|
use crate::uis::navigation_frame::NavigationState;
|
2023-08-07 22:27:27 -07:00
|
|
|
use crate::db::transaction::TransactionRecord;
|
2023-05-31 22:27:26 -07:00
|
|
|
|
2023-05-31 18:00:17 -07:00
|
|
|
pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
2023-05-28 22:18:30 -07:00
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub struct StatefulList<T> {
|
|
|
|
pub state: ListState,
|
|
|
|
pub items: Vec<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> StatefulList<T> {
|
|
|
|
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
|
|
|
StatefulList {
|
|
|
|
state: ListState::default(),
|
|
|
|
items,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next(&mut self) {
|
|
|
|
let i = match self.state.selected() {
|
|
|
|
Some(i) => {
|
|
|
|
if i >= self.items.len() - 1 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => 0,
|
|
|
|
};
|
|
|
|
self.state.select(Some(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn previous(&mut self) {
|
|
|
|
let i = match self.state.selected() {
|
|
|
|
Some(i) => {
|
|
|
|
if i == 0 {
|
|
|
|
self.items.len() - 1
|
|
|
|
} else {
|
|
|
|
i - 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => 0,
|
|
|
|
};
|
|
|
|
self.state.select(Some(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn unselect(&mut self) {
|
|
|
|
self.state.select(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum InputMode {
|
|
|
|
Insert,
|
|
|
|
Normal,
|
2023-05-31 22:27:26 -07:00
|
|
|
}
|
|
|
|
|
2023-05-28 22:18:30 -07:00
|
|
|
pub struct App<'a> {
|
|
|
|
pub running: bool,
|
2023-05-31 22:27:26 -07:00
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub states: States<'a>,
|
2023-05-31 22:27:26 -07:00
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub input_mode: InputMode,
|
2023-08-07 22:27:27 -07:00
|
|
|
|
|
|
|
pub db: Arc<tokio::sync::Mutex<DB>>,
|
|
|
|
pub records: Arc<Mutex<Vec<TransactionRecord>>>,
|
2023-05-28 22:18:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> App<'a> {
|
2023-08-09 22:39:00 -07:00
|
|
|
pub fn new(db: DB, records: Arc<Mutex<Vec<TransactionRecord>>>, r: Receiver<bool>) -> App<'a> {
|
2023-05-31 18:00:17 -07:00
|
|
|
App {
|
|
|
|
running: true,
|
2023-08-09 22:39:00 -07:00
|
|
|
states: States::new(r),
|
2023-06-01 20:36:56 -07:00
|
|
|
input_mode: InputMode::Normal,
|
2023-08-07 22:27:27 -07:00
|
|
|
db: Arc::new(tokio::sync::Mutex::new(db)),
|
|
|
|
records,
|
2023-05-31 18:00:17 -07:00
|
|
|
}
|
2023-05-28 22:18:30 -07:00
|
|
|
}
|
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub fn poll_events(&mut self) -> AppResult<()> {
|
|
|
|
if let Event::Key(key) = event::read()? {
|
|
|
|
// 'q' needs to be handled at the top level so it can't be
|
|
|
|
// accidentally handed to a dead end
|
|
|
|
if let KeyCode::Char('q') = key.code {
|
|
|
|
if let Some(_) = self.states.nav_state.message.clone() {
|
|
|
|
self.states.nav_state.message = None;
|
|
|
|
} else {
|
|
|
|
if let ActiveFrame::Navigation = self.states.active_frame {
|
|
|
|
self.running = false;
|
|
|
|
} else {
|
|
|
|
self.states.active_frame = ActiveFrame::Navigation;
|
|
|
|
}
|
|
|
|
}
|
2023-08-07 21:01:13 -07:00
|
|
|
} else if let KeyCode::Esc = key.code {
|
|
|
|
if let Some(_) = self.states.nav_state.message.clone() {
|
|
|
|
self.states.nav_state.message = None;
|
|
|
|
} else {
|
|
|
|
if let ActiveFrame::Navigation = self.states.active_frame {
|
|
|
|
self.running = false;
|
|
|
|
} else {
|
|
|
|
self.states.active_frame = ActiveFrame::Navigation;
|
|
|
|
}
|
|
|
|
}
|
2023-06-01 20:36:56 -07:00
|
|
|
}
|
2023-08-07 22:27:27 -07:00
|
|
|
if let KeyCode::Char('r') = key.code {
|
|
|
|
self.refresh();
|
|
|
|
}
|
2023-06-01 20:36:56 -07:00
|
|
|
match self.states.active_frame {
|
|
|
|
ActiveFrame::Navigation => {
|
|
|
|
NavigationState::handle_event(key, self);
|
|
|
|
}
|
|
|
|
ActiveFrame::History => {
|
|
|
|
HistoryState::handle_event(key, self);
|
|
|
|
}
|
|
|
|
ActiveFrame::NewTransaction => {
|
|
|
|
NewTransactionTabState::handle_event(key, self);
|
|
|
|
}
|
|
|
|
}
|
2023-05-28 22:18:30 -07:00
|
|
|
}
|
2023-06-01 20:36:56 -07:00
|
|
|
return Ok(())
|
2023-05-31 22:27:26 -07:00
|
|
|
}
|
2023-08-07 22:27:27 -07:00
|
|
|
|
|
|
|
pub fn refresh(&mut self) {
|
|
|
|
|
|
|
|
let fut = Arc::clone(&self.db);
|
|
|
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
let res = fut.lock().await.get_all_records().await;
|
|
|
|
match res {
|
|
|
|
Ok(_) => {},
|
|
|
|
Err(e) => warn!("{}", e)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
2023-06-01 20:36:56 -07:00
|
|
|
}
|
2023-05-31 22:27:26 -07:00
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub enum ActiveFrame {
|
|
|
|
Navigation,
|
|
|
|
NewTransaction,
|
|
|
|
History,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct States<'a> {
|
|
|
|
pub nav_state: NavigationState<'a>,
|
|
|
|
pub transactions: NewTransactionTabState<'a>,
|
|
|
|
pub history: HistoryState,
|
|
|
|
|
2023-08-09 22:39:00 -07:00
|
|
|
pub new_data: Receiver<bool>,
|
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
pub active_frame: ActiveFrame,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> States<'a> {
|
2023-08-09 22:39:00 -07:00
|
|
|
pub fn new(r: Receiver<bool>) -> States<'a> {
|
2023-06-01 20:36:56 -07:00
|
|
|
States {
|
|
|
|
nav_state: NavigationState::new(),
|
|
|
|
transactions: NewTransactionTabState::new(),
|
|
|
|
history: HistoryState::new(),
|
2023-05-31 22:27:26 -07:00
|
|
|
|
2023-08-09 22:39:00 -07:00
|
|
|
new_data: r,
|
|
|
|
|
2023-06-01 20:36:56 -07:00
|
|
|
active_frame: ActiveFrame::Navigation,
|
|
|
|
}
|
2023-05-31 22:27:26 -07:00
|
|
|
}
|
|
|
|
}
|