[dependencies]
gtk = { version = "0.3.0" ,features=["v3_18"]}
reqwest = {version="0.9"}
serde_json = "1.0.96"
serde_derive = "1.0.163"
serde = "1.0.163"
crossbeam = "0.8"
// hews/src/hackernews.rs
use crate::hackernews::top_stories;
use crate::hackernews::Story;
use gtk::*;
use reqwest::Client;
use std::process;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
pub enum Msg {
NewStory(Story),
Loading,
Loaded,
Refresh,
}
#[derive(Clone)]
pub struct Header {
pub header: HeaderBar,
pub refresh_btn: Button,
}
impl Header {
pub fn new(story_container: gtk::Box, tx: Sender) -> Header {
let header = HeaderBar::new();
let refresh_btn = gtk::Button::new_with_label("Refresh");
refresh_btn.set_sensitive(false);
header.pack_start(&refresh_btn);
header.set_title("Hews - popular stories from hacker news");
header.set_show_close_button(true);
refresh_btn.connect_clicked(move |_| {
for i in story_container.get_children().iter().skip(1) {
story_container.remove(i);
}
tx.send(Msg::Refresh).unwrap();
});
Header {
header,
refresh_btn,
}
}
fn disable_refresh(&self) {
self.refresh_btn.set_label("Loading");
self.refresh_btn.set_sensitive(false);
}
fn enable_refresh(&self) {
self.refresh_btn.set_label("Refresh");
self.refresh_btn.set_sensitive(true);
}
}
pub struct App {
window: Window,
header: Header,
stories: gtk::Box,
spinner: Spinner,
tx: Sender,
}
impl App {
pub fn new() -> (App, Receiver) {
if gtk::init().is_err() {
println!("Failed to init hews window");
process::exit(1);
}
let (tx, rx) = channel();
let window = gtk::Window::new(gtk::WindowType::Toplevel);
let sw = ScrolledWindow::new(None, None);
let stories = gtk::Box::new(gtk::Orientation::Vertical, 20);
let spinner = gtk::Spinner::new();
let header = Header::new(stories.clone(), tx.clone());
stories.pack_start(&spinner, false, false, 2);
sw.add(&stories);
window.add(&sw);
window.set_default_size(600, 350);
window.set_titlebar(&header.header);
window.connect_delete_event(move |_, _| {
main_quit();
Inhibit(false)
});
(
App {
window,
header,
stories,
spinner,
tx,
},
rx,
)
}
pub fn launch(&self, rx: Receiver) {
self.window.show_all();
let client = Arc::new(reqwest::Client::new());
self.fetch_posts(client.clone());
self.run_event_loop(rx, client);
}
fn fetch_posts(&self, client: Arc) {
self.spinner.start();
self.tx.send(Msg::Loading).unwrap();
let tx_clone = self.tx.clone();
top_stories(client, 10, &tx_clone);
}
fn render_story(s: Story, stories: >k::Box) {
let title_with_score = format!("{} ({})", s.title, s.score);
let label = gtk::Label::new(&*title_with_score);
let story_url = s.url.unwrap_or("N/A".to_string());
let link_label = gtk::Label::new(&*story_url);
let label_markup = format!("{}", story_url, story_url);
link_label.set_markup(&label_markup);
stories.pack_start(&label, false, false, 2);
stories.pack_start(&link_label, false, false, 2);
stories.show_all();
}
fn run_event_loop(&self, rx: Receiver, client: Arc) {
let container = self.stories.clone();
let spinner = self.spinner.clone();
let header = self.header.clone();
let tx_clone = self.tx.clone();
gtk::timeout_add(100, move || {
match rx.try_recv() {
Ok(Msg::NewStory(s)) => App::render_story(s, &container),
Ok(Msg::Loading) => header.disable_refresh(),
Ok(Msg::Loaded) => {
spinner.stop();
header.enable_refresh();
}
Ok(Msg::Refresh) => {
spinner.start();
spinner.show();
(&tx_clone).send(Msg::Loading).unwrap();
top_stories(client.clone(), 10, &tx_clone);
}
Err(_) => {}
}
gtk::Continue(true)
});
gtk::main();
}
}
// hews/src/hackernews.rs
use crate::app::Msg;
use reqwest::Client;
use serde_derive::Deserialize;
use serde_json::Value;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::thread;
const HN_BASE_URL: &str = "https://hacker-news.firebaseio.com/v0/";
#[derive(Deserialize, Debug)]
pub struct Story {
pub by: String,
pub id: u32,
pub score: u64,
pub time: u64,
pub title: String,
#[serde(rename = "type")]
pub _type: String,
pub url: Option,
pub kids: Option,
pub descendents: Option,
}
fn fetch_stories_parsed(client: &Client) -> Result {
let stories_url = format!("{}topstories.json", HN_BASE_URL);
let body = client.get(&stories_url).send()?.text()?;
let story_ids: Value = serde_json::from_str(&body).unwrap();
Ok(story_ids)
}
pub fn top_stories(client: Arc, count: usize, tx: &Sender) {
let tx_clone = tx.clone();
thread::spawn(move || {
let story_ids = fetch_stories_parsed(&client).unwrap();
let filtered: Vec<&Value> = story_ids.as_array()
.unwrap()
.iter()
.take(count)
.collect();
let loaded = !filtered.is_empty();
for id in filtered {
let id = id.as_u64().unwrap();
let story_url = format!("{}item/{}.json", HN_BASE_URL, id);
let story = client.get(&story_url)
.send()
.unwrap()
.text()
.unwrap();
let story: Story = serde_json::from_str(&story).unwrap();
tx_clone.send(Msg::NewStory(story)).unwrap();
}
if loaded {
tx_clone.send(Msg::Loaded).unwrap();
}
});
}
// hews/src/main.rs
mod app;
mod hackernews;
use crate::app::App;
fn main() {
let (app, rx) = App::new();
app.launch(rx);
}
页面更新:2024-04-14
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号