diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
index 1d05485..e3d7ccb 100644
--- a/.idea/material_theme_project_new.xml
+++ b/.idea/material_theme_project_new.xml
@@ -3,7 +3,9 @@
diff --git a/Cargo.lock b/Cargo.lock
index 892d616..b105b2d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -108,6 +108,7 @@ dependencies = [
"colored",
"reqwest",
"serde",
+ "serde_json",
"termion",
"tokio",
]
diff --git a/Cargo.toml b/Cargo.toml
index 0c362dd..980ab24 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
\ No newline at end of file
+termion = "4.0.5"
+serde_json = "1.0.140"
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index a89e068..716057f 100644
--- a/src/main.rs
+++ b/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> {
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> {
display_loading_animation(loading_clone, term_width, term_height).await;
});
+ let mut verse_history: VecDeque = 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> {
+ 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, Box> {
+ 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())
+ }
+}
\ No newline at end of file