new features

This commit is contained in:
rattatwinko
2025-04-30 20:37:59 +02:00
parent eddc034dea
commit 1f69006187
4 changed files with 135 additions and 38 deletions

View File

@@ -3,7 +3,9 @@
<component name="MaterialThemeProjectNewConfig"> <component name="MaterialThemeProjectNewConfig">
<option name="metadata"> <option name="metadata">
<MTProjectMetadataState> <MTProjectMetadataState>
<option name="userId" value="ee548a4:19685882df7:-7ff2" /> <option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-482e1190:19649c22859:-7ffe" />
</MTProjectMetadataState> </MTProjectMetadataState>
</option> </option>
</component> </component>

1
Cargo.lock generated
View File

@@ -108,6 +108,7 @@ dependencies = [
"colored", "colored",
"reqwest", "reqwest",
"serde", "serde",
"serde_json",
"termion", "termion",
"tokio", "tokio",
] ]

View File

@@ -11,3 +11,4 @@ serde = { version = "1.0", features = ["derive"] }
clap = { version = "4.4", features = ["cargo"] } clap = { version = "4.4", features = ["cargo"] }
colored = "3.0.0" colored = "3.0.0"
termion = "4.0.5" termion = "4.0.5"
serde_json = "1.0.140"

View File

@@ -1,20 +1,24 @@
use clap::Command; use clap::Command;
use colored::*; use colored::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::error::Error; use std::{
use std::io::{stdout, Write}; collections::VecDeque,
use std::sync::{Arc, Mutex}; error::Error,
fs::{self, File},
io::{stdout, Read, Write},
path::PathBuf,
sync::{Arc, Mutex},
};
use termion::{clear, cursor, event::Key, input::TermRead, raw::IntoRawMode, terminal_size}; use termion::{clear, cursor, event::Key, input::TermRead, raw::IntoRawMode, terminal_size};
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct BibleVerse { struct BibleVerse {
translation: Translation, translation: Translation,
random_verse: RandomVerse, random_verse: RandomVerse,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct Translation { struct Translation {
identifier: String, identifier: String,
name: String, name: String,
@@ -23,7 +27,7 @@ struct Translation {
license: String, license: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct RandomVerse { struct RandomVerse {
book_id: String, book_id: String,
book: String, book: String,
@@ -31,6 +35,7 @@ struct RandomVerse {
verse: i32, verse: i32,
text: String, text: String,
} }
// Fetch a random verse from the API // Fetch a random verse from the API
async fn fetch_random_verse() -> Result<BibleVerse, Box<dyn Error>> { async fn fetch_random_verse() -> Result<BibleVerse, Box<dyn Error>> {
let url = "https://bible-api.com/data/web/random"; let url = "https://bible-api.com/data/web/random";
@@ -152,7 +157,13 @@ fn display_verse(verse: &BibleVerse, term_width: u16, term_height: u16) {
print!("{}", cursor::Goto(start_x as u16 + 2, (start_y + 7 + wrapped_text.len()) as u16)); print!("{}", cursor::Goto(start_x as u16 + 2, (start_y + 7 + wrapped_text.len()) as u16));
print!("{}", "[N]ext Verse".truecolor(98, 76, 171).bold()); print!("{}", "[N]ext Verse".truecolor(98, 76, 171).bold());
print!("{}", cursor::Goto(start_x as u16 + box_width as u16 - 14, (start_y + 7 + wrapped_text.len()) as u16)); print!("{}", cursor::Goto(start_x as u16 + box_width as u16 / 2 - 4, (start_y + 7 + wrapped_text.len()) as u16));
print!("{}", "[B]ack".truecolor(98, 76, 171).bold());
print!("{}", cursor::Goto(start_x as u16 + box_width as u16 - 20, (start_y + 7 + wrapped_text.len()) as u16));
print!("{}", "[S]ave/[O]pen".truecolor(98, 76, 171).bold());
print!("{}", cursor::Goto(start_x as u16 + box_width as u16 - 8, (start_y + 7 + wrapped_text.len()) as u16));
print!("{}", "[Q]uit".truecolor(98, 76, 171).bold()); print!("{}", "[Q]uit".truecolor(98, 76, 171).bold());
stdout().flush().unwrap(); stdout().flush().unwrap();
@@ -202,28 +213,36 @@ async fn main() -> Result<(), Box<dyn Error>> {
display_loading_animation(loading_clone, term_width, term_height).await; display_loading_animation(loading_clone, term_width, term_height).await;
}); });
let mut verse_history: VecDeque<BibleVerse> = VecDeque::new();
let mut current_verse = fetch_random_verse().await?; let mut current_verse = fetch_random_verse().await?;
verse_history.push_back(current_verse.clone());
let history_index = Arc::new(Mutex::new(0)); // Track current position in history
*loading.lock().unwrap() = false; *loading.lock().unwrap() = false;
animation_task.await?; animation_task.await?;
loop { loop {
let (term_width, term_height) = terminal_size()?; let (term_width, term_height) = terminal_size()?;
display_verse(&current_verse, term_width, term_height); let current_index = *history_index.lock().unwrap();
if let Some(verse) = verse_history.get(current_index) {
display_verse(verse, term_width, term_height);
}
let mut key_pressed = false; let mut key_pressed = false;
while !key_pressed { while !key_pressed {
if let Some(Ok(key)) = stdin.next() { if let Some(Ok(key)) = stdin.next() {
match key { match key {
Key::Char('q') | Key::Char('Q') | Key::Esc => { Key::Char('q') | Key::Char('Q') | Key::Esc => {
// Proper cleanup
write!(stdout, "{}{}", clear::All, cursor::Show)?; write!(stdout, "{}{}", clear::All, cursor::Show)?;
stdout.flush()?; stdout.flush()?;
return Ok(()); return Ok(());
}, }
Key::Char('n') | Key::Char('N') | Key::Char(' ') => { Key::Char('n') | Key::Char('N') | Key::Char(' ') => {
key_pressed = true; key_pressed = true;
let mut index = history_index.lock().unwrap();
if *index < verse_history.len() - 1 {
*index += 1;
} else {
let loading = Arc::new(Mutex::new(true)); let loading = Arc::new(Mutex::new(true));
let loading_clone = loading.clone(); let loading_clone = loading.clone();
@@ -238,24 +257,98 @@ async fn main() -> Result<(), Box<dyn Error>> {
Ok(verse) => { Ok(verse) => {
*loading.lock().unwrap() = false; *loading.lock().unwrap() = false;
animation_task.await?; animation_task.await?;
current_verse = verse; current_verse = verse.clone();
}, verse_history.push_back(current_verse.clone());
*index += 1;
}
Err(e) => { Err(e) => {
*loading.lock().unwrap() = false; *loading.lock().unwrap() = false;
animation_task.await?; animation_task.await?;
write!(stdout, "{}{}", clear::All, cursor::Show)?; write!(stdout, "{}{}", clear::All, cursor::Show)?;
stdout.flush()?; stdout.flush()?;
eprintln!("{} {}", "Error fetching verse:".bold().red(), e); eprintln!("{} {}", "Error fetching verse:".bold().red(), e);
return Err(e); return Err(e);
} }
} }
}, }
}
Key::Char('b') | Key::Char('B') => {
let mut index = history_index.lock().unwrap();
if *index > 0 {
*index -= 1;
key_pressed = true;
}
}
Key::Char('s') | Key::Char('S') => {
let current_index = *history_index.lock().unwrap();
if let Some(verse_to_save) = verse_history.get(current_index).cloned() {
match save_verse_to_json("saved_verses.json", &verse_to_save).await {
Ok(_) => {
// Optionally display a success message
eprintln!("{}", "Verse saved to saved_verses.json".green().bold());
sleep(Duration::from_secs(2)).await;
}
Err(e) => {
eprintln!("{} {}", "Error saving verse:".bold().red(), e);
sleep(Duration::from_secs(2)).await;
}
}
}
}
Key::Char('o') | Key::Char('O') => {
match open_verses_from_json("saved_verses.json").await {
Ok(loaded_verses) => {
if !loaded_verses.is_empty() {
verse_history.clear();
verse_history.extend(loaded_verses.into_iter());
*history_index.lock().unwrap() = verse_history.len() - 1; // Show the last loaded verse
key_pressed = true;
eprintln!("{}", "Loaded verses from saved_verses.json".green().bold());
sleep(Duration::from_secs(2)).await;
} else {
eprintln!("{}", "No verses found in saved_verses.json".yellow().bold());
sleep(Duration::from_secs(2)).await;
}
}
Err(e) => {
eprintln!("{} {}", "Error opening verses:".bold().red(), e);
sleep(Duration::from_secs(2)).await;
}
}
}
_ => {} _ => {}
} }
} }
}
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
} }
}
async fn save_verse_to_json(filename: &str, verse: &BibleVerse) -> Result<(), Box<dyn Error>> {
let path = PathBuf::from(filename);
let mut saved_verses = Vec::new();
if path.exists() {
let file = File::open(filename)?;
let reader = std::io::BufReader::new(file);
if let Ok(verses) = serde_json::from_reader(reader) {
saved_verses = verses;
}
}
saved_verses.push(verse.clone());
let file = File::create(filename)?;
serde_json::to_writer_pretty(file, &saved_verses)?;
Ok(())
}
async fn open_verses_from_json(filename: &str) -> Result<Vec<BibleVerse>, Box<dyn Error>> {
let path = PathBuf::from(filename);
if path.exists() {
let file = File::open(filename)?;
let reader = std::io::BufReader::new(file);
let verses = serde_json::from_reader(reader)?;
Ok(verses)
} else {
Ok(Vec::new())
} }
} }