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">
|
<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
1
Cargo.lock
generated
@@ -108,6 +108,7 @@ dependencies = [
|
|||||||
"colored",
|
"colored",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"termion",
|
"termion",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
||||||
165
src/main.rs
165
src/main.rs
@@ -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,60 +213,142 @@ 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(¤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;
|
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_clone = loading.clone();
|
||||||
|
|
||||||
let loading = Arc::new(Mutex::new(true));
|
write!(stdout, "{}", clear::All)?;
|
||||||
let loading_clone = loading.clone();
|
stdout.flush()?;
|
||||||
|
|
||||||
write!(stdout, "{}", clear::All)?;
|
let animation_task = tokio::spawn(async move {
|
||||||
stdout.flush()?;
|
display_loading_animation(loading_clone, term_width, term_height).await;
|
||||||
|
});
|
||||||
|
|
||||||
let animation_task = tokio::spawn(async move {
|
match fetch_random_verse().await {
|
||||||
display_loading_animation(loading_clone, term_width, term_height).await;
|
Ok(verse) => {
|
||||||
});
|
*loading.lock().unwrap() = false;
|
||||||
|
animation_task.await?;
|
||||||
match fetch_random_verse().await {
|
current_verse = verse.clone();
|
||||||
Ok(verse) => {
|
verse_history.push_back(current_verse.clone());
|
||||||
*loading.lock().unwrap() = false;
|
*index += 1;
|
||||||
animation_task.await?;
|
}
|
||||||
current_verse = verse;
|
Err(e) => {
|
||||||
},
|
*loading.lock().unwrap() = false;
|
||||||
Err(e) => {
|
animation_task.await?;
|
||||||
*loading.lock().unwrap() = false;
|
write!(stdout, "{}{}", clear::All, cursor::Show)?;
|
||||||
animation_task.await?;
|
stdout.flush()?;
|
||||||
|
eprintln!("{} {}", "Error fetching verse:".bold().red(), e);
|
||||||
write!(stdout, "{}{}", clear::All, cursor::Show)?;
|
return Err(e);
|
||||||
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