update
This commit is contained in:
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -108,6 +108,7 @@ dependencies = [
|
|||||||
"colored",
|
"colored",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"termion",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -663,6 +664,17 @@ version = "0.2.172"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -740,6 +752,12 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@@ -879,6 +897,12 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.15"
|
version = "0.12.15"
|
||||||
@@ -1212,6 +1236,18 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termion"
|
||||||
|
version = "4.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"numtoa",
|
||||||
|
"redox_termios",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ authors = ["Bible CLI App"]
|
|||||||
reqwest = { version = "0.12.15", features = ["json"] }
|
reqwest = { version = "0.12.15", features = ["json"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
clap = "4.4"
|
clap = { version = "4.4", features = ["cargo"] }
|
||||||
colored = "3.0.0"
|
colored = "3.0.0"
|
||||||
|
termion = "4.0.5"
|
||||||
227
src/main.rs
227
src/main.rs
@@ -1,7 +1,19 @@
|
|||||||
|
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
use colored::*;
|
use colored::{Color, Colorize};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::io::{stdout, Write};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use termion::{clear, cursor, event::Key, input::TermRead, raw::IntoRawMode, terminal_size};
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
|
const BORDER_COLOR: Color = Color::TrueColor { r: 117, g: 142, b: 205 }; // #758ECD
|
||||||
|
const REF_COLOR: Color = Color::TrueColor { r: 113, g: 137, b: 255 }; // #7189FF
|
||||||
|
const TEXT_COLOR: Color = Color::TrueColor { r: 193, g: 206, b: 254 }; // #C1CEFE
|
||||||
|
const ATTR_COLOR: Color = Color::TrueColor { r: 160, g: 221, b: 255 }; // #A0DDFF
|
||||||
|
const NEXT_BTN_COLOR: Color = Color::TrueColor { r: 98, g: 76, b: 171 }; // #624CAB
|
||||||
|
const QUIT_BTN_COLOR: Color = Color::TrueColor { r: 160, g: 221, b: 255 }; // #A0DDFF
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct BibleVerse {
|
struct BibleVerse {
|
||||||
@@ -33,24 +45,149 @@ async fn fetch_random_verse() -> Result<BibleVerse, Box<dyn Error>> {
|
|||||||
Ok(verse)
|
Ok(verse)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_verse(verse: &BibleVerse) {
|
fn word_wrap(text: &str, max_width: usize) -> Vec<String> {
|
||||||
println!("{}", "═".color("cyan").repeat(60));
|
let mut result = Vec::new();
|
||||||
|
let mut current_line = String::new();
|
||||||
|
|
||||||
|
for word in text.split_whitespace() {
|
||||||
|
if current_line.len() + word.len() + 1 > max_width {
|
||||||
|
result.push(current_line);
|
||||||
|
current_line = word.to_string();
|
||||||
|
} else {
|
||||||
|
if !current_line.is_empty() {
|
||||||
|
current_line.push(' ');
|
||||||
|
}
|
||||||
|
current_line.push_str(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_line.is_empty() {
|
||||||
|
result.push(current_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_verse(verse: &BibleVerse, term_width: u16, term_height: u16) {
|
||||||
|
let max_width = std::cmp::min(80, term_width as usize - 10);
|
||||||
|
let box_width = max_width + 8;
|
||||||
|
|
||||||
// Create a reference string like "Book chapter:verse"
|
|
||||||
let reference = format!("{} {}:{}",
|
let reference = format!("{} {}:{}",
|
||||||
verse.random_verse.book,
|
verse.random_verse.book,
|
||||||
verse.random_verse.chapter,
|
verse.random_verse.chapter,
|
||||||
verse.random_verse.verse);
|
verse.random_verse.verse);
|
||||||
|
|
||||||
println!("{}", reference.bold().color("yellow"));
|
let wrapped_text = word_wrap(&verse.random_verse.text, max_width - 4);
|
||||||
println!();
|
let attribution = format!("{} ({})", verse.translation.name, verse.translation.identifier);
|
||||||
println!("{}", verse.random_verse.text.trim());
|
|
||||||
println!();
|
let box_height = wrapped_text.len() + 7;
|
||||||
println!("─ {} ({}) [{}]",
|
let start_y = (term_height as usize / 2).saturating_sub(box_height / 2);
|
||||||
verse.translation.name.italic().color("green"),
|
let start_x = (term_width as usize / 2).saturating_sub(box_width / 2);
|
||||||
verse.translation.identifier.italic(),
|
|
||||||
verse.translation.language);
|
print!("{}{}", clear::All, cursor::Goto(1, 1));
|
||||||
println!("{}", "═".color("cyan").repeat(60));
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, start_y as u16));
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"╔".color(BORDER_COLOR).bold(),
|
||||||
|
"═".repeat(box_width - 2).color(BORDER_COLOR).bold(),
|
||||||
|
"╗".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 1) as u16));
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
" ".repeat(box_width - 2),
|
||||||
|
"║".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 2) as u16));
|
||||||
|
let centered_ref = format!("{:^width$}", reference, width = box_width - 2);
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
centered_ref.color(REF_COLOR).bold(),
|
||||||
|
"║".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 3) as u16));
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
" ".repeat(box_width - 2),
|
||||||
|
"║".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, line) in wrapped_text.iter().enumerate() {
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 4 + i) as u16));
|
||||||
|
let padding = (box_width - 2 - line.len()) / 2;
|
||||||
|
let left_padding = " ".repeat(padding);
|
||||||
|
let right_padding = " ".repeat(box_width - 2 - padding - line.len());
|
||||||
|
println!(
|
||||||
|
"{}{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
left_padding,
|
||||||
|
line.color(TEXT_COLOR),
|
||||||
|
format!("{}{}", right_padding, "║").color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 4 + wrapped_text.len()) as u16));
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
" ".repeat(box_width - 2),
|
||||||
|
"║".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 5 + wrapped_text.len()) as u16));
|
||||||
|
let centered_attr = format!("{:^width$}", attribution, width = box_width - 2);
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"║".color(BORDER_COLOR).bold(),
|
||||||
|
centered_attr.color(ATTR_COLOR).italic(),
|
||||||
|
"║".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16, (start_y + 6 + wrapped_text.len()) as u16));
|
||||||
|
println!(
|
||||||
|
"{}{}{}",
|
||||||
|
"╚".color(BORDER_COLOR).bold(),
|
||||||
|
"═".repeat(box_width - 2).color(BORDER_COLOR).bold(),
|
||||||
|
"╝".color(BORDER_COLOR).bold()
|
||||||
|
);
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16 + 4, (start_y + 8 + wrapped_text.len()) as u16));
|
||||||
|
print!("{}", "[N]ext Verse".color(NEXT_BTN_COLOR).bold());
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16 + box_width as u16 - 15, (start_y + 8 + wrapped_text.len()) as u16));
|
||||||
|
print!("{}", "[Q]uit".color(QUIT_BTN_COLOR).bold());
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(start_x as u16 + (box_width / 2) as u16 - 15, (start_y + 10 + wrapped_text.len()) as u16));
|
||||||
|
print!("{}", "Press N for next verse or Q to quit".color(REF_COLOR));
|
||||||
|
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn display_loading_animation(loading: Arc<Mutex<bool>>, term_width: u16, term_height: u16) {
|
||||||
|
let spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||||
|
let mut frame_index = 0;
|
||||||
|
let message = "Loading Bible verse";
|
||||||
|
let message_len = message.len() + 2;
|
||||||
|
|
||||||
|
while *loading.lock().unwrap() {
|
||||||
|
let spinner = spinner_frames[frame_index].color(NEXT_BTN_COLOR).bold();
|
||||||
|
let x_pos = (term_width as usize / 2).saturating_sub(message_len / 2);
|
||||||
|
let y_pos = term_height as usize / 2;
|
||||||
|
|
||||||
|
print!("{}", cursor::Goto(x_pos as u16, y_pos as u16));
|
||||||
|
print!("{} {}", spinner, message.color(REF_COLOR).bold());
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
|
frame_index = (frame_index + 1) % spinner_frames.len();
|
||||||
|
sleep(Duration::from_millis(80)).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -58,20 +195,72 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let _matches = Command::new("Bible Verse")
|
let _matches = Command::new("Bible Verse")
|
||||||
.version("1.0")
|
.version("1.0")
|
||||||
.author("BibleVerse")
|
.author("BibleVerse")
|
||||||
.about("Fetches random Bible verses")
|
.about("A beautiful Bible verse display")
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
println!("Fetching a random Bible verse...");
|
let mut stdout = stdout().into_raw_mode()?;
|
||||||
|
write!(stdout, "{}", clear::All)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
let mut stdin = termion::async_stdin().keys();
|
||||||
|
let (term_width, term_height) = terminal_size()?;
|
||||||
|
let loading = Arc::new(Mutex::new(true));
|
||||||
|
let loading_clone = loading.clone();
|
||||||
|
|
||||||
|
let animation_task = tokio::spawn(async move {
|
||||||
|
display_loading_animation(loading_clone, term_width, term_height).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut current_verse = fetch_random_verse().await?;
|
||||||
|
|
||||||
|
*loading.lock().unwrap() = false;
|
||||||
|
animation_task.await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (term_width, term_height) = terminal_size()?;
|
||||||
|
display_verse(¤t_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 => {
|
||||||
|
write!(stdout, "{}{}", clear::All, cursor::Show)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Key::Char('n') | Key::Char('N') | Key::Char(' ') => {
|
||||||
|
key_pressed = true;
|
||||||
|
let loading = Arc::new(Mutex::new(true));
|
||||||
|
let loading_clone = loading.clone();
|
||||||
|
write!(stdout, "{}", clear::All)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
let animation_task = tokio::spawn(async move {
|
||||||
|
display_loading_animation(loading_clone, term_width, term_height).await;
|
||||||
|
});
|
||||||
|
|
||||||
match fetch_random_verse().await {
|
match fetch_random_verse().await {
|
||||||
Ok(verse) => {
|
Ok(verse) => {
|
||||||
display_verse(&verse);
|
*loading.lock().unwrap() = false;
|
||||||
|
animation_task.await?;
|
||||||
|
current_verse = verse;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{} {}", "Error:".bold().color("red"), e);
|
*loading.lock().unwrap() = false;
|
||||||
eprintln!("Try checking your internet connection or the API endpoint.");
|
animation_task.await?;
|
||||||
|
write!(stdout, "{}{}", clear::All, cursor::Show)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
eprintln!("{} {}", "Error fetching verse:".bold().color(QUIT_BTN_COLOR), e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user