Renamed and comment a lot of stuff, fixed a bunch of bugs

This commit is contained in:
Thastertyn 2023-05-23 21:14:48 +02:00
parent 009bd97d4f
commit 5f817f3dfc
41 changed files with 935 additions and 782 deletions

View File

@ -18,8 +18,11 @@ import com.googlecode.lanterna.gui2.dialogs.DialogWindow;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import xyz.thastertyn.Tuples.Triplet;
import xyz.thastertyn.Types.InputtedCredentials;
/**
* A Dialog window for inputing username and password.
*/
public class CredentialsInput extends DialogWindow {
private TextBox username;
@ -129,6 +132,9 @@ public class CredentialsInput extends DialogWindow {
setComponent(mainPanel);
}
/**
* User pressed ok, check if anything is actually typed and if so, pack and return it
*/
public void onOK()
{
this.user = username.getText();
@ -148,9 +154,13 @@ public class CredentialsInput extends DialogWindow {
close();
}
/**
* returns the inputted username, password and if user chose to keep credentials stored
* @return {@link InputtedCredentials} with username first, password second and whether to store the credentials third
*/
@Override
public Triplet<String, String, Boolean> showDialog(WindowBasedTextGUI textGUI) {
public InputtedCredentials showDialog(WindowBasedTextGUI textGUI) {
super.showDialog(textGUI);
return new Triplet<String,String,Boolean>(user, pass, remember.isChecked());
return new InputtedCredentials(user, pass, remember.isChecked());
}
}

View File

@ -7,15 +7,9 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class LocalCredentials {
import xyz.thastertyn.Types.Credentials;
/**
* Checks for already saved credentials, at least if its file exists
* @return <ul>
* <li> {@code true} a file exists </li>
* <li> {@code false} doesn't exist </li>
* </ul>
*/
public class LocalCredentials {
private static LocalCredentials localCredentials = new LocalCredentials();
@ -24,6 +18,16 @@ public class LocalCredentials {
private File credentialsFile;
private File credentialsPath;
/**
* Checks for already saved credentials, at least if its file exists <br>
* for Windows it checks {@code AppData\Roaming\jecnak\credemtials.txt} <br>
* for Linux it checks {@code ~/.local/share/jecnak/credentials.txt} <br>
* @return
* <ul>
* <li> {@code true} a file exists </li>
* <li> {@code false} doesn't exist </li>
* </ul>
*/
public boolean checkForExistingCredentials()
{
if(System.getProperty("os.name").equals("Linux"))
@ -49,10 +53,17 @@ public class LocalCredentials {
return true;
}
public String[] getCredentialsFile()
/**
* Try reading the credentails from a local file
* @return String[] with username on index 0, and password on index 1, <br>
* can also return null if credentials are corrupted, inaccessible, etc.
*/
public Credentials getCredentialsFile()
{
// TODO Use hashmap instead of array
String[] result = new String[2];
String user = null;
String pass = null;
if(credentialsFile == null)
{
return null;
@ -68,27 +79,33 @@ public class LocalCredentials {
if(currentValue[0].equals("user"))
{
result[0] = currentValue[1];
user = currentValue[1];
}else if(currentValue[0].equals("pass"))
{
result[1] = currentValue[1];
pass = currentValue[1];
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
if(result[0] == null || result[1] == null)
if(user == null || pass == null)
{
return null;
}
return result;
return new Credentials(user, pass);
}
public boolean saveCredentials(String[] credentials)
/**
* appends the current credentials to the file
* @param credentials the array containing username and password. Username is on index 0, password on index 1
* @return
*/
public boolean saveCredentials(Credentials credentials)
{
if(credentialsFile == null)
{
@ -108,10 +125,10 @@ public class LocalCredentials {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(credentialsFile));
writer.append("user=" + credentials[0]);
writer.append("\n");
writer.append("pass=" + credentials[1]);
writer.append("\n");
writer.append(String.format(
"user=%s\npass=%s\n",
credentials.getUsername(),
credentials.getPassword()));
writer.close();
} catch (IOException e) {
@ -122,14 +139,20 @@ public class LocalCredentials {
return true;
}
/**
* Deletes the credentials file
* @return
*/
public boolean deleteCredentials()
{
credentialsFile.delete();
return true;
}
private LocalCredentials() {}
/**
* Returns a single instance to prevent creating and loading the files all over again when not much has changed
* @return {@link LocalCredentials} instance
*/
public static LocalCredentials getInstance()
{
return localCredentials;

View File

@ -12,16 +12,21 @@ import org.jsoup.Connection.Method;
import org.jsoup.nodes.Document;
import xyz.thastertyn.Scrape.Downloader;
import xyz.thastertyn.Types.Credentials;
public class Login {
private String Jsessionid = null;
// Check for session expiration
private long start;
private long lastCheck;
public void loginJecna(String user, String pass) throws UnknownHostException, IOException, CredentialException, TimeoutException
/**
* Tries logging into SPSE Jecna. If it succeeds it also saves the JSessionId into {@link Downloader} for ease of use. If it fails to login, the program is as good as not running at all
* @param credentials {@link Credentials} for username anas password
* @throws UnknownHostException Most likely no internet connection, spsejecna.cz could not be recognised as a host
* @throws IOException by Jsoup
* @throws CredentialException The username or password is incorrect
* @throws TimeoutException Connection timed out, most likely a very slow internet
*/
public void loginJecna(Credentials credentials) throws UnknownHostException, IOException, CredentialException, TimeoutException
{
//#region JSESSIONID
Connection.Response response = Jsoup.connect("https://www.spsejecna.cz")
@ -34,35 +39,32 @@ public class Login {
//#endregion
//#region Token3
String token3 = Downloader.download("https://www.spsejecna.cz/user/role?role=student")
String token3 = Downloader.getConnection("https://www.spsejecna.cz/user/role?role=student")
.get()
.select("input[name=token3]")
.attr("value");
//#endregion
//#region Login
Downloader.download("https://www.spsejecna.cz/user/login")
Downloader.getConnection("https://www.spsejecna.cz/user/login")
.method(Connection.Method.POST)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Origin", "https://www.spsejecna.cz")
.data("token3", token3)
.data("user", user)
.data("pass", pass)
.data("user", credentials.getUsername())
.data("pass", credentials.getPassword())
.data("submit", "P%C5%99ihl%C3%A1sit+se")
.followRedirects(true)
.execute();
//#endregion
Document test = Downloader.download("https://www.spsejecna.cz/score/student")
Document test = Downloader.getConnection("https://www.spsejecna.cz/score/student")
.get();
if(test.toString().contains("Pro pokračování se přihlaste do systému"))
{
throw new CredentialException("Incorrect username or password");
}
start = System.currentTimeMillis() / 1000L;
lastCheck = start;
}
public void loginJidelna(String user, String pass) throws UnknownHostException, IOException

View File

@ -10,8 +10,12 @@ import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import com.googlecode.lanterna.gui2.dialogs.MessageDialog;
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton;
import xyz.thastertyn.Tuples.Triplet;
import xyz.thastertyn.Types.Credentials;
import xyz.thastertyn.Types.InputtedCredentials;
/**
* Merges the functionality of {@link LocalCredentials}, {@link CredentialsInput}, and {@link Login}
*/
public class LoginController {
private WindowBasedTextGUI textGUI;
@ -24,58 +28,72 @@ public class LoginController {
this.textGUI = textGUI;
}
public void login()
/**
* Picks appropriate method for logging in
* <ul>
* <li> Locally stored credentials exist, then try using them. If they arent in good shape, use GUI
* <li> Use GUI and ask the user for credentials
* </ul>
*/
public void login(boolean forceGUI)
{
String[] credentials;
Credentials credentials;
if(localCredentials.checkForExistingCredentials()) // Credentials exist
if(!forceGUI && localCredentials.checkForExistingCredentials())
{
credentials = localCredentials.getCredentialsFile();
}else{
Triplet<String, String, Boolean> data = loginUsingGui(); // Failed to get credentials to log in, get them from user
credentials = new String[] {data.getValue0(), data.getValue1()};
if(data.getValue2())
{
localCredentials.saveCredentials(credentials);
}
credentials = loginUsingGui();
}
loginUsingCredentials(credentials);
useCredentials(credentials);
}
public Triplet<String, String, Boolean> loginUsingGui()
public Credentials loginUsingGui()
{
dialog = new CredentialsInput();
return dialog.showDialog(textGUI); // Failed to get credentials to log in, get them from user
InputtedCredentials credentials = dialog.showDialog(textGUI);
if(credentials.save())
{
localCredentials.saveCredentials(credentials.getCredentials());
}
private void loginUsingCredentials(String[] credentials)
return credentials.getCredentials();
}
/**
* Credentials were successfully obtained and are used for logging in, although a lot can go wrong
* @param credentials
*/
private void useCredentials(Credentials credentials)
{
try {
login.loginJecna(credentials[0], credentials[1]);
login.loginJecna(credentials);
}catch (TimeoutException e)
{
MessageDialog.showMessageDialog(textGUI, "Timeout", "The attempt to connect took too long.", MessageDialogButton.Retry,
MessageDialogButton.Abort);
MessageDialog.showMessageDialog(textGUI, "Timeout", "The attempt to connect took too long. The app will quit", MessageDialogButton.OK);
System.exit(0);
} catch (UnknownHostException e) {
MessageDialog.showMessageDialog(textGUI, "No Internet connection",
MessageDialog.showMessageDialog(textGUI, "No Internet connection. The app will quit",
e.getMessage(),
MessageDialogButton.OK);
login();
System.exit(0);
} catch (CredentialException e)
{
MessageDialog.showMessageDialog(textGUI, "Incorrect username or password",
e.getMessage(),
MessageDialogButton.OK);
login();
// The credentials were most likely tampered with after save, just get rid of them and save them another time
LocalCredentials.getInstance().deleteCredentials();
login(true);
} catch (IOException e)
{
MessageDialog.showMessageDialog(textGUI, "There was an error",
MessageDialog.showMessageDialog(textGUI, "There was an error. The app will quit",
e.getMessage(),
MessageDialogButton.Retry);
login();
System.exit(0);
}
}
}

View File

@ -0,0 +1,123 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Arrays;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
public class AbsenceList extends JecnaScrape {
private ArrayList<xyz.thastertyn.Types.AbsenceList> data;
private Choice currentChoice;
private Options schoolYearOptions;
@Override
public void download() throws IOException
{
parse("https://www.spsejecna.cz/absence/student");
}
@Override
public void download(Choice choice) throws IOException {
currentChoice = choice;
parse(String.format(
"https://www.spsejecna.cz/absence/student?schoolYearId=%s",
choice.getChoices().get(0)));
}
private void parse(String url) throws IOException
{
data = new ArrayList<>();
schoolYearOptions = new Options("Skolni R.");
Document absenceListHTMLDocument = Downloader.download(url);
Elements absenceLists = absenceListHTMLDocument.select("table.absence-list").select("tbody").select("tr");
for(Element e : absenceLists)
{
String date = e.child(0).text();
String text = e.child(1).text();
data.add(
new xyz.thastertyn.Types.AbsenceList(
date, text));
}
Elements options = absenceListHTMLDocument.select("form.listConfigure").select("select[id=schoolYearId]").select("option");
for(Element e : options)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
currentChoice = new Choice(Arrays.asList(schoolYearOptions.getOptions().get(0).getValue()));
}
/**
* Convert text to a {@link LocalDate} and try to guess the year
* @param text to be parsed
* @return {@link LocalDate} parsed
*/
// private LocalDate parseDate(String text)
// {
// int year = 0, month = 0, day = 0;
// String[] split = text.split("\\.");
// day = Integer.parseInt(split[0]);
// month = Integer.parseInt(split[1]);
// if(currentChoice == null)
// {
// // Pick the current year
// int currYear = LocalDate.now().getYear();
// int currMonth = LocalDate.now().getMonthValue();
// if(month > currMonth && currMonth < 8)
// {
// year = currYear;
// }else if(month < currMonth && currMonth > 8)
// {
// year = currYear + 1;
// }else if(month < currMonth && currMonth < 8)
// {
// year = currYear;
// }
// }else{
// year = Integer.parseInt(currentChoice.getChoices().get(0));
// }
// return LocalDate.of(year, Month.of(month), day);
// }
@Override
public Options[] getOptions() {
return new Options[] {schoolYearOptions};
}
public ArrayList<xyz.thastertyn.Types.AbsenceList> getData()
{
return data;
}
@Override
public String toString()
{
return (!data.isEmpty()) ? data.toString() : null;
}
}

View File

@ -5,7 +5,7 @@ import java.io.IOException;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
public class Jidelna extends JecnaScrape {
public class Canteen extends JecnaScrape {
//public void foo()
//{

View File

@ -1,24 +1,50 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class Downloader {
private static String JsessionId;
public static Connection download(String url)
/**
* Provide a general Jsoup Connection for simplicity with some predefined values and JSessionId already set
* @param url
* @return HTML Document downloaded. If it fails, null
*/
public static Document download(String url) throws IOException
{
Connection c = Jsoup.connect(url)
return Jsoup.connect(url)
.header("Connection", "keep-alive")
.cookie("role", "student")
.cookie("JSESSIONID", JsessionId)
.timeout(10000)
.get();
}
/**
* Returns a basic {@link Connection} with some predefined headers
* @param url
* @return {@link Connection} with timeout for 10s, and headers needed for downloading from spsejecna.cz
*/
public static Connection getConnection(String url)
{
return Jsoup.connect(url)
.header("Connection", "keep-alive")
.cookie("role", "student")
.cookie("JSESSIONID", JsessionId)
.timeout(10000);
return c;
}
public static void setJSessionId(String JsId)
/**
* Sets JsessionId
* @param newJsessionId JsessionId
*/
public static void setJSessionId(String newJsessionId)
{
JsessionId = JsId;
JsessionId = newJsessionId;
}
}

View File

@ -5,9 +5,28 @@ import java.io.IOException;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
/**
* Abstract class to wrap around everything that scrapes some data. Most of them also have getData or something similiar,
* but the data types are just so vastly different, they couldnt be wrapped as abstract method and abstract class
*/
public abstract class JecnaScrape {
/**
* returns possible options, like school year or half year
* @return {@link Options} array, for when there are more options available
*/
public abstract Options[] getOptions();
/**
* Downloads the chosen url, given the {@link Choice} which includes all the needed data
* @param choice of any possible data like school year or half year
* @throws IOException like any other download method, in case no internet, timeout, etc.
*/
public abstract void download(Choice choice) throws IOException;
/**
* Downloads the default url, without any parameters
* @throws IOException in case of timeout, no internet, etc.
*/
public abstract void download() throws IOException;
}

View File

@ -0,0 +1,129 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
import xyz.thastertyn.Types.Subject;
import xyz.thastertyn.Types.Mark;
public class Marks extends JecnaScrape {
// schoolYear, schoolYearId
private ArrayList<Subject> subjects;
// int znaci id roku, boolean jestli je jen prvni nebo i druhe pololeti
private Options schoolYearOptions;
private Options schoolHalfYearOptions;
public void download() throws IOException
{
download("https://www.spsejecna.cz/score/student");
}
@Override
public void download(Choice choice) throws IOException
{
download(String.format(
"https://www.spsejecna.cz/score/student?schoolYearId=%s&schoolYearHalfId=%s",
choice.getChoices().get(0),
choice.getChoices().get(1)));
}
private void download(String url) throws IOException
{
schoolHalfYearOptions = new Options("Pololeti");
schoolYearOptions = new Options("Skolni R.");
subjects = new ArrayList<>();
Document marksHTMLDocument = Downloader.download(url);
// Subjects stored as <tr>
Elements[] subjectRowsHTML = marksHTMLDocument
.select("table.score")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
int subjectIndex = 0;
for(int i = 1; i < subjectRowsHTML.length; i++)
{
String fullSubjectName = subjectRowsHTML[i].get(0).text();
// Attempt to shorten the subject name to the string in brackets
String shortSubjectName = Pattern
.compile("\\((.*?)\\)")
.matcher(fullSubjectName)
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(fullSubjectName);
if(subjectRowsHTML[i].get(2).childrenSize() == 0) // Subject doesn't have final mark yet
{
subjects.add(new Subject(shortSubjectName));
}else{
String finalMark = subjectRowsHTML[i].get(2).select("a.scoreFinal").text();
subjects.add(new Subject(shortSubjectName, FinalMark.fromValue(finalMark)));
}
for(Element znamkaElement : subjectRowsHTML[i].get(1).select("a.score"))
{
int mark;
boolean isSmall = false;
String markText = znamkaElement.select("span.value").text();
mark = markText.matches("\\d") ?
Integer.parseInt(markText)
:
-1; // Most likely N (Nehodnocen)
// Small mark will be counted with smaller weight, compared to normal one
isSmall = znamkaElement.hasClass("scoreSmall");
subjects.get(subjectIndex).addMark(new Mark(mark, isSmall, markText));
}
subjectIndex++;
}
Element optionsPanel = marksHTMLDocument.selectFirst("form.listConfigure");
Elements schoolYears = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements halfYears = optionsPanel.select("select[id=schoolYearHalfId]").select("option");
for(Element e : schoolYears)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
for(Element e : halfYears)
{
boolean isDefault = e.hasAttr("selected");
schoolHalfYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
}
public ArrayList<Subject> getSubjects()
{
return subjects;
}
public Options[] getOptions()
{
return new Options[] {schoolYearOptions, schoolHalfYearOptions};
}
}

View File

@ -1,118 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Arrays;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
public class OmluvnyList extends JecnaScrape {
private ArrayList<xyz.thastertyn.Types.OmluvnyList> data;
private Choice currentChoice;
private Options schoolYearOptions;
@Override
public void download() throws IOException
{
download("https://www.spsejecna.cz/absence/student");
}
@Override
public void download(Choice choice) throws IOException {
currentChoice = choice;
download(String.format(
"https://www.spsejecna.cz/absence/student?schoolYearId=%s",
choice.getChoices().get(0)));
}
private void download(String url) throws IOException
{
data = new ArrayList<>();
schoolYearOptions = new Options("Skolni R.");
Document omluvnyListDokumentHTML = Downloader.download(url).get();
Elements omluvneListy = omluvnyListDokumentHTML.select("table.absence-list").select("tbody").select("tr");
for(Element e : omluvneListy)
{
String date = e.child(0).text();
String text = e.child(1).text();
data.add(
new xyz.thastertyn.Types.OmluvnyList(
parseDate(date), text));
}
Elements options = omluvnyListDokumentHTML.select("form.listConfigure").select("select[id=schoolYearId]").select("option");
for(Element e : options)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
currentChoice = new Choice(Arrays.asList(schoolYearOptions.getOptions().get(0).getValue()));
}
private LocalDate parseDate(String text)
{
int year = 0, month = 0, day = 0;
String[] split = text.split("\\.");
month = Integer.parseInt(split[0]);
day = Integer.parseInt(split[1]);
if(currentChoice == null)
{
// Pick the current year
int currYear = LocalDate.now().getYear();
int currMonth = LocalDate.now().getMonthValue();
if(month > currMonth && currMonth < 8)
{
year = currYear;
}else if(month < currMonth && currMonth > 8)
{
year = currYear + 1;
}else if(month < currMonth && currMonth < 8)
{
year = currYear;
}
}else{
year = Integer.parseInt(currentChoice.getChoices().get(0));
}
return LocalDate.of(year, Month.of(month), day);
}
@Override
public Options[] getOptions() {
return new Options[] {schoolYearOptions};
}
public ArrayList<xyz.thastertyn.Types.OmluvnyList> getData()
{
return data;
}
@Override
public String toString()
{
return (!data.isEmpty()) ? data.toString() : null;
}
}

View File

@ -11,17 +11,17 @@ import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Options;
public class Sdeleni extends JecnaScrape {
public class Reports extends JecnaScrape {
ArrayList<xyz.thastertyn.Types.Sdeleni> sdeleniList = new ArrayList<>();
ArrayList<xyz.thastertyn.Types.Reports> reportList = new ArrayList<>();
public void download() throws UnknownHostException, IOException
{
Document sdeleniDoc = Downloader.download("https://www.spsejecna.cz/user-student/record-list").get();
Document reportDoc = Downloader.download("https://www.spsejecna.cz/user-student/record-list");
Elements sdeleni = sdeleniDoc.select("ul.list li");
Elements report = reportDoc.select("ul.list li");
for(Element e : sdeleni)
for(Element e : report)
{
boolean isPositive = false;
String label = "";
@ -29,15 +29,15 @@ public class Sdeleni extends JecnaScrape {
Elements spans = e.select("li").select("a.item").select("span");
isPositive = spans.get(0).hasClass("sprite-icon-tick-16");
label = spans.get(1).text();
label = spans.get(1).select("span.label").text();
sdeleniList.add(new xyz.thastertyn.Types.Sdeleni(label, isPositive));
reportList.add(new xyz.thastertyn.Types.Reports(label, isPositive));
}
}
public ArrayList<xyz.thastertyn.Types.Sdeleni> getSdeleni()
public ArrayList<xyz.thastertyn.Types.Reports> getSdeleni()
{
return sdeleniList;
return reportList;
}
@Override

View File

@ -1,7 +1,6 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.regex.Pattern;
@ -13,25 +12,14 @@ import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
import xyz.thastertyn.Types.Timetable;
/**
* Jeden radek v rozvrhu
*/
public class Rozvrh extends JecnaScrape {
public class Timetable extends JecnaScrape {
private Timetable timetable;
private xyz.thastertyn.Types.Timetable timetable;
private Options schoolYearOptions;
private Options timetableOptions;
/**
* Stahne rozvrh z www.spsejecna.cz a dale ho zpracuje do formy
* se kterou da pracovat
* @param Jsessionid ze stranek
* @throws UnknownHostException kdyz neni pripojeni k internetu
* @throws IOException ostatni exceptiony nejsou dulezite, tak jsou zahrnuty v jednom
*/
@Override
public void download() throws IOException
{
@ -51,32 +39,32 @@ public class Rozvrh extends JecnaScrape {
schoolYearOptions = new Options("Skolni R.");
timetableOptions = new Options("Obdobi");
timetable = new Timetable();
Document rozvrhDokumentHTML = Downloader.download(url).get();
timetable = new xyz.thastertyn.Types.Timetable();
Document timetableHTMLDocument = Downloader.download(url);
Elements[] radkyRozvrhuHTML = rozvrhDokumentHTML
Elements[] timetableHTMLRow = timetableHTMLDocument
.select("table.timetable")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
for(int i = 0; i < 5; i++)
for(int i = 0; i < 5; i++) // Days
{
for(int j = 0; j < 10; j++)
for(int j = 0; j < 10; j++) // Individual hours
{
String predmet = radkyRozvrhuHTML[i+1].get(j+1).select("span.subject").text();
String subject = timetableHTMLRow[i+1].get(j+1).select("span.subject").text();
// Subjects like CEL are thrice, even though everyone has them, make it single
String[] split = predmet.split(" ");
String[] split = subject.split(" ");
HashSet<String> set = new HashSet<>(Arrays.asList(split));
String pr = String.join("/", set);
String subj = String.join("/", set);
timetable.get(i).set(j, pr);
timetable.get(i).set(j, subj);
}
}
Element optionsPanel = rozvrhDokumentHTML.selectFirst("form.listConfigure");
Element optionsPanel = timetableHTMLDocument.selectFirst("form.listConfigure");
Elements schoolYear = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements timetableId = optionsPanel.select("select[id=timetableId]").select("option");
@ -89,20 +77,21 @@ public class Rozvrh extends JecnaScrape {
for(Element e : timetableId)
{
String text = Pattern
// Try to extract the precise school years to make the text shorter
String optionDisplayText = Pattern
.compile("(Od .* do .*)")
.matcher(e.text())
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(e.text());
.orElse(e.text()); // Just use the whole thing if it doesn't follow the pattern
boolean isDefault = e.hasAttr("selected");
timetableOptions.addOption(new Option(text, e.attr("value"), isDefault));
boolean isDefault = e.hasAttr("selected"); // To prevent the last option added be picked as default always
timetableOptions.addOption(new Option(optionDisplayText, e.attr("value"), isDefault));
}
}
public Timetable getRozvrh()
public xyz.thastertyn.Types.Timetable getRozvrh()
{
return timetable;
}

View File

@ -1,130 +0,0 @@
package xyz.thastertyn.Scrape;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import xyz.thastertyn.Types.Choice;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Option;
import xyz.thastertyn.Types.Options;
import xyz.thastertyn.Types.Predmet;
import xyz.thastertyn.Types.Znamka;
public class Znamky extends JecnaScrape {
// schoolYear, schoolYearId
private ArrayList<Predmet> predmety;
// int znaci id roku, boolean jestli je jen prvni nebo i druhe pololeti
private Options schoolYearOptions;
private Options schoolHalfYearOptions;
public void download() throws IOException
{
download("https://www.spsejecna.cz/score/student");
}
@Override
public void download(Choice choice) throws IOException
{
download(String.format(
"https://www.spsejecna.cz/score/student?schoolYearId=%s&schoolYearHalfId=%s",
choice.getChoices().get(0),
choice.getChoices().get(1)));
}
private void download(String url) throws IOException
{
schoolHalfYearOptions = new Options("Pololeti");
schoolYearOptions = new Options("Skolni R.");
predmety = new ArrayList<>();
Document znamkyDokumentHTML = Downloader.download(url).get();
// Predmety ulozene v <tr>
Elements[] radkyPredmetuHTML = znamkyDokumentHTML
.select("table.score")
.select("tr")
.stream()
.map(Element::children)
.toArray(Elements[]::new);
int subjectIndex = 0;
for(int i = 1; i < radkyPredmetuHTML.length; i++)
{
String plnyNazevPredmetu = radkyPredmetuHTML[i].get(0).text();
String jmenoPredmetu = Pattern
.compile("\\((.*?)\\)")
.matcher(plnyNazevPredmetu)
.results()
.findFirst()
.map(m -> m.group(1))
.orElse(plnyNazevPredmetu);
if(radkyPredmetuHTML[i].get(2).childrenSize() == 0) // Predmet jeste nema vyslednou znamku
{
predmety.add(new Predmet(jmenoPredmetu));
}else{
String vyslednaZnamka = radkyPredmetuHTML[i].get(2).select("a.scoreFinal").text();
predmety.add(new Predmet(jmenoPredmetu, FinalMark.fromValue(vyslednaZnamka)));
}
for(Element znamkaElement : radkyPredmetuHTML[i].get(1).select("a.score"))
{
int znamka;
boolean jeMala = false;
String textZnamky = znamkaElement.select("span.value").text();
znamka = textZnamky.matches("\\d") ?
Integer.parseInt(textZnamky)
:
-1; // Nejspis se jedna o N (Nehodnocen)
// Mala znamka se bude pocitat jako polovicni vaha
jeMala = znamkaElement.hasClass("scoreSmall");
predmety.get(subjectIndex).addZnamka(new Znamka(znamka, jeMala, textZnamky));
}
predmety.get(subjectIndex).calculateFinalMark();
subjectIndex++;
}
Element optionsPanel = znamkyDokumentHTML.selectFirst("form.listConfigure");
Elements skolniRoky = optionsPanel.select("select[id=schoolYearId]").select("option");
Elements pololeti = optionsPanel.select("select[id=schoolYearHalfId]").select("option");
for(Element e : skolniRoky)
{
boolean isDefault = e.hasAttr("selected");
schoolYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
for(Element e : pololeti)
{
boolean isDefault = e.hasAttr("selected");
schoolHalfYearOptions.addOption(new Option(e.text(), e.attr("value"), isDefault));
}
}
public ArrayList<Predmet> getPredmety()
{
return predmety;
}
public Options[] getOptions()
{
return new Options[] {schoolYearOptions, schoolHalfYearOptions};
}
}

View File

@ -1,54 +0,0 @@
package xyz.thastertyn.Tuples;
/**
* Ekvitalent Tuplu, ktery neni zabudovan v jave
*/
public class Pair<T1, T2> {
private T1 value0;
private T2 value1;
public Pair(T1 value0, T2 value1)
{
this.value0 = value0;
this.value1 = value1;
}
public Pair()
{
}
public void put(T1 value0, T2 value1)
{
this.value0 = value0;
this.value1 = value1;
}
public T1 getValue0()
{
return value0;
}
public T2 getValue1()
{
return value1;
}
public void setValue0(T1 value0)
{
this.value0 = value0;
}
public void setValue1(T2 value1)
{
this.value1 = value1;
}
@Override
public String toString()
{
return "[" + value0 + ", " + value1 + "]";
}
}

View File

@ -1,66 +0,0 @@
package xyz.thastertyn.Tuples;
/**
* Ekvitalent Tuplu, ktery neni zabudovan v jave
*/
public class Triplet<T1, T2, T3> {
private T1 value0;
private T2 value1;
private T3 value2;
public Triplet(T1 value0, T2 value1, T3 value2)
{
this.value0 = value0;
this.value1 = value1;
this.value2 = value2;
}
public Triplet()
{
}
public void put(T1 value0, T2 value1, T3 value2)
{
this.value0 = value0;
this.value1 = value1;
this.value2 = value2;
}
public T1 getValue0()
{
return value0;
}
public T2 getValue1()
{
return value1;
}
public T3 getValue2()
{
return value2;
}
public void setValue0(T1 value0)
{
this.value0 = value0;
}
public void setValue1(T2 value1)
{
this.value1 = value1;
}
public void setValue2(T3 value2)
{
this.value2 = value2;
}
@Override
public String toString()
{
return "[" + value0 + ", " + value1 + "," + value2 + "]";
}
}

View File

@ -0,0 +1,31 @@
package xyz.thastertyn.Types;
public class AbsenceList {
private String date;
private String description;
public AbsenceList(String date, String description)
{
this.date = date;
this.description = description;
}
public String getDate() {
return date;
}
public void setDate(String datum) {
this.date = datum;
}
public String getDescription() {
return description;
}
public void setDescription(String popis) {
this.description = popis;
}
}

View File

@ -2,6 +2,9 @@ package xyz.thastertyn.Types;
import java.util.List;
/**
* A choice parsed from dropdowns on the site. Usually contains something like school year or half year id
*/
public class Choice {
private List<String> choices;

View File

@ -0,0 +1,23 @@
package xyz.thastertyn.Types;
/**
* Stores username and password
*/
public class Credentials {
private String username;
private String password;
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@ -1,5 +1,8 @@
package xyz.thastertyn.Types;
/**
* Stores a single day from the timetable as an array of strings
*/
public class DayOfTimetable {
private String[] subjects = new String[10];

View File

@ -1,5 +1,8 @@
package xyz.thastertyn.Types;
/**
* Stores the final mark from a subject
*/
public class FinalMark{
public static final FinalMark VYBORNY = new FinalMark(1, "1");
public static final FinalMark CHVALITEBNY = new FinalMark(2, "2");
@ -7,6 +10,8 @@ public class FinalMark{
public static final FinalMark DOSTATECNY = new FinalMark(4, "4");
public static final FinalMark NEDOSTATECNY = new FinalMark(5, "5");
public static final FinalMark NOTHING = new FinalMark(0, "");
public static final FinalMark NEHODNOCEN = new FinalMark(-1, "N");
public static final FinalMark NAPOMENUT_ZA_NEKLASIFIKACI = new FinalMark(-2, "N?");

View File

@ -0,0 +1,30 @@
package xyz.thastertyn.Types;
/**
* Wrapper around credentials with the change of having a boolean for whether the user wants to save the credentials or not
*/
public class InputtedCredentials extends Credentials {
private boolean save;
public InputtedCredentials(String username, String password, boolean save)
{
super(username, password);
this.save = save;
}
public InputtedCredentials(Credentials credentials, boolean save)
{
super(credentials.getUsername(), credentials.getPassword());
this.save = save;
}
public Credentials getCredentials()
{
return new Credentials(getUsername(), getPassword());
}
public boolean save() {
return save;
}
}

View File

@ -0,0 +1,34 @@
package xyz.thastertyn.Types;
/**
* A single mark. holds numeric value, weight - small = 1, normal 2
*/
public class Mark {
private int mark;
private String markAsText;
private int weight;
public Mark(int mark, boolean isSmall, String markAsText)
{
this.mark = mark;
this.weight = (isSmall) ? 1 : 2;
this.markAsText = markAsText;
}
public int getMark()
{
return mark;
}
public int getWeight()
{
return weight;
}
@Override
public String toString()
{
return markAsText;
}
}

View File

@ -1,33 +0,0 @@
package xyz.thastertyn.Types;
import java.time.LocalDate;
public class OmluvnyList {
private LocalDate datum;
private String popis;
public OmluvnyList(LocalDate datum, String popis)
{
this.datum = datum;
this.popis = popis;
}
public LocalDate getDatum() {
return datum;
}
public void setDatum(LocalDate datum) {
this.datum = datum;
}
public String getPopis() {
return popis;
}
public void setPopis(String popis) {
this.popis = popis;
}
}

View File

@ -1,12 +1,16 @@
package xyz.thastertyn.Types;
/**
* A single option. Holds a text description, the thing displayed and a string value, the one used in url
*/
public class Option {
private String text;
private String displayText;
private String value;
private boolean isDefault;
public Option(String text, String value, boolean isDefault) {
this.text = text;
this.displayText = text;
this.value = value;
this.isDefault = isDefault;
}
@ -18,6 +22,6 @@ public class Option {
}
@Override
public String toString() {
return text;
return displayText;
}
}

View File

@ -2,6 +2,9 @@ package xyz.thastertyn.Types;
import java.util.ArrayList;
/**
* Holds all possible a single dropdown on the website can have.
*/
public class Options {
private ArrayList<Option> options = new ArrayList<>();

View File

@ -1,67 +0,0 @@
package xyz.thastertyn.Types;
import java.util.ArrayList;
public class Predmet {
private ArrayList<Znamka> marks = new ArrayList<>();
private String subjectName = "";
private boolean isFinal = false;
private FinalMark finalMark = FinalMark.UNKNOWN;
public Predmet(String subjectName)
{
this.subjectName = subjectName;
}
public Predmet(String subjectName, FinalMark finalMark)
{
this.subjectName = subjectName;
this.finalMark = finalMark;
isFinal = true;
}
public void addZnamka(Znamka newMark)
{
marks.add(newMark);
}
public void calculateFinalMark()
{
int total = marks
.stream()
.mapToInt((z) -> z.getZnamka() * z.getVaha())
.sum();
int weight = marks
.stream()
.mapToInt((z) -> z.getVaha())
.sum();
double finalMark = ((double) total / weight);
this.finalMark = new FinalMark(finalMark);
}
public FinalMark getFinalMark()
{
return finalMark;
}
public boolean isFinal()
{
return isFinal;
}
public String getSubjectName()
{
return subjectName;
}
public ArrayList<Znamka> getMarks()
{
return marks;
}
}

View File

@ -1,11 +1,14 @@
package xyz.thastertyn.Types;
public class Sdeleni {
/**
* Reports for parents. Holds a string of text and a boolean for positivity to determine color for display
*/
public class Reports {
private String text;
private boolean isPositive;
public Sdeleni(String text, boolean isPositive)
public Reports(String text, boolean isPositive)
{
this.text = text;
this.isPositive = isPositive;

View File

@ -0,0 +1,80 @@
package xyz.thastertyn.Types;
import java.util.ArrayList;
/**
* Subject for marks. Holds a name, all its marks and the final mark
*/
public class Subject {
private ArrayList<Mark> marks = new ArrayList<>();
private String subjectName = "";
private boolean isFinal = false;
private FinalMark finalMark = FinalMark.UNKNOWN;
public Subject(String subjectName)
{
this.subjectName = subjectName;
}
public Subject(String subjectName, FinalMark finalMark)
{
this.subjectName = subjectName;
this.finalMark = finalMark;
isFinal = true;
}
public void addMark(Mark newMark)
{
marks.add(newMark);
}
private void calculateFinalMark()
{
if(marks.isEmpty())
{
this.finalMark = FinalMark.NOTHING;
}else{
int total = marks
.stream()
.mapToInt((z) -> z.getMark() * z.getWeight())
.sum();
int weight = marks
.stream()
.mapToInt((z) -> z.getWeight())
.sum();
double finalMark = ((double) total / weight);
this.finalMark = new FinalMark(finalMark);
}
}
public FinalMark getFinalMark()
{
if(!isFinal)
{
calculateFinalMark();
}
return finalMark;
}
public boolean isFinal()
{
return isFinal;
}
public String getSubjectName()
{
return subjectName;
}
public ArrayList<Mark> getMarks()
{
return marks;
}
}

View File

@ -1,5 +1,8 @@
package xyz.thastertyn.Types;
/**
* Whole timetable. holds an array of {@link DayOfTimetable} for each day
*/
public class Timetable {
private DayOfTimetable[] timetable = new DayOfTimetable[5];

View File

@ -1,30 +0,0 @@
package xyz.thastertyn.Types;
public class Znamka {
private int znamka;
private String znamkaJakoText;
private int vaha;
public Znamka(int znamka, boolean malaZnamka, String znamkaJakoText/*, String text, String datum*/)
{
this.znamka = znamka;
this.vaha = (malaZnamka) ? 1 : 2;
this.znamkaJakoText = znamkaJakoText;
}
public int getZnamka()
{
return znamka;
}
public int getVaha()
{
return vaha;
}
public String getText()
{
return znamkaJakoText;
}
}

View File

@ -10,11 +10,11 @@ import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class OmluvnyList extends JecnaContent{
public class AbsenceList extends JecnaContent{
private xyz.thastertyn.Scrape.OmluvnyList omluvnyList = new xyz.thastertyn.Scrape.OmluvnyList();
private xyz.thastertyn.Scrape.AbsenceList omluvnyList = new xyz.thastertyn.Scrape.AbsenceList();
public OmluvnyList(UpdateListener listener)
public AbsenceList(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel()
@ -27,15 +27,15 @@ public class OmluvnyList extends JecnaContent{
protected void setGUI()
{
this.mainPanel.removeAllComponents();
ArrayList<xyz.thastertyn.Types.OmluvnyList> a = omluvnyList.getData();
ArrayList<xyz.thastertyn.Types.AbsenceList> a = omluvnyList.getData();
Panel content = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.addTo(mainPanel);
for(xyz.thastertyn.Types.OmluvnyList omluvnyList : a)
for(xyz.thastertyn.Types.AbsenceList omluvnyList : a)
{
content.addComponent(new Label(omluvnyList.getDatum() + " - " + omluvnyList.getPopis()));
content.addComponent(new Label(omluvnyList.getDate() + " - " + omluvnyList.getDescription()));
}
}
}

View File

@ -1,5 +1,5 @@
package xyz.thastertyn.UserInterface.Content;
public class Jidelna {
public class Canteen {
}

View File

@ -23,6 +23,9 @@ public abstract class JecnaContent {
protected UpdateListener listener;
protected JecnaScrape scraper;
/**
* Prepares it's content to be display by gettings the panel from {@link #getPanel()}
*/
protected abstract void setGUI();
public JecnaContent(UpdateListener listener)
@ -30,12 +33,18 @@ public abstract class JecnaContent {
this.listener = listener;
}
/**
* Call its scraper class to download content using the provided choice
* @param choice
* @throws IOException in case download fails in some way
*/
protected void download(Choice choice) throws IOException
{
if(choice != null)
{
scraper.download(choice);
setGUI();
mainPanel.setPreferredSize(mainPanel.calculatePreferredSize());
listener.updatePanel();
}else{
scraper.download();
@ -45,11 +54,20 @@ public abstract class JecnaContent {
hasStarted = true;
}
/**
* Tells scraper to download the default content and parse it
* @throws IOException
*/
public void downloadDefault() throws IOException
{
download(null);
}
/**
* Creates a {@link OptionsDialog} to display its options
* @param textGUI where to display the dialog
* @throws IOException upon ok, conent is immediatelly downloaded using {@link #download( choice_from_dialog )}
*/
public void showOptions(WindowBasedTextGUI textGUI) throws IOException {
if(scraper.getOptions() == null)
@ -67,16 +85,27 @@ public abstract class JecnaContent {
}
}
/**
* Returns the panel where the content should be held and already arranged for display
* @return
*/
public Panel getPanel()
{
return this.mainPanel;
}
/**
* Check if something has been downloaded already
* @return true or false depending on the download state
*/
public boolean hasStarted()
{
return this.hasStarted;
}
/**
* Returns a label with the title text like "Známky", etc.
*/
public Label getLabel()
{
return this.borderLabel;

View File

@ -14,11 +14,11 @@ import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Predmet;
import xyz.thastertyn.Types.Znamka;
import xyz.thastertyn.Types.Subject;
import xyz.thastertyn.Types.Mark;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Znamky extends JecnaContent {
public class Marks extends JecnaContent {
private final TextColor.RGB VYBORNY = new TextColor.RGB(85,212,0);
private final TextColor.RGB CHVALITEBNY = new TextColor.RGB(196,224,80);
@ -39,16 +39,16 @@ public class Znamky extends JecnaContent {
true,
false);
private xyz.thastertyn.Scrape.Znamky znamky = new xyz.thastertyn.Scrape.Znamky();
private xyz.thastertyn.Scrape.Marks marks = new xyz.thastertyn.Scrape.Marks();
public Znamky(UpdateListener listener)
public Marks(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel()
.setLayoutManager(new GridLayout(3));
this.borderLabel = new Label("Znamky");
super.scraper = this.znamky;
super.scraper = this.marks;
}
@Override
@ -56,8 +56,9 @@ public class Znamky extends JecnaContent {
{
mainPanel.removeAllComponents();
ArrayList<Predmet> subjects = znamky.getPredmety();
ArrayList<Subject> subjects = marks.getSubjects();
// Colors are used a lot and instead of referencing them in lengthy lines, this seems shorter
HashMap<Integer, SimpleTheme> colors = new HashMap<>();
colors.put(1, new SimpleTheme(ANSI.BLACK, VYBORNY));
colors.put(2, new SimpleTheme(ANSI.BLACK, CHVALITEBNY));
@ -67,51 +68,50 @@ public class Znamky extends JecnaContent {
colors.put(-1, new SimpleTheme(ANSI.WHITE, NEHODNOCEN));
colors.put(-2, new SimpleTheme(ANSI.WHITE, NEHODNOCEN));
// Sloupec pro jmena predmetu
Panel jmemaPredmetu = new Panel()
// Column for subject names
Panel subjectNames = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Sloupec pro znamky z predmetu
Panel znamky = new Panel()
// Column for marks
Panel marks = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_LEFT)
.addTo(mainPanel);
// Sloupec pro vyslednou znamku
Panel vysledneZnamky = new Panel()
// Column for final mark
Panel finalMarks = new Panel()
.setLayoutManager(new LinearLayout(Direction.VERTICAL))
.setLayoutData(ALIGN_RIGHT)
.addTo(mainPanel);
for(Predmet predmet : subjects)
for(Subject subject : subjects)
{
Panel jednotliveZnamky = new Panel()
Panel individualMarks = new Panel()
.setLayoutManager(new LinearLayout(Direction.HORIZONTAL))
.addTo(znamky);
.addTo(marks);
if(predmet.getMarks().isEmpty())
if(subject.getMarks().isEmpty())
{
jednotliveZnamky.addComponent(new Label(""));
individualMarks.addComponent(new Label(""));
}
for(Znamka znamka : predmet.getMarks())
for(Mark mark : subject.getMarks())
{
Label znamkaLabel = new Label(znamka.getText());
znamkaLabel.setTheme(colors.get(znamka.getZnamka()));
jednotliveZnamky.addComponent(znamkaLabel);
Label markLabel = new Label(mark.toString());
markLabel.setTheme(colors.get(mark.getMark()));
individualMarks.addComponent(markLabel);
}
FinalMark finalMark = predmet.getFinalMark();
FinalMark finalMark = subject.getFinalMark();
Label vysl = new Label(finalMark.toString());
vysl.setTheme(colors.get((int) Math.round(finalMark.getValue())));
jmemaPredmetu.addComponent(new Label(predmet.getSubjectName()));
vysledneZnamky.addComponent(vysl);
subjectNames.addComponent(new Label(subject.getSubjectName()));
finalMarks.addComponent(vysl);
}
}
}

View File

@ -11,18 +11,18 @@ import com.googlecode.lanterna.gui2.Panel;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Sdeleni extends JecnaContent {
public class Reports extends JecnaContent {
private xyz.thastertyn.Scrape.Sdeleni sdeleni = new xyz.thastertyn.Scrape.Sdeleni();
private xyz.thastertyn.Scrape.Reports reports = new xyz.thastertyn.Scrape.Reports();
public Sdeleni(UpdateListener listener)
public Reports(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel().setLayoutManager(new GridLayout(1)
.setLeftMarginSize(1)
.setRightMarginSize(1));
this.borderLabel = new Label("Sdeleni R.");
super.scraper = this.sdeleni;
super.scraper = this.reports;
}
@Override
@ -30,26 +30,25 @@ public class Sdeleni extends JecnaContent {
{
mainPanel.removeAllComponents();
ArrayList<xyz.thastertyn.Types.Sdeleni> sdeleniList = sdeleni.getSdeleni();
ArrayList<xyz.thastertyn.Types.Reports> reportsList = reports.getSdeleni();
for(xyz.thastertyn.Types.Sdeleni sdeleni : sdeleniList)
for(xyz.thastertyn.Types.Reports reports : reportsList)
{
Panel row = new Panel().setLayoutManager(new LinearLayout(Direction.HORIZONTAL));
Label checkmark = new Label("");
Label text = new Label(reports.getText());
Label check = new Label("");
Label text = new Label(sdeleni.getText());
if(sdeleni.isPositive())
if(reports.isPositive())
{
check.setForegroundColor(ANSI.GREEN);
check.setText("");
checkmark.setForegroundColor(ANSI.GREEN);
checkmark.setText("");
}else{
check.setForegroundColor(ANSI.RED);
check.setText("");
checkmark.setForegroundColor(ANSI.RED);
checkmark.setText("");
}
row.addComponent(check)
row.addComponent(checkmark)
.addComponent(text)
.addTo(mainPanel);
}

View File

@ -7,12 +7,11 @@ import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.table.Table;
import xyz.thastertyn.Types.Timetable;
import xyz.thastertyn.UserInterface.Listeners.UpdateListener;
public class Rozvrh extends JecnaContent {
public class Timetable extends JecnaContent {
private xyz.thastertyn.Scrape.Rozvrh rozvrh = new xyz.thastertyn.Scrape.Rozvrh();
private xyz.thastertyn.Scrape.Timetable rozvrh = new xyz.thastertyn.Scrape.Timetable();
private String[] labels = {"Den", "7:30-8:15", "8:25-9:10", "9:20-10:05", "10:20-11:05", "11:15-12:00", "12:10-12:55", "13:05-13:50", "14:00-14:45", "14:55-15:40", "15:50-16:35"};
//private String[] labels = {"Den", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10."};
@ -21,7 +20,7 @@ public class Rozvrh extends JecnaContent {
Table<String> table;
public Rozvrh(UpdateListener listener)
public Timetable(UpdateListener listener)
{
super(listener);
this.mainPanel = new Panel();
@ -34,7 +33,7 @@ public class Rozvrh extends JecnaContent {
{
mainPanel.removeAllComponents();
table = new Table<>(labels);
Timetable timetable = rozvrh.getRozvrh();
xyz.thastertyn.Types.Timetable timetable = rozvrh.getRozvrh();
for(int day = 0; day < 5; day++)
{

View File

@ -62,7 +62,7 @@ public class EscapeDialog extends DialogWindow {
{
close();
LoginController controller = new LoginController(textGUI);
controller.loginUsingGui();
controller.login(true);
resetListener.reset();
}

View File

@ -1,5 +1,8 @@
package xyz.thastertyn.UserInterface.Listeners;
/**
* Interface for when accounts have been switched and a complete wipe of content is needed
*/
public interface ContentResetListener {
public void reset();
}

View File

@ -19,20 +19,24 @@ import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
import xyz.thastertyn.UserInterface.Content.JecnaContent;
import xyz.thastertyn.UserInterface.Content.OmluvnyList;
import xyz.thastertyn.UserInterface.Content.Rozvrh;
import xyz.thastertyn.UserInterface.Content.Sdeleni;
import xyz.thastertyn.UserInterface.Content.Znamky;
import xyz.thastertyn.UserInterface.Content.AbsenceList;
import xyz.thastertyn.UserInterface.Content.Timetable;
import xyz.thastertyn.UserInterface.Content.Reports;
import xyz.thastertyn.UserInterface.Content.Marks;
import xyz.thastertyn.UserInterface.Dialogs.EscapeDialog;
/**
* Holds all the content displayables and moves between them when needed on tab, or shift tab press
*/
// The biggest and greatest.
public class WindowSwitchListener implements WindowListener, UpdateListener, ContentResetListener {
private WindowBasedTextGUI textGUI;
private JecnaContent[] contents = {
new Rozvrh(this),
new Znamky(this), // This being first doesn't resize properly for some reason
new Sdeleni(this),
new OmluvnyList(this)
new Timetable(this),
new Marks(this), // This being first doesn't resize properly for some reason
new Reports(this),
new AbsenceList(this)
};
private Label[] tabs = new Label[contents.length];
@ -105,32 +109,40 @@ public class WindowSwitchListener implements WindowListener, UpdateListener, Con
}
}
/**
* If a panel is smaller than the bar with labels, make it at least the bar size
*/
private void setPanelPreferedSize()
{
int tabColumns = tabsPanel
contents[current].getPanel().setSize(contents[current].getPanel().calculatePreferredSize()); // Fix for reports not being big enough
int labelTabColumnSize = tabsPanel
.getSize()
.getColumns();
int currentColumns = contents[current]
int currentPanelColumnSize = contents[current]
.getPanel()
.getSize()
.getColumns();
if(currentColumns < tabColumns)
if(currentPanelColumnSize < labelTabColumnSize)
{
int currentPanelRowSize = contents[current].getPanel().getPreferredSize().getRows();
holderPanel.addComponent(
contents[current]
.getPanel()
.setPreferredSize(
new TerminalSize(
tabColumns,
contents[current].getPanel().getPreferredSize().getRows())));
.setPreferredSize(new TerminalSize(
labelTabColumnSize,
currentPanelRowSize))); // Pretty janky, but basically just create a new TerminalSize with height the same and the width of label bar
return;
}
holderPanel.addComponent(contents[current].getPanel());
}
/**
* Gets all labels again and properly sets their borders to single or double depending on {@link #current}
*/
private void updateLabels()
{
for(int i = 0; i < tabs.length; i++)
@ -144,6 +156,9 @@ public class WindowSwitchListener implements WindowListener, UpdateListener, Con
}
}
/**
* Calls the {@link #current}'s showOptions with a textGui
*/
private void showOptions()
{
try{
@ -163,7 +178,7 @@ public class WindowSwitchListener implements WindowListener, UpdateListener, Con
case Tab:
next();
break;
case ReverseTab:
case ReverseTab: // Shift + Tab
previous();
break;
case Character:
@ -196,24 +211,26 @@ public class WindowSwitchListener implements WindowListener, UpdateListener, Con
window.invalidate();
}
@Override
public void updatePanel() {
holderPanel.removeAllComponents();
contents[current].getPanel().setPreferredSize(contents[current].getPanel().calculatePreferredSize());
setPanelPreferedSize();
holderPanel.addComponent(contents[current].getPanel());
}
/**
* On user change should wipe everything
*/
@Override
public void reset()
{
contents = new JecnaContent[] {
new Rozvrh(this),
new Znamky(this),
new Sdeleni(this),
new OmluvnyList(this)
new Timetable(this),
new Marks(this),
new Reports(this),
new AbsenceList(this)
};
loadPanel();
}
}

View File

@ -28,7 +28,14 @@ public class MainWindow {
private Window window;
private MultiWindowTextGUI textGUI;
/**
* Create:
* <ul>
* <li> All the main components like Terminal, and Screen </li>
* <li> {@link LoginController} and try logging in using any of its methods </li>
* <li> {@link WindowSwitchListener} for the purpose of switching between tabs using {@code Tab} and {@code ReverseTab} (Shift + Tab) </li>
* </ul>
*/
public void run()
{
try {
@ -47,23 +54,25 @@ public class MainWindow {
textGUI = new MultiWindowTextGUI(screen, new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLACK_BRIGHT));
//#endregion
// Create panel to hold components
// Create panel to hold all components
final Panel mainPanel = new Panel();
mainPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL));
window.setComponent(mainPanel);
// Create a secondary panel, as working with the mainPanel is quite buggy
Panel content = new Panel();
mainPanel.addComponent(content.withBorder(Borders.singleLine("Jecnak")));
// Try logging in using any method
LoginController controller = new LoginController(textGUI);
controller.login();
controller.login(false);
// Create a WindowListener for tab and shift tab for moving between tabs
window.addWindowListener(new WindowSwitchListener(content, textGUI));
textGUI.addWindowAndWait(window);
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -1,9 +1,14 @@
package xyz.thastertyn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import xyz.thastertyn.Types.FinalMark;
import xyz.thastertyn.Types.Mark;
import xyz.thastertyn.Types.Subject;
/**
* Unit test for simple App.
*/
@ -19,21 +24,50 @@ public class AppTest
}
@Test
public void hasJavaAbove11()
public void noMarksPresentTest()
{
// https://stackoverflow.com/questions/2591083/getting-java-version-at-runtime#2591122
String version = System.getProperty("java.version");
int v;
// No marks are present on start
Subject subject = new Subject("Test Subject");
if(version.startsWith("1."))
assertTrue(subject.getMarks().isEmpty());
}
@Test
public void correctAverage()
{
version = version.substring(2, 3);
}else {
int dot = version.indexOf(".");
if(dot != -1) { version = version.substring(0, dot); }
}
v = Integer.parseInt(version);
Subject subject = new Subject("Test");
assertTrue("Java >= 11 is required", (v >= 11));
// Average is calculated properly
subject.addMark(new Mark(1, false, "1"));
subject.addMark(new Mark(3, false, "3"));
assertTrue(subject.getFinalMark().getValue() == 2.0);
}
@Test
public void correctSmallMarkAverage()
{
// Small marks are calculated properly
Subject subject = new Subject("Test");
subject.addMark(new Mark(1, false, "1"));
subject.addMark(new Mark(3, false, "3"));
subject.addMark(new Mark(5, true, "5"));
subject.addMark(new Mark(5, true, "5"));
assertTrue(subject.getFinalMark().getValue() == 3.0);
}
@Test
public void marksDontChangeFinalMark()
{
// Once a final mark is given, no additional marks should change it
Subject subject = new Subject("Test", FinalMark.NEDOSTATECNY);
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
subject.addMark(new Mark(1, false, "a"));
assertEquals(FinalMark.NEDOSTATECNY, subject.getFinalMark());
}
}