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 @@
+
+ IJ_BASE_PACKAGE
+
\ 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);
+ }
+
+}