import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.JPasswordField; import javax.swing.JTextArea; import javax.swing.JCheckBox; import javax.swing.JOptionPane; import javax.swing.BorderFactory; import javax.swing.border.Border; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Dimension; import java.awt.Font; import java.awt.Cursor; import java.awt.Desktop; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import cafe.biskuteri.hinoki.Tree; import java.net.URI; import java.io.IOException; class LoginWindow extends JFrame { private JKomasto primaire; private MastodonApi api; // - -%- - private LoginComponent display; private boolean serverContacted = false, haveAppCredentials = false, haveAccessToken = false, haveAccountDetails = false; // ---%-@-%--- public void updateStatusDisplay() { char prefix1 = serverContacted ? '✓' : '✕'; char prefix2 = haveAppCredentials ? '✓' : '✕'; char prefix3 = haveAccessToken ? '✓' : '✕'; char prefix4 = haveAccountDetails ? '✓' : '✕'; StringBuilder b = new StringBuilder(); b.append(prefix1 + " Have connection to server\n"); b.append(prefix2 + " Have app credentials\n"); b.append(prefix3 + " Have access token\n"); b.append(prefix4 + " Have account details\n"); display.setText(b.toString()); display.paintImmediately(display.getBounds(null)); } public void useCache() { display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { api.loadCache(); haveAppCredentials = true; haveAccessToken = true; display.setInstanceUrl(api.getInstanceUrl()); updateStatusDisplay(); } catch (IOException eIo) { JOptionPane.showMessageDialog( this, "We couldn't get login details from the cache.." + "\n" + eIo.getClass() + ": " + eIo.getMessage() ); display.setAutoLoginToggled(false); } display.setCursor(null); if (!haveAccessToken) return; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getAccountDetails(new RequestListener() { public void connectionFailed(IOException eIo) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get account details, failed.." + "\n" + eIo.getMessage() ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get account details, failed.." + "\n" + json.get("error").value + "\n(HTTP error code: " + httpCode + ")" ); } public void requestSucceeded(Tree json) { api.setAccountDetails(json); serverContacted = true; haveAccountDetails = true; updateStatusDisplay(); } }); display.setCursor(null); if (!haveAccountDetails) return; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { api.saveToCache(); } catch (IOException eIo) { JOptionPane.showMessageDialog( this, "We couldn't save login details into the cache.." + "\n" + eIo.getClass() + ": " + eIo.getMessage() ); } display.setCursor(null); primaire.finishedLogin(); } public void useInstanceUrl() { if (display.isAutoLoginToggled()) { useCache(); return; } String url = display.getInstanceUrl(); if (url.trim().isEmpty()) { // Should we show an error dialog..? display.setInstanceUrl(""); return; } if (!hasProtocol(url)) { url = "https://" + url; display.setInstanceUrl(url); display.paintImmediately(display.getBounds(null)); } serverContacted = false; haveAppCredentials = false; haveAccessToken = false; haveAccountDetails = false; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.testUrlConnection(url, new RequestListener() { public void connectionFailed(IOException eIo) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to connect to URL, failed.." + "\n" + eIo.getClass() + ": " + eIo.getMessage() ); } public void requestFailed(int httpCode, Tree response) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to connect to URL, failed.." + "\n" + response.get("body").value ); } public void requestSucceeded(Tree response) { serverContacted = true; updateStatusDisplay(); } }); display.setCursor(null); if (!serverContacted) return; api.setInstanceUrl(url); display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getAppCredentials(new RequestListener() { public void connectionFailed(IOException eIo) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get app credentials, failed.." + "\n" + eIo.getMessage() ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get app credentials, failed.." + "\n" + json.get("error").value + "\n(HTTP error code: " + httpCode + ")" ); } public void requestSucceeded(Tree json) { api.setAppCredentials(json); haveAppCredentials = true; updateStatusDisplay(); } }); display.setCursor(null); if (!haveAppCredentials) return; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); URI uri = api.getAuthorisationURL(); final String MESSAGE1 = "We will need you to login through a web browser,\n" + "and they will give you an authorisation code\n" + "that you will paste here. Sorry..!"; boolean supported = Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE); if (supported) { JOptionPane.showMessageDialog( LoginWindow.this, MESSAGE1, "Authorisation code renewal", JOptionPane.INFORMATION_MESSAGE ); try { Desktop.getDesktop().browse(uri); } catch (IOException eIo) { supported = false; } } if (!supported) { final String MESSAGE2 = "\nWe cannot use Desktop.browse(URI) on your\n" + "computer.. You'll have to open your web\n" + "browser yourself, and copy this URL in."; JTextField field = new JTextField(); field.setText(uri.toString()); field.setPreferredSize(new Dimension(120, 32)); field.selectAll(); JOptionPane.showMessageDialog( LoginWindow.this, new Object[] { MESSAGE1, MESSAGE2, field }, "Authorisation code renewal", JOptionPane.INFORMATION_MESSAGE ); } display.receiveAuthorisationCode(); display.setCursor(null); } public void useAuthorisationCode() { String code = display.getAuthorisationCode(); display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getAccessToken(code, new RequestListener() { public void connectionFailed(IOException eIo) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get app token, failed.." + "\n" + eIo.getMessage() ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get app token, failed.." + "\n" + json.get("error").value + "\n(HTTP error code: " + httpCode + ")" ); } public void requestSucceeded(Tree json) { api.setAccessToken(json); haveAccessToken = true; updateStatusDisplay(); } }); display.setCursor(null); if (!haveAccessToken) return; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); api.getAccountDetails(new RequestListener() { public void connectionFailed(IOException eIo) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get account details, failed.." + "\n" + eIo.getMessage() ); } public void requestFailed(int httpCode, Tree json) { JOptionPane.showMessageDialog( LoginWindow.this, "Tried to get account details, failed.." + "\n" + json.get("error").value + "\n(HTTP error code: " + httpCode + ")" ); } public void requestSucceeded(Tree json) { api.setAccountDetails(json); haveAccountDetails = true; updateStatusDisplay(); } }); display.setCursor(null); if (!haveAccountDetails) return; display.setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { api.saveToCache(); } catch (IOException eIo) { JOptionPane.showMessageDialog( this, "We couldn't save login details into the cache.." + "\n" + eIo.getClass() + ": " + eIo.getMessage() ); } display.setCursor(null); primaire.finishedLogin(); } // - -%- - public static boolean hasProtocol(String url) { return url.matches("^.+://.*"); } // ---%-@-%--- LoginWindow(JKomasto primaire) { super("JKomasto - Login"); this.primaire = primaire; this.api = primaire.getMastodonApi(); setLocationByPlatform(true); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); display = new LoginComponent(this); updateStatusDisplay(); display.setPreferredSize(new Dimension(320, 280)); setContentPane(display); pack(); setIconImage(primaire.getProgramIcon()); } } class LoginComponent extends JPanel implements ActionListener { private LoginWindow primaire; // - -%- - private JTextArea statusDisplay; private JTextField instanceUrlField, authorisationCodeField; private JLabel instanceUrlLabel, authorisationCodeLabel; private JButton instanceUrlButton, authorisationCodeButton; private JPanel labelArea, forField, accountsPanel; private JCheckBox autoLoginToggle; // ---%-@-%--- public String getInstanceUrl() { return instanceUrlField.getText(); } public void setInstanceUrl(String url) { instanceUrlField.setText(url); } public String getAuthorisationCode() { return authorisationCodeField.getText(); } public void setText(String status) { statusDisplay.setText(status); } public void setAutoLoginToggled(boolean a) { autoLoginToggle.setSelected(a); } public boolean isAutoLoginToggled() { return autoLoginToggle.isSelected(); } public void actionPerformed(ActionEvent eA) { if (eA.getSource() == instanceUrlButton) primaire.useInstanceUrl(); if (eA.getSource() == authorisationCodeButton) primaire.useAuthorisationCode(); } public void receiveInstanceUrl() { labelArea.remove(authorisationCodeLabel); forField.remove(authorisationCodeButton); accountsPanel.remove(authorisationCodeField); labelArea.add(instanceUrlLabel, BorderLayout.NORTH); forField.add(instanceUrlButton, BorderLayout.EAST); accountsPanel.add(instanceUrlField); revalidate(); } public void receiveAuthorisationCode() { labelArea.remove(instanceUrlLabel); forField.remove(instanceUrlButton); accountsPanel.remove(instanceUrlField); labelArea.add(authorisationCodeLabel, BorderLayout.NORTH); forField.add(authorisationCodeButton, BorderLayout.EAST); accountsPanel.add(authorisationCodeField); revalidate(); } // ---%-@-%--- LoginComponent(LoginWindow primaire) { this.primaire = primaire; Border b1, b2, bi, bo, be; b1 = BorderFactory.createEtchedBorder(); b2 = BorderFactory.createEmptyBorder(8, 8, 8, 8); bi = BorderFactory.createCompoundBorder(b1, b2); bo = BorderFactory.createEmptyBorder(8, 8, 8, 8); be = BorderFactory.createEmptyBorder(); Font f1, f2; f1 = new Font("Dialog", Font.PLAIN, 12); f2 = new Font("Dialog", Font.PLAIN, 16); // We can't use swing.Box for layout, // it's malfunctioning for some reason. instanceUrlField = new JTextField(); instanceUrlField.setFont(f1); String s1 = "Enter an instance URL!"; instanceUrlLabel = new JLabel(s1); instanceUrlLabel.setLabelFor(instanceUrlField); instanceUrlLabel.setFont(f1); instanceUrlButton = new JButton("Connect"); instanceUrlButton.setFont(f1); instanceUrlButton.setMnemonic(KeyEvent.VK_C); instanceUrlButton.addActionListener(this); authorisationCodeField = new JPasswordField(); authorisationCodeField.setFont(f1); String s2 = "Paste the authorisation code!"; authorisationCodeLabel = new JLabel(s2); authorisationCodeLabel.setLabelFor(authorisationCodeField); authorisationCodeLabel.setFont(f1); authorisationCodeButton = new JButton("Login"); authorisationCodeButton.setFont(f1); authorisationCodeButton.setMnemonic(KeyEvent.VK_L); authorisationCodeButton.addActionListener(this); labelArea = new JPanel(); labelArea.setLayout(new BorderLayout()); forField = new JPanel(); forField.setOpaque(false); forField.setLayout(new BorderLayout()); forField.add(labelArea, BorderLayout.WEST); String s3 = "Automatically login if possible"; autoLoginToggle = new JCheckBox(s3); autoLoginToggle.setMnemonic(KeyEvent.VK_A); autoLoginToggle.setBorder(be); autoLoginToggle.setFont(f1); JPanel optionsPanel = new JPanel(); optionsPanel.setOpaque(false); optionsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); optionsPanel.setBorder(bi); optionsPanel.add(autoLoginToggle); accountsPanel = new JPanel(); accountsPanel.setOpaque(false); accountsPanel.setLayout(new BorderLayout(0, 4)); accountsPanel.add(instanceUrlField); accountsPanel.add(forField, BorderLayout.SOUTH); statusDisplay = new JTextArea(); statusDisplay.setEditable(false); statusDisplay.setBackground(null); statusDisplay.setBorder(bi); statusDisplay.setFont(f2); receiveInstanceUrl(); setLayout(new BorderLayout(0, 8)); add(accountsPanel, BorderLayout.NORTH); add(optionsPanel, BorderLayout.CENTER); add(statusDisplay, BorderLayout.SOUTH); setBorder(bo); } }