2021-07-16 00:37:03 +02:00
|
|
|
|
2021-07-16 01:07:23 +02:00
|
|
|
import javax.swing.JFrame;
|
|
|
|
import javax.swing.JPanel;
|
2021-07-16 00:37:03 +02:00
|
|
|
import javax.swing.JComponent;
|
2021-07-17 11:06:50 +02:00
|
|
|
import javax.swing.JButton;
|
2021-07-16 00:37:03 +02:00
|
|
|
import javax.swing.JLabel;
|
2021-07-17 12:03:59 +02:00
|
|
|
import javax.swing.JMenu;
|
2021-07-31 06:01:20 +02:00
|
|
|
import javax.swing.JMenuItem;
|
2021-07-17 12:03:59 +02:00
|
|
|
import javax.swing.JMenuBar;
|
|
|
|
import javax.swing.JSeparator;
|
2021-07-16 00:37:03 +02:00
|
|
|
import javax.swing.Box;
|
2021-07-17 11:06:50 +02:00
|
|
|
import javax.swing.BorderFactory;
|
2021-07-16 00:37:03 +02:00
|
|
|
import java.awt.BorderLayout;
|
2021-07-17 11:06:50 +02:00
|
|
|
import java.awt.GridBagLayout;
|
|
|
|
import java.awt.GridBagConstraints;
|
|
|
|
import java.awt.FlowLayout;
|
2021-07-16 00:37:03 +02:00
|
|
|
import java.awt.Font;
|
2021-07-16 11:46:17 +02:00
|
|
|
import java.awt.Dimension;
|
2021-07-17 11:06:50 +02:00
|
|
|
import java.awt.Insets;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.ArrayList;
|
2021-08-14 20:56:37 +02:00
|
|
|
import java.util.Deque;
|
|
|
|
import java.util.LinkedList;
|
2021-07-31 06:01:20 +02:00
|
|
|
import java.awt.event.ActionListener;
|
|
|
|
import java.awt.event.ActionEvent;
|
2021-07-16 00:37:03 +02:00
|
|
|
|
2021-07-16 01:07:23 +02:00
|
|
|
class
|
2021-07-31 06:01:20 +02:00
|
|
|
TimelineWindow extends JFrame
|
|
|
|
implements ActionListener {
|
2021-07-16 01:07:23 +02:00
|
|
|
|
2021-07-16 11:46:17 +02:00
|
|
|
private Timeline
|
|
|
|
timeline;
|
|
|
|
|
|
|
|
// - -%- -
|
|
|
|
|
2021-07-16 01:07:23 +02:00
|
|
|
private TimelineComponent
|
|
|
|
display;
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
private JMenuItem
|
|
|
|
openHome,
|
|
|
|
// umm, what about the timeline that's like, notes that your
|
|
|
|
// post was favourited or replied to? those aren't messages..
|
|
|
|
openMessages,
|
|
|
|
openLocal,
|
|
|
|
openFederated,
|
|
|
|
createPost,
|
|
|
|
openAutoPostView,
|
|
|
|
quit;
|
|
|
|
|
|
|
|
private JMenuItem
|
|
|
|
flipToNewestPost;
|
|
|
|
|
2021-08-14 20:56:37 +02:00
|
|
|
private Deque<Deque<String>>
|
|
|
|
postIDsSeen;
|
|
|
|
|
2021-07-17 11:46:27 +02:00
|
|
|
// ---%-@-%---
|
|
|
|
|
|
|
|
public void
|
|
|
|
setTimeline(Timeline timeline)
|
|
|
|
{
|
2021-07-29 11:40:52 +02:00
|
|
|
if (timeline == null)
|
|
|
|
{
|
|
|
|
timeline = new Timeline();
|
|
|
|
timeline.type = TimelineType.LOCAL;
|
|
|
|
}
|
|
|
|
|
2021-07-17 11:46:27 +02:00
|
|
|
assert timeline != null;
|
|
|
|
this.timeline = timeline;
|
2021-07-29 11:40:52 +02:00
|
|
|
|
|
|
|
// Here, we should request for posts for the timeline.
|
|
|
|
// And pass data to TimelineComponent.
|
2021-07-17 11:46:27 +02:00
|
|
|
}
|
|
|
|
|
2021-07-31 13:28:46 +02:00
|
|
|
public void
|
|
|
|
nextPage()
|
|
|
|
{
|
2021-08-14 20:56:37 +02:00
|
|
|
/*
|
|
|
|
* uhh, what if the ID we provide is invalid.
|
|
|
|
* Or does Mastodon keep IDs as 'deleted'? That
|
|
|
|
* would make things a lot simpler.
|
|
|
|
*/
|
2021-07-31 13:28:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void
|
|
|
|
previousPage()
|
|
|
|
{
|
2021-08-14 20:56:37 +02:00
|
|
|
if (postIDsSeen.isEmpty())
|
|
|
|
{
|
|
|
|
// Just request the latest first page.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Deque<String> lastPagePostIDs = postIDsSeen.pop();
|
|
|
|
|
|
|
|
while (!lastPagePostIDs.isEmpty())
|
|
|
|
{
|
|
|
|
String idToTry = lastPagePostIDs.pop();
|
|
|
|
|
|
|
|
// Fetch page for this ID.
|
|
|
|
// If successful, render and return.
|
|
|
|
}
|
2021-07-31 13:28:46 +02:00
|
|
|
|
2021-08-14 20:56:37 +02:00
|
|
|
// Didn't return from above. Our current page "replaced"
|
|
|
|
// the previous page, so the previous page's predecessor
|
|
|
|
// is our previous page. Try to flip to that.
|
|
|
|
previousPage();
|
2021-07-31 13:28:46 +02:00
|
|
|
}
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
// - -%- -
|
|
|
|
|
|
|
|
public void
|
|
|
|
actionPerformed(ActionEvent eA)
|
|
|
|
{
|
|
|
|
Object src = eA.getSource();
|
|
|
|
|
|
|
|
if (src == quit)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Umm.. should we even have a quit option?
|
|
|
|
* Wouldn't closing every window work? By
|
|
|
|
* disposing of everyone. We won't be having any
|
|
|
|
* background threads IIRC (and they can check
|
|
|
|
* if the Swing thread is alive).
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 01:07:23 +02:00
|
|
|
// ---%-@-%---
|
|
|
|
|
|
|
|
TimelineWindow()
|
|
|
|
{
|
2021-07-16 11:46:17 +02:00
|
|
|
getContentPane().setPreferredSize(new Dimension(300, 400));
|
|
|
|
pack();
|
|
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
openHome = new JMenuItem("Open home timeline");
|
|
|
|
openFederated = new JMenuItem("Open federated timeline");
|
|
|
|
createPost = new JMenuItem("Create a post");
|
|
|
|
openAutoPostView = new JMenuItem("Open auto post view");
|
|
|
|
quit = new JMenuItem("Quit");
|
|
|
|
openHome.addActionListener(this);
|
|
|
|
openFederated.addActionListener(this);
|
|
|
|
createPost.addActionListener(this);
|
|
|
|
openAutoPostView.addActionListener(this);
|
|
|
|
quit.addActionListener(this);
|
|
|
|
|
|
|
|
flipToNewestPost = new JMenuItem("Flip to newest post");
|
|
|
|
flipToNewestPost.addActionListener(this);
|
|
|
|
|
2021-07-17 12:03:59 +02:00
|
|
|
JMenu programMenu = new JMenu("Program");
|
2021-07-31 06:01:20 +02:00
|
|
|
programMenu.add(openHome);
|
|
|
|
programMenu.add(openFederated);
|
2021-07-17 12:03:59 +02:00
|
|
|
programMenu.add(new JSeparator());
|
2021-07-31 06:01:20 +02:00
|
|
|
programMenu.add(createPost);
|
|
|
|
programMenu.add(openAutoPostView);
|
2021-07-17 12:03:59 +02:00
|
|
|
programMenu.add(new JSeparator());
|
2021-07-31 06:01:20 +02:00
|
|
|
programMenu.add(quit);
|
2021-07-17 12:03:59 +02:00
|
|
|
JMenu timelineMenu = new JMenu("Timeline");
|
2021-07-31 06:01:20 +02:00
|
|
|
timelineMenu.add(flipToNewestPost);
|
2021-07-17 12:03:59 +02:00
|
|
|
JMenuBar menuBar = new JMenuBar();
|
|
|
|
menuBar.add(programMenu);
|
|
|
|
menuBar.add(timelineMenu);
|
|
|
|
setJMenuBar(menuBar);
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
display = new TimelineComponent(this);
|
2021-07-17 12:03:59 +02:00
|
|
|
|
|
|
|
setContentPane(display);
|
2021-07-16 01:07:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class
|
2021-07-31 06:01:20 +02:00
|
|
|
TimelineComponent extends JPanel
|
|
|
|
implements ActionListener {
|
|
|
|
|
|
|
|
private TimelineWindow
|
|
|
|
primaire;
|
|
|
|
|
|
|
|
private final List<Post>
|
|
|
|
posts = new ArrayList<>();
|
|
|
|
|
|
|
|
// - -%- -
|
2021-07-16 01:07:23 +02:00
|
|
|
|
2021-07-17 11:06:50 +02:00
|
|
|
private JButton
|
|
|
|
next, prev;
|
|
|
|
|
|
|
|
private JLabel
|
|
|
|
pageLabel;
|
|
|
|
|
2021-07-17 11:46:27 +02:00
|
|
|
private final List<PostPreviewComponent>
|
|
|
|
postPreviews = new ArrayList<>();
|
2021-07-17 11:06:50 +02:00
|
|
|
|
2021-07-29 11:40:52 +02:00
|
|
|
// ---%-@-%---
|
|
|
|
|
|
|
|
public void
|
2021-07-31 06:01:20 +02:00
|
|
|
setPosts(List<Post> posts)
|
|
|
|
{
|
|
|
|
if (posts == null)
|
|
|
|
{
|
|
|
|
posts = new ArrayList<>();
|
|
|
|
// Insert sample timeline posts here
|
|
|
|
}
|
|
|
|
|
|
|
|
this.posts.clear();
|
|
|
|
this.posts.addAll(posts);
|
|
|
|
syncPreviewsToPosts();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void
|
|
|
|
setPageLabel(String label)
|
|
|
|
{
|
|
|
|
assert label != null;
|
|
|
|
pageLabel.setText("" + label);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void
|
|
|
|
setNextPageAvailable(boolean available)
|
|
|
|
{
|
|
|
|
next.setEnabled(available);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void
|
|
|
|
setPreviousPageAvailable(boolean available)
|
|
|
|
{
|
|
|
|
prev.setEnabled(available);
|
|
|
|
}
|
|
|
|
|
|
|
|
// - -%- -
|
|
|
|
|
|
|
|
private void
|
|
|
|
syncPreviewsToPosts()
|
2021-07-29 11:40:52 +02:00
|
|
|
{
|
2021-07-31 06:01:20 +02:00
|
|
|
for (PostPreviewComponent p: postPreviews)
|
|
|
|
{
|
|
|
|
p.setTopLeft("Top left");
|
|
|
|
p.setTopRight("Top right");
|
|
|
|
p.setBottom("Bottom");
|
|
|
|
// (In reality we are supposed to map with posts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void
|
|
|
|
actionPerformed(ActionEvent eA)
|
|
|
|
{
|
|
|
|
Object src = eA.getSource();
|
|
|
|
|
2021-07-31 13:28:46 +02:00
|
|
|
if (src == next) primaire.nextPage();
|
|
|
|
else if (src == prev) primaire.previousPage();
|
2021-07-29 11:40:52 +02:00
|
|
|
/*
|
2021-07-31 06:01:20 +02:00
|
|
|
* I think the page previews will just forward to us.
|
|
|
|
* But I think they'll have to tell us where they are
|
|
|
|
* in the list somehow, because we need to show only
|
|
|
|
* one post as selected.
|
2021-07-29 11:40:52 +02:00
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2021-07-17 11:06:50 +02:00
|
|
|
// ---%-@-%---
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
TimelineComponent(TimelineWindow primaire)
|
2021-07-16 01:07:23 +02:00
|
|
|
{
|
2021-07-31 06:01:20 +02:00
|
|
|
this.primaire = primaire;
|
|
|
|
|
2021-07-17 11:06:50 +02:00
|
|
|
prev = new JButton("<");
|
|
|
|
next = new JButton(">");
|
2021-07-31 06:01:20 +02:00
|
|
|
prev.setEnabled(false);
|
|
|
|
next.setEnabled(false);
|
|
|
|
|
|
|
|
pageLabel = new JLabel("0");
|
2021-07-17 11:06:50 +02:00
|
|
|
|
|
|
|
Box bottom = Box.createHorizontalBox();
|
|
|
|
bottom.add(Box.createGlue());
|
|
|
|
bottom.add(prev);
|
|
|
|
bottom.add(Box.createHorizontalStrut(8));
|
|
|
|
bottom.add(next);
|
|
|
|
|
|
|
|
JPanel centre = new JPanel();
|
|
|
|
centre.setOpaque(false);
|
|
|
|
centre.setLayout(new GridBagLayout());
|
|
|
|
GridBagConstraints constraints = new GridBagConstraints();
|
|
|
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
|
|
|
constraints.gridx = 0;
|
|
|
|
constraints.weightx = 1;
|
|
|
|
constraints.insets = new Insets(4, 0, 4, 0);
|
|
|
|
for (int n = 8; n > 0; --n)
|
|
|
|
{
|
2021-07-17 11:46:27 +02:00
|
|
|
PostPreviewComponent p = new PostPreviewComponent();
|
|
|
|
centre.add(p, constraints);
|
|
|
|
postPreviews.add(p);
|
2021-07-17 11:06:50 +02:00
|
|
|
}
|
|
|
|
|
2021-07-16 01:07:23 +02:00
|
|
|
setLayout(new BorderLayout());
|
2021-07-17 11:06:50 +02:00
|
|
|
add(centre, BorderLayout.CENTER);
|
|
|
|
add(bottom, BorderLayout.SOUTH);
|
|
|
|
|
|
|
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
2021-07-31 06:01:20 +02:00
|
|
|
|
|
|
|
setPosts(null);
|
2021-07-16 01:07:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-07-16 00:37:03 +02:00
|
|
|
class
|
2021-07-17 11:46:27 +02:00
|
|
|
PostPreviewComponent extends JComponent {
|
2021-07-16 00:37:03 +02:00
|
|
|
|
|
|
|
private JLabel
|
|
|
|
topLeft, topRight, bottom;
|
|
|
|
|
|
|
|
// ---%-@-%---
|
|
|
|
|
|
|
|
public void
|
|
|
|
setTopLeft(String text) { topLeft.setText(text); }
|
|
|
|
|
|
|
|
public void
|
|
|
|
setTopRight(String text) { topRight.setText(text); }
|
|
|
|
|
|
|
|
public void
|
|
|
|
setBottom(String text) { bottom.setText(text); }
|
|
|
|
|
|
|
|
// ---%-@-%---
|
|
|
|
|
|
|
|
public
|
2021-07-17 11:46:27 +02:00
|
|
|
PostPreviewComponent()
|
2021-07-16 00:37:03 +02:00
|
|
|
{
|
|
|
|
Font font;
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
topLeft = new JLabel();
|
2021-07-16 00:37:03 +02:00
|
|
|
font = topLeft.getFont();
|
|
|
|
topLeft.setFont(font.deriveFont(Font.PLAIN, 12f));
|
|
|
|
setOpaque(false);
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
topRight = new JLabel();
|
2021-07-16 00:37:03 +02:00
|
|
|
topRight.setHorizontalAlignment(JLabel.RIGHT);
|
|
|
|
font = topRight.getFont();
|
|
|
|
topRight.setFont(font.deriveFont(Font.ITALIC, 12f));
|
|
|
|
setOpaque(false);
|
|
|
|
|
2021-07-31 06:01:20 +02:00
|
|
|
bottom = new JLabel();
|
2021-07-16 00:37:03 +02:00
|
|
|
font = bottom.getFont();
|
|
|
|
bottom.setFont(font.deriveFont(Font.PLAIN, 16f));
|
|
|
|
bottom.setOpaque(false);
|
|
|
|
|
|
|
|
Box top = Box.createHorizontalBox();
|
|
|
|
top.add(topLeft);
|
|
|
|
top.add(Box.createGlue());
|
|
|
|
top.add(topRight);
|
|
|
|
|
|
|
|
setLayout(new BorderLayout());
|
|
|
|
add(top, BorderLayout.NORTH);
|
|
|
|
add(bottom, BorderLayout.SOUTH);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|