new features
This commit is contained in:
4
.idea/material_theme_project_new.xml
generated
4
.idea/material_theme_project_new.xml
generated
@@ -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
1
Cargo.lock
generated
@@ -108,6 +108,7 @@ dependencies = [
|
||||
"colored",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"termion",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
165
src/main.rs
165
src/main.rs
@@ -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(¤t_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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user