diff --git a/.circleci/config.yml b/.circleci/config.yml index 841ff9e0..16ad564b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ references: container_config: &container_config docker: - - image: circleci/android:api-27-alpha + - image: circleci/android:api-26-alpha working_directory: *workspace_root environment: environment: @@ -126,6 +126,9 @@ jobs: name: Launch emulator command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator64-arm -avd test -noaudio -no-boot-anim -no-window -accel on background: true + - run: + name: Change circle-android script file permissions + command: sudo chmod +rx /bin/circle-android - run: name: Wait emulator command: | diff --git a/.gitignore b/.gitignore index 3b524b65..eec8adbe 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ local.properties .idea/tasks.xml .idea/vcs.xml .idea/workspace.xml +.idea/caches/ +.idea/codeStyles/ *.iml # OS-specific files diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d8fdd058..bf80ae04 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ build: script: - ./gradlew --no-daemon --stacktrace dependencies || true - ./gradlew --no-daemon --stacktrace assembleDebug - - mv app/build/outputs/apk/app-debug.apk . + - mv app/build/outputs/apk/debug/app-debug.apk . artifacts: name: "${CI_PROJECT_NAME}_${CI_BUILD_REF_NAME}-${CI_BUILD_ID}" paths: @@ -26,7 +26,7 @@ tests: - .gradle policy: pull script: - - ./gradlew --no-daemon --stacktrace test + - ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease test artifacts: paths: - app/build/reports/tests @@ -39,7 +39,7 @@ lint: - .gradle policy: pull script: - - ./gradlew --no-daemon --stacktrace lint + - ./gradlew --no-daemon --stacktrace -x fabricGenerateResourcesRelease lint artifacts: paths: - app/build/reports diff --git a/README.md b/README.md index 65cf14af..12607f05 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ [![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/) +[![Scrutinizer](https://img.shields.io/scrutinizer/g/wulkanowy/wulkanowy.svg)](https://scrutinizer-ci.com/g/wulkanowy/wulkanowy/?branch=master) [![Known Vulnerabilities](https://snyk.io/test/github/wulkanowy/wulkanowy/badge.svg?targetFile=app%2Fbuild.gradle&style=flat-square)](https://snyk.io/test/github/wulkanowy/wulkanowy?targetFile=app%2Fbuild.gradle) [![Bintray](https://img.shields.io/bintray/v/wulkanowy/wulkanowy/api.svg?style=flat-square)](https://bintray.com/wulkanowy/wulkanowy/api) [Pobierz wersję rozwojową](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/app-debug-bitrise-signed.apk) -Wulkanowy to aplikacja na androida polepszająca wygodę używania dziennika UONET+. +Androidowy klient dziennika VULCAN UONET+. diff --git a/android-sonarqube.gradle b/android-sonarqube.gradle index b9284eea..3ad2c5ff 100644 --- a/android-sonarqube.gradle +++ b/android-sonarqube.gradle @@ -5,7 +5,7 @@ sonarqube { //noinspection GroovyAssignabilityCheck properties { def files = fileTree("${rootProject.projectDir}/api/build/libs/").filter { it.isFile() }.files.name - def libraries = project.android.sdkDirectory.getPath() + "/platforms/android-27/android.jar," + + def libraries = project.android.sdkDirectory.getPath() + "/platforms/android-26/android.jar," + "${project.rootDir}/api/build/libs/" + files[0] property "sonar.projectName", GROUP_ID + ":app" diff --git a/api/build.gradle b/api/build.gradle index ebaca4c8..001d2579 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -28,12 +28,12 @@ jacocoTestReport { } dependencies { - implementation 'org.jsoup:jsoup:1.10.3' - implementation 'org.apache.commons:commons-lang3:3.7' - implementation 'com.google.code.gson:gson:2.8.2' + implementation "org.jsoup:jsoup:$jsoup" + implementation "org.apache.commons:commons-lang3:$apacheLang" + implementation "com.google.code.gson:gson:$gson" - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.13.0' + testImplementation "junit:junit:$junit" + testImplementation "org.mockito:mockito-core:$mockito" } version = PUBLISH_VERSION diff --git a/api/src/main/java/io/github/wulkanowy/api/Client.java b/api/src/main/java/io/github/wulkanowy/api/Client.java index 16b7982c..dde27c68 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Client.java +++ b/api/src/main/java/io/github/wulkanowy/api/Client.java @@ -21,11 +21,9 @@ public class Client { private String password; - private String symbol = "Default"; + private String symbol; - private Login login; - - private Date lastSuccessRequest = new Date(); + private Date lastSuccessRequest = null; private Cookies cookies = new Cookies(); @@ -56,25 +54,16 @@ public class Client { return; } - this.symbol = getLogin().login(email, password, symbol); + this.cookies = new Cookies(); + this.symbol = new Login(this).login(email, password, symbol); } private boolean isLoggedIn() { - return getCookies().size() > 0 && + return getCookies().size() > 0 && lastSuccessRequest != null && 29 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime()); } - Login getLogin() { - if (null != login) { - return login; - } - - login = new Login(this); - - return login; - } - public String getSymbol() { return symbol; } @@ -83,6 +72,10 @@ public class Client { this.symbol = symbol; } + public void addCookies(Map items) { + cookies.addItems(items); + } + private Map getCookies() { return cookies.getItems(); } @@ -98,8 +91,22 @@ public class Client { .replace("{symbol}", symbol); } - Document getPageByUrl(String url) throws IOException, VulcanException { - login(); + public Document getPageByUrl(String url) throws IOException, VulcanException { + return getPageByUrl(url, true, null); + } + + public Document getPageByUrl(String url, boolean loginBefore) throws IOException, VulcanException { + return getPageByUrl(url, loginBefore, null); + } + + public Document getPageByUrl(String url, boolean loginBefore, Map cookies) throws IOException, VulcanException { + if (loginBefore) { + login(); + } + + if (null != cookies) { + this.cookies.addItems(cookies); + } Connection.Response response = Jsoup.connect(getFilledUrl(url)) .followRedirects(true) @@ -108,7 +115,13 @@ public class Client { this.cookies.addItems(response.cookies()); - return checkForErrors(response.parse()); + Document doc = checkForErrors(response.parse()); + + if (loginBefore) { + lastSuccessRequest = new Date(); + } + + return doc; } public Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException { @@ -165,15 +178,21 @@ public class Client { } Document checkForErrors(Document doc) throws VulcanException { - if ("Przerwa techniczna".equals(doc.select("title").text())) { - throw new VulcanOfflineException(); + lastSuccessRequest = null; + + String title = doc.select("title").text(); + if ("Przerwa techniczna".equals(title)) { + throw new VulcanOfflineException(title); } - if ("Zaloguj się".equals(doc.select(".loginButton").text())) { - throw new NotLoggedInErrorException(); + String singIn = doc.select(".loginButton").text(); + if ("Zaloguj się".equals(singIn)) { + throw new NotLoggedInErrorException(singIn); } - lastSuccessRequest = new Date(); + if ("Błąd strony".equals(title)) { + throw new VulcanException("Nieznany błąd"); + } return doc; } diff --git a/api/src/main/java/io/github/wulkanowy/api/Diary.java b/api/src/main/java/io/github/wulkanowy/api/Diary.java new file mode 100644 index 00000000..3c20f9e1 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/Diary.java @@ -0,0 +1,50 @@ +package io.github.wulkanowy.api; + +public class Diary implements ParamItem { + + private String id = ""; + + private String studentId = ""; + + private String name = ""; + + private boolean current = false; + + public String getId() { + return id; + } + + public Diary setId(String id) { + this.id = id; + return this; + } + + public String getStudentId() { + return studentId; + } + + @Override + public Diary setStudentId(String studentId) { + this.studentId = studentId; + return this; + } + + public String getName() { + return name; + } + + public Diary setName(String name) { + this.name = name; + return this; + } + + @Override + public boolean isCurrent() { + return current; + } + + public Diary setCurrent(boolean current) { + this.current = current; + return this; + } +} diff --git a/api/src/main/java/io/github/wulkanowy/api/NotLoggedInErrorException.java b/api/src/main/java/io/github/wulkanowy/api/NotLoggedInErrorException.java index 86372266..179fc8cc 100644 --- a/api/src/main/java/io/github/wulkanowy/api/NotLoggedInErrorException.java +++ b/api/src/main/java/io/github/wulkanowy/api/NotLoggedInErrorException.java @@ -1,4 +1,8 @@ package io.github.wulkanowy.api; public class NotLoggedInErrorException extends VulcanException { + + public NotLoggedInErrorException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/ParamItem.java b/api/src/main/java/io/github/wulkanowy/api/ParamItem.java new file mode 100644 index 00000000..8aaab7ca --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/ParamItem.java @@ -0,0 +1,14 @@ +package io.github.wulkanowy.api; + +interface ParamItem { + + ParamItem setId(String id); + + ParamItem setStudentId(String id); + + ParamItem setName(String name); + + ParamItem setCurrent(boolean isCurrent); + + boolean isCurrent(); +} diff --git a/api/src/main/java/io/github/wulkanowy/api/Semester.java b/api/src/main/java/io/github/wulkanowy/api/Semester.java index 64a07c97..6d57e667 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Semester.java +++ b/api/src/main/java/io/github/wulkanowy/api/Semester.java @@ -1,21 +1,14 @@ package io.github.wulkanowy.api; -public class Semester { - - private String number = ""; +public class Semester implements ParamItem { private String id = ""; - private boolean isCurrent = false; + private String studentId = ""; - public String getNumber() { - return number; - } + private String name = ""; - public Semester setNumber(String number) { - this.number = number; - return this; - } + private boolean current = false; public String getId() { return id; @@ -26,12 +19,31 @@ public class Semester { return this; } + public String getStudentId() { + return studentId; + } + + @Override + public Semester setStudentId(String studentId) { + this.studentId = studentId; + return this; + } + + public String getName() { + return name; + } + + public Semester setName(String number) { + this.name = number; + return this; + } + public boolean isCurrent() { - return isCurrent; + return current; } public Semester setCurrent(boolean current) { - isCurrent = current; + this.current = current; return this; } } diff --git a/api/src/main/java/io/github/wulkanowy/api/SnP.java b/api/src/main/java/io/github/wulkanowy/api/SnP.java index adbffd4b..7f074071 100644 --- a/api/src/main/java/io/github/wulkanowy/api/SnP.java +++ b/api/src/main/java/io/github/wulkanowy/api/SnP.java @@ -8,17 +8,25 @@ import java.util.List; public interface SnP { - String getId(); + String getSchoolID(); - StudentAndParent storeContextCookies() throws IOException, VulcanException; + void setDiaryID(String id); + + String getStudentID(); + + List getStudents() throws IOException, VulcanException; + + StudentAndParent setUp() throws IOException, VulcanException; String getRowDataChildValue(Element e, int index); Document getSnPPageDocument(String url) throws IOException, VulcanException; + List getDiaries() throws IOException, VulcanException; + List getSemesters() throws IOException, VulcanException; List getSemesters(Document gradesPage); - Semester getCurrentSemester(List semesterList); + T getCurrent(List list); } diff --git a/api/src/main/java/io/github/wulkanowy/api/Student.java b/api/src/main/java/io/github/wulkanowy/api/Student.java new file mode 100644 index 00000000..10ca4caf --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/Student.java @@ -0,0 +1,46 @@ +package io.github.wulkanowy.api; + +public class Student implements ParamItem { + + private String id = ""; + + private String name = ""; + + private boolean current = false; + + public String getId() { + return id; + } + + public Student setId(String id) { + this.id = id; + return this; + } + + public String getStudentId() { + return getId(); + } + + @Override + public Student setStudentId(String studentId) { + return setId(studentId); + } + + public String getName() { + return name; + } + + public Student setName(String name) { + this.name = name; + return this; + } + + public boolean isCurrent() { + return current; + } + + public Student setCurrent(boolean current) { + this.current = current; + return this; + } +} diff --git a/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java b/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java index c014e96c..5379053f 100644 --- a/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java +++ b/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java @@ -5,8 +5,11 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class StudentAndParent implements SnP { @@ -18,28 +21,47 @@ public class StudentAndParent implements SnP { private Client client; - private String id; + private String schoolID; - StudentAndParent(Client client, String id) { + private String studentID; + + private String diaryID; + + StudentAndParent(Client client, String schoolID, String studentID, String diaryID) { this.client = client; - this.id = id; + this.schoolID = schoolID; + this.studentID = studentID; + this.diaryID = diaryID; } - private String getBaseUrl() { - return BASE_URL.replace("{ID}", getId()); - } + public StudentAndParent setUp() throws IOException, VulcanException { + if (null == getStudentID() || "".equals(getStudentID())) { + Document doc = client.getPageByUrl(getSnpHomePageUrl()); - public String getId() { - return id; - } + Student student = getCurrent(getStudents(doc)); + studentID = student.getId(); + + Diary diary = getCurrent(getDiaries(doc)); + diaryID = diary.getId(); + } - public StudentAndParent storeContextCookies() throws IOException, VulcanException { - client.getPageByUrl(getSnpHomePageUrl()); return this; } + public String getSchoolID() { + return schoolID; + } + + public String getStudentID() { + return studentID; + } + + private String getBaseUrl() { + return BASE_URL.replace("{ID}", getSchoolID()); + } + String getSnpHomePageUrl() throws IOException, VulcanException { - if (null != getId()) { + if (null != getSchoolID()) { return getBaseUrl(); } @@ -48,12 +70,12 @@ public class StudentAndParent implements SnP { Element studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a").first(); if (null == studentTileLink) { - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("You are probably not logged in. Force login"); } String snpPageUrl = studentTileLink.attr("href"); - this.id = getExtractedIdFromUrl(snpPageUrl); + this.schoolID = getExtractedIdFromUrl(snpPageUrl); return snpPageUrl; } @@ -62,7 +84,7 @@ public class StudentAndParent implements SnP { String[] path = snpPageUrl.split(client.getHost())[1].split("/"); if (5 != path.length) { - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("You are probably not logged in"); } return path[2]; @@ -72,8 +94,39 @@ public class StudentAndParent implements SnP { return e.select(".daneWiersz .wartosc").get(index - 1).text(); } + public void setDiaryID(String id) { + this.diaryID = id; + } + public Document getSnPPageDocument(String url) throws IOException, VulcanException { - return client.getPageByUrl(getBaseUrl() + url); + Map cookies = new HashMap<>(); + cookies.put("idBiezacyDziennik", diaryID); + cookies.put("idBiezacyUczen", studentID); + client.addCookies(cookies); + + Document doc = client.getPageByUrl(getBaseUrl() + url, true, cookies); + + if ("Witryna ucznia i rodzica – Strona główna".equals(doc.select("title").first().text())) { + throw new VulcanException("Sesja została nieprawidłowo zainicjowana"); + } + + return doc; + } + + public List getDiaries() throws IOException, VulcanException { + return getDiaries(client.getPageByUrl(getBaseUrl())); + } + + private List getDiaries(Document doc) throws IOException, VulcanException { + return getList(doc.select("#dziennikDropDownList option"), Diary.class); + } + + public List getStudents() throws IOException, VulcanException { + return getStudents(client.getPageByUrl(getBaseUrl())); + } + + private List getStudents(Document doc) throws IOException, VulcanException { + return getList(doc.select("#uczenDropDownList option"), Student.class); } public List getSemesters() throws IOException, VulcanException { @@ -88,9 +141,9 @@ public class StudentAndParent implements SnP { for (Element e : semesterOptions) { Semester semester = new Semester() .setId(e.text()) - .setNumber(e.attr("value")); + .setName(e.attr("value")); - if ("selected".equals(e.attr("selected"))) { + if (isCurrent(e)) { semester.setCurrent(true); } @@ -100,15 +153,47 @@ public class StudentAndParent implements SnP { return semesters; } - public Semester getCurrentSemester(List semesterList) { - Semester current = null; - for (Semester s : semesterList) { + @SuppressWarnings("unchecked") + private List getList(Elements options, Class type) throws IOException, VulcanException { + List list = new ArrayList<>(); + + for (Element e : options) { + URL url = new URL(e.val()); + try { + ParamItem item = type.newInstance() + .setId(url.getQuery().split("=")[1]) + .setName(e.text()); + + if (isCurrent(e)) { + item.setCurrent(true); + } + if (item instanceof Diary) { + item.setStudentId(getStudentID()); + } + + list.add((T) item); + } catch (Exception ex) { + throw new VulcanException("Error while trying to parse params list", ex); + } + } + + return list; + } + + @SuppressWarnings("unchecked") + public T getCurrent(List list) { + ParamItem current = null; + for (ParamItem s : list) { if (s.isCurrent()) { current = s; break; } } - return current; + return (T) current; + } + + private boolean isCurrent(Element e) { + return "selected".equals(e.attr("selected")); } } diff --git a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java index c16ef088..d90d3874 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java +++ b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java @@ -18,21 +18,27 @@ import io.github.wulkanowy.api.user.FamilyInformation; public class Vulcan { - private String id; - private SnP snp; private Client client; - public void setCredentials(String email, String password, String symbol, String id) { - client = new Client(email, password, symbol); + private String schoolId; - this.id = id; + private String studentId; + + private String diaryId; + + public void setCredentials(String email, String password, String symbol, String schoolId, String studentId, String diaryId) { + this.schoolId = schoolId; + this.studentId = studentId; + this.diaryId = diaryId; + + client = new Client(email, password, symbol); } public Client getClient() throws NotLoggedInErrorException { if (null == client) { - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("Use setCredentials() method first"); } return client; @@ -43,20 +49,17 @@ public class Vulcan { } - public SnP getStudentAndParent() throws IOException, VulcanException { + public SnP getStudentAndParent() throws VulcanException, IOException { if (null != this.snp) { return this.snp; } - this.snp = new StudentAndParent(getClient(), id).storeContextCookies(); + this.snp = new StudentAndParent(getClient(), schoolId, studentId, diaryId) + .setUp(); return this.snp; } - public String getId() throws IOException, VulcanException { - return getStudentAndParent().getId(); - } - public AttendanceTable getAttendanceTable() throws IOException, VulcanException { return new AttendanceTable(getStudentAndParent()); } diff --git a/api/src/main/java/io/github/wulkanowy/api/VulcanException.java b/api/src/main/java/io/github/wulkanowy/api/VulcanException.java index 0e7ed243..aba02057 100644 --- a/api/src/main/java/io/github/wulkanowy/api/VulcanException.java +++ b/api/src/main/java/io/github/wulkanowy/api/VulcanException.java @@ -1,4 +1,12 @@ package io.github.wulkanowy.api; -public abstract class VulcanException extends Exception { +public class VulcanException extends Exception { + + protected VulcanException(String message) { + super(message); + } + + protected VulcanException(String message, Exception e) { + super(message, e); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/VulcanOfflineException.java b/api/src/main/java/io/github/wulkanowy/api/VulcanOfflineException.java index 497fba94..24ab48e6 100644 --- a/api/src/main/java/io/github/wulkanowy/api/VulcanOfflineException.java +++ b/api/src/main/java/io/github/wulkanowy/api/VulcanOfflineException.java @@ -1,4 +1,8 @@ package io.github.wulkanowy.api; public class VulcanOfflineException extends VulcanException { + + VulcanOfflineException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/grades/GradesList.java b/api/src/main/java/io/github/wulkanowy/api/grades/GradesList.java index ce8c0f77..6e96c3a8 100644 --- a/api/src/main/java/io/github/wulkanowy/api/grades/GradesList.java +++ b/api/src/main/java/io/github/wulkanowy/api/grades/GradesList.java @@ -22,7 +22,7 @@ public class GradesList { private static final String GRADES_PAGE_URL = "Oceny/Wszystkie?details=2&okres="; - private SnP snp = null; + private SnP snp; private List grades = new ArrayList<>(); @@ -41,43 +41,61 @@ public class GradesList { public List getAll(String semester) throws IOException, ParseException, VulcanException { Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester); Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr"); - Semester currentSemester = snp.getCurrentSemester(snp.getSemesters(gradesPage)); + + if ("".equals(semester)) { + List semesterList = snp.getSemesters(gradesPage); + Semester currentSemester = snp.getCurrent(semesterList); + semester = currentSemester.getName(); + } for (Element row : gradesRows) { if ("Brak ocen".equals(row.select("td:nth-child(2)").text())) { continue; } - String descriptions = row.select("td:nth-child(3)").text(); - String symbol = descriptions.split(", ")[0]; - String description = descriptions.replaceFirst(symbol, "").replaceFirst(", ", ""); - - Pattern pattern = Pattern.compile("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"); - Matcher matcher = pattern.matcher(row.select("td:nth-child(2) span.ocenaCzastkowa") - .attr("style")); - - String color = ""; - while (matcher.find()) { - color = matcher.group(1); - } - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT); - Date d = sdf.parse(row.select("td:nth-child(5)").text()); - sdf.applyPattern("yyyy-MM-dd"); - - grades.add(new Grade() - .setSubject(row.select("td:nth-child(1)").text()) - .setValue(row.select("td:nth-child(2)").text()) - .setColor(color) - .setSymbol(symbol) - .setDescription(description) - .setWeight(row.select("td:nth-child(4)").text()) - .setDate(sdf.format(d)) - .setTeacher(row.select("td:nth-child(6)").text()) - .setSemester(currentSemester.getNumber()) - ); + grades.add(getGrade(row, semester)); } return grades; } + + private Grade getGrade(Element row, String semester) throws ParseException { + String descriptions = row.select("td:nth-child(3)").text(); + + String symbol = descriptions.split(", ")[0]; + String description = descriptions.replaceFirst(symbol, "").replaceFirst(", ", ""); + String color = getColor(row.select("td:nth-child(2) span.ocenaCzastkowa").attr("style")); + String date = formatDate(row.select("td:nth-child(5)").text()); + + return new Grade() + .setSubject(row.select("td:nth-child(1)").text()) + .setValue(row.select("td:nth-child(2)").text()) + .setColor(color) + .setSymbol(symbol) + .setDescription(description) + .setWeight(row.select("td:nth-child(4)").text()) + .setDate(date) + .setTeacher(row.select("td:nth-child(6)").text()) + .setSemester(semester); + } + + private String getColor(String styleAttr) { + Pattern pattern = Pattern.compile("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"); + Matcher matcher = pattern.matcher(styleAttr); + + String color = ""; + while (matcher.find()) { + color = matcher.group(1); + } + + return color; + } + + private String formatDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT); + Date d = sdf.parse(date); + sdf.applyPattern("yyyy-MM-dd"); + + return sdf.format(d); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/login/AccountPermissionException.java b/api/src/main/java/io/github/wulkanowy/api/login/AccountPermissionException.java index 99feb86f..de3901ff 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/AccountPermissionException.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/AccountPermissionException.java @@ -3,4 +3,8 @@ package io.github.wulkanowy.api.login; import io.github.wulkanowy.api.VulcanException; public class AccountPermissionException extends VulcanException { + + AccountPermissionException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/login/BadCredentialsException.java b/api/src/main/java/io/github/wulkanowy/api/login/BadCredentialsException.java index 1ac37f9c..13efd4a1 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/BadCredentialsException.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/BadCredentialsException.java @@ -3,4 +3,8 @@ package io.github.wulkanowy.api.login; import io.github.wulkanowy.api.VulcanException; public class BadCredentialsException extends VulcanException { + + BadCredentialsException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/login/Login.java b/api/src/main/java/io/github/wulkanowy/api/login/Login.java index 7f88037d..2814950f 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/Login.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/Login.java @@ -2,6 +2,7 @@ package io.github.wulkanowy.api.login; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; @@ -17,56 +18,60 @@ public class Login { "{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" + "{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx"; - private static final String LOGIN_ENDPOINT_PAGE_URL = - "{schema}://uonetplus.{host}/{symbol}/LoginEndpoint.aspx"; - private Client client; - private String symbol; - public Login(Client client) { this.client = client; } public String login(String email, String password, String symbol) throws VulcanException, IOException { - String certificate = sendCredentials(email, password, symbol); + Document certDoc = sendCredentials(email, password); - return sendCertificate(certificate, symbol); + return sendCertificate(certDoc, symbol); } - String sendCredentials(String email, String password, String symbol) throws IOException, VulcanException { - this.symbol = symbol; - + Document sendCredentials(String email, String password) throws IOException, VulcanException { Document html = client.postPageByUrl(LOGIN_PAGE_URL, new String[][]{ {"LoginName", email}, {"Password", password} }); - if (null != html.select(".ErrorMessage").first()) { - throw new BadCredentialsException(); + Element errorMessage = html.select(".ErrorMessage").first(); + if (null != errorMessage) { + throw new BadCredentialsException(errorMessage.text()); } - return html.select("input[name=wresult]").attr("value"); + return html; } - String sendCertificate(String certificate, String defaultSymbol) throws IOException, VulcanException { - this.symbol = findSymbol(defaultSymbol, certificate); - client.setSymbol(this.symbol); + String sendCertificate(Document doc, String defaultSymbol) throws IOException, VulcanException { + String certificate = doc.select("input[name=wresult]").val(); - String title = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{ - {"wa", "wsignin1.0"}, - {"wresult", certificate} - }).select("title").text(); + String symbol = findSymbol(defaultSymbol, certificate); + client.setSymbol(symbol); + + Document targetDoc = sendCertData(doc); + String title = targetDoc.select("title").text(); if ("Logowanie".equals(title)) { - throw new AccountPermissionException(); + throw new AccountPermissionException("No account access. Try another symbol"); } if (!"Uonet+".equals(title)) { - throw new LoginErrorException(); + throw new LoginErrorException("Expected page title `UONET+`, got " + title); } - return this.symbol; + return symbol; + } + + private Document sendCertData(Document doc) throws IOException, VulcanException { + String url = doc.select("form[name=hiddenform]").attr("action"); + + return client.postPageByUrl(url.replaceFirst("Default", "{symbol}"), new String[][]{ + {"wa", "wsignin1.0"}, + {"wresult", doc.select("input[name=wresult]").val()}, + {"wctx", doc.select("input[name=wctx]").val()} + }); } private String findSymbol(String symbol, String certificate) { @@ -78,14 +83,14 @@ public class Login { } String findSymbolInCertificate(String certificate) { - Elements els = Jsoup + Elements instances = Jsoup .parse(certificate.replaceAll(":", ""), "", Parser.xmlParser()) .select("[AttributeName=\"UserInstance\"] samlAttributeValue"); - if (els.isEmpty()) { + if (instances.isEmpty()) { return ""; } - return els.get(1).text(); + return instances.get(1).text(); } } diff --git a/api/src/main/java/io/github/wulkanowy/api/login/LoginErrorException.java b/api/src/main/java/io/github/wulkanowy/api/login/LoginErrorException.java index e264dc67..be7439df 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/LoginErrorException.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/LoginErrorException.java @@ -2,5 +2,9 @@ package io.github.wulkanowy.api.login; import io.github.wulkanowy.api.NotLoggedInErrorException; -public class LoginErrorException extends NotLoggedInErrorException { +class LoginErrorException extends NotLoggedInErrorException { + + LoginErrorException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/messages/BadRequestException.java b/api/src/main/java/io/github/wulkanowy/api/messages/BadRequestException.java index ed407b4d..14dca67b 100644 --- a/api/src/main/java/io/github/wulkanowy/api/messages/BadRequestException.java +++ b/api/src/main/java/io/github/wulkanowy/api/messages/BadRequestException.java @@ -3,4 +3,8 @@ package io.github.wulkanowy.api.messages; import io.github.wulkanowy.api.VulcanException; class BadRequestException extends VulcanException { + + BadRequestException(String message) { + super(message); + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/messages/Messages.java b/api/src/main/java/io/github/wulkanowy/api/messages/Messages.java index eb5f8bba..ec2a3fba 100644 --- a/api/src/main/java/io/github/wulkanowy/api/messages/Messages.java +++ b/api/src/main/java/io/github/wulkanowy/api/messages/Messages.java @@ -59,10 +59,10 @@ public class Messages { messages = new Gson().fromJson(res, MessagesContainer.class).data; } catch (JsonParseException e) { if (res.contains(ERROR_TITLE)) { - throw new BadRequestException(); + throw new BadRequestException(ERROR_TITLE); } - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("You are probably not logged in"); } return messages; @@ -80,10 +80,10 @@ public class Messages { message = new Gson().fromJson(res, MessageContainer.class).data; } catch (JsonParseException e) { if (res.contains(ERROR_TITLE)) { - throw new BadRequestException(); + throw new BadRequestException(ERROR_TITLE); } - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("You are probably not logged in. Force login"); } return message; diff --git a/api/src/test/java/io/github/wulkanowy/api/ClientTest.java b/api/src/test/java/io/github/wulkanowy/api/ClientTest.java index 4aa1be91..38f85617 100644 --- a/api/src/test/java/io/github/wulkanowy/api/ClientTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/ClientTest.java @@ -1,13 +1,10 @@ package io.github.wulkanowy.api; -import org.hamcrest.CoreMatchers; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.junit.Assert; import org.junit.Test; -import io.github.wulkanowy.api.login.Login; - public class ClientTest { private String getFixtureAsString(String fixtureFileName) { @@ -49,20 +46,6 @@ public class ClientTest { client.checkForErrors(doc); } - @Test - public void getClientTest() throws Exception { - Client client = new Client("", "", ""); - - Assert.assertThat(client.getLogin(), CoreMatchers.instanceOf(Login.class)); - } - - @Test - public void getClientTwiceTest() throws Exception { - Client client = new Client("", "", ""); - - Assert.assertEquals(client.getLogin(), client.getLogin()); - } - @Test public void getFilledUrlTest() throws Exception { Client client = new Client("http://fakelog.cf\\\\admin", "", "symbol123"); diff --git a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java index 5cde9b64..ca6ca316 100644 --- a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java @@ -7,6 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -22,18 +23,21 @@ public class StudentAndParentTest { client = Mockito.mock(Client.class); Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(gradesPageDocument); + Mockito.when(client.getPageByUrl( + Mockito.anyString(), + Mockito.anyBoolean(), Mockito.anyMap())).thenReturn(gradesPageDocument); } @Test - public void snpTest() throws Exception { - StudentAndParent snp = new StudentAndParent(client, "id123"); - Assert.assertEquals("id123", snp.getId()); + public void snpTest() { + StudentAndParent snp = new StudentAndParent(client, "id123", null, null); + Assert.assertEquals("id123", snp.getSchoolID()); } @Test public void getSnpPageUrlWithIdTest() throws Exception { Assert.assertEquals("{schema}://uonetplus-opiekun.{host}/{symbol}/123456/", - (new StudentAndParent(client, "123456")).getSnpHomePageUrl()); + (new StudentAndParent(client, "123456", null, null)).getSnpHomePageUrl()); } @Test @@ -43,7 +47,7 @@ public class StudentAndParentTest { Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(startPageDocument); - StudentAndParent snp = new StudentAndParent(client, null); + StudentAndParent snp = new StudentAndParent(client, null, null, null); Assert.assertEquals("https://uonetplus-opiekun.vulcan.net.pl/symbol/534213/Start/Index/", snp.getSnpHomePageUrl()); @@ -56,7 +60,7 @@ public class StudentAndParentTest { ); Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(wrongPageDocument); - StudentAndParent snp = new StudentAndParent(client, null); + StudentAndParent snp = new StudentAndParent(client, null, null, null); snp.getSnpHomePageUrl(); } @@ -64,7 +68,7 @@ public class StudentAndParentTest { @Test public void getExtractedIDStandardTest() throws Exception { Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); + StudentAndParent snp = new StudentAndParent(client, "symbol", null, null); Assert.assertEquals("123456", snp.getExtractedIdFromUrl("https://uonetplus-opiekun" + ".vulcan.net.pl/powiat/123456/Start/Index/")); } @@ -72,7 +76,7 @@ public class StudentAndParentTest { @Test public void getExtractedIDDemoTest() throws Exception { Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); + StudentAndParent snp = new StudentAndParent(client, "symbol", null, null); Assert.assertEquals("demo12345", snp.getExtractedIdFromUrl("https://uonetplus-opiekun.vulcan.net.pl/demoupowiat/demo12345/Start/Index/")); } @@ -80,44 +84,75 @@ public class StudentAndParentTest { @Test(expected = NotLoggedInErrorException.class) public void getExtractedIDNotLoggedTest() throws Exception { Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); + StudentAndParent snp = new StudentAndParent(client, "symbol", null, null); Assert.assertEquals("123", snp.getExtractedIdFromUrl("https://uonetplus.vulcan.net.pl/powiat/")); } @Test public void getSemestersTest() throws Exception { - SnP snp = new StudentAndParent(client, "123456"); + SnP snp = new StudentAndParent(client, "123456", null, null); List semesters = snp.getSemesters(); Assert.assertEquals(2, semesters.size()); Assert.assertEquals("1", semesters.get(0).getId()); - Assert.assertEquals("1234", semesters.get(0).getNumber()); + Assert.assertEquals("1234", semesters.get(0).getName()); Assert.assertFalse(semesters.get(0).isCurrent()); Assert.assertEquals("2", semesters.get(1).getId()); - Assert.assertEquals("1235", semesters.get(1).getNumber()); + Assert.assertEquals("1235", semesters.get(1).getName()); Assert.assertTrue(semesters.get(1).isCurrent()); } @Test - public void getCurrentSemesterTest() throws Exception { + public void getCurrentSemesterTest() { List semesters = new ArrayList<>(); - semesters.add(new Semester().setNumber("1500100900").setId("1").setCurrent(false)); - semesters.add(new Semester().setNumber("1500100901").setId("2").setCurrent(true)); + semesters.add(new Semester().setName("1500100900").setId("1").setCurrent(false)); + semesters.add(new Semester().setName("1500100901").setId("2").setCurrent(true)); - SnP snp = new StudentAndParent(client, ""); - Assert.assertTrue(snp.getCurrentSemester(semesters).isCurrent()); - Assert.assertEquals("2", snp.getCurrentSemester(semesters).getId()); - Assert.assertEquals("1500100901", snp.getCurrentSemester(semesters).getNumber()); + SnP snp = new StudentAndParent(client, "", null, null); + Semester semester = snp.getCurrent(semesters); + + Assert.assertTrue(semester.isCurrent()); + Assert.assertEquals("2", semester.getId()); + Assert.assertEquals("1500100901", semester.getName()); } @Test - public void getCurrentSemesterFromEmptyTest() throws Exception { - SnP snp = new StudentAndParent(client, ""); + public void getCurrentSemesterFromEmptyTest() { + SnP snp = new StudentAndParent(client, "", null, null); List semesters = new ArrayList<>(); - Assert.assertNull(snp.getCurrentSemester(semesters)); + Assert.assertNull(snp.getCurrent(semesters)); + } + + @Test + public void getDiariesAndStudentTest() throws IOException, VulcanException { + Document snpHome = Jsoup.parse(FixtureHelper.getAsString( + getClass().getResourceAsStream("StudentAndParent.html"))); + + client = Mockito.mock(Client.class); + Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(snpHome); + SnP snp = new StudentAndParent(client, "", null, null); + + snp.setUp(); + + Assert.assertEquals("3Ti 2017", snp.getDiaries().get(0).getName()); + Assert.assertEquals("2Ti 2016", snp.getDiaries().get(1).getName()); + Assert.assertEquals("1Ti 2015", snp.getDiaries().get(2).getName()); + + Assert.assertEquals("1300", snp.getDiaries().get(0).getId()); + Assert.assertEquals("1200", snp.getDiaries().get(1).getId()); + Assert.assertEquals("1100", snp.getDiaries().get(2).getId()); + + Assert.assertTrue(snp.getDiaries().get(0).isCurrent()); + Assert.assertFalse(snp.getDiaries().get(1).isCurrent()); + Assert.assertFalse(snp.getDiaries().get(2).isCurrent()); + + Assert.assertEquals("100", snp.getDiaries().get(0).getStudentId()); + + Assert.assertEquals("Jan Kowal", snp.getStudents().get(0).getName()); + Assert.assertEquals("100", snp.getStudents().get(0).getStudentId()); } } diff --git a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java index b18076cf..8d32291f 100644 --- a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java +++ b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java @@ -16,7 +16,7 @@ public abstract class StudentAndParentTestCase { Mockito.when(snp.getSnPPageDocument(Mockito.anyString())) .thenReturn(tablePageDocument); Mockito.when(snp.getSemesters(Mockito.any(Document.class))).thenCallRealMethod(); - Mockito.when(snp.getCurrentSemester(Mockito.anyList())) + Mockito.when(snp.getCurrent(Mockito.anyList())) .thenCallRealMethod(); Mockito.when(snp.getRowDataChildValue(Mockito.any(Element.class), Mockito.anyInt())).thenCallRealMethod(); diff --git a/api/src/test/java/io/github/wulkanowy/api/VulcanTest.java b/api/src/test/java/io/github/wulkanowy/api/VulcanTest.java index bda66e8d..287935c0 100644 --- a/api/src/test/java/io/github/wulkanowy/api/VulcanTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/VulcanTest.java @@ -16,7 +16,7 @@ public class VulcanTest { @Test public void getClientTest() throws Exception { Vulcan vulcan = new Vulcan(); - vulcan.setCredentials("email", "password", "symbol", null); + vulcan.setCredentials("email", "password", "symbol", null, null, null); Assert.assertThat(vulcan.getClient(), CoreMatchers.instanceOf(Client.class)); } @@ -24,7 +24,7 @@ public class VulcanTest { @Test public void getClientTwiceTest() throws Exception { Vulcan vulcan = new Vulcan(); - vulcan.setCredentials("email", "password", "symbol", null); + vulcan.setCredentials("email", "password", "symbol", null, null, null); Assert.assertEquals(vulcan.getClient(), vulcan.getClient()); } diff --git a/api/src/test/java/io/github/wulkanowy/api/login/LoginTest.java b/api/src/test/java/io/github/wulkanowy/api/login/LoginTest.java index 69118018..aac3b795 100644 --- a/api/src/test/java/io/github/wulkanowy/api/login/LoginTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/login/LoginTest.java @@ -11,12 +11,16 @@ import io.github.wulkanowy.api.FixtureHelper; public class LoginTest { + private Document getFixtureAsDocument(String fixtureFileName) { + return Jsoup.parse(getFixtureAsString(fixtureFileName)); + } + private String getFixtureAsString(String fixtureFileName) { return FixtureHelper.getAsString(getClass().getResourceAsStream(fixtureFileName)); } private Client getClient(String fixtureFileName) throws Exception { - Document doc = Jsoup.parse(getFixtureAsString(fixtureFileName)); + Document doc = getFixtureAsDocument(fixtureFileName); Client client = Mockito.mock(Client.class); Mockito.when(client.postPageByUrl(Mockito.anyString(), Mockito.any(String[][].class))).thenReturn(doc); @@ -26,25 +30,36 @@ public class LoginTest { @Test public void loginTest() throws Exception { - Login login = new Login(getClient("Logowanie-success.html")); + Client client = getClient("Logowanie-success.html"); + Mockito.when(client.getPageByUrl(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(getFixtureAsDocument("Logowanie-error.html")); + Login login = new Login(client); Assert.assertEquals("d123", login.login("a@a", "pswd", "d123")); } @Test(expected = BadCredentialsException.class) public void sendWrongCredentialsTest() throws Exception { - Login login = new Login(getClient("Logowanie-error.html")); + Client client = getClient("Logowanie-error.html"); + Mockito.when(client.getPageByUrl(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(getFixtureAsDocument("Logowanie-error.html")); // -error.html because it html with form used by + Login login = new Login(client); - login.sendCredentials("a@a", "pswd", "d123"); + login.sendCredentials("a@a", "pswd"); } @Test public void sendCredentialsCertificateTest() throws Exception { - Login login = new Login(getClient("Logowanie-certyfikat.html")); + Client client = getClient("Logowanie-certyfikat.html"); + Mockito.when(client.getPageByUrl(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(getFixtureAsDocument("Logowanie-error.html")); // -error.html because it html with form used by + Login login = new Login(client); Assert.assertEquals( getFixtureAsString("cert.xml").replaceAll("\\s+",""), - login.sendCredentials("a@a", "passwd", "d123").replaceAll("\\s+","") + login.sendCredentials("a@a", "passwd") + .select("input[name=wresult]").attr("value") + .replaceAll("\\s+","") ); } @@ -53,7 +68,7 @@ public class LoginTest { Login login = new Login(getClient("Logowanie-success.html")); Assert.assertEquals("wulkanowyschool321", - login.sendCertificate("", "wulkanowyschool321")); + login.sendCertificate(new Document(""), "wulkanowyschool321")); } @Test @@ -61,21 +76,21 @@ public class LoginTest { Login login = new Login(getClient("Logowanie-success.html")); Assert.assertEquals("demo12345", - login.sendCertificate(getFixtureAsString("cert.xml"), "Default")); + login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "Default")); } @Test(expected = AccountPermissionException.class) public void sendCertificateAccountPermissionTest() throws Exception { Login login = new Login(getClient("Logowanie-brak-dostepu.html")); - login.sendCertificate(getFixtureAsString("cert.xml"), "demo123"); + login.sendCertificate(getFixtureAsDocument("cert.xml"), "demo123"); } @Test(expected = LoginErrorException.class) public void sendCertificateLoginErrorTest() throws Exception { Login login = new Login(getClient("Logowanie-certyfikat.html")); // change to other document - login.sendCertificate(getFixtureAsString("cert.xml"), "demo123"); + login.sendCertificate(getFixtureAsDocument("cert.xml"), "demo123"); } @Test diff --git a/api/src/test/resources/io/github/wulkanowy/api/OcenyWszystkie-semester.html b/api/src/test/resources/io/github/wulkanowy/api/OcenyWszystkie-semester.html index f4b712c1..dc7c6c3b 100644 --- a/api/src/test/resources/io/github/wulkanowy/api/OcenyWszystkie-semester.html +++ b/api/src/test/resources/io/github/wulkanowy/api/OcenyWszystkie-semester.html @@ -15,7 +15,8 @@ - + +
wersja: 17.05.0000.24042
diff --git a/api/src/test/resources/io/github/wulkanowy/api/StudentAndParent.html b/api/src/test/resources/io/github/wulkanowy/api/StudentAndParent.html new file mode 100644 index 00000000..66e2942f --- /dev/null +++ b/api/src/test/resources/io/github/wulkanowy/api/StudentAndParent.html @@ -0,0 +1,37 @@ + + + + + Witryna ucznia i rodzica – Strona główna + + +
    +
  • + + +
  • +
  • + + +
  • +
+ +
wersja: 17.09.0008.26553
+ + diff --git a/app/build.gradle b/app/build.gradle index d930afc0..13a84a8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,15 +21,15 @@ apply from: '../jacoco.gradle' apply from: '../android-sonarqube.gradle' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 26 + buildToolsVersion '27.0.3' defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" minSdkVersion 15 - targetSdkVersion 27 - versionCode 3 - versionName "0.2.0" + targetSdkVersion 26 + versionCode 7 + versionName "0.3.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true manifestPlaceholders = [ @@ -62,48 +62,45 @@ android { } greendao { - schemaVersion 22 + schemaVersion 23 generateTests = true } dependencies { implementation project(':api') - implementation 'com.android.support:appcompat-v7:27.1.0' - implementation 'com.android.support:design:27.1.0' - implementation 'com.android.support:support-v4:27.1.0' - implementation 'com.android.support:recyclerview-v7:27.1.0' - implementation 'com.android.support:cardview-v7:27.1.0' - implementation 'com.android.support:customtabs:27.1.0' - implementation 'com.firebase:firebase-jobdispatcher:0.8.5' - implementation 'org.apache.commons:commons-lang3:3.7' - implementation 'eu.davidea:flexible-adapter:5.0.0-rc4' - implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1' - implementation 'org.apache.commons:commons-collections4:4.1' - implementation 'org.greenrobot:greendao:3.2.2' - implementation 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.0.2' - implementation 'com.jakewharton:butterknife:8.8.1' - implementation 'joda-time:joda-time:2.9.9' - implementation 'com.google.dagger:dagger-android:2.14.1' - implementation 'com.google.dagger:dagger-android-support:2.14.1' - implementation 'com.aurelhubert:ahbottomnavigation:2.1.0' + implementation "com.android.support:support-v4:$supportVersion" + implementation "com.android.support:design:$supportVersion" + implementation "com.android.support:cardview-v7:$supportVersion" + implementation "com.android.support:customtabs:$supportVersion" + implementation "com.android.support:preference-v14:$supportVersion" + implementation "com.firebase:firebase-jobdispatcher:$firebaseJob" + implementation "org.apache.commons:commons-lang3:$apacheLang" + implementation "org.apache.commons:commons-collections4:$apacheCollections" + implementation "eu.davidea:flexible-adapter:$flexibleAdapter" + implementation "eu.davidea:flexible-adapter-ui:$flexibleUi" + implementation "org.greenrobot:greendao:$greenDao" + implementation "com.jakewharton:butterknife:$butterknife" + implementation "com.google.dagger:dagger-android-support:$dagger2" + implementation "com.aurelhubert:ahbottomnavigation:$ahbottom" + implementation "com.jakewharton.threetenabp:threetenabp:$threeTenABP" - implementation('com.crashlytics.sdk.android:crashlytics:2.8.0@aar') { + implementation("com.crashlytics.sdk.android:crashlytics:$crashlyticsSdk@aar") { transitive = true } - implementation('com.crashlytics.sdk.android:answers:1.4.1@aar') { + implementation("com.crashlytics.sdk.android:answers:$crashlyticsAnswers@aar") { transitive = true } - annotationProcessor 'com.google.dagger:dagger-android-processor:2.14.1' - annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + annotationProcessor "com.google.dagger:dagger-android-processor:$dagger2" + annotationProcessor "com.google.dagger:dagger-compiler:$dagger2" + annotationProcessor "com.jakewharton:butterknife-compiler:$butterknife" - debugImplementation 'com.amitshekhar.android:debug-db:1.0.1' - debugImplementation 'net.zetetic:android-database-sqlcipher:3.5.9' + debugImplementation "com.amitshekhar.android:debug-db:$debugDb" + debugImplementation "net.zetetic:android-database-sqlcipher:$sqlcipher" - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.13.0' + testImplementation "junit:junit:$junit" + testImplementation "org.mockito:mockito-core:$mockito" - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'org.mockito:mockito-android:2.13.0' + androidTestImplementation "com.android.support.test:runner:$testRunner" + androidTestImplementation "org.mockito:mockito-android:$mockito" } diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLessonTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLessonTest.java index 492d642d..0758f2cc 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLessonTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLessonTest.java @@ -2,9 +2,6 @@ package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; -import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; -import io.github.wulkanowy.data.db.dao.entities.AttendanceLessonDao; - public class AttendanceLessonTest extends AbstractDaoTestLongPk { public AttendanceLessonTest() { diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DiaryTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DiaryTest.java new file mode 100644 index 00000000..f7f56ccb --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DiaryTest.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class DiaryTest extends AbstractDaoTestLongPk { + + public DiaryTest() { + super(DiaryDao.class); + } + + @Override + protected Diary createEntity(Long key) { + Diary entity = new Diary(); + entity.setId(key); + entity.setIsCurrent(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/TimetableLessonTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/TimetableLessonTest.java index b215b6be..9796f988 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/TimetableLessonTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/TimetableLessonTest.java @@ -2,9 +2,6 @@ package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; -import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; -import io.github.wulkanowy.data.db.dao.entities.TimetableLessonDao; - public class TimetableLessonTest extends AbstractDaoTestLongPk { public TimetableLessonTest() { diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java index 4b8866ce..ab94afa6 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java @@ -4,6 +4,7 @@ import android.app.Application; import com.crashlytics.android.Crashlytics; import com.crashlytics.android.core.CrashlyticsCore; +import com.jakewharton.threetenabp.AndroidThreeTen; import org.greenrobot.greendao.query.QueryBuilder; @@ -16,6 +17,7 @@ import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.di.component.ApplicationComponent; import io.github.wulkanowy.di.component.DaggerApplicationComponent; import io.github.wulkanowy.di.modules.ApplicationModule; +import io.github.wulkanowy.utils.LogUtils; public class WulkanowyApp extends Application { @@ -27,6 +29,8 @@ public class WulkanowyApp extends Application { @Override public void onCreate() { super.onCreate(); + AndroidThreeTen.init(this); + applicationComponent = DaggerApplicationComponent .builder() .applicationModule(new ApplicationModule(this)) @@ -37,6 +41,17 @@ public class WulkanowyApp extends Application { enableDebugLog(); } initializeFabric(); + initializeUserSession(); + } + + private void initializeUserSession() { + if (repository.getCurrentUserId() != 0) { + try { + repository.initLastUser(); + } catch (Exception e) { + LogUtils.error("An error occurred when the application was started", e); + } + } } private void enableDebugLog() { diff --git a/app/src/main/java/io/github/wulkanowy/data/Repository.java b/app/src/main/java/io/github/wulkanowy/data/Repository.java index d25be8b5..7b6caa8a 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Repository.java +++ b/app/src/main/java/io/github/wulkanowy/data/Repository.java @@ -68,6 +68,31 @@ public class Repository implements RepositoryContract { return sharedPref.getCurrentUserId(); } + @Override + public int getStartupTab() { + return sharedPref.getStartupTab(); + } + + @Override + public int getServicesInterval() { + return sharedPref.getServicesInterval(); + } + + @Override + public boolean isServicesEnable() { + return sharedPref.isServicesEnable(); + } + + @Override + public boolean isNotifyEnable() { + return sharedPref.isNotifyEnable(); + } + + @Override + public boolean isMobileDisable() { + return sharedPref.isMobileDisable(); + } + @Override public String[] getSymbolsKeysArray() { return resources.getSymbolsKeysArray(); diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java index f06c4762..55287263 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java @@ -21,6 +21,16 @@ public interface RepositoryContract extends ResourcesContract, AccountSyncContra long getCurrentUserId(); + int getStartupTab(); + + boolean isServicesEnable(); + + boolean isNotifyEnable(); + + int getServicesInterval(); + + boolean isMobileDisable(); + void syncGrades() throws VulcanException, IOException, ParseException; void syncSubjects() throws VulcanException, IOException, ParseException; diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java index a67e9b7f..ad743a33 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java @@ -3,19 +3,20 @@ package io.github.wulkanowy.data.db.dao; import android.content.Context; import android.database.sqlite.SQLiteDatabase; -import com.github.yuweiguocn.library.greendao.MigrationHelper; - import org.greenrobot.greendao.database.Database; import org.greenrobot.greendao.database.StandardDatabase; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import javax.inject.Inject; import javax.inject.Singleton; -import io.github.wulkanowy.BuildConfig; -import io.github.wulkanowy.data.db.dao.entities.AccountDao; +import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.data.db.dao.entities.DaoMaster; -import io.github.wulkanowy.data.db.dao.entities.GradeDao; -import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.data.db.dao.migrations.Migration23; import io.github.wulkanowy.data.db.shared.SharedPrefContract; import io.github.wulkanowy.di.annotations.ApplicationContext; import io.github.wulkanowy.di.annotations.DatabaseInfo; @@ -24,29 +25,16 @@ import io.github.wulkanowy.utils.LogUtils; @Singleton public class DbHelper extends DaoMaster.OpenHelper { - private SharedPrefContract sharedPref; + private final SharedPrefContract sharedPref; + + private final Vulcan vulcan; @Inject DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName, - SharedPrefContract sharedPref) { + SharedPrefContract sharedPref, Vulcan vulcan) { super(context, dbName); this.sharedPref = sharedPref; - } - - @Override - @SuppressWarnings("unchecked") - public void onUpgrade(Database db, int oldVersion, int newVersion) { - MigrationHelper.DEBUG = BuildConfig.DEBUG; - MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() { - @Override - public void onCreateAllTables(Database db, boolean ifNotExists) { - DaoMaster.createAllTables(db, ifNotExists); - } - @Override - public void onDropAllTables(Database db, boolean ifExists) { - DaoMaster.dropAllTables(db, ifExists); - } - }, AccountDao.class, SubjectDao.class, GradeDao.class); + this.vulcan = vulcan; } @Override @@ -55,7 +43,49 @@ public class DbHelper extends DaoMaster.OpenHelper { DaoMaster.dropAllTables(database, true); onCreate(database); sharedPref.setCurrentUserId(0); - LogUtils.info("Cleaning user data oldVersion=" + oldVersion + " newVersion=" + newVersion); } + + @Override + public void onUpgrade(Database db, int oldVersion, int newVersion) { + List migrations = getMigrations(); + + // Only run migrations past the old version + for (Migration migration : migrations) { + if (oldVersion < migration.getVersion()) { + try { + LogUtils.info("Applying migration to db schema v" + migration.getVersion() + "..."); + migration.runMigration(db, sharedPref, vulcan); + LogUtils.info("Migration " + migration.getVersion() + " complete"); + } catch (Exception e) { + e.printStackTrace(); + DaoMaster.dropAllTables(db, true); + sharedPref.setCurrentUserId(0); + break; + } + } + } + } + + private List getMigrations() { + List migrations = new ArrayList<>(); + migrations.add(new Migration23()); + + // Sorting just to be safe, in case other people add migrations in the wrong order. + Comparator migrationComparator = new Comparator() { + @Override + public int compare(Migration m1, Migration m2) { + return m1.getVersion().compareTo(m2.getVersion()); + } + }; + Collections.sort(migrations, migrationComparator); + + return migrations; + } + + public interface Migration { + Integer getVersion(); + + void runMigration(Database db, SharedPrefContract sharedPref, Vulcan vulcan) throws Exception; + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java index 20a47ac2..1ed7548f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java @@ -4,6 +4,8 @@ import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; +import org.greenrobot.greendao.annotation.JoinProperty; import org.greenrobot.greendao.annotation.Property; import org.greenrobot.greendao.annotation.ToMany; @@ -18,20 +20,29 @@ public class Account { @Id(autoincrement = true) private Long id; + @Index(unique = true) + @Property(nameInDb = "REAL_ID") + private String realId; + + @Property(nameInDb = "SYMBOL") + private String symbol; + + @Property(nameInDb = "SCHOOL_ID") + private String schoolId; + @Property(nameInDb = "NAME") private String name; - @Property(nameInDb = "E-MAIL") + @Property(nameInDb = "E_MAIL") private String email; @Property(nameInDb = "PASSWORD") private String password; - @Property(nameInDb = "SYMBOL") - private String symbol; - - @Property(nameInDb = "SNPID") - private String snpId; + @ToMany(joinProperties = { + @JoinProperty(name = "realId", referencedName = "studentId") + }) + private List diaryList; @ToMany(referencedJoinProperty = "userId") private List subjectList; @@ -54,15 +65,16 @@ public class Account { @Generated(hash = 335469827) private transient AccountDao myDao; - @Generated(hash = 735765217) - public Account(Long id, String name, String email, String password, String symbol, - String snpId) { + @Generated(hash = 727721142) + public Account(Long id, String realId, String symbol, String schoolId, String name, + String email, String password) { this.id = id; + this.realId = realId; + this.symbol = symbol; + this.schoolId = schoolId; this.name = name; this.email = email; this.password = password; - this.symbol = symbol; - this.snpId = snpId; } @Generated(hash = 882125521) @@ -78,6 +90,15 @@ public class Account { return this; } + public String getRealId() { + return realId; + } + + public Account setRealId(String realId) { + this.realId = realId; + return this; + } + public String getName() { return name; } @@ -114,15 +135,83 @@ public class Account { return this; } - public String getSnpId() { - return this.snpId; + public String getSchoolId() { + return schoolId; } - public Account setSnpId(String snpId) { - this.snpId = snpId; + public Account setSchoolId(String schoolId) { + this.schoolId = schoolId; return this; } + public Account setDiaryList(List diaryList) { + this.diaryList = diaryList; + return this; + } + + public Account setSubjectList(List subjectList) { + this.subjectList = subjectList; + return this; + } + + public Account setGradeList(List gradeList) { + this.gradeList = gradeList; + return this; + } + + public Account setDayList(List dayList) { + this.dayList = dayList; + return this; + } + + public DaoSession getDaoSession() { + return daoSession; + } + + public Account setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + return this; + } + + public AccountDao getMyDao() { + return myDao; + } + + public Account setMyDao(AccountDao myDao) { + this.myDao = myDao; + return this; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1472214466) + public List getDiaryList() { + if (diaryList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + DiaryDao targetDao = daoSession.getDiaryDao(); + List diaryListNew = targetDao._queryAccount_DiaryList(realId); + synchronized (this) { + if (diaryList == null) { + diaryList = diaryListNew; + } + } + } + return diaryList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1078514341) + public synchronized void resetDiaryList() { + diaryList = null; + } + /** * To-many relationship, resolved on first access (and after reset). * Changes to to-many relations are not persisted, make changes to the target entity. @@ -183,6 +272,36 @@ public class Account { gradeList = null; } + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 300459794) + public List getDayList() { + if (dayList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + DayDao targetDao = daoSession.getDayDao(); + List dayListNew = targetDao._queryAccount_DayList(id); + synchronized (this) { + if (dayList == null) { + dayList = dayListNew; + } + } + } + return dayList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1010399236) + public synchronized void resetDayList() { + dayList = null; + } + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. * Entity must attached to an entity context. @@ -219,36 +338,6 @@ public class Account { myDao.update(this); } - /** - * To-many relationship, resolved on first access (and after reset). - * Changes to to-many relations are not persisted, make changes to the target entity. - */ - @Generated(hash = 300459794) - public List getDayList() { - if (dayList == null) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - DayDao targetDao = daoSession.getDayDao(); - List dayListNew = targetDao._queryAccount_DayList(id); - synchronized (this) { - if (dayList == null) { - dayList = dayListNew; - } - } - } - return dayList; - } - - /** - * Resets a to-many relationship, making the next get call to query for a fresh result. - */ - @Generated(hash = 1010399236) - public synchronized void resetDayList() { - dayList = null; - } - /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 1812283172) public void __setDaoSession(DaoSession daoSession) { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Diary.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Diary.java new file mode 100644 index 00000000..03ff9b60 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Diary.java @@ -0,0 +1,161 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Property; + +@Entity( + nameInDb = "Diaries", + active = true +) +public class Diary { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "STUDENT_ID") + private String studentId; + + @Property(nameInDb = "NAME") + private String name; + + @Property(nameInDb = "VALUE") + private String value; + + @Property(nameInDb = "IS_CURRENT") + private boolean isCurrent; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 21166549) + private transient DiaryDao myDao; + + @Generated(hash = 459332202) + public Diary(Long id, String studentId, String name, String value, + boolean isCurrent) { + this.id = id; + this.studentId = studentId; + this.name = name; + this.value = value; + this.isCurrent = isCurrent; + } + + @Generated(hash = 112123061) + public Diary() { + } + + public Long getId() { + return id; + } + + public Diary setId(Long id) { + this.id = id; + return this; + } + + public String getStudentId() { + return studentId; + } + + public Diary setStudentId(String studentId) { + this.studentId = studentId; + return this; + } + + public String getName() { + return name; + } + + public Diary setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return value; + } + + public Diary setValue(String value) { + this.value = value; + return this; + } + + public boolean getIsCurrent() { + return isCurrent; + } + + public Diary setIsCurrent(boolean current) { + isCurrent = current; + return this; + } + + public DaoSession getDaoSession() { + return daoSession; + } + + public Diary setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + return this; + } + + public DiaryDao getMyDao() { + return myDao; + } + + public Diary setMyDao(DiaryDao myDao) { + this.myDao = myDao; + return this; + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 128553479) + public void delete() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.delete(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 1942392019) + public void refresh() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.refresh(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 713229351) + public void update() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.update(this); + } + + /** called by internal mechanisms, do not call yourself. */ + @Generated(hash = 629297785) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getDiaryDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration23.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration23.java new file mode 100644 index 00000000..9a497ceb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration23.java @@ -0,0 +1,114 @@ +package io.github.wulkanowy.data.db.dao.migrations; + +import android.database.Cursor; +import android.os.AsyncTask; + +import org.greenrobot.greendao.database.Database; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.github.wulkanowy.api.Diary; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.utils.security.Scrambler; + +public class Migration23 implements DbHelper.Migration { + + @Override + public Integer getVersion() { + return 23; + } + + @Override + public void runMigration(final Database db, final SharedPrefContract sharedPref, final Vulcan vulcan) throws Exception { + createDiaryTable(db); + migrateAccountsTable(db); + + final Map user = getAccountData(db); + vulcan.setCredentials( + user.get("email"), + Scrambler.decrypt(user.get("email"), user.get("password")), + user.get("symbol"), + user.get("school_id"), + "", // inserted in code bellow + "" + ); + + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + insertDiaries(db, vulcan.getStudentAndParent().getDiaries()); + updateAccount(db, vulcan.getStudentAndParent().getStudentID()); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private void createDiaryTable(Database db) { + db.execSQL("DROP TABLE IF EXISTS Diaries"); + db.execSQL("CREATE TABLE IF NOT EXISTS \"Diaries\" (" + // + "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id + "\"STUDENT_ID\" TEXT," + // 1: studentId + "\"NAME\" TEXT," + // 2: name + "\"VALUE\" TEXT," + // 3: value + "\"IS_CURRENT\" INTEGER NOT NULL );"); // 4: isCurrent + } + + private void migrateAccountsTable(Database db) { + db.execSQL("DROP TABLE IF EXISTS tmp_account"); + db.execSQL("ALTER TABLE Accounts RENAME TO tmp_account"); + db.execSQL("CREATE TABLE IF NOT EXISTS \"Accounts\" (" + // + "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id + "\"REAL_ID\" TEXT," + // 1: realId + "\"SYMBOL\" TEXT," + // 2: symbol + "\"SCHOOL_ID\" TEXT," + // 3: schoolId + "\"NAME\" TEXT," + // 4: name + "\"E_MAIL\" TEXT," + // 5: email + "\"PASSWORD\" TEXT);"); // 6: password + // Add Indexes + db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS IDX_Accounts_REAL_ID ON \"Accounts\" (\"REAL_ID\" ASC);"); + db.execSQL("INSERT INTO Accounts(NAME, E_MAIL, PASSWORD, SYMBOL, SCHOOL_ID)" + + "SELECT `NAME`, `E-MAIL`, `PASSWORD`, `SYMBOL`, `SNPID` FROM tmp_account"); + db.execSQL("DROP TABLE tmp_account"); + } + + private Map getAccountData(Database db) { + Map values = new HashMap<>(); + Cursor cursor = db.rawQuery("SELECT SYMBOL, SCHOOL_ID, NAME, E_MAIL, PASSWORD FROM Accounts", null); + + if (cursor.moveToFirst()) { + do { + values.put("symbol", cursor.getString(cursor.getColumnIndex("SYMBOL"))); + values.put("school_id", cursor.getString(cursor.getColumnIndex("SCHOOL_ID"))); + values.put("name", cursor.getString(cursor.getColumnIndex("NAME"))); + values.put("email", cursor.getString(cursor.getColumnIndex("E_MAIL"))); + values.put("password", cursor.getString(cursor.getColumnIndex("PASSWORD"))); + } while (cursor.moveToNext()); + } + + cursor.close(); + + return values; + } + + private void insertDiaries(Database db, List list) { + for (Diary diary : list) { + db.execSQL("INSERT INTO Diaries(STUDENT_ID, NAME, VALUE, IS_CURRENT) VALUES(" + + "\"" + diary.getStudentId() + "\"," + + "\"" + diary.getName() + "\"," + + "\"" + diary.getId() + "\"," + + "\"" + (diary.isCurrent() ? "1" : "0") + "\"" + + ")"); + } + } + + private void updateAccount(Database db, String realId) { + db.execSQL("UPDATE Accounts SET REAL_ID = ?", new String[]{realId}); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java index 349ac177..5b1f8718 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java @@ -3,6 +3,8 @@ package io.github.wulkanowy.data.db.resources; import android.content.Context; import android.content.res.Resources; +import com.crashlytics.android.Crashlytics; + import java.io.IOException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; @@ -12,7 +14,6 @@ import javax.inject.Singleton; import io.github.wulkanowy.R; import io.github.wulkanowy.api.NotLoggedInErrorException; -import io.github.wulkanowy.api.VulcanOfflineException; import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; import io.github.wulkanowy.di.annotations.ApplicationContext; import io.github.wulkanowy.utils.AppConstant; @@ -42,6 +43,7 @@ public class AppResources implements ResourcesContract { @Override public String getErrorLoginMessage(Exception exception) { LogUtils.error(AppConstant.APP_NAME + " encountered a error", exception); + Crashlytics.logException(exception); if (exception instanceof CryptoException) { return resources.getString(R.string.encrypt_failed_text); @@ -51,8 +53,6 @@ public class AppResources implements ResourcesContract { return resources.getString(R.string.generic_timeout_error); } else if (exception instanceof NotLoggedInErrorException || exception instanceof IOException) { return resources.getString(R.string.login_denied_text); - } else if (exception instanceof VulcanOfflineException) { - return resources.getString(R.string.error_host_offline); } else { return exception.getMessage(); } @@ -67,23 +67,23 @@ public class AppResources implements ResourcesContract { } if (lesson.getIsAbsenceExcused()) { - id = R.string.attendance_absence_excused; + id = R.string.attendance_absence_excused; } if (lesson.getIsAbsenceUnexcused()) { - id = R.string.attendance_absence_unexcused; + id = R.string.attendance_absence_unexcused; } if (lesson.getIsExemption()) { - id = R.string.attendance_exemption; + id = R.string.attendance_exemption; } if (lesson.getIsExcusedLateness()) { - id = R.string.attendance_excused_lateness; + id = R.string.attendance_excused_lateness; } if (lesson.getIsUnexcusedLateness()) { - id = R.string.attendance_unexcused_lateness; + id = R.string.attendance_unexcused_lateness; } return resources.getString(id); diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java index ba437f88..6034c99c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java @@ -2,32 +2,62 @@ package io.github.wulkanowy.data.db.shared; import android.content.Context; import android.content.SharedPreferences; +import android.preference.PreferenceManager; import javax.inject.Inject; import javax.inject.Singleton; import io.github.wulkanowy.di.annotations.ApplicationContext; import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; +import io.github.wulkanowy.ui.main.settings.SettingsFragment; @Singleton public class SharedPref implements SharedPrefContract { private static final String SHARED_KEY_USER_ID = "USER_ID"; - private final SharedPreferences sharedPreferences; + private final SharedPreferences appSharedPref; + + private final SharedPreferences settingsSharedPref; @Inject SharedPref(@ApplicationContext Context context, @SharedPreferencesInfo String sharedName) { - sharedPreferences = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); + appSharedPref = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); + settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(context); } @Override public long getCurrentUserId() { - return sharedPreferences.getLong(SHARED_KEY_USER_ID, 0); + return appSharedPref.getLong(SHARED_KEY_USER_ID, 0); } @Override public void setCurrentUserId(long userId) { - sharedPreferences.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); + appSharedPref.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); + } + + @Override + public int getStartupTab() { + return Integer.parseInt(settingsSharedPref.getString(SettingsFragment.SHARED_KEY_START_TAB, "2")); + } + + @Override + public int getServicesInterval() { + return Integer.parseInt(settingsSharedPref.getString(SettingsFragment.SHARED_KEY_SERVICES_INTERVAL, "60")); + } + + @Override + public boolean isServicesEnable() { + return settingsSharedPref.getBoolean(SettingsFragment.SHARED_KEY_SERVICES_ENABLE, true); + } + + @Override + public boolean isNotifyEnable() { + return settingsSharedPref.getBoolean(SettingsFragment.SHARED_KEY_NOTIFY_ENABLE, true); + } + + @Override + public boolean isMobileDisable() { + return settingsSharedPref.getBoolean(SettingsFragment.SHARED_KEY_SERVICES_MOBILE_DISABLED, false); } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java index 7f540acf..b00d9e4c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java @@ -5,4 +5,14 @@ public interface SharedPrefContract { long getCurrentUserId(); void setCurrentUserId(long userId); + + int getStartupTab(); + + int getServicesInterval(); + + boolean isMobileDisable(); + + boolean isServicesEnable(); + + boolean isNotifyEnable(); } diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSync.java index d56f9990..550c1f53 100644 --- a/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSync.java +++ b/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSync.java @@ -3,6 +3,7 @@ package io.github.wulkanowy.data.sync.account; import android.content.Context; import java.io.IOException; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @@ -11,8 +12,11 @@ import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.api.VulcanException; import io.github.wulkanowy.data.db.dao.entities.Account; import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Diary; +import io.github.wulkanowy.data.db.dao.entities.DiaryDao; import io.github.wulkanowy.data.db.shared.SharedPrefContract; import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.utils.DataObjectConverter; import io.github.wulkanowy.utils.LogUtils; import io.github.wulkanowy.utils.security.CryptoException; import io.github.wulkanowy.utils.security.Scrambler; @@ -43,22 +47,27 @@ public class AccountSync implements AccountSyncContract { LogUtils.debug("Register new user email=" + email); - vulcan.setCredentials(email, password, symbol, null); + vulcan.setCredentials(email, password, symbol, null, null, null); Account account = new Account() .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) .setEmail(email) .setPassword(Scrambler.encrypt(email, password, context)) .setSymbol(vulcan.getSymbol()) - .setSnpId(vulcan.getStudentAndParent().getId()); + .setSchoolId(vulcan.getStudentAndParent().getSchoolID()) + .setRealId(vulcan.getStudentAndParent().getStudentID()); + + List diaryList = DataObjectConverter.diariesToDiaryEntities( + vulcan.getStudentAndParent().getDiaries()); daoSession.getAccountDao().insert(account); + daoSession.getDiaryDao().insertInTx(diaryList); sharedPref.setCurrentUserId(account.getId()); } @Override - public void initLastUser() throws VulcanException, IOException, CryptoException { + public void initLastUser() throws IOException, CryptoException { long userId = sharedPref.getCurrentUserId(); @@ -73,6 +82,10 @@ public class AccountSync implements AccountSyncContract { vulcan.setCredentials(account.getEmail(), Scrambler.decrypt(account.getEmail(), account.getPassword()), account.getSymbol(), - account.getSnpId()); + account.getSchoolId(), + account.getRealId(), + daoSession.getDiaryDao().queryBuilder() + .where(DiaryDao.Properties.IsCurrent.eq(true)).unique().getValue() + ); } } diff --git a/app/src/main/java/io/github/wulkanowy/services/GradeNotify.java b/app/src/main/java/io/github/wulkanowy/services/GradeNotify.java new file mode 100644 index 00000000..609354e2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/GradeNotify.java @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services; + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; + +import io.github.wulkanowy.R; + +class GradeNotify extends NotificationService { + + private static final String CHANNEL_ID = "Grade_Notify"; + + GradeNotify(Context context) { + super(context); + } + + @Override + @TargetApi(26) + void createChannel() { + String channelName = getString(R.string.notify_grade_channel); + + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, channelName, + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.enableLights(true); + notificationChannel.enableVibration(true); + notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + getManager().createNotificationChannel(notificationChannel); + } + + @Override + String getChannelId() { + return CHANNEL_ID; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java index ab721f46..b6c49896 100644 --- a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java +++ b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java @@ -3,29 +3,22 @@ package io.github.wulkanowy.services; import android.annotation.TargetApi; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.os.Build; +import android.support.annotation.StringRes; import android.support.v4.app.NotificationCompat; import java.util.Random; -class NotificationService { - - private static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; - - private static final String CHANNEL_NAME = "New Grade Channel"; +public class NotificationService { private NotificationManager manager; private Context context; - NotificationService(Context context) { + public NotificationService(Context context) { this.context = context; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createChannel(); - } } void notify(Notification notification) { @@ -33,23 +26,32 @@ class NotificationService { } NotificationCompat.Builder notificationBuilder() { - return new NotificationCompat.Builder(context, CHANNEL_ID); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + return new NotificationCompat.Builder(context, getChannelId()); } - @TargetApi(26) - private void createChannel() { - NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - notificationChannel.enableLights(true); - notificationChannel.enableVibration(true); - notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - getManager().createNotificationChannel(notificationChannel); + public void cancelAll() { + getManager().cancelAll(); } - private NotificationManager getManager() { + NotificationManager getManager() { if (manager == null) { manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } return manager; } -} + + String getString(@StringRes int stringId) { + return context.getString(stringId); + } + + @TargetApi(26) + void createChannel() { + } + + String getChannelId() { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java index e7b0908f..43cde908 100644 --- a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java +++ b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java @@ -4,6 +4,7 @@ import android.app.PendingIntent; import android.content.Context; import android.support.v4.app.NotificationCompat; +import com.crashlytics.android.Crashlytics; import com.firebase.jobdispatcher.Constraint; import com.firebase.jobdispatcher.FirebaseJobDispatcher; import com.firebase.jobdispatcher.GooglePlayDriver; @@ -14,6 +15,7 @@ import com.firebase.jobdispatcher.RetryStrategy; import com.firebase.jobdispatcher.SimpleJobService; import com.firebase.jobdispatcher.Trigger; +import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -27,32 +29,34 @@ import io.github.wulkanowy.utils.LogUtils; public class SyncJob extends SimpleJobService { - private static final int DEFAULT_INTERVAL_START = 60 * 50; - - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); - public static final String EXTRA_INTENT_KEY = "cardId"; - private List gradeList; + public static final String JOB_TAG = "SyncJob"; + + private List gradeList = new ArrayList<>(); @Inject RepositoryContract repository; - public static void start(Context context) { + public static void start(Context context, int interval, boolean useOnlyWifi) { FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); dispatcher.mustSchedule(dispatcher.newJobBuilder() .setLifetime(Lifetime.FOREVER) .setService(SyncJob.class) - .setTag("SyncJob") + .setTag(JOB_TAG) .setRecurring(true) - .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) - .setConstraints(Constraint.ON_ANY_NETWORK) + .setTrigger(Trigger.executionWindow(interval * 60, (interval + 10) * 60)) + .setConstraints(useOnlyWifi ? Constraint.ON_UNMETERED_NETWORK : Constraint.ON_ANY_NETWORK) .setReplaceCurrent(false) .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) .build()); } + public static void stop(Context context) { + new FirebaseJobDispatcher(new GooglePlayDriver(context)).cancel(JOB_TAG); + } + @Override public void onCreate() { super.onCreate(); @@ -67,23 +71,24 @@ public class SyncJob extends SimpleJobService { gradeList = repository.getNewGrades(); - if (!gradeList.isEmpty()) { + if (!gradeList.isEmpty() && repository.isNotifyEnable()) { showNotification(); } return JobService.RESULT_SUCCESS; } catch (Exception e) { + Crashlytics.logException(e); LogUtils.error("During background synchronization an error occurred", e); return JobService.RESULT_FAIL_RETRY; } } private void showNotification() { - NotificationService service = new NotificationService(getApplicationContext()); + GradeNotify gradeNotify = new GradeNotify(getApplicationContext()); - service.notify(service.notificationBuilder() + gradeNotify.notify(gradeNotify.notificationBuilder() .setContentTitle(getStringTitle()) .setContentText(getStringContent()) - .setSmallIcon(R.drawable.ic_stat_notify) + .setSmallIcon(R.drawable.ic_notify_grade) .setAutoCancel(true) .setDefaults(NotificationCompat.DEFAULT_ALL) .setPriority(NotificationCompat.PRIORITY_HIGH) diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java index 95c5548b..9d6e8b12 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.design.widget.TextInputLayout; +import android.support.v7.app.ActionBar; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; @@ -209,6 +210,18 @@ public class LoginActivity extends BaseActivity implements LoginContract.View { }); } + @Override + public void showActionBar(boolean show) { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + if (show) { + actionBar.show(); + } else { + actionBar.hide(); + } + } + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java index debfbbde..570e1b26 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java @@ -33,6 +33,8 @@ public interface LoginContract { void hideSoftInput(); + void showActionBar(boolean show); + } @PerActivity diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java index 2b6d173d..b547f3f6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java @@ -55,6 +55,7 @@ public class LoginPresenter extends BasePresenter @Override public void onStartAsync() { if (isViewAttached()) { + getView().showActionBar(false); getView().showLoginProgress(true); } } @@ -84,24 +85,25 @@ public class LoginPresenter extends BasePresenter public void onEndAsync(boolean success, Exception exception) { if (success) { getView().openMainActivity(); + return; } else if (exception instanceof BadCredentialsException) { getView().setErrorPassIncorrect(); getView().showSoftInput(); - getView().showLoginProgress(false); } else if (exception instanceof AccountPermissionException) { getView().setErrorSymbolRequired(); getView().showSoftInput(); - getView().showLoginProgress(false); } else { getView().onError(getRepository().getErrorLoginMessage(exception)); - getView().showLoginProgress(false); } + getView().showActionBar(true); + getView().showLoginProgress(false); } @Override public void onCanceledAsync() { if (isViewAttached()) { + getView().showActionBar(true); getView().showLoginProgress(false); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java index cc9b1dc8..3656e167 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java @@ -21,13 +21,12 @@ import io.github.wulkanowy.ui.base.BaseActivity; import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.settings.SettingsFragment; import io.github.wulkanowy.ui.main.timetable.TimetableFragment; public class MainActivity extends BaseActivity implements MainContract.View, AHBottomNavigation.OnTabSelectedListener, OnFragmentIsReadyListener { - private int initTabPosition = 0; - @BindView(R.id.main_activity_nav) AHBottomNavigation bottomNavigation; @@ -52,15 +51,10 @@ public class MainActivity extends BaseActivity implements MainContract.View, super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - initTabPosition = getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, initTabPosition); - getActivityComponent().inject(this); setButterKnife(ButterKnife.bind(this)); - presenter.onStart(this); - - initiationViewPager(); - initiationBottomNav(); + presenter.onStart(this, getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, -1)); } @Override @@ -102,48 +96,44 @@ public class MainActivity extends BaseActivity implements MainContract.View, presenter.onFragmentIsReady(); } - private void initiationBottomNav() { - bottomNavigation.addItem(new AHBottomNavigationItem( - getString(R.string.grades_text), - getResources().getDrawable(R.drawable.ic_menu_grade_26dp) - )); - bottomNavigation.addItem(new AHBottomNavigationItem( - getString(R.string.attendance_text), - getResources().getDrawable(R.drawable.ic_menu_attendance_24dp) - )); - bottomNavigation.addItem(new AHBottomNavigationItem( - getString(R.string.dashboard_text), - getResources().getDrawable(R.drawable.ic_menu_dashboard_24dp) - )); - bottomNavigation.addItem(new AHBottomNavigationItem( - getString(R.string.lessonplan_text), - getResources().getDrawable(R.drawable.ic_menu_timetable_24dp) - )); - bottomNavigation.addItem(new AHBottomNavigationItem( - getString(R.string.settings_text), - getResources().getDrawable(R.drawable.ic_menu_other_24dp) - )); + @Override + public void initiationBottomNav(int tabPosition) { + bottomNavigation.addItem(new AHBottomNavigationItem(getString(R.string.grades_text), + R.drawable.ic_menu_grade_26dp)); + + bottomNavigation.addItem(new AHBottomNavigationItem(getString(R.string.attendance_text), + R.drawable.ic_menu_attendance_24dp)); + + bottomNavigation.addItem(new AHBottomNavigationItem(getString(R.string.dashboard_text), + R.drawable.ic_menu_dashboard_24dp)); + + bottomNavigation.addItem(new AHBottomNavigationItem(getString(R.string.timetable_text), + R.drawable.ic_menu_timetable_24dp)); + + bottomNavigation.addItem(new AHBottomNavigationItem(getString(R.string.settings_text), + R.drawable.ic_menu_other_24dp)); bottomNavigation.setAccentColor(getResources().getColor(R.color.colorPrimary)); bottomNavigation.setInactiveColor(Color.BLACK); bottomNavigation.setBackgroundColor(getResources().getColor(R.color.colorBackgroundBottomNav)); bottomNavigation.setTitleState(AHBottomNavigation.TitleState.ALWAYS_SHOW); bottomNavigation.setOnTabSelectedListener(this); - bottomNavigation.setCurrentItem(initTabPosition); + bottomNavigation.setCurrentItem(tabPosition); bottomNavigation.setBehaviorTranslationEnabled(false); } - private void initiationViewPager() { + @Override + public void initiationViewPager(int tabPosition) { pagerAdapter.addFragment(new GradesFragment()); pagerAdapter.addFragment(new AttendanceFragment()); pagerAdapter.addFragment(new DashboardFragment()); pagerAdapter.addFragment(new TimetableFragment()); - pagerAdapter.addFragment(new DashboardFragment()); + pagerAdapter.addFragment(new SettingsFragment()); viewPager.setPagingEnabled(false); viewPager.setAdapter(pagerAdapter); viewPager.setOffscreenPageLimit(4); - viewPager.setCurrentItem(initTabPosition, false); + viewPager.setCurrentItem(tabPosition, false); } @Override diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java index cb1d5d7c..d4d8156b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java @@ -14,11 +14,17 @@ public interface MainContract { void showActionBar(); void hideActionBar(); + + void initiationViewPager(int tabPosition); + + void initiationBottomNav(int tabPosition); } @PerActivity interface Presenter extends BaseContract.Presenter { + void onStart(View view, int tabPositionIntent); + void onTabSelected(int position, boolean wasSelected); void onFragmentIsReady(); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java index 149e49e2..6a727b7a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java @@ -17,10 +17,21 @@ public class MainPresenter extends BasePresenter } @Override - public void onStart(MainContract.View view) { + public void onStart(MainContract.View view, int tabPositionIntent) { super.onStart(view); getView().showProgressBar(true); getView().hideActionBar(); + + int tabPosition; + + if (tabPositionIntent != -1) { + tabPosition = tabPositionIntent; + } else { + tabPosition = getRepository().getStartupTab(); + } + + getView().initiationBottomNav(tabPosition); + getView().initiationViewPager(tabPosition); } @Override @@ -32,11 +43,11 @@ public class MainPresenter extends BasePresenter @Override public void onFragmentIsReady() { - if (fragmentCount < 5) { + if (fragmentCount < 4) { fragmentCount++; } - if (fragmentCount == 5) { + if (fragmentCount == 4) { getView().showActionBar(); getView().showProgressBar(false); } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java index f8d3b302..545e300e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java @@ -17,20 +17,16 @@ public interface AttendanceContract { void setAdapterWithTabLayout(); - void setChildFragmentSelected(int position, boolean selected); - boolean isMenuVisible(); } @PerActivity interface Presenter extends BaseContract.Presenter { - void onFragmentVisible(boolean isVisible); - - void onTabSelected(int position); - - void onTabUnselected(int position); + void onFragmentActivated(boolean isVisible); void onStart(View view, OnFragmentIsReadyListener listener); + + void setRestoredPosition(int position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java index 82bb2520..4dd179b1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java @@ -20,7 +20,9 @@ import io.github.wulkanowy.ui.base.BaseFragment; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; import io.github.wulkanowy.ui.main.TabsData; -public class AttendanceFragment extends BaseFragment implements AttendanceContract.View, TabLayout.OnTabSelectedListener { +public class AttendanceFragment extends BaseFragment implements AttendanceContract.View { + + private static final String CURRENT_ITEM_KEY = "CurrentItem"; @BindView(R.id.attendance_fragment_viewpager) ViewPager viewPager; @@ -45,6 +47,10 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra component.inject(this); setButterKnife(ButterKnife.bind(this, view)); presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + + if (savedInstanceState != null) { + presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY)); + } } return view; @@ -54,25 +60,10 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (presenter != null) { - presenter.onFragmentVisible(menuVisible); + presenter.onFragmentActivated(menuVisible); } } - @Override - public void onTabSelected(TabLayout.Tab tab) { - presenter.onTabSelected(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - presenter.onTabUnselected(tab.getPosition()); - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - //do nothing - } - @Override public void setTabDataToAdapter(TabsData tabsData) { pagerAdapter.setTabsData(tabsData); @@ -81,14 +72,7 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra @Override public void setAdapterWithTabLayout() { viewPager.setAdapter(pagerAdapter); - tabLayout.setupWithViewPager(viewPager); - tabLayout.addOnTabSelectedListener(this); - } - - @Override - public void setChildFragmentSelected(int position, boolean selected) { - ((AttendanceTabFragment) pagerAdapter.getItem(position)).setSelected(selected); } @Override @@ -109,6 +93,12 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra } } + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem()); + super.onSaveInstanceState(outState); + } + @Override public void onDestroyView() { presenter.onDestroy(); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceHeaderItem.java index b103a007..a055156d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceHeaderItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceHeaderItem.java @@ -1,5 +1,8 @@ package io.github.wulkanowy.ui.main.attendance; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -16,6 +19,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.ExpandableViewHolder; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.Day; @@ -55,12 +59,13 @@ public class AttendanceHeaderItem } @Override - public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { return new HeaderViewHolder(view, adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, + int position, List payloads) { holder.onBind(day, getSubItems()); } @@ -87,10 +92,16 @@ public class AttendanceHeaderItem @BindColor(R.color.free_day) int backgroundFreeDay; + @BindColor(android.R.color.black) + int black; + + private Context context; + HeaderViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); view.setOnClickListener(this); ButterKnife.bind(this, view); + context = view.getContext(); } void onBind(Day item, List subItems) { @@ -103,14 +114,30 @@ public class AttendanceHeaderItem description.setVisibility(numberOfHours > 0 ? View.VISIBLE : View.INVISIBLE); alert.setVisibility(isSubItemsHasChanges(subItems) ? View.VISIBLE : View.INVISIBLE); freeName.setVisibility(subItems.isEmpty() ? View.VISIBLE : View.INVISIBLE); + setInactiveHeader(item.getAttendanceLessons().isEmpty()); + } - if (item.getAttendanceLessons().isEmpty()) { - ((FrameLayout) getContentView()).setForeground(null); + + private void setInactiveHeader(boolean inactive) { + ((FrameLayout) getContentView()).setForeground(inactive ? null : getSelectableDrawable()); + dayName.setTextColor(inactive ? secondaryColor : black); + + if (inactive) { getContentView().setBackgroundColor(backgroundFreeDay); - dayName.setTextColor(secondaryColor); + } else { + getContentView().setBackgroundDrawable(context.getResources() + .getDrawable(R.drawable.ic_border)); } } + private Drawable getSelectableDrawable() { + int[] attrs = new int[]{R.attr.selectableItemBackground}; + TypedArray typedArray = context.obtainStyledAttributes(attrs); + Drawable drawable = typedArray.getDrawable(0); + typedArray.recycle(); + return drawable; + } + private int countNotPresentHours(List subItems) { int i = 0; for (AttendanceSubItem subItem : subItems) { @@ -118,17 +145,16 @@ public class AttendanceHeaderItem i++; } } - return i; } private boolean isSubItemsHasChanges(List subItems) { for (AttendanceSubItem subItem : subItems) { - if (subItem.getLesson().getIsAbsenceUnexcused() || subItem.getLesson().getIsUnexcusedLateness()) { + if (subItem.getLesson().getIsAbsenceUnexcused() || subItem.getLesson() + .getIsUnexcusedLateness()) { return true; } } - return false; } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java index 73dd44ca..bad68e16 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java @@ -24,7 +24,7 @@ public class AttendancePresenter extends BasePresenter private OnFragmentIsReadyListener listener; - private int positionToScroll; + private int positionToScroll = 0; private boolean isFirstSight = false; @@ -45,7 +45,10 @@ public class AttendancePresenter extends BasePresenter if (dates.isEmpty()) { dates = TimeUtils.getMondaysFromCurrentSchoolYear(); } - positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + + if (positionToScroll == 0) { + positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + } if (!isFirstSight) { isFirstSight = true; @@ -57,22 +60,12 @@ public class AttendancePresenter extends BasePresenter } @Override - public void onFragmentVisible(boolean isVisible) { + public void onFragmentActivated(boolean isVisible) { if (isVisible) { getView().setActivityTitle(); } } - @Override - public void onTabSelected(int position) { - getView().setChildFragmentSelected(position, true); - } - - @Override - public void onTabUnselected(int position) { - getView().setChildFragmentSelected(position, false); - } - @Override public void onDoInBackgroundLoading() throws Exception { for (String date : dates) { @@ -97,6 +90,11 @@ public class AttendancePresenter extends BasePresenter } } + @Override + public void setRestoredPosition(int position) { + this.positionToScroll = position; + } + @Override public void onDestroy() { isFirstSight = false; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceSubItem.java index 6da35555..8249a513 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceSubItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceSubItem.java @@ -16,6 +16,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.FlexibleViewHolder; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; @@ -60,12 +61,13 @@ class AttendanceSubItem } @Override - public AttendanceSubItem.SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { - return new AttendanceSubItem.SubItemViewHolder(view, adapter); + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, AttendanceSubItem.SubItemViewHolder holder, int position, List payloads) { + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, + int position, List payloads) { holder.onBind(lesson); } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabContract.java index 1bac005d..dcb16bf8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabContract.java @@ -21,12 +21,10 @@ public interface AttendanceTabContract { interface Presenter extends BaseContract.Presenter { - void onFragmentSelected(boolean isSelected); + void onFragmentActivated(boolean isSelected); void setArgumentDate(String date); - void onStart(AttendanceTabContract.View view, boolean isPrimary); - void onRefresh(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabFragment.java index 90313daa..e5e65bb9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabFragment.java @@ -27,12 +27,6 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab private static final String ARGUMENT_KEY = "date"; - private static final String SAVED_KEY = "isSelected"; - - private boolean isPrimary = false; - - private boolean isSelected = false; - @BindView(R.id.attendance_tab_fragment_recycler) RecyclerView recyclerView; @@ -51,6 +45,8 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab @Inject FlexibleAdapter adapter; + private boolean isFragmentVisible = false; + public static AttendanceTabFragment newInstance(String date) { AttendanceTabFragment fragmentTab = new AttendanceTabFragment(); @@ -61,15 +57,6 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab return fragmentTab; } - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null) { - isSelected = savedInstanceState.getBoolean(SAVED_KEY, isSelected); - } - } - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -84,7 +71,8 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); } - presenter.onStart(this, isPrimary); + presenter.onStart(this); + presenter.onFragmentActivated(isFragmentVisible); } return view; } @@ -111,10 +99,9 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); - if (presenter != null && getView() != null) { - presenter.onFragmentSelected(isSelected); - } else if (isSelected) { - isPrimary = true; + isFragmentVisible = menuVisible; + if (presenter != null) { + presenter.onFragmentActivated(menuVisible); } } @@ -125,7 +112,7 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab @Override public void onRefreshSuccess() { - onError(R.string.timetable_refresh_success); + onError(R.string.sync_completed); } @Override @@ -143,10 +130,6 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); } - public void setSelected(boolean selected) { - isSelected = selected; - } - @Override public void onError(String message) { if (getActivity() != null) { @@ -155,15 +138,8 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(SAVED_KEY, isSelected); - super.onSaveInstanceState(outState); - } - @Override public void onDestroyView() { - isPrimary = false; presenter.onDestroy(); super.onDestroyView(); } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabPresenter.java index d0d448ca..1db29cb1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabPresenter.java @@ -33,21 +33,22 @@ public class AttendanceTabPresenter extends BasePresenter subItems) { boolean isRead = true; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java index 8bdad12a..4ea950de 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java @@ -109,6 +109,7 @@ public class GradesPresenter extends BasePresenter headerItems = new ArrayList<>(); for (Subject subject : subjectList) { + subject.resetGradeList(); List gradeList = subject.getGradeList(); if (!gradeList.isEmpty()) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java index 102a232f..e8bd71ca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java @@ -37,7 +37,7 @@ public class GradesSubItem return grade; } - public void setSubjectAlertImage(View subjectAlertImage) { + void setSubjectAlertImage(View subjectAlertImage) { this.subjectAlertImage = subjectAlertImage; } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/settings/SettingsFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/settings/SettingsFragment.java new file mode 100644 index 00000000..d1483123 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/settings/SettingsFragment.java @@ -0,0 +1,70 @@ +package io.github.wulkanowy.ui.main.settings; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v7.preference.PreferenceFragmentCompat; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.services.SyncJob; + +public class SettingsFragment extends PreferenceFragmentCompat + implements SharedPreferences.OnSharedPreferenceChangeListener { + + public static final String SHARED_KEY_START_TAB = "startup_tab"; + + public static final String SHARED_KEY_SERVICES_ENABLE = "services_enable"; + + public static final String SHARED_KEY_NOTIFY_ENABLE = "notify_enable"; + + public static final String SHARED_KEY_SERVICES_INTERVAL = "services_interval"; + + public static final String SHARED_KEY_SERVICES_MOBILE_DISABLED = "services_disable_mobile"; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(SHARED_KEY_SERVICES_ENABLE) || key.equals(SHARED_KEY_SERVICES_INTERVAL) + || key.equals(SHARED_KEY_SERVICES_MOBILE_DISABLED)) { + launchServices(sharedPreferences.getBoolean(SHARED_KEY_SERVICES_ENABLE, true), + sharedPreferences); + } + } + + private void launchServices(boolean start, SharedPreferences sharedPref) { + if (start) { + int newInterval = Integer.parseInt(sharedPref.getString(SHARED_KEY_SERVICES_INTERVAL, "60")); + boolean useOnlyWifi = sharedPref.getBoolean(SHARED_KEY_SERVICES_MOBILE_DISABLED, false); + + SyncJob.stop(getContext()); + SyncJob.start(getContext(), newInterval, useOnlyWifi); + } else { + SyncJob.stop(getContext()); + } + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getActivity().setTitle(R.string.settings_text); + } + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java index eaad0505..aa4576bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java @@ -17,20 +17,16 @@ public interface TimetableContract { void setAdapterWithTabLayout(); - void setChildFragmentSelected(int position, boolean selected); - boolean isMenuVisible(); } @PerActivity interface Presenter extends BaseContract.Presenter { - void onFragmentVisible(boolean isVisible); - - void onTabSelected(int position); - - void onTabUnselected(int position); + void onFragmentActivated(boolean isVisible); void onStart(View view, OnFragmentIsReadyListener listener); + + void setRestoredPosition(int position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java index 8bef4fb2..5d9d96bf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java @@ -20,7 +20,9 @@ import io.github.wulkanowy.ui.base.BaseFragment; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; import io.github.wulkanowy.ui.main.TabsData; -public class TimetableFragment extends BaseFragment implements TimetableContract.View, TabLayout.OnTabSelectedListener { +public class TimetableFragment extends BaseFragment implements TimetableContract.View { + + private static final String CURRENT_ITEM_KEY = "CurrentItem"; @BindView(R.id.timetable_fragment_viewpager) ViewPager viewPager; @@ -44,6 +46,10 @@ public class TimetableFragment extends BaseFragment implements TimetableContract component.inject(this); setButterKnife(ButterKnife.bind(this, view)); presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + + if (savedInstanceState != null) { + presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY)); + } } return view; } @@ -52,25 +58,10 @@ public class TimetableFragment extends BaseFragment implements TimetableContract public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (presenter != null) { - presenter.onFragmentVisible(menuVisible); + presenter.onFragmentActivated(menuVisible); } } - @Override - public void onTabSelected(TabLayout.Tab tab) { - presenter.onTabSelected(tab.getPosition()); - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) { - presenter.onTabUnselected(tab.getPosition()); - } - - @Override - public void onTabReselected(TabLayout.Tab tab) { - //do nothing - } - @Override public void setTabDataToAdapter(TabsData tabsData) { pagerAdapter.setTabsData(tabsData); @@ -79,14 +70,7 @@ public class TimetableFragment extends BaseFragment implements TimetableContract @Override public void setAdapterWithTabLayout() { viewPager.setAdapter(pagerAdapter); - tabLayout.setupWithViewPager(viewPager); - tabLayout.addOnTabSelectedListener(this); - } - - @Override - public void setChildFragmentSelected(int position, boolean selected) { - ((TimetableTabFragment) pagerAdapter.getItem(position)).setSelected(selected); } @Override @@ -96,7 +80,7 @@ public class TimetableFragment extends BaseFragment implements TimetableContract @Override public void setActivityTitle() { - setTitle(getString(R.string.lessonplan_text)); + setTitle(getString(R.string.timetable_text)); } @Override @@ -107,6 +91,12 @@ public class TimetableFragment extends BaseFragment implements TimetableContract } } + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem()); + super.onSaveInstanceState(outState); + } + @Override public void onDestroyView() { presenter.onDestroy(); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java index 39a8f012..c904d931 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java @@ -1,5 +1,8 @@ package io.github.wulkanowy.ui.main.timetable; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -16,6 +19,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.ExpandableViewHolder; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.Day; @@ -55,12 +59,13 @@ public class TimetableHeaderItem } @Override - public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { return new HeaderViewHolder(view, adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, + int position, List payloads) { holder.onBind(day, getSubItems()); } @@ -84,10 +89,16 @@ public class TimetableHeaderItem @BindColor(R.color.free_day) int backgroundFreeDay; + @BindColor(android.R.color.black) + int black; + + private Context context; + HeaderViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); view.setOnClickListener(this); ButterKnife.bind(this, view); + context = view.getContext(); } void onBind(Day item, List subItems) { @@ -96,14 +107,29 @@ public class TimetableHeaderItem alert.setVisibility(isSubItemNewMovedInOrChanged(subItems) ? View.VISIBLE : View.INVISIBLE); freeName.setVisibility(item.getIsFreeDay() ? View.VISIBLE : View.INVISIBLE); freeName.setText(item.getFreeDayName()); + setInactiveHeader(item.getIsFreeDay()); + } - if (item.getIsFreeDay()) { - ((FrameLayout) getContentView()).setForeground(null); + private void setInactiveHeader(boolean inactive) { + ((FrameLayout) getContentView()).setForeground(inactive ? null : getSelectableDrawable()); + dayName.setTextColor(inactive ? secondaryColor : black); + + if (inactive) { getContentView().setBackgroundColor(backgroundFreeDay); - dayName.setTextColor(secondaryColor); + } else { + getContentView().setBackgroundDrawable(context.getResources() + .getDrawable(R.drawable.ic_border)); } } + private Drawable getSelectableDrawable() { + int[] attrs = new int[]{R.attr.selectableItemBackground}; + TypedArray typedArray = context.obtainStyledAttributes(attrs); + Drawable drawable = typedArray.getDrawable(0); + typedArray.recycle(); + return drawable; + } + private boolean isSubItemNewMovedInOrChanged(List subItems) { boolean isAlertActive = false; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java index e816a190..080ec90b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java @@ -24,7 +24,7 @@ public class TimetablePresenter extends BasePresenter private OnFragmentIsReadyListener listener; - private int positionToScroll; + private int positionToScroll = 0; private boolean isFirstSight = false; @@ -45,7 +45,10 @@ public class TimetablePresenter extends BasePresenter if (dates.isEmpty()) { dates = TimeUtils.getMondaysFromCurrentSchoolYear(); } - positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + + if (positionToScroll == 0) { + positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + } if (!isFirstSight) { isFirstSight = true; @@ -57,22 +60,12 @@ public class TimetablePresenter extends BasePresenter } @Override - public void onFragmentVisible(boolean isVisible) { + public void onFragmentActivated(boolean isVisible) { if (isVisible) { getView().setActivityTitle(); } } - @Override - public void onTabSelected(int position) { - getView().setChildFragmentSelected(position, true); - } - - @Override - public void onTabUnselected(int position) { - getView().setChildFragmentSelected(position, false); - } - @Override public void onDoInBackgroundLoading() throws Exception { for (String date : dates) { @@ -84,7 +77,6 @@ public class TimetablePresenter extends BasePresenter @Override public void onCanceledLoadingAsync() { //do nothing - } @Override @@ -97,6 +89,11 @@ public class TimetablePresenter extends BasePresenter } } + @Override + public void setRestoredPosition(int position) { + this.positionToScroll = position; + } + @Override public void onDestroy() { isFirstSight = false; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java index ca82f783..3d85772a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java @@ -17,6 +17,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.FlexibleViewHolder; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; @@ -27,7 +28,7 @@ public class TimetableSubItem private TimetableLesson lesson; - public TimetableSubItem(TimetableHeaderItem header, TimetableLesson lesson) { + TimetableSubItem(TimetableHeaderItem header, TimetableLesson lesson) { super(header); this.lesson = lesson; } @@ -62,12 +63,13 @@ public class TimetableSubItem } @Override - public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { return new SubItemViewHolder(view, adapter); } @Override - public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, + int position, List payloads) { holder.onBind(lesson); } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java index d17e6e55..0d6bdf2a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java @@ -23,12 +23,10 @@ public interface TimetableTabContract { interface Presenter extends BaseContract.Presenter { - void onFragmentSelected(boolean isSelected); + void onFragmentActivated(boolean isSelected); void setArgumentDate(String date); - void onStart(View view, boolean isPrimary); - void onRefresh(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java index 65841247..53d9a67b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java @@ -28,12 +28,6 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo private static final String ARGUMENT_KEY = "date"; - private static final String SAVED_KEY = "isSelected"; - - private boolean isPrimary = false; - - private boolean isSelected = false; - @BindView(R.id.timetable_tab_fragment_recycler) RecyclerView recyclerView; @@ -55,6 +49,8 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo @Inject FlexibleAdapter adapter; + private boolean isFragmentVisible = false; + public static TimetableTabFragment newInstance(String date) { TimetableTabFragment fragmentTab = new TimetableTabFragment(); @@ -65,14 +61,6 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo return fragmentTab; } - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - isSelected = savedInstanceState.getBoolean(SAVED_KEY, isSelected); - } - } - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { @@ -86,8 +74,8 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo if (getArguments() != null) { presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); } - - presenter.onStart(this, isPrimary); + presenter.onStart(this); + presenter.onFragmentActivated(isFragmentVisible); } return view; } @@ -114,10 +102,9 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); - if (presenter != null && getView() != null) { - presenter.onFragmentSelected(isSelected); - } else if (isSelected) { - isPrimary = true; + isFragmentVisible = menuVisible; + if (presenter != null) { + presenter.onFragmentActivated(menuVisible); } } @@ -133,7 +120,7 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo @Override public void onRefreshSuccess() { - onError(R.string.timetable_refresh_success); + onError(R.string.sync_completed); } @Override @@ -151,10 +138,6 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); } - public void setSelected(boolean selected) { - isSelected = selected; - } - @Override public void onError(String message) { if (getActivity() != null) { @@ -163,15 +146,8 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(SAVED_KEY, isSelected); - super.onSaveInstanceState(outState); - } - @Override public void onDestroyView() { - isPrimary = false; presenter.onDestroy(); super.onDestroyView(); } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java index e5eb499f..dcfe2612 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java @@ -36,21 +36,22 @@ public class TimetableTabPresenter extends BasePresenter implements SplashContract.Presenter { @@ -19,17 +18,17 @@ public class SplashPresenter extends BasePresenter @Override public void onStart(@NonNull SplashContract.View activity) { super.onStart(activity); - getView().startSyncService(); + getView().cancelNotifications(); + + if (getRepository().isServicesEnable()) { + getView().startSyncService(getRepository().getServicesInterval(), + getRepository().isMobileDisable()); + } if (getRepository().getCurrentUserId() == 0) { getView().openLoginActivity(); } else { - try { - getRepository().initLastUser(); - getView().openMainActivity(); - } catch (Exception e) { - LogUtils.error("An error occurred when the application was started", e); - } + getView().openMainActivity(); } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java index a22e4d8b..bc17427e 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java +++ b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java @@ -6,6 +6,7 @@ import java.util.List; import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Diary; import io.github.wulkanowy.data.db.dao.entities.Grade; import io.github.wulkanowy.data.db.dao.entities.Subject; import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; @@ -17,8 +18,22 @@ public final class DataObjectConverter { throw new IllegalStateException("Utility class"); } - public static List subjectsToSubjectEntities(List subjectList) { + public static List diariesToDiaryEntities(List diaryList) { + List diaryEntityList = new ArrayList<>(); + for (io.github.wulkanowy.api.Diary diary : diaryList) { + Diary diaryEntity = new Diary() + .setValue(diary.getId()) + .setName(diary.getName()) + .setStudentId(diary.getStudentId()) + .setIsCurrent(diary.isCurrent()); + diaryEntityList.add(diaryEntity); + } + + return diaryEntityList; + } + + public static List subjectsToSubjectEntities(List subjectList) { List subjectEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.grades.Subject subject : subjectList) { @@ -33,7 +48,6 @@ public final class DataObjectConverter { } public static List gradesToGradeEntities(List gradeList) { - List gradeEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.grades.Grade grade : gradeList) { @@ -67,7 +81,6 @@ public final class DataObjectConverter { public static List daysToDaysEntities(List dayList) { - List dayEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Day day : dayList) { @@ -110,7 +123,6 @@ public final class DataObjectConverter { } public static List lessonsToTimetableLessonsEntities(List lessonList) { - List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) { @@ -120,7 +132,6 @@ public final class DataObjectConverter { } public static List lessonsToAttendanceLessonsEntities(List lessonList) { - List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java index 717ed42a..a7162d15 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java @@ -1,8 +1,8 @@ package io.github.wulkanowy.utils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; -import org.joda.time.LocalDate; +import org.threeten.bp.DayOfWeek; +import org.threeten.bp.LocalDate; +import org.threeten.bp.format.DateTimeFormatter; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -13,14 +13,14 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; -import static io.github.wulkanowy.utils.AppConstant.DATE_PATTERN; - public final class TimeUtils { private static final long TICKS_AT_EPOCH = 621355968000000000L; private static final long TICKS_PER_MILLISECOND = 10000; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(AppConstant.DATE_PATTERN); + private TimeUtils() { throw new IllegalStateException("Utility class"); } @@ -33,7 +33,7 @@ public final class TimeUtils { } public static long getNetTicks(String dateString) throws ParseException { - return getNetTicks(dateString, DATE_PATTERN); + return getNetTicks(dateString, AppConstant.DATE_PATTERN); } public static long getNetTicks(String dateString, String dateFormat) throws ParseException { @@ -49,12 +49,12 @@ public final class TimeUtils { } public static List getMondaysFromCurrentSchoolYear() { - LocalDate startDate = new LocalDate(getCurrentSchoolYear(), 9, 1); - LocalDate endDate = new LocalDate(getCurrentSchoolYear() + 1, 8, 31); + LocalDate startDate = LocalDate.of(getCurrentSchoolYear(), 9, 1); + LocalDate endDate = LocalDate.of(getCurrentSchoolYear() + 1, 8, 31); List dateList = new ArrayList<>(); - LocalDate thisMonday = startDate.withDayOfWeek(DateTimeConstants.MONDAY); + LocalDate thisMonday = startDate.with(DayOfWeek.MONDAY); if (startDate.isAfter(thisMonday)) { startDate = thisMonday.plusWeeks(1); @@ -63,27 +63,27 @@ public final class TimeUtils { } while (startDate.isBefore(endDate)) { - dateList.add(startDate.toString(DATE_PATTERN)); + dateList.add(startDate.format(formatter)); startDate = startDate.plusWeeks(1); } return dateList; } public static int getCurrentSchoolYear() { - DateTime dateTime = new DateTime(); - return dateTime.getMonthOfYear() <= 8 ? dateTime.getYear() - 1 : dateTime.getYear(); + LocalDate localDate = LocalDate.now(); + return localDate.getMonthValue() <= 8 ? localDate.getYear() - 1 : localDate.getYear(); } public static String getDateOfCurrentMonday(boolean normalize) { - DateTime currentDate = new DateTime(); + LocalDate currentDate = LocalDate.now(); - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY && normalize) { + if (currentDate.getDayOfWeek() == DayOfWeek.SATURDAY && normalize) { currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY && normalize) { + } else if (currentDate.getDayOfWeek() == DayOfWeek.SUNDAY && normalize) { currentDate = currentDate.plusDays(1); } else { - currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); + currentDate = currentDate.with(DayOfWeek.MONDAY); } - return currentDate.toString(DATE_PATTERN); + return currentDate.format(formatter); } } diff --git a/app/src/main/res/drawable-hdpi/ic_notify_grade.png b/app/src/main/res/drawable-hdpi/ic_notify_grade.png new file mode 100644 index 00000000..ea5b85c8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notify_grade.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_notify.png b/app/src/main/res/drawable-hdpi/ic_stat_notify.png deleted file mode 100644 index 34544980..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_stat_notify.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_notify_grade.png b/app/src/main/res/drawable-mdpi/ic_notify_grade.png new file mode 100644 index 00000000..64fd285d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notify_grade.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_notify.png b/app/src/main/res/drawable-mdpi/ic_stat_notify.png deleted file mode 100644 index 5564049a..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_stat_notify.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_notify_grade.png b/app/src/main/res/drawable-xhdpi/ic_notify_grade.png new file mode 100644 index 00000000..83b2c443 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notify_grade.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_notify.png b/app/src/main/res/drawable-xhdpi/ic_stat_notify.png deleted file mode 100644 index fabb8760..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_stat_notify.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_notify_grade.png b/app/src/main/res/drawable-xxhdpi/ic_notify_grade.png new file mode 100644 index 00000000..cba48ec2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notify_grade.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_notify.png b/app/src/main/res/drawable-xxhdpi/ic_stat_notify.png deleted file mode 100644 index c1d78d46..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_stat_notify.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_notify.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_notify.png deleted file mode 100644 index ee89e601..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_stat_notify.png and /dev/null differ diff --git a/app/src/main/res/values-pl/prefernces_array.xml b/app/src/main/res/values-pl/prefernces_array.xml new file mode 100644 index 00000000..4e66a373 --- /dev/null +++ b/app/src/main/res/values-pl/prefernces_array.xml @@ -0,0 +1,12 @@ + + + + 10 minut + 30 minut + 1 godzinę + 2 godziny + 6 godzin + 12 godzin + 24 godzin + + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9642a53d..e44fd960 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -33,13 +33,12 @@ Dashboard Oceny Frekwencja - Plan lekcji + Plan lekcji Ustawienia Ta część aplikacji jest w budowie Brak ocen Brak połączenia z internetem - Przerwa techniczna. Spróbuj ponownie później Szyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone Wersja %1$s "Podczas odświeżania zawartości wystąpił błąd. " @@ -59,7 +58,7 @@ Godziny Zmiany - Poprawnie zsynchronizowano + Synchronizacja zakończona Szczegóły Zamknij @@ -114,4 +113,15 @@ %1$d nieobecności %1$d nieobecności + + Widok + Domyślny widok + Powiadomienia + Pokazuj powiadomienia + Usługi + Włącz odświeżanie danych w tle + Interwał między odświeżaniem danych + Synchronizacja tylko przez WiFi + + Nowe oceny diff --git a/app/src/main/res/values/prefernces_array.xml b/app/src/main/res/values/prefernces_array.xml new file mode 100644 index 00000000..8bf159f3 --- /dev/null +++ b/app/src/main/res/values/prefernces_array.xml @@ -0,0 +1,33 @@ + + + + @string/grades_text + @string/attendance_text + @string/dashboard_text + @string/timetable_text + + + 0 + 1 + 2 + 3 + + + 10 minutes + 30 minutes + 1 hour + 2 hours + 6 hours + 12 hours + 24 hours + + + 10 + 30 + 60 + 120 + 360 + 720 + 1440 + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 315fee4f..05355477 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,7 +33,7 @@ Dashboard Grades Attendance - Timetable + Timetable Settings This section of app is under construction. No grades @@ -58,7 +58,7 @@ Times Changes - Successful synchronisation + Synchronization completed Details Close @@ -73,7 +73,6 @@ Average: %1$.2f No average No lesson in this week - Technical break Room %s @@ -110,4 +109,15 @@ %1$d absences %1$d absences + + Default view after startup + View + Notifications + Show the notifications + Services + Enable background data refreshing + Interval between data refreshing + Synchronization via WiFi only + + New grades diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ed5c249d..fff8e94c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -13,6 +13,7 @@ @android:color/primary_text_dark @android:color/primary_text_dark @android:color/white + @style/PreferenceThemeOverlay.v14.Material