summaryrefslogtreecommitdiffstats
path: root/gui/src/timeline.rs
blob: 0dace40cc3035d6eb1165e8243212c52b5f30847 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use anyhow::Result;
use futures::StreamExt;

use iced_native::widget::scrollable::State as ScrollableState;

use crate::app::Message;
use crate::post::Post;
use distrox_lib::client::Client;
use distrox_lib::stream::NodeStreamBuilder;
use distrox_lib::types::Payload;

#[derive(Debug)]
pub struct Timeline {
    posts: Vec<Post>,
    scrollable: ScrollableState,
}

impl Timeline {
    pub fn new() -> Self {
        Self {
            posts: Vec::new(),
            scrollable: ScrollableState::new(),
        }
    }

    pub fn push(&mut self, payload: Payload, content: String) {
        self.posts.push(Post::new(payload, content));
    }

    pub fn view(&mut self) -> iced::Element<Message> {
        let scrollable = iced::Scrollable::new(&mut self.scrollable)
            .padding(10)
            .spacing(20)
            .width(iced::Length::Fill)
            .height(iced::Length::Fill)
            .on_scroll(move |offset| {
                Message::TimelineScrolled(offset)
            });

        self.posts
            .iter()
            .fold(scrollable, |scrollable, post| {
                scrollable.push(post.view())
            })
            .into()
    }
}

pub struct PostLoadingRecipe {
    client: Client,
    head: cid::Cid,
}

impl PostLoadingRecipe {
    pub fn new(client: Client, head: cid::Cid) -> Self {
        Self { client, head }
    }
}

// Make sure iced can use our download stream
impl<H, I> iced_native::subscription::Recipe<H, I> for PostLoadingRecipe
where
    H: std::hash::Hasher,
{
    type Output = Message;

    fn hash(&self, state: &mut H) {
        use std::hash::Hash;
        self.head.to_bytes().hash(state);
    }

    fn stream(self: Box<Self>, _input: futures::stream::BoxStream<'static, I>) -> futures::stream::BoxStream<'static, Self::Output>
    {
        log::debug!("Streaming posts starting at HEAD = {:?}", self.head);
        Box::pin({
            NodeStreamBuilder::starting_from(self.head.clone())
                .into_stream(self.client.clone())
                .then(move |node| {
                    let client = self.client.clone();

                    async move {
                        let payload = client.get_payload(node?.payload()).await?;
                        let content = client.get_content_text(payload.content()).await?;

                        Ok((payload, content))
                    }
                })
                .map(|res: Result<_>| match res {
                    Err(_) => Message::PostLoadingFailed,
                    Ok(p) => Message::PostLoaded(p),
                })
        })
    }
}