/* copyright This file is part of JKomasto2. Written in 2022 by Usawashi This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . copyright */ 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.awt.Image; import java.awt.Graphics; import java.util.List; import java.util.ArrayList; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.Cursor; import java.awt.Insets; 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 notifications; private MastodonApi api; // - -%- - private NotificationsComponent display; private boolean showingLatest; // - -%- - private static int ROW_COUNT = NotificationsComponent.ROW_COUNT; // ---%-@-%--- public synchronized void readEntity(Tree entity) { notifications = new ArrayList<>(); for (Tree t: entity) { 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 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) { Post post = new Post(t.get("status")); post.resolveApproximateText(); n.postId = post.id; n.postText = post.approximateText; } notifications.add(n); } } public void refresh() { String firstId = null; if (!showingLatest) { assert !notifications.isEmpty(); firstId = notifications.get(0).id; } if (fetchPage(firstId, null)) { if (notifications.size() < ROW_COUNT) showLatestPage(); display.showNotifications(notifications); display.repaint(); } } public void showLatestPage() { if (fetchPage(null, null)) { display.showNotifications(notifications); showingLatest = true; primaire.getWindowUpdater().add(this); } } public void showPrevPage() { assert !notifications.isEmpty(); if (fetchPage(null, notifications.get(0).id)) { if (notifications.size() < ROW_COUNT) showLatestPage(); display.showNotifications(notifications); showingLatest = false; primaire.getWindowUpdater().remove(this); } } public void showNextPage() { assert !notifications.isEmpty(); int last = notifications.size() - 1; if (fetchPage(notifications.get(last).id, null)) { display.showNotifications(notifications); showingLatest = false; primaire.getWindowUpdater().remove(this); } } // - -%- - private boolean fetchPage(String maxId, String minId) { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); class Handler implements RequestListener { boolean succeeded = false; // -=%=- public void connectionFailed(IOException eIo) { eIo.printStackTrace(); } public void requestFailed(int httpCode, Tree json) { System.err.println(httpCode + json.get("error").value); } public void requestSucceeded(Tree json) { readEntity(json); succeeded = true; } } Handler handler = new Handler(); api.getNotifications(ROW_COUNT, maxId, minId, handler); display.setCursor(null); repaint(); return handler.succeeded; } // ---%-@-%--- NotificationsWindow(JKomasto primaire) { super("Notifications"); this.primaire = primaire; this.api = primaire.getMastodonApi(); notifications = new ArrayList<>(); display = new NotificationsComponent(this); display.setPreferredSize(new Dimension(256, 260)); setContentPane(display); pack(); setIconImage(primaire.getProgramIcon()); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setVisible(true); } } class NotificationsComponent extends JPanel implements ActionListener { private NotificationsWindow primaire; private JButton prev, next; // - -%- - private List rows; // - -%- - static final int ROW_COUNT = 10; // ---%-@-%--- public void showNotifications(List notifications) { for (int o = 0; o < notifications.size(); ++o) { Notification n = notifications.get(o); NotificationComponent c = rows.get(o); c.setName(n.actorName); switch (n.type) { case MENTION: c.setType("mentioned"); break; case BOOST: c.setType("boosted"); break; case FAVOURITE: c.setType("favourited"); break; case FOLLOW: c.setType("followed"); break; case FOLLOWREQ: c.setType("req. follow"); break; case POLL: c.setType("poll ended"); break; case ALERT: c.setType("posted"); break; } c.setText(n.postText); } for (int o = notifications.size(); o < rows.size(); ++o) { NotificationComponent c = rows.get(o); c.setName(""); c.setType(""); c.setText(""); } prev.setEnabled(true); next.setEnabled(!notifications.isEmpty()); } // - -%- - 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 ImageComponent typeImg; private JLabel name, text; // ---%-@-%--- public void setType(String n) { type.setText(n); typeImg.image = ImageApi.local(n + "Notification"); } public void setName(String n) { name.setText(n); } public void setText(String n) { text.setText(n); } // - -%- - public void doLayout() { int w = getWidth(), h = getHeight(); int x0 = w * 0/20; int x1 = w * 7/20; int x2 = w * 13/20; int x3 = w * 15/20; int x4 = w * 20/20; name.setLocation(x0 + 4, 0); name.setSize((x1 - 4) - (x0 + 4), h); type.setLocation(x1 + 4, 0); type.setSize((x2 - 1) - (x1 + 4), h); typeImg.setLocation((x2 + 1), 2); typeImg.setSize((x3 - 4) - (x2 + 1), (h - 2) - 2); text.setLocation(x3 + 4, 0); text.setSize((x4 - 4) - (x3 + 4), h); } public void componentResized(ComponentEvent eC) { doLayout(); } public void componentShown(ComponentEvent eC) { } public void componentHidden(ComponentEvent eC) { } public void componentMoved(ComponentEvent eC) { } // ---%-@-%--- private static class ImageComponent extends JComponent { private Image image, scaled; private boolean snapdown = false; // -=%=- protected void paintComponent(Graphics g) { if (image == null) return; int w = getWidth(), h = getHeight(); int ow = image.getWidth(this); int oh = image.getHeight(this); int nh = h; int nw = ow * nh/oh; if (snapdown) { int sw, sh; for (sw = 1; sw < nw; sw *= 2); for (sh = 1; sh < nh; sh *= 2); nw = sw / 2; nh = sh / 2; } if (scaled == null) scaled = image.getScaledInstance( nw, nh, Image.SCALE_SMOOTH ); int x = (w - nw) / 2; int y = (h - nh) / 2; g.drawImage(scaled, x, y, this); } } // ---%-@-%--- NotificationComponent() { Font f = new Font("Dialog", Font.PLAIN, 12); Font f1 = f.deriveFont(Font.PLAIN, 14); Font f2 = f.deriveFont(Font.PLAIN, 11); Font f3 = f.deriveFont(Font.ITALIC, 14); Color c = new Color(0, 0, 0, 25); Border b1 = BorderFactory.createMatteBorder(0, 0, 1, 0, c); name = new JLabel(); name.setFont(f1); type = new JLabel(); type.setFont(f2); type.setHorizontalAlignment(JLabel.RIGHT); typeImg = new ImageComponent(); text = new JLabel(); text.setFont(f3); setLayout(null); add(name); add(type); add(typeImg); add(text); this.addComponentListener(this); setBorder(b1); } }