Added notifications window.

This commit is contained in:
Snowyfox 2022-04-16 10:54:17 -04:00
parent d546ef3210
commit 100f11c6e2
5 changed files with 422 additions and 24 deletions

View File

@ -28,6 +28,9 @@ JKomasto {
private ImageWindow
mediaWindow;
private NotificationsWindow
notificationsWindow;
private TimelineWindowUpdater
timelineWindowUpdater;
@ -45,6 +48,7 @@ JKomasto {
autoViewWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
timelineWindow.setCursor(new Cursor(Cursor.WAIT_CURSOR));
notificationsWindow.showLatestPage();
timelineWindow.showLatestPage();
timelineWindow.setLocationByPlatform(true);
timelineWindow.setVisible(true);
@ -68,6 +72,9 @@ JKomasto {
public ImageWindow
getMediaWindow() { return mediaWindow; }
public NotificationsWindow
getNotificationsWindow() { return notificationsWindow; }
// ---%-@-%---
public static void
@ -85,11 +92,13 @@ JKomasto {
autoViewWindow = new PostWindow(this);
loginWindow = new LoginWindow(this);
mediaWindow = new ImageWindow();
notificationsWindow = new NotificationsWindow(this);
composeWindow.dispose();
autoViewWindow.dispose();
timelineWindow.dispose();
mediaWindow.dispose();
notificationsWindow.dispose();
loginWindow.setLocationByPlatform(true);
loginWindow.setVisible(true);
@ -116,12 +125,23 @@ TimelineType {
FEDERATED,
LOCAL,
HOME,
NOTIFICATIONS,
CONVERSATIONS,
LIST
}
enum
NotificationType {
MENTION,
BOOST,
FAVOURITE,
FOLLOW,
FOLLOWREQ,
POLL,
ALERT
}
class
@ -131,7 +151,7 @@ TimelinePage {
type;
public String
accountNumId;
accountNumId, listId;
public List<Post>
posts;
@ -180,6 +200,20 @@ Post {
}
class
Notification {
public NotificationType
type;
public String
id;
public String
postId, postText, actorNumId, actorName;
}
class
Attachment {

View File

@ -178,29 +178,29 @@ MastodonApi {
public void
getTimelinePage(
TimelineType type, String accountId,
TimelineType type,
int count, String maxId, String minId,
String accountId, String listId,
RequestListener handler)
{
String token = accessToken.get("access_token").value;
assert !(accountId != null && listId != null);
String url = instanceUrl + "/api/v1";
if (accountId != null)
{
url += "/accounts/" + accountId + "/statuses";
}
else if (listId != null)
{
url += "/lists/" + listId;
}
else switch (type)
{
case FEDERATED:
case LOCAL: url += "/timelines/public"; break;
case HOME: url += "/timelines/home"; break;
case NOTIFICATIONS:
url += "/notifications";
// Note that this endpoint returns Notifications,
// not Statuses. But we uniformly return Tree<String>,
// we expect the caller can handle it.
break;
case CONVERSATIONS: url += "/timelines/public"; break;
default: assert false;
}
url += "?limit=" + count;
@ -325,6 +325,32 @@ MastodonApi {
catch (IOException eIo) { handler.connectionFailed(eIo); }
}
public void
getNotifications(
int count, String maxId, String minId,
RequestListener handler)
{
String token = accessToken.get("access_token").value;
String url = instanceUrl + "/api/v1/notifications";
url += "?limit=" + count;
if (maxId != null) url += "&max_id=" + maxId;
if (minId != null) url += "&min_id=" + minId;
try
{
URL endpoint = new URL(url);
HttpURLConnection conn;
conn = (HttpURLConnection)endpoint.openConnection();
String s1 = "Bearer " + token;
conn.setRequestProperty("Authorization", s1);
conn.connect();
doStandardJsonReturn(conn, handler);
}
catch (IOException eIo) { handler.connectionFailed(eIo); }
}
public void
monitorTimeline(
TimelineType type, ServerSideEventsListener handler)
@ -336,8 +362,7 @@ MastodonApi {
{
case FEDERATED: url += "/public"; break;
case LOCAL: url += "/public/local"; break;
case HOME:
case NOTIFICATIONS: url += "/user"; break;
case HOME: url += "/user"; break;
default: assert false;
}

330
NotificationsWindow.java Normal file
View File

@ -0,0 +1,330 @@
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
import java.awt.Font;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.util.List;
import java.util.ArrayList;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Cursor;
import cafe.biskuteri.hinoki.Tree;
import java.io.IOException;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;
class
NotificationsWindow extends JFrame {
private JKomasto
primaire;
private List<Notification>
notifications;
private MastodonApi
api;
// - -%- -
private NotificationsComponent
display;
// - -%- -
private static int
ROW_COUNT = NotificationsComponent.ROW_COUNT;
// ---%-@-%---
public void
showLatestPage()
{
fetchPage(null, null);
display.showNotifications(notifications);
}
public void
showPrevPage()
{
assert !notifications.isEmpty();
fetchPage(null, notifications.get(0).id);
if (notifications.size() < ROW_COUNT) showLatestPage();
display.showNotifications(notifications);
}
public void
showNextPage()
{
assert !notifications.isEmpty();
int last = notifications.size() - 1;
fetchPage(notifications.get(last).id, null);
display.showNotifications(notifications);
}
// - -%- -
private void
fetchPage(String maxId, String minId)
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getNotifications(
ROW_COUNT, maxId, minId,
new RequestListener() {
public void
connectionFailed(IOException eIo)
{
eIo.printStackTrace();
}
public void
requestFailed(int httpCode, Tree<String> json)
{
System.err.println(httpCode + json.get("error").value);
}
public void
requestSucceeded(Tree<String> json)
{
notifications = new ArrayList<>();
for (Tree<String> t: json)
{
Notification n = new Notification();
n.id = t.get("id").value;
String type = t.get("type").value;
if (type.equals("favourite"))
n.type = NotificationType.FAVOURITE;
else if (type.equals("reblog"))
n.type = NotificationType.BOOST;
else if (type.equals("mention"))
n.type = NotificationType.MENTION;
else if (type.equals("follow"))
n.type = NotificationType.FOLLOW;
else if (type.equals("follow_request"))
n.type = NotificationType.FOLLOWREQ;
else if (type.equals("poll"))
n.type = NotificationType.POLL;
else if (type.equals("status"))
n.type = NotificationType.ALERT;
Tree<String> actor = t.get("account");
String aid, aname, adisp;
aid = actor.get("id").value;
aname = actor.get("username").value;
adisp = actor.get("display_name").value;
if (!adisp.isEmpty()) n.actorName = adisp;
else n.actorName = aname;
n.actorNumId = aid;
if (n.type != NotificationType.FOLLOW)
{
Tree<String> post = t.get("status");
String pid = post.get("id").value;
String ptext = post.get("content").value;
n.postId = pid;
n.postText = ptext;
// Should we ask TimelineWindow for help here?
// Or should we break our text parsers into
// a separate class?
}
notifications.add(n);
}
}
}
);
display.setCursor(null);
repaint();
}
// ---%-@-%---
NotificationsWindow(JKomasto primaire)
{
super("Notifications");
this.primaire = primaire;
this.api = primaire.getMastodonApi();
notifications = new ArrayList<>();
display = new NotificationsComponent(this);
display.setPreferredSize(new Dimension(256, 400));
setContentPane(display);
pack();
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setVisible(true);
}
}
class
NotificationsComponent extends JPanel
implements ActionListener {
private NotificationsWindow
primaire;
private JButton
prev, next;
// - -%- -
private List<NotificationComponent>
rows;
// - -%- -
static final int
ROW_COUNT = 16;
// ---%-@-%---
public void
showNotifications(List<Notification> notifications)
{
assert notifications.size() == rows.size();
for (int o = 0; o < rows.size(); ++o)
{
Notification n = notifications.get(o);
NotificationComponent c = rows.get(o);
c.setName(n.actorName);
c.setType(n.type.toString());
c.setText(n.postText);
}
}
// - -%- -
public void
actionPerformed(ActionEvent eA)
{
if (eA.getSource() == prev) primaire.showPrevPage();
if (eA.getSource() == next) primaire.showNextPage();
}
// ---%-@-%---
NotificationsComponent(NotificationsWindow primaire)
{
this.primaire = primaire;
Border b = BorderFactory.createEmptyBorder(8, 8, 8, 8);
rows = new ArrayList<>();
for (int n = ROW_COUNT; n > 0; --n)
rows.add(new NotificationComponent());
prev = new JButton("<");
next = new JButton(">");
prev.addActionListener(this);
next.addActionListener(this);
JPanel centre = new JPanel();
centre.setLayout(new GridLayout(ROW_COUNT, 1));
for (NotificationComponent c: rows) centre.add(c);
Box bottom = Box.createHorizontalBox();
bottom.add(Box.createGlue());
bottom.add(prev);
bottom.add(Box.createHorizontalStrut(8));
bottom.add(next);
bottom.setBorder(b);
setLayout(new BorderLayout());
add(centre);
add(bottom, BorderLayout.SOUTH);
}
}
class
NotificationComponent extends JComponent
implements ComponentListener {
private JLabel
type;
private JLabel
name, text;
// ---%-@-%---
public void
setType(String n) { type.setText(n); }
public void
setName(String n) { name.setText(n); }
public void
setText(String n) { text.setText(n); }
// - -%- -
public void
componentResized(ComponentEvent eC)
{
int w = getWidth(), h = getHeight();
name.setPreferredSize(new Dimension(w * 4 / 10, h));
type.setPreferredSize(new Dimension(w * 3 / 10, h));
text.setPreferredSize(new Dimension(w * 2 / 10, h));
name.setMaximumSize(new Dimension(w * 4 / 10, h));
type.setMaximumSize(new Dimension(w * 3 / 10, h));
text.setMaximumSize(new Dimension(w * 2 / 10, h));
}
public void
componentShown(ComponentEvent eC) { }
public void
componentHidden(ComponentEvent eC) { }
public void
componentMoved(ComponentEvent eC) { }
// ---%-@-%---
NotificationComponent()
{
Font f1 = new Font("Dialog", Font.PLAIN, 12);
Font f2 = new Font("Dialog", Font.PLAIN, 10);
Font f3 = new Font("Dialog", Font.ITALIC, 12);
name = new JLabel();
type = new JLabel();
text = new JLabel();
name.setFont(f1);
type.setFont(f2);
text.setFont(f3);
type.setHorizontalAlignment(JLabel.RIGHT);
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(name);
add(Box.createHorizontalStrut(4));
add(type);
add(Box.createHorizontalStrut(4));
add(text);
this.addComponentListener(this);
setBorder(
BorderFactory.createMatteBorder
(1, 0, 0, 0, new Color(0, 0, 0, 25))
);
}
}

View File

@ -65,6 +65,7 @@ implements ActionListener {
openMessages,
openLocal,
openFederated,
openNotifications,
createPost,
openAutoPostView,
quit;
@ -113,8 +114,9 @@ implements ActionListener {
{
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type, page.accountNumId,
PREVIEW_COUNT, null, null,
page.type,
PREVIEW_COUNT, null, null,
page.accountNumId, page.listId,
new RequestListener() {
public void
@ -159,8 +161,9 @@ implements ActionListener {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type, page.accountNumId,
PREVIEW_COUNT, last.postId, null,
page.type,
PREVIEW_COUNT, last.postId, null,
page.accountNumId, page.listId,
new RequestListener() {
public void
@ -208,8 +211,9 @@ implements ActionListener {
display.setCursor(new Cursor(Cursor.WAIT_CURSOR));
api.getTimelinePage(
page.type, page.accountNumId,
PREVIEW_COUNT, null, first.postId,
page.type,
PREVIEW_COUNT, null, first.postId,
page.accountNumId, page.listId,
new RequestListener() {
public void
@ -291,6 +295,12 @@ implements ActionListener {
PostWindow w = primaire.getAutoViewWindow();
w.setLocation(getX() + 10 + getWidth(), getY());
w.setVisible(true);
}
if (src == openNotifications)
{
NotificationsWindow w = primaire.getNotificationsWindow();
w.setLocationByPlatform(true);
w.setVisible(true);
}
if (src == flipToNewestPost)
{
@ -447,11 +457,13 @@ implements ActionListener {
openHome = new JMenuItem("Open home timeline");
openFederated = new JMenuItem("Open federated timeline");
openNotifications = new JMenuItem("Open notifications");
createPost = new JMenuItem("Create a post");
openAutoPostView = new JMenuItem("Open auto post view");
quit = new JMenuItem("Quit");
openHome.addActionListener(this);
openFederated.addActionListener(this);
openNotifications.addActionListener(this);
createPost.addActionListener(this);
openAutoPostView.addActionListener(this);
quit.addActionListener(this);
@ -463,6 +475,7 @@ implements ActionListener {
programMenu.setMnemonic(KeyEvent.VK_P);
programMenu.add(openHome);
programMenu.add(openFederated);
programMenu.add(openNotifications);
programMenu.add(new JSeparator());
programMenu.add(createPost);
programMenu.add(openAutoPostView);

View File

@ -26,8 +26,7 @@ TimelineWindowUpdater {
private Thread
federated,
local,
home,
notifications;
home;
// ---%-@-%---
@ -49,9 +48,6 @@ TimelineWindowUpdater {
case HOME:
if (home != null) return;
home = t; break;
case NOTIFICATIONS:
if (notifications != null) return;
notifications = t; break;
default: return;
}
t.start();