diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21b4487 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Project exclude paths +/out/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/artifacts/ZoomManager_jar.xml b/.idea/artifacts/ZoomManager_jar.xml new file mode 100644 index 0000000..8eb828d --- /dev/null +++ b/.idea/artifacts/ZoomManager_jar.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/out/artifacts/ZoomManager_jar + + + + + \ No newline at end of file diff --git a/.idea/description.html b/.idea/description.html new file mode 100644 index 0000000..db5f129 --- /dev/null +++ b/.idea/description.html @@ -0,0 +1 @@ +Simple Java application that includes a class with main() method \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3afb4ee --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..789a4f2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/project-template.xml b/.idea/project-template.xml new file mode 100644 index 0000000..1f08b88 --- /dev/null +++ b/.idea/project-template.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..76c51b1 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/ZoomManager.iml b/ZoomManager.iml new file mode 100644 index 0000000..730a8f1 --- /dev/null +++ b/ZoomManager.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 0000000..af3cdae Binary files /dev/null and b/res/icon.png differ diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e42c7b7 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mt.Main + diff --git a/src/com/mt/Debug.java b/src/com/mt/Debug.java new file mode 100644 index 0000000..19f3e0d --- /dev/null +++ b/src/com/mt/Debug.java @@ -0,0 +1,18 @@ +package com.mt; + +import javax.swing.*; + +public class Debug { + public static int INF = JOptionPane.INFORMATION_MESSAGE; + public static int ERR = JOptionPane.ERROR_MESSAGE; + public static int TST = JOptionPane.PLAIN_MESSAGE; + public static int WARN = JOptionPane.WARNING_MESSAGE; + + public static void callCrashDialog(String title, String message, int msgtype) { + JOptionPane.showMessageDialog(null, message, title, msgtype); + } + + public static void callCrashDialog(String title, Object message, int msgtype) { + JOptionPane.showMessageDialog(null, message, title, msgtype); + } +} diff --git a/src/com/mt/JoinButton.java b/src/com/mt/JoinButton.java new file mode 100644 index 0000000..2c958e4 --- /dev/null +++ b/src/com/mt/JoinButton.java @@ -0,0 +1,47 @@ +package com.mt; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class JoinButton extends javax.swing.JButton{ + ZoomMeeting meeting; + public JoinButton() { + + } + + public JoinButton(Icon icon) { + super(icon); + } + + public JoinButton(String text) { + super(text); + } + + public JoinButton(Action a) { + super(a); + } + + public JoinButton(String text, Icon icon) { + super(text, icon); + } + + public JoinButton(String text, ZoomMeeting meeting) { + super(text); + this.meeting = meeting; + this.addActionListener(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + meeting.joinMeeting(); + } + }); + } + + public ZoomMeeting getMeeting() { + return meeting; + } + + public void setMeeting(ZoomMeeting meeting) { + this.meeting = meeting; + } +} diff --git a/src/com/mt/Main.java b/src/com/mt/Main.java new file mode 100644 index 0000000..103e522 --- /dev/null +++ b/src/com/mt/Main.java @@ -0,0 +1,177 @@ +package com.mt; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +public class Main { + + /* + TODO: + Refactor class: + Currently there are lots of inner classes which could easily be separated. + One obvious benefit is geting rid of DefaultListModel finalZmmodel = zmmodel; on line ~55 + */ + public static final int MEETING_LIMIT = 16; + public static int meetingCounter = 0; + public static void main(String[] args) { + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch(Exception e){ + Debug.callCrashDialog("Error", "The System LaF could not be set. Reason: " + e.getMessage(), Debug.ERR); + } + + final JFrame window = new JFrame("Zoom Manager"); + window.setSize(800, 600); + window.setResizable(false); + window.setLayout(new BorderLayout()); + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + try { + window.setIconImage(ImageIO.read(new File("res/icon.png"))); + } catch (IOException e) { + e.printStackTrace(); + Debug.callCrashDialog("Error", "An erorr occured while trying to set the App's ImageIcon.\nReason: " + e.getMessage(), Debug.ERR); + } + DefaultListModel zmmodel = new DefaultListModel<>(); + //zmmodel.addElement(new ZoomMeeting("Test", "96151030602", "a25IYjRvS0lBL0pvWVlma0FWWWpjZz09", "Test123")); + + Path workingDir = Paths.get(System.getProperty("user.home"), "Documents", "MajickTek", "ZoomManager"); + try { + Files.createDirectories(workingDir); + if(Files.exists(workingDir)) { + FileInputStream fis = new FileInputStream(Paths.get(workingDir.toString(), "ZoomMeetings.lstbin").toString()); + ObjectInputStream ois = new ObjectInputStream(fis); + zmmodel = (DefaultListModel) ois.readObject(); + Debug.callCrashDialog("Success!", "File has loaded successfully!\nUnless it hasn't, in which case you may see another popup in the near future.", Debug.INF); + } + } catch (IOException | ClassNotFoundException ioException) { + ioException.printStackTrace(); + //Debug.callCrashDialog("Error", String.format("Something failed while loading Save File paths/files.%nReason:%s%nFor more, view the stack trace in the debug console.%nP.S. This could just mean that there is no previous Save File.", ioException.getMessage()), Debug.ERR); + } + + JList zmlist = new JList<>(zmmodel); + zmlist.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "addItem"); + DefaultListModel finalZmmodel = zmmodel; + + zmlist.getActionMap().put("addItem", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + meetingCounter++; + if(meetingCounter <= MEETING_LIMIT) { + //finalZmmodel.addElement(MeetingItemPrompt.getPrompt()); + finalZmmodel.addElement(MeetingItemPrompt.editInfo(null)); + zmlist.updateUI(); + } + } + }); + + + + zmlist.getInputMap().put(KeyStroke.getKeyStroke("DELETE"), "removeItem"); + zmlist.getActionMap().put("removeItem", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + finalZmmodel.removeRange(zmlist.getMinSelectionIndex(), zmlist.getMaxSelectionIndex()); + zmlist.updateUI(); + } + }); + + zmlist.getInputMap().put(KeyStroke.getKeyStroke("ctrl R"), "refresh"); + zmlist.getActionMap().put("refresh", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + zmlist.updateUI(); + } + }); + + zmlist.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "showInfo"); + zmlist.getActionMap().put("showInfo", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + MeetingItemPrompt.editInfo(finalZmmodel.elementAt(zmlist.getSelectedIndex())); + zmlist.updateUI(); + } + }); + + zmlist.getInputMap().put(KeyStroke.getKeyStroke("E"), "launchMeeting"); + zmlist.getActionMap().put("launchMeeting", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + finalZmmodel.elementAt(zmlist.getSelectedIndex()).joinMeeting(); + + } + }); + + zmlist.getInputMap().put(KeyStroke.getKeyStroke("ctrl S"), "saveList"); + zmlist.getActionMap().put("saveList", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + Path path = Paths.get(System.getProperty("user.home"), "Documents", "MajickTek", "ZoomManager"); + try { + Files.createDirectories(path); + if(Files.exists(path)) { + FileOutputStream fos = new FileOutputStream(Paths.get(path.toString(), "ZoomMeetings.lstbin").toString()); + ObjectOutputStream ost = new ObjectOutputStream(fos); + ost.writeObject(finalZmmodel); + Debug.callCrashDialog("Success!", "File has saved successfully!\nUnless it hasn't, in which case you may see another popup in the near future.", Debug.INF); + } + } catch (IOException ioException) { + ioException.printStackTrace(); + Debug.callCrashDialog("Error", String.format("Something failed while creating Save File paths/files.%nReason:%s%nFor more, view the stack trace in the debug console.", ioException.getMessage()), Debug.ERR); + } + + } + }); + + window.add(zmlist, BorderLayout.CENTER); + + + + JMenuBar mb = new JMenuBar(); + JMenu help = new JMenu("Help"); + JMenuItem about = new JMenuItem("About"); + about.addActionListener(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + JPanel aboutPanel = new JPanel(); + aboutPanel.setLayout(new BoxLayout(aboutPanel, BoxLayout.Y_AXIS)); + JTextArea info = new JTextArea("A minimalist, open-source app to manage your Zoom meetings. Intended for use by students.\nAuthor: MajickTek\nhttps://github.com/MajickTek\nCurrent meeting limit: " + MEETING_LIMIT); + info.setEditable(false); + aboutPanel.add(info); + JLabel iconLabel = new JLabel(); + try { + iconLabel.setIcon(new ImageIcon(ImageIO.read(new File("res/icon.png")).getScaledInstance(64, 64, Image.SCALE_SMOOTH))); + } catch (IOException ioException) { + ioException.printStackTrace(); + }; + aboutPanel.add(iconLabel); + Debug.callCrashDialog("About", aboutPanel, Debug.INF); + } + }); + JMenuItem guide = new JMenuItem("Guide"); + guide.addActionListener(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + openGuideBox(); + } + }); + help.add(about); + help.add(guide); + mb.add(help); + + window.add(mb, BorderLayout.NORTH); + window.setVisible(true); + } + + private static void openGuideBox() { + Debug.callCrashDialog("Guide", "Add Meeting: SPACE\nRemove Single Meeting: Select with LEFT MOUSE, DELETE\nRemove Multiple Meetings: Select with SHIFT+LEFT MOUSE, DELETE\nLaunch Meeting: E\nRefresh: CTRL + R", Debug.INF); + } +} diff --git a/src/com/mt/MeetingItemPrompt.java b/src/com/mt/MeetingItemPrompt.java new file mode 100644 index 0000000..7fa66df --- /dev/null +++ b/src/com/mt/MeetingItemPrompt.java @@ -0,0 +1,53 @@ +package com.mt; + +import javax.swing.*; +import java.awt.*; + +public class MeetingItemPrompt { + + /** + * This method can be called in 2 ways. + *
+ * Way 1: MeetingItemPrompt.editInfo(...) + *
+ * When used in this way, the return value is null. The function simply modifies its parameter. + *
+ * Way 2: model.addElement(MeetingItemPrompt.editInfo(null) + *
+ * When used this way, the return value is a ZoomMeeting (as a new one is being created). No old ZoomMeeting objects are being modified, so the parameter can be null. + * @param zoomMeeting if null, create a new object. If not null, edit an existing object. + * @return A new ZoomMeeting if zoomMeeting is null. If not null, edit zoomMeeting. + */ + public static ZoomMeeting editInfo(ZoomMeeting zoomMeeting) { + JPanel dialogPanel = new JPanel(); + BoxLayout bl = new BoxLayout(dialogPanel, BoxLayout.Y_AXIS); + dialogPanel.setLayout(bl); + JTextField meetingName = new JTextField(zoomMeeting != null ? zoomMeeting.meeting_Name : "", 20), + meetingID = new JTextField(zoomMeeting != null ? zoomMeeting.meeting_ID : "", 20), + meetingPass = new JTextField(zoomMeeting != null ? zoomMeeting.meeting_PSWD: "", 20), + meetingUsername = new JTextField(zoomMeeting != null ? zoomMeeting.join_Name: "", 20), + date = new JTextField(zoomMeeting != null ? zoomMeeting.date : "", 20); + dialogPanel.add(new JLabel("Meeting Name:")); + dialogPanel.add(meetingName); + dialogPanel.add(new JLabel("Meeting ID:")); + dialogPanel.add(meetingID); + dialogPanel.add(new JLabel("Meeting Password:")); + dialogPanel.add(meetingPass); + dialogPanel.add(new JLabel("Meeting Username:")); + dialogPanel.add(meetingUsername); + dialogPanel.add(new JLabel("Meeting Date:")); + dialogPanel.add(date); + + int result = JOptionPane.showConfirmDialog(null, dialogPanel, "Edit Meeting", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if(result == JOptionPane.OK_OPTION && zoomMeeting != null) { + zoomMeeting.setMeeting_Name(meetingName.getText()); + zoomMeeting.setMeeting_ID(meetingID.getText()); + zoomMeeting.setMeeting_PSWD(meetingPass.getText()); + zoomMeeting.setJoin_Name(meetingUsername.getText()); + zoomMeeting.setDate(date.getText()); + } else if(zoomMeeting == null) { + return new ZoomMeeting(meetingName.getText(), meetingID.getText(), meetingPass.getText(), meetingUsername.getText(), date.getText()); + } + return null; // i don't use it like this - this is for calling it without assigning it to a variable + } +} diff --git a/src/com/mt/URLOpener.java b/src/com/mt/URLOpener.java new file mode 100644 index 0000000..33283a7 --- /dev/null +++ b/src/com/mt/URLOpener.java @@ -0,0 +1,16 @@ +package com.mt; + +import java.awt.*; +import java.net.URI; + +public class URLOpener { + public static void openURL(String url) { + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (Exception e) { + e.printStackTrace(); + Debug.callCrashDialog("Error", "Could not join meeting.\nReason: " + e.getMessage(), Debug.ERR); + } + } + +} diff --git a/src/com/mt/ZoomMeeting.java b/src/com/mt/ZoomMeeting.java new file mode 100644 index 0000000..d9f227d --- /dev/null +++ b/src/com/mt/ZoomMeeting.java @@ -0,0 +1,91 @@ +package com.mt; + +import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +public class ZoomMeeting implements Serializable { + + String meeting_Name, meeting_ID, meeting_PSWD, join_Name, date; + public ZoomMeeting(String meeting_Name, String meeting_ID, String meeting_PSWD, String join_Name, String date) { + meeting_ID = meeting_ID.replace("-", ""); // this way if you use xxx-xxx-xxxx it gets shortened. no validation for length + if(join_Name.isEmpty()) { + join_Name = "zm_"+new Random().nextInt(10000); + } + join_Name = join_Name.replace(" ", "%20"); + this.meeting_ID = meeting_ID; + this.meeting_PSWD = meeting_PSWD; + this.join_Name = join_Name; + this.meeting_Name = meeting_Name; + this.date = date; + } + + + public String getDate() { + return date; + } + + public Date getDateParsed() throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat("E hh:mm a"); + Date d = sdf.parse(this.date); + return d; + } + + public void setDate(String date) { + this.date = date; + } + + public String getMeeting_ID() { + return meeting_ID; + } + + public void setMeeting_ID(String meeting_ID) { + meeting_ID = meeting_ID.replace("-", ""); + this.meeting_ID = meeting_ID; + } + + public String getMeeting_PSWD() { + return meeting_PSWD; + } + + public void setMeeting_PSWD(String meeting_PSWD) { + this.meeting_PSWD = meeting_PSWD; + } + + public String getJoin_Name() { + return join_Name; + } + + public void setJoin_Name(String join_Name) { + this.join_Name = join_Name; + } + + public String getMeeting_Name() { + return meeting_Name; + } + + public void setMeeting_Name(String meeting_Name) { + this.meeting_Name = meeting_Name; + } + + public String getMeetingLink() { + /* + Desktop: zoommtg:// + Mobile: zoomus:// + */ + return String.format("zoommtg://zoom.us/join?confno=%s&pwd=%s&uname=%s", this.meeting_ID, this.meeting_PSWD, this.join_Name); + } + + public void joinMeeting() { + URLOpener.openURL(this.getMeetingLink()); + } + + @Override + public String toString() { + return String.format("%s | %s", this.meeting_Name, this.date); + } + +}