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">
<option name="metadata">
<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>
</option>
</component>

1
Cargo.lock generated
View File

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

View File

@@ -10,4 +10,5 @@ tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
clap = { version = "4.4", features = ["cargo"] }
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 colored::*;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::io::{stdout, Write};
use std::sync::{Arc, Mutex};
use std::{
collections::VecDeque,
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 tokio::time::{sleep, Duration};
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct BibleVerse {
translation: Translation,
random_verse: RandomVerse,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Translation {
identifier: String,
name: String,
@@ -23,7 +27,7 @@ struct Translation {
license: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
struct RandomVerse {
book_id: String,
book: String,
@@ -31,6 +35,7 @@ struct RandomVerse {
verse: i32,
text: String,
}
// Fetch a random verse from the API
async fn fetch_random_verse() -> Result<BibleVerse, Box<dyn Error>> {
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!("{}", "[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());
stdout().flush().unwrap();
@@ -202,60 +213,142 @@ async fn main() -> Result<(), Box<dyn Error>> {
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?;
verse_history.push_back(current_verse.clone());
let history_index = Arc::new(Mutex::new(0)); // Track current position in history
*loading.lock().unwrap() = false;
animation_task.await?;
loop {
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;
while !key_pressed {
if let Some(Ok(key)) = stdin.next() {
match key {
Key::Char('q') | Key::Char('Q') | Key::Esc => {
// Proper cleanup
write!(stdout, "{}{}", clear::All, cursor::Show)?;
stdout.flush()?;
return Ok(());
},
}
Key::Char('n') | Key::Char('N') | Key::Char(' ') => {
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_clone = loading.clone();
let loading = Arc::new(Mutex::new(true));
let loading_clone = loading.clone();
write!(stdout, "{}", clear::All)?;
stdout.flush()?;
write!(stdout, "{}", clear::All)?;
stdout.flush()?;
let animation_task = tokio::spawn(async move {
display_loading_animation(loading_clone, term_width, term_height).await;
});
let animation_task = tokio::spawn(async move {
display_loading_animation(loading_clone, term_width, term_height).await;
});
match fetch_random_verse().await {
Ok(verse) => {
*loading.lock().unwrap() = false;
animation_task.await?;
current_verse = verse;
},
Err(e) => {
*loading.lock().unwrap() = false;
animation_task.await?;
write!(stdout, "{}{}", clear::All, cursor::Show)?;
stdout.flush()?;
eprintln!("{} {}", "Error fetching verse:".bold().red(), e);
return Err(e);
match fetch_random_verse().await {
Ok(verse) => {
*loading.lock().unwrap() = false;
animation_task.await?;
current_verse = verse.clone();
verse_history.push_back(current_verse.clone());
*index += 1;
}
Err(e) => {
*loading.lock().unwrap() = false;
animation_task.await?;
write!(stdout, "{}{}", clear::All, cursor::Show)?;
stdout.flush()?;
eprintln!("{} {}", "Error fetching verse:".bold().red(), 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())
}
}