diff --git a/.circleci/config.yml b/.circleci/config.yml index 841ff9e0..387c5edd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,9 +31,18 @@ jobs: - run: name: Setup environment command: ./gradlew dependencies --no-daemon --stacktrace --console=plain -PdisablePreDex || true + - run: + name: Decrypt keys + command: | + openssl aes-256-cbc -d -in ./app/key-encrypted.p12 -k $ENCRYPT_KEY >> ./app/key.p12 + openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks - run: name: Initial build command: ./gradlew build assembleDebug -x test -x lint -x fabricGenerateResourcesRelease --no-daemon --stacktrace --console=plain -PdisablePreDex + - run: + name: Clear keys + command: | + rm ./app/key.p12 ./app/upload-key.jks - store_artifacts: path: ./app/build/outputs/apk/ destination: apks/ @@ -78,11 +87,8 @@ jobs: name: Upload unit code coverage to codecov command: bash <(curl -s https://codecov.io/bash) -F app - store_artifacts: - path: ./app/build/reports/tests/ - destination: tests_reports/ - - store_artifacts: - path: ./app/build/reports/jacoco/jacocoTestDebugUnitTestReport/ - destination: coverage_reports/ + path: ./app/build/reports/ + destination: reports/ - store_test_results: path: ./app/build/test-results - persist_to_workspace: @@ -103,11 +109,8 @@ jobs: name: Upload code coverage to codecov command: bash <(curl -s https://codecov.io/bash) -F api - store_artifacts: - path: ./api/build/reports/tests/ - destination: tests_reports/ - - store_artifacts: - path: ./api/build/reports/jacoco/test/ - destination: coverage_reports/ + path: ./api/build/reports/ + destination: reports/ - store_test_results: path: ./api/build/test-results - persist_to_workspace: @@ -121,11 +124,14 @@ jobs: - *attach_workspace - run: name: Setup emulator - command: sdkmanager "system-images;android-19;google_apis;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-19;google_apis;armeabi-v7a" + command: sdkmanager "system-images;android-16;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-16;default;armeabi-v7a" - run: 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: | @@ -169,30 +175,69 @@ jobs: name: Run sonarqube runner command: ./gradlew -x test -x lint sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_KEY -Dsonar.branch.name=$CIRCLE_BRANCH --no-daemon --stacktrace --console=plain -PdisablePreDex + deploy: + <<: *container_config + steps: + - *attach_workspace + - restore_cache: + <<: *general_cache_key + - run: + name: Decrypt keys + command: | + openssl aes-256-cbc -d -in ./app/key-encrypted.p12 -k $ENCRYPT_KEY >> ./app/key.p12 + openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks + - run: + name: Publish release + command: ./gradlew publishRelease --no-daemon --stacktrace --console=plain -PdisablePreDex + workflows: version: 2 - build_check_tests: + build-test-deploy: jobs: - - build + - build: + filters: + tags: + only: /.*/ - lint: + filters: + tags: + only: /.*/ requires: - build - app-test: + filters: + tags: + only: /.*/ requires: - build - api-test: + filters: + tags: + only: /.*/ requires: - build - instrumented: - requires: - - build + filters: + tags: + only: /.*/ requires: - build - sonarcube: + filters: + tags: + only: /.*/ requires: - build - lint - app-test - api-test - instrumented + - deploy: + requires: + - instrumented + filters: + tags: + only: /\d+\.\d+\.\d+/ + branches: + ignore: /.*/ diff --git a/.gitignore b/.gitignore index 3b524b65..8ad04ebf 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 @@ -42,3 +44,7 @@ local.properties .Trashes ehthumbs.db Thumbs.db +.idea/codeStyles/ +.idea/caches/ +./app/key.p12 +./app/upload-key.jks 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..ee11a9c6 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,12 @@ [![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/) -[![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) +[![Scrutinizer](https://img.shields.io/scrutinizer/g/wulkanowy/wulkanowy.svg?style=flat-square)](https://scrutinizer-ci.com/g/wulkanowy/wulkanowy/?branch=master) [![Bintray](https://img.shields.io/bintray/v/wulkanowy/wulkanowy/api.svg?style=flat-square)](https://bintray.com/wulkanowy/wulkanowy/api) +[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) -[Pobierz wersję rozwojową](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/app-debug-bitrise-signed.apk) +[Pobierz wersję beta](https://play.google.com/store/apps/details?id=io.github.wulkanowy&utm_source=vcs) -Wulkanowy to aplikacja na androida polepszająca wygodę używania dziennika UONET+. +[Pobierz wersję DEV](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/0) + +Androidowy klient dziennika VULCAN UONET+. diff --git a/api/build.gradle b/api/build.gradle index ebaca4c8..5e5eeee3 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java-library' +apply plugin: 'kotlin' apply plugin: 'org.sonarqube' apply plugin: 'jacoco' apply plugin: 'com.jfrog.bintray' @@ -28,12 +29,15 @@ 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" + implementation "org.slf4j:slf4j-api:$slf4jApi" - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.13.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + 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..c6360d04 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Client.java +++ b/api/src/main/java/io/github/wulkanowy/api/Client.java @@ -3,12 +3,16 @@ package io.github.wulkanowy.api; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import io.github.wulkanowy.api.generic.School; import io.github.wulkanowy.api.login.Login; public class Client { @@ -21,18 +25,23 @@ public class Client { private String password; - private String symbol = "Default"; + private String symbol; - private Login login; + private String schoolId; - private Date lastSuccessRequest = new Date(); + private List schools; + + private Date lastSuccessRequest; private Cookies cookies = new Cookies(); - Client(String email, String password, String symbol) { + private static final Logger logger = LoggerFactory.getLogger(Client.class); + + Client(String email, String password, String symbol, String schoolId) { this.email = email; this.password = password; this.symbol = symbol; + this.schoolId = schoolId; setFullEndpointInfo(email); } @@ -46,7 +55,11 @@ public class Client { String[] url = creds[0].split("://"); protocol = url[0]; - host = url[1]; + String[] path = url[1].split("/"); + host = path[0]; + if (path.length > 1) { + symbol = path[1]; + } email = creds[2]; } } @@ -56,23 +69,22 @@ public class Client { return; } - this.symbol = getLogin().login(email, password, symbol); + logger.info("Not logged. Login..."); + + clearCookies(); + new Login(this).login(email, password, symbol); + lastSuccessRequest = new Date(); + + logger.info("Login successful on {} at {}", getHost(), new Date()); } private boolean isLoggedIn() { - return getCookies().size() > 0 && - 29 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime()); + logger.trace("Last success request: {}", lastSuccessRequest); + logger.trace("Cookies: {}", getCookies().size()); - } + return getCookies().size() > 0 && lastSuccessRequest != null && + 5 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime()); - Login getLogin() { - if (null != login) { - return login; - } - - login = new Login(this); - - return login; } public String getSymbol() { @@ -87,32 +99,79 @@ public class Client { return cookies.getItems(); } - String getHost() { + public void clearCookies() { + cookies = new Cookies(); + } + + public String getHost() { return host; } + public void setSchools(List schools) { + this.schools = schools; + this.schoolId = schools.get(0).getId(); + } + + public List getSchools() throws IOException, VulcanException { + login(); + return schools; + } + + public String getSchoolId() throws IOException, VulcanException { + return schoolId != null ? schoolId : getSchools().get(0).getId(); + } + String getFilledUrl(String url) { return url .replace("{schema}", protocol) - .replace("{host}", host.replace(":", "%253A")) - .replace("{symbol}", symbol); + .replace("{host}", host) + .replace("{symbol}", symbol) + .replace("{ID}", schoolId != null ? schoolId : ""); } - Document getPageByUrl(String url) throws IOException, VulcanException { - login(); + public Document getPageByUrl(String url) throws IOException, VulcanException { + return getPageByUrl(url, true, null); + } - Connection.Response response = Jsoup.connect(getFilledUrl(url)) + public Document getPageByUrl(String url, boolean loginBefore) throws IOException, VulcanException { + return getPageByUrl(url, loginBefore, null); + } + + public synchronized Document getPageByUrl(String url, boolean loginBefore, Map cookies) throws IOException, VulcanException { + if (loginBefore) { + login(); + } + + if (null != cookies) { + this.cookies.addItems(cookies); + } + + url = getFilledUrl(url); + + logger.debug("GET {}", url); + + Connection.Response response = Jsoup.connect(url) .followRedirects(true) .cookies(getCookies()) .execute(); this.cookies.addItems(response.cookies()); - return checkForErrors(response.parse()); + Document doc = checkForErrors(response.parse(), response.statusCode()); + + if (loginBefore) { + lastSuccessRequest = new Date(); + } + + return doc; } - public Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException { - Connection connection = Jsoup.connect(getFilledUrl(url)); + public synchronized Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException { + url = getFilledUrl(url); + + logger.debug("POST {}", url); + + Connection connection = Jsoup.connect(url); for (String[] data : params) { connection.data(data[0], data[1]); @@ -126,13 +185,19 @@ public class Client { this.cookies.addItems(response.cookies()); - return checkForErrors(response.parse()); + response.bufferUp(); // fixes cert parsing issues #109 + + return checkForErrors(response.parse(), response.statusCode()); } public String getJsonStringByUrl(String url) throws IOException, VulcanException { login(); - Connection.Response response = Jsoup.connect(getFilledUrl(url)) + url = getFilledUrl(url); + + logger.debug("GET {}", url); + + Connection.Response response = Jsoup.connect(url) .followRedirects(true) .ignoreContentType(true) .cookies(getCookies()) @@ -146,7 +211,11 @@ public class Client { public String postJsonStringByUrl(String url, String[][] params) throws IOException, VulcanException { login(); - Connection connection = Jsoup.connect(getFilledUrl(url)); + url = getFilledUrl(url); + + logger.debug("POST {}", url); + + Connection connection = Jsoup.connect(url); for (String[] data : params) { connection.data(data[0], data[1]); @@ -164,16 +233,22 @@ public class Client { return response.body(); } - Document checkForErrors(Document doc) throws VulcanException { - if ("Przerwa techniczna".equals(doc.select("title").text())) { - throw new VulcanOfflineException(); + Document checkForErrors(Document doc, int code) throws VulcanException { + 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 NotLoggedInErrorException(title + " " + doc.body() + ", status: " + code); + } return doc; } diff --git a/api/src/main/java/io/github/wulkanowy/api/DateTimeUtils.kt b/api/src/main/java/io/github/wulkanowy/api/DateTimeUtils.kt new file mode 100644 index 00000000..3b620db5 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/DateTimeUtils.kt @@ -0,0 +1,53 @@ +package io.github.wulkanowy.api + +import java.text.SimpleDateFormat +import java.util.* + +const val LOG_DATE_PATTERN = "dd.MM.yyyy" +const val API_DATE_PATTERN = "yyyy-MM-dd" + +const val TICKS_AT_EPOCH = 621355968000000000L +const val TICKS_PER_MILLISECOND = 10000 + +fun getFormattedDate(date: String): String { + return getFormattedDate(date, API_DATE_PATTERN) +} + +fun getFormattedDate(date: String, format: String): String { + return getFormattedDate(date, LOG_DATE_PATTERN, format) +} + +fun getFormattedDate(date: String, fromFormat: String, toFormat: String): String { + val sdf = SimpleDateFormat(fromFormat, Locale.ROOT) + val d = sdf.parse(date) + sdf.applyPattern(toFormat) + + return sdf.format(d) +} + +fun getDateAsTick(dateString: String?): String { + if (dateString.isNullOrEmpty()) { + return "" + } + + return getDateAsTick(dateString as String, API_DATE_PATTERN).toString() +} + +fun getDateAsTick(dateString: String, dateFormat: String): Long { + val format = SimpleDateFormat(dateFormat, Locale.ROOT) + format.timeZone = TimeZone.getTimeZone("UTC") + val dateObject = format.parse(dateString) + + return getDateAsTick(dateObject) +} + +fun getDateAsTick(date: Date): Long { + val calendar = Calendar.getInstance() + calendar.time = date + + return calendar.timeInMillis * TICKS_PER_MILLISECOND + TICKS_AT_EPOCH +} + +fun getDate(netTicks: Long): Date { + return Date((netTicks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND) +} 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/Semester.java b/api/src/main/java/io/github/wulkanowy/api/Semester.java deleted file mode 100644 index 64a07c97..00000000 --- a/api/src/main/java/io/github/wulkanowy/api/Semester.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.wulkanowy.api; - -public class Semester { - - private String number = ""; - - private String id = ""; - - private boolean isCurrent = false; - - public String getNumber() { - return number; - } - - public Semester setNumber(String number) { - this.number = number; - return this; - } - - public String getId() { - return id; - } - - public Semester setId(String id) { - this.id = id; - return this; - } - - public boolean isCurrent() { - return isCurrent; - } - - public Semester setCurrent(boolean current) { - isCurrent = 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..84ec1aed 100644 --- a/api/src/main/java/io/github/wulkanowy/api/SnP.java +++ b/api/src/main/java/io/github/wulkanowy/api/SnP.java @@ -6,19 +6,30 @@ import org.jsoup.nodes.Element; import java.io.IOException; import java.util.List; +import io.github.wulkanowy.api.generic.Diary; +import io.github.wulkanowy.api.generic.ParamItem; +import io.github.wulkanowy.api.generic.Semester; +import io.github.wulkanowy.api.generic.Student; + public interface SnP { - String getId(); + void setDiaryID(String id); - StudentAndParent storeContextCookies() throws IOException, VulcanException; + 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/StudentAndParent.java b/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java index c014e96c..ba527f6a 100644 --- a/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java +++ b/api/src/main/java/io/github/wulkanowy/api/StudentAndParent.java @@ -3,77 +3,105 @@ package io.github.wulkanowy.api; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import io.github.wulkanowy.api.generic.Diary; +import io.github.wulkanowy.api.generic.ParamItem; +import io.github.wulkanowy.api.generic.Semester; +import io.github.wulkanowy.api.generic.Student; public class StudentAndParent implements SnP { - private static final String START_PAGE_URL = "{schema}://uonetplus.{host}/{symbol}/Start.mvc/Index"; - private static final String BASE_URL = "{schema}://uonetplus-opiekun.{host}/{symbol}/{ID}/"; private static final String GRADES_PAGE_URL = "Oceny/Wszystkie"; private Client client; - private String id; + private String studentID; - StudentAndParent(Client client, String id) { + private String diaryID; + + private static final Logger logger = LoggerFactory.getLogger(StudentAndParent.class); + + StudentAndParent(Client client, String studentID, String diaryID) { this.client = client; - this.id = id; + 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(BASE_URL); - public String getId() { - return id; - } + if (doc.select("#idSection").isEmpty()) { + logger.error("Expected SnP page, got page with title: {} {}", doc.title(), doc.selectFirst("body")); + throw new VulcanException("Nieznany błąd podczas pobierania danych. Strona: " + doc.title()); + } + + 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; } - String getSnpHomePageUrl() throws IOException, VulcanException { - if (null != getId()) { - return getBaseUrl(); - } - - // get url to uonetplus-opiekun.vulcan.net.pl - Document startPage = client.getPageByUrl(START_PAGE_URL); - Element studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a").first(); - - if (null == studentTileLink) { - throw new NotLoggedInErrorException(); - } - - String snpPageUrl = studentTileLink.attr("href"); - - this.id = getExtractedIdFromUrl(snpPageUrl); - - return snpPageUrl; - } - - String getExtractedIdFromUrl(String snpPageUrl) throws NotLoggedInErrorException { - String[] path = snpPageUrl.split(client.getHost())[1].split("/"); - - if (5 != path.length) { - throw new NotLoggedInErrorException(); - } - - return path[2]; + public String getStudentID() { + return studentID; } public String getRowDataChildValue(Element e, int index) { 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); + + Document doc = client.getPageByUrl(BASE_URL + url, true, cookies); + + if (!doc.title().startsWith("Witryna ucznia i rodzica")) { + logger.error("Expected SnP page, got page with title: {} {}", doc.title(), doc.selectFirst("body")); + throw new VulcanException("Nieznany błąd podczas pobierania danych. Strona: " + doc.title()); + } + + if (doc.title().endsWith("Strona główna")) { + throw new VulcanException("Sesja została nieprawidłowo zainicjowana"); + } + + return doc; + } + + public List getDiaries() throws IOException, VulcanException { + return getDiaries(client.getPageByUrl(BASE_URL)); + } + + 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(BASE_URL)); + } + + private List getStudents(Document doc) throws IOException, VulcanException { + return getList(doc.select("#uczenDropDownList option"), Student.class); } public List getSemesters() throws IOException, VulcanException { @@ -87,10 +115,10 @@ public class StudentAndParent implements SnP { for (Element e : semesterOptions) { Semester semester = new Semester() - .setId(e.text()) - .setNumber(e.attr("value")); + .setId(e.attr("value")) + .setName(e.text()); - if ("selected".equals(e.attr("selected"))) { + if (isCurrent(e)) { semester.setCurrent(true); } @@ -100,15 +128,44 @@ 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); + } + + 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..bf482969 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java +++ b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java @@ -1,13 +1,20 @@ package io.github.wulkanowy.api; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; +import java.util.List; import io.github.wulkanowy.api.attendance.AttendanceStatistics; import io.github.wulkanowy.api.attendance.AttendanceTable; import io.github.wulkanowy.api.exams.ExamsWeek; +import io.github.wulkanowy.api.generic.School; import io.github.wulkanowy.api.grades.GradesList; import io.github.wulkanowy.api.grades.SubjectsList; import io.github.wulkanowy.api.messages.Messages; +import io.github.wulkanowy.api.mobile.RegisterDevice; +import io.github.wulkanowy.api.mobile.RegisteredDevices; import io.github.wulkanowy.api.notes.AchievementsList; import io.github.wulkanowy.api.notes.NotesList; import io.github.wulkanowy.api.school.SchoolInfo; @@ -18,21 +25,28 @@ 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 studentId; - this.id = id; + private String diaryId; + + private static final Logger logger = LoggerFactory.getLogger(Vulcan.class); + + public void setCredentials(String email, String password, String symbol, String schoolId, String studentId, String diaryId) { + this.studentId = studentId; + this.diaryId = diaryId; + + client = new Client(email, password, symbol, schoolId); + + logger.debug("Client created with symbol " + symbol); } public Client getClient() throws NotLoggedInErrorException { if (null == client) { - throw new NotLoggedInErrorException(); + throw new NotLoggedInErrorException("Vulcan must be initialized by calling setCredentials() prior to fetch data"); } return client; @@ -40,23 +54,23 @@ public class Vulcan { public String getSymbol() throws NotLoggedInErrorException { return getClient().getSymbol(); - } - public SnP getStudentAndParent() throws IOException, VulcanException { + public List getSchools() throws VulcanException, IOException { + return getClient().getSchools(); + } + + 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(), 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()); } @@ -105,6 +119,14 @@ public class Vulcan { return new FamilyInformation(getStudentAndParent()); } + public RegisteredDevices getRegisteredDevices() throws VulcanException, IOException { + return new RegisteredDevices(getStudentAndParent()); + } + + public RegisterDevice getRegisterDevice() throws VulcanException, IOException { + return new RegisterDevice(getStudentAndParent()); + } + public Messages getMessages() throws VulcanException { return new Messages(getClient()); } 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..0bc0c51f 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 { + + public 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/attendance/AttendanceTable.java b/api/src/main/java/io/github/wulkanowy/api/attendance/AttendanceTable.java index c45cbe6a..7caa70d8 100644 --- a/api/src/main/java/io/github/wulkanowy/api/attendance/AttendanceTable.java +++ b/api/src/main/java/io/github/wulkanowy/api/attendance/AttendanceTable.java @@ -4,12 +4,8 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; import io.github.wulkanowy.api.SnP; import io.github.wulkanowy.api.VulcanException; @@ -17,6 +13,9 @@ import io.github.wulkanowy.api.generic.Day; import io.github.wulkanowy.api.generic.Lesson; import io.github.wulkanowy.api.generic.Week; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getDateAsTick; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getFormattedDate; + public class AttendanceTable { private final static String ATTENDANCE_PAGE_URL = "Frekwencja.mvc?data="; @@ -27,13 +26,12 @@ public class AttendanceTable { this.snp = snp; } - public Week getWeekTable() throws IOException, ParseException, VulcanException { + public Week getWeekTable() throws IOException, VulcanException { return getWeekTable(""); } - public Week getWeekTable(String tick) throws IOException, ParseException, VulcanException { - Element table = snp.getSnPPageDocument(ATTENDANCE_PAGE_URL + tick) - + public Week getWeekTable(String date) throws IOException, VulcanException { + Element table = snp.getSnPPageDocument(ATTENDANCE_PAGE_URL + getDateAsTick(date)) .select(".mainContainer .presentData").first(); Elements headerCells = table.select("thead th"); @@ -42,14 +40,10 @@ public class AttendanceTable { for (int i = 1; i < headerCells.size(); i++) { String[] dayHeaderCell = headerCells.get(i).html().split("
"); - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT); - Date d = sdf.parse(dayHeaderCell[1].trim()); - sdf.applyPattern("yyyy-MM-dd"); - - Day day = new Day(); - day.setDayName(dayHeaderCell[0]); - day.setDate(sdf.format(d)); - days.add(day); + days.add(new Day() + .setDayName(dayHeaderCell[0]) + .setDate(getFormattedDate(dayHeaderCell[1].trim())) + ); } Elements hoursInDays = table.select("tbody tr"); @@ -63,7 +57,7 @@ public class AttendanceTable { for (int i = 1; i < size; i++) { Lesson lesson = new Lesson(); lesson.setDate(days.get(i - 1).getDate()); - lesson.setNumber(hours.get(0).text()); + lesson.setNumber(Integer.valueOf(hours.get(0).text())); addLessonDetails(lesson, hours.get(i)); diff --git a/api/src/main/java/io/github/wulkanowy/api/exams/ExamsWeek.java b/api/src/main/java/io/github/wulkanowy/api/exams/ExamsWeek.java index 3dd056d9..391056ac 100644 --- a/api/src/main/java/io/github/wulkanowy/api/exams/ExamsWeek.java +++ b/api/src/main/java/io/github/wulkanowy/api/exams/ExamsWeek.java @@ -1,5 +1,6 @@ package io.github.wulkanowy.api.exams; +import org.apache.commons.lang3.StringUtils; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -12,6 +13,9 @@ import io.github.wulkanowy.api.SnP; import io.github.wulkanowy.api.VulcanException; import io.github.wulkanowy.api.generic.Week; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getDateAsTick; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getFormattedDate; + public class ExamsWeek { private static final String EXAMS_PAGE_URL = "Sprawdziany.mvc/Terminarz?rodzajWidoku=2&data="; @@ -26,8 +30,8 @@ public class ExamsWeek { return getWeek("", true); } - public Week getWeek(String tick, final boolean onlyNotEmpty) throws IOException, VulcanException { - Document examsPage = snp.getSnPPageDocument(EXAMS_PAGE_URL + tick); + public Week getWeek(String date, final boolean onlyNotEmpty) throws IOException, VulcanException { + Document examsPage = snp.getSnPPageDocument(EXAMS_PAGE_URL + getDateAsTick(date)); Elements examsDays = examsPage.select(".mainContainer > div:not(.navigation)"); List days = new ArrayList<>(); @@ -41,7 +45,9 @@ public class ExamsWeek { } if (null != dayHeading) { - day.setDate(dayHeading.text().split(", ")[1]); + String[] dateHeader = dayHeading.text().split(", "); + day.setDayName(StringUtils.capitalize(dateHeader[0])); + day.setDate(getFormattedDate(dateHeader[1])); } Elements exams = item.select("article"); @@ -51,15 +57,17 @@ public class ExamsWeek { .setType(snp.getRowDataChildValue(e, 2)) .setDescription(snp.getRowDataChildValue(e, 3)) .setTeacher(snp.getRowDataChildValue(e, 4).split(", ")[0]) - .setEntryDate(snp.getRowDataChildValue(e, 4).split(", ")[1]) + .setEntryDate(getFormattedDate(snp.getRowDataChildValue(e, 4).split(", ")[1])) ); } days.add(day); } + return new Week() - .setStartDayDate(examsDays.select("h2").first().text().split(" ")[1]) + .setStartDayDate(getFormattedDate(examsPage.select(".mainContainer > h2") + .first().text().split(" ")[1])) .setDays(days); } } diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/Day.java b/api/src/main/java/io/github/wulkanowy/api/generic/Day.java index 6bf26c04..fbb5ee0a 100644 --- a/api/src/main/java/io/github/wulkanowy/api/generic/Day.java +++ b/api/src/main/java/io/github/wulkanowy/api/generic/Day.java @@ -11,10 +11,6 @@ public class Day { private String dayName = ""; - private boolean isFreeDay = false; - - private String freeDayName = ""; - public Lesson getLesson(int index) { return lessons.get(index); } @@ -41,23 +37,8 @@ public class Day { return dayName; } - public void setDayName(String dayName) { + public Day setDayName(String dayName) { this.dayName = dayName; - } - - public boolean isFreeDay() { - return isFreeDay; - } - - public void setFreeDay(boolean freeDay) { - isFreeDay = freeDay; - } - - public String getFreeDayName() { - return freeDayName; - } - - public void setFreeDayName(String freeDayName) { - this.freeDayName = freeDayName; + return this; } } diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/Diary.java b/api/src/main/java/io/github/wulkanowy/api/generic/Diary.java new file mode 100644 index 00000000..5c7c8593 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/generic/Diary.java @@ -0,0 +1,38 @@ +package io.github.wulkanowy.api.generic; + +public class Diary implements ParamItem { + + private String id = ""; + + private String name = ""; + + private boolean current = false; + + public String getId() { + return id; + } + + public Diary setId(String id) { + this.id = id; + 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/generic/Lesson.java b/api/src/main/java/io/github/wulkanowy/api/generic/Lesson.java index 8dd65327..27bf9bf0 100644 --- a/api/src/main/java/io/github/wulkanowy/api/generic/Lesson.java +++ b/api/src/main/java/io/github/wulkanowy/api/generic/Lesson.java @@ -2,7 +2,7 @@ package io.github.wulkanowy.api.generic; public class Lesson { - private String number = ""; + private int number = 0; private String subject = ""; @@ -48,12 +48,13 @@ public class Lesson { private boolean isExemption = false; - public String getNumber() { + public int getNumber() { return number; } - public void setNumber(String number) { + public Lesson setNumber(int number) { this.number = number; + return this; } public String getSubject() { diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/ParamItem.java b/api/src/main/java/io/github/wulkanowy/api/generic/ParamItem.java new file mode 100644 index 00000000..e7edfbf4 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/generic/ParamItem.java @@ -0,0 +1,12 @@ +package io.github.wulkanowy.api.generic; + +public interface ParamItem { + + ParamItem setId(String id); + + ParamItem setName(String name); + + ParamItem setCurrent(boolean isCurrent); + + boolean isCurrent(); +} diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/School.kt b/api/src/main/java/io/github/wulkanowy/api/generic/School.kt new file mode 100644 index 00000000..c14fc5d2 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/generic/School.kt @@ -0,0 +1,7 @@ +package io.github.wulkanowy.api.generic + +data class School( + val name: String, + val id: String, + val current: Boolean +) diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/Semester.java b/api/src/main/java/io/github/wulkanowy/api/generic/Semester.java new file mode 100644 index 00000000..db4a724d --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/generic/Semester.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.api.generic; + +public class Semester implements ParamItem { + + private String id = ""; + + private String name = ""; + + private boolean current = false; + + public String getId() { + return id; + } + + public Semester setId(String id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Semester setName(String number) { + this.name = number; + return this; + } + + public boolean isCurrent() { + return current; + } + + public Semester setCurrent(boolean current) { + this.current = current; + return this; + } +} diff --git a/api/src/main/java/io/github/wulkanowy/api/generic/Student.java b/api/src/main/java/io/github/wulkanowy/api/generic/Student.java new file mode 100644 index 00000000..4ed5dd37 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/generic/Student.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.api.generic; + +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 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/grades/Grade.java b/api/src/main/java/io/github/wulkanowy/api/grades/Grade.java index 564b3e47..31e1bbbd 100644 --- a/api/src/main/java/io/github/wulkanowy/api/grades/Grade.java +++ b/api/src/main/java/io/github/wulkanowy/api/grades/Grade.java @@ -18,8 +18,6 @@ public class Grade { private String teacher = ""; - private String semester = ""; - public String getSubject() { return subject; } @@ -99,14 +97,4 @@ public class Grade { return this; } - - public String getSemester() { - return semester; - } - - public Grade setSemester(String semester) { - this.semester = semester; - - return this; - } } 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..cb9fe465 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 @@ -5,79 +5,71 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -import io.github.wulkanowy.api.Semester; import io.github.wulkanowy.api.SnP; import io.github.wulkanowy.api.VulcanException; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getFormattedDate; + public class GradesList { private static final String GRADES_PAGE_URL = "Oceny/Wszystkie?details=2&okres="; - private SnP snp = null; - - private List grades = new ArrayList<>(); + private SnP snp; public GradesList(SnP snp) { this.snp = snp; } - private String getGradesPageUrl() { - return GRADES_PAGE_URL; - } - - public List getAll() throws IOException, ParseException, VulcanException { - return getAll(""); - } - - public List getAll(String semester) throws IOException, ParseException, VulcanException { - Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester); + public List getAll(String semester) throws IOException, VulcanException { + Document gradesPage = snp.getSnPPageDocument(GRADES_PAGE_URL + semester); Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr"); - Semester currentSemester = snp.getCurrentSemester(snp.getSemesters(gradesPage)); + + List grades = new ArrayList<>(); 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)); } return grades; } + + private Grade getGrade(Element row) { + String descriptions = row.select("td:nth-child(3)").text(); + + String symbol = descriptions.split(", ")[0]; + String description = descriptions.replaceFirst(Pattern.quote(symbol), "").replaceFirst(", ", ""); + String color = getColor(row.select("td:nth-child(2) span.ocenaCzastkowa").attr("style")); + String date = getFormattedDate(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()); + } + + 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; + } } diff --git a/api/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java b/api/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java index 99359413..37902d0d 100644 --- a/api/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java +++ b/api/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java @@ -13,16 +13,21 @@ import io.github.wulkanowy.api.VulcanException; public class SubjectsList { - private static final String SUBJECTS_PAGE_URL = "Oceny/Wszystkie?details=1"; + private static final String SUBJECTS_PAGE_URL = "Oceny/Wszystkie?details=1&okres="; - private SnP snp = null; + private SnP snp; public SubjectsList(SnP snp) { this.snp = snp; } + public List getAll() throws IOException, VulcanException { - Document subjectPage = snp.getSnPPageDocument(SUBJECTS_PAGE_URL); + return getAll(""); + } + + public List getAll(String semester) throws IOException, VulcanException { + Document subjectPage = snp.getSnPPageDocument(SUBJECTS_PAGE_URL + semester); Elements rows = subjectPage.select(".ocenyZwykle-table > tbody > tr"); 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..443a9387 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,74 +2,125 @@ 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import io.github.wulkanowy.api.Client; +import io.github.wulkanowy.api.NotLoggedInErrorException; import io.github.wulkanowy.api.VulcanException; public class Login { - private static final String LOGIN_PAGE_URL = "{schema}://cufs.{host}/{symbol}/Account/LogOn" + - "?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" + + protected static final String LOGIN_PAGE_URL = "{schema}://cufs.{host}/{symbol}/Account/LogOn"; + + private static final String LOGIN_PAGE_URL_QUERY = "?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" + "{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; + private static final Logger logger = LoggerFactory.getLogger(Login.class); 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); + public void login(String email, String password, String symbol) throws VulcanException, IOException { + Document certDoc = sendCredentials(email, password); - return sendCertificate(certificate, symbol); - } - - String sendCredentials(String email, String password, String symbol) throws IOException, VulcanException { - this.symbol = symbol; - - Document html = client.postPageByUrl(LOGIN_PAGE_URL, new String[][]{ - {"LoginName", email}, - {"Password", password} - }); - - if (null != html.select(".ErrorMessage").first()) { - throw new BadCredentialsException(); + if ("Błąd".equals(certDoc.title())) { + client.clearCookies(); + throw new NotLoggedInErrorException(certDoc.body().text()); } - return html.select("input[name=wresult]").attr("value"); + sendCertificate(certDoc, symbol); } - String sendCertificate(String certificate, String defaultSymbol) throws IOException, VulcanException { - this.symbol = findSymbol(defaultSymbol, certificate); - client.setSymbol(this.symbol); + Document sendCredentials(String email, String password) throws IOException, VulcanException { + String[][] credentials = new String[][]{ + {"LoginName", email}, + {"Password", password} + }; - String title = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{ - {"wa", "wsignin1.0"}, - {"wresult", certificate} - }).select("title").text(); + Document nextDoc = sendCredentialsData(credentials, LOGIN_PAGE_URL + LOGIN_PAGE_URL_QUERY.replace(":", "%253A")); + + Element errorMessage = nextDoc.selectFirst(".ErrorMessage, #ErrorTextLabel"); + if (null != errorMessage) { + throw new BadCredentialsException(errorMessage.text()); + } + + return nextDoc; + } + + private Document sendCredentialsData(String[][] credentials, String nextUrl) throws IOException, VulcanException { + Element formFirst = client.getPageByUrl(nextUrl, false).selectFirst("#form1"); + + if (null != formFirst) { // only on adfs login + Document formSecond = client.postPageByUrl( + formFirst.attr("abs:action"), + getFormStateParams(formFirst, "", "") + ); + credentials = getFormStateParams(formSecond, credentials[0][1], credentials[1][1]); + nextUrl = formSecond.selectFirst("#form1").attr("abs:action"); + } + + return client.postPageByUrl(nextUrl, credentials); + } + + private String[][] getFormStateParams(Element form, String email, String password) { + return new String[][]{ + {"__VIEWSTATE", form.select("#__VIEWSTATE").val()}, + {"__VIEWSTATEGENERATOR", form.select("#__VIEWSTATEGENERATOR").val()}, + {"__EVENTVALIDATION", form.select("#__EVENTVALIDATION").val()}, + {"__db", form.select("input[name=__db]").val()}, + {"PassiveSignInButton.x", "0"}, + {"PassiveSignInButton.y", "0"}, + {"SubmitButton.x", "0"}, + {"SubmitButton.y", "0"}, + {"UsernameTextBox", email}, + {"PasswordTextBox", password}, + }; + } + + void sendCertificate(Document doc, String defaultSymbol) throws IOException, VulcanException { + client.setSymbol(findSymbol(defaultSymbol, doc.select("input[name=wresult]").val())); + + Document targetDoc = sendCertData(doc); + String title = targetDoc.title(); + + if ("Working...".equals(title)) { // on adfs login + logger.info("ADFS login"); + title = sendCertData(targetDoc).title(); + } if ("Logowanie".equals(title)) { - throw new AccountPermissionException(); + throw new AccountPermissionException("No account access. Try another symbol"); } if (!"Uonet+".equals(title)) { - throw new LoginErrorException(); + logger.debug("Login failed. Body: {}", targetDoc.body()); + throw new LoginErrorException("Expected page title `UONET+`, got " + title); } - return this.symbol; + client.setSchools(new StartPage(client).getSchools(targetDoc)); } - private String findSymbol(String symbol, String certificate) { + 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) throws AccountPermissionException { if ("Default".equals(symbol)) { return findSymbolInCertificate(certificate); } @@ -77,15 +128,19 @@ public class Login { return symbol; } - String findSymbolInCertificate(String certificate) { - Elements els = Jsoup + String findSymbolInCertificate(String certificate) throws AccountPermissionException { + Elements instances = Jsoup .parse(certificate.replaceAll(":", ""), "", Parser.xmlParser()) .select("[AttributeName=\"UserInstance\"] samlAttributeValue"); - if (els.isEmpty()) { + if (instances.isEmpty()) { // on adfs login return ""; } - return els.get(1).text(); + if (instances.size() < 2) { // 1st index is always `Default` + throw new AccountPermissionException("First login detected, specify symbol"); + } + + 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/login/StartPage.kt b/api/src/main/java/io/github/wulkanowy/api/login/StartPage.kt new file mode 100644 index 00000000..6f6b1490 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/login/StartPage.kt @@ -0,0 +1,45 @@ +package io.github.wulkanowy.api.login + +import io.github.wulkanowy.api.Client +import io.github.wulkanowy.api.VulcanException +import io.github.wulkanowy.api.generic.School +import org.jsoup.nodes.Document +import org.slf4j.LoggerFactory + +class StartPage(val client: Client) { + + private val logger = LoggerFactory.getLogger(StartPage::class.java) + + fun getSchools(startPage: Document): MutableList { + val schoolList = mutableListOf() + + val snpLinks = startPage.select(".panel.linkownia.pracownik.klient a") + + logger.debug("SnP links: {}", snpLinks.size) + + if (snpLinks.isEmpty()) { + throw VulcanException("Na pewno używasz konta z dostępem do Witryny ucznia i rodzica?") + } + + snpLinks.map { + schoolList.add(School( + it.text(), + getExtractedIdFromUrl(it.attr("href")), + it == snpLinks.first() + )) + } + + return schoolList + } + + internal fun getExtractedIdFromUrl(snpPageUrl: String): String { + val path = snpPageUrl.split(client.host).getOrNull(1)?.split("/") + + if (6 != path?.size) { + logger.error("Expected snp url, got {}", snpPageUrl) + throw VulcanException("Na pewno używasz konta z dostępem do Witryny ucznia i rodzica?") + } + + return path[2] + } +} 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/main/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt b/api/src/main/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt new file mode 100644 index 00000000..f0da01e9 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.api.mobile + +import io.github.wulkanowy.api.SnP +import org.jsoup.nodes.Element + +class RegisterDevice(private val snp: SnP) { + + companion object { + const val REGISTER_URL = "DostepMobilny.mvc/Rejestruj" + } + + data class Token( + val token: String, + val symbol: String, + val pin: String + ) + + fun getToken(): Token { + val form = snp.getSnPPageDocument(REGISTER_URL).selectFirst("#rejestracja-formularz") + + val fields = form.select(".blockElement") + + return Token( + getValue(fields[1]), + getValue(fields[2]), + getValue(fields[3]) + ) + } + + fun getValue(e: Element): String { + return e.text().split(":")[1].trim() + } +} diff --git a/api/src/main/java/io/github/wulkanowy/api/mobile/RegisteredDevices.kt b/api/src/main/java/io/github/wulkanowy/api/mobile/RegisteredDevices.kt new file mode 100644 index 00000000..d622b4de --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/mobile/RegisteredDevices.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.api.mobile + +import io.github.wulkanowy.api.SnP +import io.github.wulkanowy.api.getFormattedDate + +class RegisteredDevices(private val snp: SnP) { + + companion object { + const val DEVICES_LIST_URL = "DostepMobilny.mvc" + } + + data class Device( + val name: String, + val system: String, + val date: String, + val id: Int + ) + + fun getList(): List { + val items = snp.getSnPPageDocument(DEVICES_LIST_URL).select("table tbody tr") + val devices: MutableList = mutableListOf() + + for (item in items) { + val cells = item.select("td") + val system = cells[0].text().split("(").last().removeSuffix(")") + + devices.add(Device( + cells[0].text().replace(" ($system)", ""), + system, + getFormattedDate(cells[1].text(), "dd.MM.yyyy 'godz:' HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), + cells[2].select("a").attr("href") + .split("/").last().toInt() + )) + } + + return devices + } +} diff --git a/api/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java b/api/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java index 11d01342..346df832 100644 --- a/api/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java +++ b/api/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java @@ -14,9 +14,7 @@ public class AchievementsList { private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie"; - private SnP snp = null; - - private List achievements = new ArrayList<>(); + private SnP snp; public AchievementsList(SnP snp) { this.snp = snp; @@ -27,6 +25,8 @@ public class AchievementsList { .select(".mainContainer > div").get(1); Elements items = pageFragment.select("article"); + List achievements = new ArrayList<>(); + for (Element item : items) { achievements.add(item.text()); } diff --git a/api/src/main/java/io/github/wulkanowy/api/notes/NotesList.java b/api/src/main/java/io/github/wulkanowy/api/notes/NotesList.java index 0c8a30b6..54f57edf 100644 --- a/api/src/main/java/io/github/wulkanowy/api/notes/NotesList.java +++ b/api/src/main/java/io/github/wulkanowy/api/notes/NotesList.java @@ -10,13 +10,13 @@ import java.util.List; import io.github.wulkanowy.api.SnP; import io.github.wulkanowy.api.VulcanException; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getFormattedDate; + public class NotesList { private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie"; - private SnP snp = null; - - private List notes = new ArrayList<>(); + private SnP snp; public NotesList(SnP snp) { this.snp = snp; @@ -28,10 +28,12 @@ public class NotesList { Elements items = pageFragment.select("article"); Elements dates = pageFragment.select("h2"); + List notes = new ArrayList<>(); + int index = 0; for (Element item : items) { notes.add(new Note() - .setDate(dates.get(index++).text()) + .setDate(getFormattedDate(dates.get(index++).text())) .setTeacher(snp.getRowDataChildValue(item, 1)) .setCategory(snp.getRowDataChildValue(item, 2)) .setContent(snp.getRowDataChildValue(item, 3)) diff --git a/api/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java b/api/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java index 51a7278b..f0dcd43d 100644 --- a/api/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java +++ b/api/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java @@ -11,7 +11,7 @@ public class SchoolInfo { private static final String SCHOOL_PAGE_URL = "Szkola.mvc/Nauczyciele"; - private SnP snp = null; + private SnP snp; public SchoolInfo(SnP snp) { this.snp = snp; diff --git a/api/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java b/api/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java index bbf5f5d7..ec8429bd 100644 --- a/api/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java +++ b/api/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java @@ -15,7 +15,7 @@ public class TeachersInfo { private static final String SCHOOL_PAGE_URL = "Szkola.mvc/Nauczyciele"; - private SnP snp = null; + private SnP snp; public TeachersInfo(SnP snp) { this.snp = snp; diff --git a/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java b/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java index f575edb3..95c40167 100644 --- a/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java +++ b/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java @@ -3,61 +3,64 @@ package io.github.wulkanowy.api.timetable; import org.apache.commons.lang3.StringUtils; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; import io.github.wulkanowy.api.SnP; import io.github.wulkanowy.api.VulcanException; -import io.github.wulkanowy.api.generic.Day; import io.github.wulkanowy.api.generic.Lesson; import io.github.wulkanowy.api.generic.Week; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getDateAsTick; +import static io.github.wulkanowy.api.DateTimeUtilsKt.getFormattedDate; + public class Timetable { - private static final String TIMETABLE_PAGE_URL = "Lekcja.mvc/PlanLekcji?data="; + private static final String TIMETABLE_PAGE_URL = "Lekcja.mvc/PlanZajec?data="; private SnP snp; + private static final Logger logger = LoggerFactory.getLogger(Timetable.class); + public Timetable(SnP snp) { this.snp = snp; } - public Week getWeekTable() throws IOException, ParseException, VulcanException { + public Week getWeekTable() throws IOException, VulcanException { return getWeekTable(""); } - public Week getWeekTable(final String tick) throws IOException, ParseException, VulcanException { - Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + tick) + public Week getWeekTable(final String date) throws IOException, VulcanException { + Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + getDateAsTick(date)) .select(".mainContainer .presentData").first(); - List days = getDays(table.select("thead th")); + List days = getDays(table.select("thead th")); setLessonToDays(table, days); - return new Week() + return new Week() .setStartDayDate(days.get(0).getDate()) .setDays(days); } - private List getDays(Elements tableHeaderCells) throws ParseException { - List days = new ArrayList<>(); + private List getDays(Elements tableHeaderCells) { + List days = new ArrayList<>(); + int numberOfDays = tableHeaderCells.size(); - for (int i = 2; i < 7; i++) { + if (numberOfDays > 7) { + logger.info("Number of days: {}", numberOfDays); + } + + for (int i = 2; i < numberOfDays; i++) { String[] dayHeaderCell = tableHeaderCells.get(i).html().split("
"); - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT); - Date d = sdf.parse(dayHeaderCell[1].trim()); - sdf.applyPattern("yyyy-MM-dd"); - - Day day = new Day(); + TimetableDay day = new TimetableDay(); day.setDayName(dayHeaderCell[0]); - day.setDate(sdf.format(d)); + day.setDate(getFormattedDate(dayHeaderCell[1].trim())); if (tableHeaderCells.get(i).hasClass("free-day")) { day.setFreeDay(true); @@ -70,7 +73,7 @@ public class Timetable { return days; } - private void setLessonToDays(Element table, List days) { + private void setLessonToDays(Element table, List days) { for (Element row : table.select("tbody tr")) { Elements hours = row.select("td"); @@ -82,7 +85,7 @@ public class Timetable { lesson.setStartTime(startEndEnd[0]); lesson.setEndTime(startEndEnd[1]); lesson.setDate(days.get(i - 2).getDate()); - lesson.setNumber(hours.get(0).text()); + lesson.setNumber(Integer.valueOf(hours.get(0).text())); addLessonDetails(lesson, hours.get(i).select("div")); @@ -99,7 +102,16 @@ public class Timetable { addLessonInfoFromElement(lesson, e.first()); break; case 2: - addLessonInfoFromElement(lesson, e.last()); + Element span = e.last().selectFirst("span"); + if (null == span) { + addLessonInfoFromElement(lesson, e.first()); + } else if (span.hasClass(LessonTypes.CLASS_MOVED_OR_CANCELED)) { + lesson.setNewMovedInOrChanged(true); + lesson.setDescription("poprzednio: " + getLessonAndGroupInfoFromSpan(span)[0]); + addLessonInfoFromElement(lesson, e.first()); + } else { + addLessonInfoFromElement(lesson, e.last()); + } break; case 3: addLessonInfoFromElement(lesson, e.get(1)); @@ -114,7 +126,9 @@ public class Timetable { Elements warn = e.select(".uwaga-panel"); if (!warn.isEmpty()) { - e.select(".x-treelabel-rlz").last().text("(" + warn.text() + ")"); + e.select("span").last() + .addClass("x-treelabel-rlz") + .text(warn.text()); e.remove(1); } } @@ -122,6 +136,11 @@ public class Timetable { private void addLessonInfoFromElement(Lesson lesson, Element e) { Elements spans = e.select("span"); + if (spans.isEmpty()) { + logger.warn("Lesson span is empty"); + return; + } + addTypeInfo(lesson, spans); addNormalLessonInfo(lesson, spans); addChangesInfo(lesson, spans); @@ -165,7 +184,8 @@ public class Timetable { lesson.setRoom(spans.get(5).text()); lesson.setMovedOrCanceled(false); lesson.setNewMovedInOrChanged(true); - lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")") + lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween( + spans.last().text(), "(", ")"), spans.last().text()) + " (poprzednio: " + spans.get(0).text() + ")"); } else if (9 == spans.size()) { String[] subjectAndGroupInfo = getLessonAndGroupInfoFromSpan(spans.get(4)); @@ -176,13 +196,15 @@ public class Timetable { lesson.setMovedOrCanceled(false); lesson.setNewMovedInOrChanged(true); lesson.setDivisionIntoGroups(true); - lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")") + lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween( + spans.last().text(), "(", ")"), spans.last().text()) + " (poprzednio: " + getLessonAndGroupInfoFromSpan(spans.get(0))[0] + ")"); } else if (4 <= spans.size()) { lesson.setSubject(spans.get(0).text()); lesson.setTeacher(spans.get(1).text()); lesson.setRoom(spans.get(2).text()); - lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")")); + lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween( + spans.last().text(), "(", ")"), spans.last().text())); } } @@ -205,12 +227,17 @@ public class Timetable { } private String[] getLessonAndGroupInfoFromSpan(Element span) { + if (!span.text().contains("[")) { + return new String[] {span.text(), ""}; + } + String[] subjectNameArray = span.text().split(" "); String groupName = subjectNameArray[subjectNameArray.length - 1]; return new String[]{ span.text().replace(" " + groupName, ""), - StringUtils.substringBetween(groupName, "[", "]") + StringUtils.defaultString(StringUtils.substringBetween( + groupName, "[", "]"), groupName) }; } } diff --git a/api/src/main/java/io/github/wulkanowy/api/timetable/TimetableDay.java b/api/src/main/java/io/github/wulkanowy/api/timetable/TimetableDay.java new file mode 100644 index 00000000..1aa29de2 --- /dev/null +++ b/api/src/main/java/io/github/wulkanowy/api/timetable/TimetableDay.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.api.timetable; + +import io.github.wulkanowy.api.generic.Day; + +public class TimetableDay extends Day { + + private boolean isFreeDay = false; + + private String freeDayName = ""; + + public boolean isFreeDay() { + return isFreeDay; + } + + public void setFreeDay(boolean freeDay) { + isFreeDay = freeDay; + } + + public String getFreeDayName() { + return freeDayName; + } + + public void setFreeDayName(String freeDayName) { + this.freeDayName = freeDayName; + } +} 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..298affac 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) { @@ -15,66 +12,67 @@ public class ClientTest { } @Test - public void setFullEndpointInfoTest() throws Exception { - Client client = new Client("http://fakelog.net\\\\admin", "pass", "Default"); + public void setFullEndpointInfoTest() { + Client client = new Client("http://fakelog.cf\\\\admin", "pass", "Default", "123"); - Assert.assertEquals("fakelog.net", client.getHost()); + Assert.assertEquals("fakelog.cf", client.getHost()); Assert.assertEquals("Default", client.getSymbol()); } + @Test + public void setFullEndpointInfoWithSymbolTest() { + Client client = new Client("http://fakelog.cf/notdefault\\\\admin", "pass", "Default", "123"); + + Assert.assertEquals("fakelog.cf", client.getHost()); + Assert.assertEquals("notdefault", client.getSymbol()); // + } + @Test public void checkForNoErrorsTest() throws Exception { - Client client = new Client("", "", ""); + Client client = new Client("", "", "", "123"); Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-success.html")); - Assert.assertEquals(doc, client.checkForErrors(doc)); + Assert.assertEquals(doc, client.checkForErrors(doc, 200)); } @Test(expected = VulcanOfflineException.class) public void checkForErrorsOffline() throws Exception { - Client client = new Client("", "", ""); + Client client = new Client("", "", "", "123"); Document doc = Jsoup.parse(getFixtureAsString("login/PrzerwaTechniczna.html")); - client.checkForErrors(doc); + client.checkForErrors(doc, 200); } @Test(expected = NotLoggedInErrorException.class) public void checkForErrors() throws Exception { - Client client = new Client("", "", ""); + Client client = new Client("", "", "", "123"); Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-notLoggedIn.html")); - 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()); + client.checkForErrors(doc, 200); } @Test public void getFilledUrlTest() throws Exception { - Client client = new Client("http://fakelog.cf\\\\admin", "", "symbol123"); + Client client = new Client("http://fakelog.cf\\\\admin", "", "symbol123", "321"); - Assert.assertEquals("http://uonetplus.fakelog.cf/symbol123/LoginEndpoint.aspx", - client.getFilledUrl("{schema}://uonetplus.{host}/{symbol}/LoginEndpoint.aspx")); + Assert.assertEquals("http://uonetplus-opiekun.fakelog.cf/symbol123/321/Oceny/Wszystkie", + client.getFilledUrl("{schema}://uonetplus-opiekun.{host}/{symbol}/{ID}/Oceny/Wszystkie")); } @Test - public void getSymbolTest() throws Exception { - Client client = new Client("", "", "symbol4321"); + public void getSymbolTest() { + Client client = new Client("", "", "symbol4321", "123"); Assert.assertEquals("symbol4321", client.getSymbol()); } + + @Test + public void getSchoolIdTest() throws Exception { + Client client = new Client("", "", "1", "123456"); + + Assert.assertEquals("123456", client.getSchoolId()); + } } diff --git a/api/src/test/java/io/github/wulkanowy/api/DateTimeUtilsTest.kt b/api/src/test/java/io/github/wulkanowy/api/DateTimeUtilsTest.kt new file mode 100644 index 00000000..1105b2a7 --- /dev/null +++ b/api/src/test/java/io/github/wulkanowy/api/DateTimeUtilsTest.kt @@ -0,0 +1,53 @@ +package io.github.wulkanowy.api + +import org.junit.Assert +import org.junit.Test +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class DateTimeUtilsTest { + + @Test + fun getTicksDateObjectTest() { + val format = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT) + format.timeZone = TimeZone.getTimeZone("UTC") + val date = format.parse("31.07.2017") + + Assert.assertEquals(636370560000000000L, getDateAsTick(date)) + + val calendar = Calendar.getInstance() + calendar.time = date + calendar.add(Calendar.DAY_OF_YEAR, -14) + val dateTwoWeekBefore = calendar.time + + Assert.assertEquals(636358464000000000L, getDateAsTick(dateTwoWeekBefore)) + } + + @Test(expected = ParseException::class) + fun getTicsStringInvalidFormatTest() { + Assert.assertEquals(636370560000000000L, getDateAsTick("31.07.2017", "dd.MMM.yyyy")) + } + + @Test + fun getTicsStringFormatTest() { + Assert.assertEquals(636370560000000000L, getDateAsTick("31.07.2017", "dd.MM.yyyy")) + } + + @Test + fun getTicsStringTest() { + Assert.assertEquals("636370560000000000", getDateAsTick("2017-07-31")) + Assert.assertEquals("636334272000000000", getDateAsTick("2017-06-19")) + Assert.assertEquals("636189120000000000", getDateAsTick("2017-01-02")) + Assert.assertEquals("636080256000000000", getDateAsTick("2016-08-29")) + } + + @Test + fun getDateTest() { + val format = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT) + format.timeZone = TimeZone.getTimeZone("UTC") + val date = format.parse("31.07.2017") + + Assert.assertEquals(date, getDate(636370560000000000L)) + } +} 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..4c6443b1 100644 --- a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTest.java @@ -7,9 +7,12 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import io.github.wulkanowy.api.generic.Semester; + public class StudentAndParentTest { private Client client; @@ -22,102 +25,79 @@ 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()); - } - - @Test - public void getSnpPageUrlWithIdTest() throws Exception { - Assert.assertEquals("{schema}://uonetplus-opiekun.{host}/{symbol}/123456/", - (new StudentAndParent(client, "123456")).getSnpHomePageUrl()); - } - - @Test - public void getSnpPageUrlWithoutIdTest() throws Exception { - String input = FixtureHelper.getAsString(getClass().getResourceAsStream("Start.html")); - Document startPageDocument = Jsoup.parse(input); - - Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(startPageDocument); - StudentAndParent snp = new StudentAndParent(client, null); - - Assert.assertEquals("https://uonetplus-opiekun.vulcan.net.pl/symbol/534213/Start/Index/", - snp.getSnpHomePageUrl()); - } - - @Test(expected = NotLoggedInErrorException.class) - public void getSnpPageUrlWithWrongPage() throws Exception { - Document wrongPageDocument = Jsoup.parse( - FixtureHelper.getAsString(getClass().getResourceAsStream("OcenyWszystkie-semester.html")) - ); - - Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(wrongPageDocument); - StudentAndParent snp = new StudentAndParent(client, null); - - snp.getSnpHomePageUrl(); - } - - @Test - public void getExtractedIDStandardTest() throws Exception { - Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); - Assert.assertEquals("123456", snp.getExtractedIdFromUrl("https://uonetplus-opiekun" - + ".vulcan.net.pl/powiat/123456/Start/Index/")); - } - - @Test - public void getExtractedIDDemoTest() throws Exception { - Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); - Assert.assertEquals("demo12345", - snp.getExtractedIdFromUrl("https://uonetplus-opiekun.vulcan.net.pl/demoupowiat/demo12345/Start/Index/")); - } - - @Test(expected = NotLoggedInErrorException.class) - public void getExtractedIDNotLoggedTest() throws Exception { - Mockito.when(client.getHost()).thenReturn("vulcan.net.pl"); - StudentAndParent snp = new StudentAndParent(client, "symbol"); - Assert.assertEquals("123", - snp.getExtractedIdFromUrl("https://uonetplus.vulcan.net.pl/powiat/")); + public void snpTest() { + StudentAndParent snp = new StudentAndParent(client, "1234", null); + Assert.assertEquals("1234", snp.getStudentID()); } @Test public void getSemestersTest() throws Exception { - SnP snp = new StudentAndParent(client, "123456"); + SnP snp = new StudentAndParent(client, 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("1", semesters.get(0).getName()); + Assert.assertEquals("1234", semesters.get(0).getId()); Assert.assertFalse(semesters.get(0).isCurrent()); - Assert.assertEquals("2", semesters.get(1).getId()); - Assert.assertEquals("1235", semesters.get(1).getNumber()); + Assert.assertEquals("2", semesters.get(1).getName()); + Assert.assertEquals("1235", semesters.get(1).getId()); 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 { + String input = FixtureHelper.getAsString(getClass().getResourceAsStream("WitrynaUczniaIRodzica.html")); + Document snpHome = Jsoup.parse(input); + + 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("Jan Kowal", snp.getStudents().get(0).getName()); + Assert.assertEquals("100", snp.getStudents().get(0).getId()); } } 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..764e8009 100644 --- a/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java +++ b/api/src/test/java/io/github/wulkanowy/api/StudentAndParentTestCase.java @@ -5,6 +5,8 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.mockito.Mockito; +import io.github.wulkanowy.api.generic.Semester; + public abstract class StudentAndParentTestCase { protected StudentAndParent getSnp(String fixtureFileName) throws Exception { @@ -16,7 +18,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/exams/ExamsWeekTest.java b/api/src/test/java/io/github/wulkanowy/api/exams/ExamsWeekTest.java index 9b57ddd6..0a99c465 100644 --- a/api/src/test/java/io/github/wulkanowy/api/exams/ExamsWeekTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/exams/ExamsWeekTest.java @@ -12,20 +12,25 @@ public class ExamsWeekTest extends StudentAndParentTestCase { private ExamsWeek onePerDay; + private ExamsWeek empty; + @Before public void getCurrent() throws Exception { onePerDay = new ExamsWeek(getSnp("Sprawdziany-one-per-day.html")); + empty = new ExamsWeek(getSnp("Sprawdziany-empty.html")); } @Test public void getWeekTest() throws Exception { - Assert.assertEquals("23.10.2017", onePerDay.getCurrent().getStartDayDate()); + Assert.assertEquals("2017-10-23", onePerDay.getCurrent().getStartDayDate()); + Assert.assertEquals("2018-04-30", empty.getCurrent().getStartDayDate()); } @Test public void getDaysListTest() throws Exception { Assert.assertEquals(3, onePerDay.getCurrent().getDays().size()); Assert.assertEquals(7, onePerDay.getWeek("", false).getDays().size()); + Assert.assertEquals(0, empty.getCurrent().getDays().size()); } @Test @@ -45,9 +50,18 @@ public class ExamsWeekTest extends StudentAndParentTestCase { public void getDayDateTest() throws Exception { List dayList = onePerDay.getCurrent().getDays(); - Assert.assertEquals("23.10.2017", dayList.get(0).getDate()); - Assert.assertEquals("24.10.2017", dayList.get(1).getDate()); - Assert.assertEquals("27.10.2017", dayList.get(2).getDate()); + Assert.assertEquals("2017-10-23", dayList.get(0).getDate()); + Assert.assertEquals("2017-10-24", dayList.get(1).getDate()); + Assert.assertEquals("2017-10-27", dayList.get(2).getDate()); + } + + @Test + public void getDayNameTest() throws Exception { + List dayList = onePerDay.getCurrent().getDays(); + + Assert.assertEquals("Poniedziałek", dayList.get(0).getDayName()); + Assert.assertEquals("Wtorek", dayList.get(1).getDayName()); + Assert.assertEquals("Piątek", dayList.get(2).getDayName()); } @Test @@ -90,8 +104,8 @@ public class ExamsWeekTest extends StudentAndParentTestCase { public void getExamEntryDateTest() throws Exception { List dayList = onePerDay.getCurrent().getDays(); - Assert.assertEquals("16.10.2017", dayList.get(0).getExamList().get(0).getEntryDate()); - Assert.assertEquals("17.10.2017", dayList.get(1).getExamList().get(0).getEntryDate()); - Assert.assertEquals("16.10.2017", dayList.get(2).getExamList().get(0).getEntryDate()); + Assert.assertEquals("2017-10-16", dayList.get(0).getExamList().get(0).getEntryDate()); + Assert.assertEquals("2017-10-17", dayList.get(1).getExamList().get(0).getEntryDate()); + Assert.assertEquals("2017-10-16", dayList.get(2).getExamList().get(0).getEntryDate()); } } diff --git a/api/src/test/java/io/github/wulkanowy/api/grades/GradesListTest.java b/api/src/test/java/io/github/wulkanowy/api/grades/GradesListTest.java index bee5b4fe..deab7617 100644 --- a/api/src/test/java/io/github/wulkanowy/api/grades/GradesListTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/grades/GradesListTest.java @@ -19,12 +19,12 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getAllTest() throws Exception { - Assert.assertEquals(6, filled.getAll().size()); // 2 items are skipped + Assert.assertEquals(7, filled.getAll("").size()); // 2 items are skipped } @Test public void getSubjectTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("Zajęcia z wychowawcą", list.get(0).getSubject()); Assert.assertEquals("Język angielski", list.get(3).getSubject()); @@ -34,7 +34,7 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getValueTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("5", list.get(0).getValue()); Assert.assertEquals("5", list.get(3).getValue()); @@ -44,7 +44,7 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getColorTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("000000", list.get(0).getColor()); Assert.assertEquals("1289F7", list.get(3).getColor()); @@ -54,27 +54,29 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getSymbolTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("A1", list.get(0).getSymbol()); Assert.assertEquals("BW3", list.get(3).getSymbol()); Assert.assertEquals("STR", list.get(4).getSymbol()); Assert.assertEquals("K", list.get(5).getSymbol()); + Assert.assertEquals("+Odp", list.get(6).getSymbol()); } @Test public void getDescriptionTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("Dzień Kobiet w naszej klasie", list.get(0).getDescription()); Assert.assertEquals("Writing", list.get(3).getDescription()); Assert.assertEquals("", list.get(4).getDescription()); Assert.assertEquals("Kordian", list.get(5).getDescription()); + Assert.assertEquals("Kordian", list.get(6).getDescription()); } @Test public void getWeightTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("1,00", list.get(0).getWeight()); Assert.assertEquals("3,00", list.get(3).getWeight()); @@ -84,7 +86,7 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getDateTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("2017-03-21", list.get(0).getDate()); Assert.assertEquals("2017-06-02", list.get(3).getDate()); @@ -94,21 +96,11 @@ public class GradesListTest extends StudentAndParentTestCase { @Test public void getTeacherTest() throws Exception { - List list = filled.getAll(); + List list = filled.getAll(""); Assert.assertEquals("Patryk Maciejewski", list.get(0).getTeacher()); Assert.assertEquals("Oliwia Woźniak", list.get(3).getTeacher()); Assert.assertEquals("Klaudia Dziedzic", list.get(4).getTeacher()); Assert.assertEquals("Amelia Stępień", list.get(5).getTeacher()); } - - @Test - public void getSemesterTest() throws Exception { - List list = filled.getAll(); - - Assert.assertEquals("7654321", list.get(0).getSemester()); - Assert.assertEquals("7654321", list.get(3).getSemester()); - Assert.assertEquals("7654321", list.get(4).getSemester()); - Assert.assertEquals("7654321", list.get(5).getSemester()); - } } 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..86a3ef40 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,86 +11,121 @@ 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); + Mockito.when(client.getPageByUrl(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(doc); return client; } @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")); + Mockito.when(client.postPageByUrl(Mockito.eq(Login.LOGIN_PAGE_URL), Mockito.any(String[][].class))) + .thenReturn(getFixtureAsDocument("Logowanie-certyfikat.html")); + Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString()); + Mockito.when(client.getSymbol()).thenCallRealMethod(); + Mockito.when(client.getHost()).thenReturn("fakelog.cf"); + Login login = new Login(client); + login.login("a@a", "pswd", "d123"); - Assert.assertEquals("d123", login.login("a@a", "pswd", "d123")); + Assert.assertEquals("d123", client.getSymbol()); } @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+","") + getFixtureAsString("cert-stock.xml").replaceAll("\\s+", ""), + login.sendCredentials("a@a", "passwd") + .select("input[name=wresult]") + .attr("value") + .replaceAll("\\s+", "") ); } @Test public void sendCertificateNotDefaultSymbolSuccessTest() throws Exception { - Login login = new Login(getClient("Logowanie-success.html")); + Client client = getClient("Logowanie-success.html"); + Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString()); + Mockito.when(client.getSymbol()).thenCallRealMethod(); + Mockito.when(client.getHost()).thenReturn("fakelog.cf"); + Login login = new Login(client); - Assert.assertEquals("wulkanowyschool321", - login.sendCertificate("", "wulkanowyschool321")); + login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "wulkanowyschool321"); + + Assert.assertEquals("wulkanowyschool321", client.getSymbol()); } @Test public void sendCertificateDefaultSymbolSuccessTest() throws Exception { - Login login = new Login(getClient("Logowanie-success.html")); + Client client = getClient("Logowanie-success.html"); + Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString()); + Mockito.when(client.getSymbol()).thenCallRealMethod(); + Mockito.when(client.getHost()).thenReturn("fakelog.cf"); + Login login = new Login(client); - Assert.assertEquals("demo12345", - login.sendCertificate(getFixtureAsString("cert.xml"), "Default")); + login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "Default"); + + Assert.assertEquals("demo12345", client.getSymbol()); } @Test(expected = AccountPermissionException.class) public void sendCertificateAccountPermissionTest() throws Exception { - Login login = new Login(getClient("Logowanie-brak-dostepu.html")); + Client client = getClient("Logowanie-brak-dostepu.html"); - login.sendCertificate(getFixtureAsString("cert.xml"), "demo123"); + Login login = new Login(client); + + login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "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("Logowanie-certyfikat.html"), "demo123"); } @Test public void findSymbolInCertificateTest() throws Exception { Login login = new Login(getClient("Logowanie-certyfikat.html")); - String certificate = getFixtureAsString("cert.xml"); + String certificate = getFixtureAsString("cert-stock.xml"); Assert.assertEquals("demo12345", login.findSymbolInCertificate(certificate)); } - @Test - public void findSymbolInInvalidCertificateTest() throws Exception { + @Test(expected = AccountPermissionException.class) + public void findSymbolInCertificateWithoutSecondInstanceTest() throws Exception { Login login = new Login(getClient("Logowanie-certyfikat.html")); - Assert.assertEquals("", login.findSymbolInCertificate("")); // change to real cert with empty symbols + login.findSymbolInCertificate(getFixtureAsString("cert-no-symbols.xml")); } } diff --git a/api/src/test/java/io/github/wulkanowy/api/login/StartPageTest.kt b/api/src/test/java/io/github/wulkanowy/api/login/StartPageTest.kt new file mode 100644 index 00000000..271b3bb0 --- /dev/null +++ b/api/src/test/java/io/github/wulkanowy/api/login/StartPageTest.kt @@ -0,0 +1,68 @@ +package io.github.wulkanowy.api.login + +import io.github.wulkanowy.api.Client +import io.github.wulkanowy.api.FixtureHelper +import io.github.wulkanowy.api.VulcanException +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito +import org.mockito.Mockito.mock + +class StartPageTest { + + private val client: Client = mock(Client::class.java) + + @Before fun setUp() { + Mockito.`when`(client.host).thenReturn("fakelog.cf") + } + + private fun getDoc(name: String): Document = Jsoup.parse(FixtureHelper.getAsString(javaClass.getResourceAsStream(name))) + + @Test fun getSchoolTest() { + assertEquals("534213", StartPage(client).getSchools(getDoc("../Start-std.html"))[0].id) + } + + @Test fun getMultiSchoolTest() { + val schools = StartPage(client).getSchools(getDoc("../Start-multi.html")) + + assertEquals("123456", schools[0].id) + assertEquals("123457", schools[1].id) + } + + @Test fun getSchoolNameTest() { + assertEquals("Uczeń", StartPage(client).getSchools(getDoc("../Start-std.html"))[0].name) + } + + @Test fun getMultiSchoolNameTest() { + val schools = StartPage(client).getSchools(getDoc("../Start-multi.html")) + + assertEquals("GIMBB", schools[0].name) + assertEquals("SPBB", schools[1].name) + } + + @Test(expected = VulcanException::class) + fun getSnpPageUrlWithWrongPage() { + StartPage(client).getSchools(getDoc("../OcenyWszystkie-semester.html")) + } + + @Test + fun getExtractedIDStandardTest() { + assertEquals("123456", StartPage(client) + .getExtractedIdFromUrl("https://uonetplus-opiekun.fakelog.cf/powiat/123456/Start/Index/")) + } + + @Test + fun getExtractedIDDemoTest() { + assertEquals("demo12345", StartPage(client) + .getExtractedIdFromUrl("https://uonetplus-opiekun.fakelog.cf/demoupowiat/demo12345/Start/Index/")) + } + + @Test(expected = VulcanException::class) + fun getExtractedIDNotLoggedTest() { + assertEquals("123", StartPage(client) + .getExtractedIdFromUrl("https://uonetplus.NOTfakelog.cf/powiat/")) + } +} diff --git a/api/src/test/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt b/api/src/test/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt new file mode 100644 index 00000000..f67310c8 --- /dev/null +++ b/api/src/test/java/io/github/wulkanowy/api/mobile/RegisterDevice.kt @@ -0,0 +1,17 @@ +package io.github.wulkanowy.api.mobile + +import io.github.wulkanowy.api.StudentAndParentTestCase +import org.junit.Assert.assertEquals +import org.junit.Test + +class RegisterDeviceTest : StudentAndParentTestCase() { + + @Test + fun getTokenTest() { + val registration = RegisterDevice(getSnp("Rejestruj.html")) + + assertEquals("3S1A1B2C", registration.getToken().token) + assertEquals("Default", registration.getToken().symbol) + assertEquals("1234567", registration.getToken().pin) + } +} diff --git a/api/src/test/java/io/github/wulkanowy/api/mobile/RegisteredDevicesListTest.kt b/api/src/test/java/io/github/wulkanowy/api/mobile/RegisteredDevicesListTest.kt new file mode 100644 index 00000000..3cd8b97c --- /dev/null +++ b/api/src/test/java/io/github/wulkanowy/api/mobile/RegisteredDevicesListTest.kt @@ -0,0 +1,37 @@ +package io.github.wulkanowy.api.mobile + +import io.github.wulkanowy.api.StudentAndParentTestCase +import org.junit.Assert.assertEquals +import org.junit.Test + +class RegisteredDevicesListTest : StudentAndParentTestCase() { + + private val filled = RegisteredDevices(getSnp("DostepMobilny-filled.html")) + + @Test + fun getListTest() { + assertEquals(2, filled.getList().size) + } + + @Test + fun getNameTest() { + assertEquals("google Android SDK built for x86", filled.getList()[0].name) + assertEquals("google (Android SDK) built for x86", filled.getList()[1].name) + } + + @Test + fun getSystemTest() { + assertEquals("Android 8.1.0", filled.getList()[0].system) + assertEquals("Android 8.1.0", filled.getList()[1].system) + } + + @Test + fun getDateTest() { + assertEquals("2018-01-20 22:35:30", filled.getList()[0].date) + } + + @Test + fun getIdTest() { + assertEquals(321, filled.getList()[0].id) + } +} diff --git a/api/src/test/java/io/github/wulkanowy/api/notes/NotesListTest.java b/api/src/test/java/io/github/wulkanowy/api/notes/NotesListTest.java index d76c0648..304eb01a 100644 --- a/api/src/test/java/io/github/wulkanowy/api/notes/NotesListTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/notes/NotesListTest.java @@ -30,8 +30,8 @@ public class NotesListTest extends StudentAndParentTestCase { public void getDateTest() throws Exception { List filledList = filled.getAllNotes(); - Assert.assertEquals("06.06.2017", filledList.get(0).getDate()); - Assert.assertEquals("01.10.2016", filledList.get(2).getDate()); + Assert.assertEquals("2017-06-06", filledList.get(0).getDate()); + Assert.assertEquals("2016-10-01", filledList.get(2).getDate()); } @Test diff --git a/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java b/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java index 65267634..7b3cce6e 100644 --- a/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java @@ -83,11 +83,11 @@ public class TimetableTest extends StudentAndParentTestCase { @Test public void getLessonNumberTest() throws Exception { - Assert.assertEquals("2", std.getWeekTable().getDay(0).getLesson(1).getNumber()); - Assert.assertEquals("5", std.getWeekTable().getDay(2).getLesson(4).getNumber()); - Assert.assertEquals("0", full.getWeekTable().getDay(0).getLesson(0).getNumber()); - Assert.assertEquals("13", full.getWeekTable().getDay(4).getLesson(13).getNumber()); - Assert.assertEquals("3", holidays.getWeekTable().getDay(3).getLesson(3).getNumber()); + Assert.assertEquals(2, std.getWeekTable().getDay(0).getLesson(1).getNumber()); + Assert.assertEquals(5, std.getWeekTable().getDay(2).getLesson(4).getNumber()); + Assert.assertEquals(0, full.getWeekTable().getDay(0).getLesson(0).getNumber()); + Assert.assertEquals(13, full.getWeekTable().getDay(4).getLesson(13).getNumber()); + Assert.assertEquals(3, holidays.getWeekTable().getDay(3).getLesson(3).getNumber()); } @Test @@ -105,11 +105,13 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertEquals("Zajęcia techniczne", std.getWeekTable().getDay(2).getLesson(4).getSubject()); Assert.assertEquals("Wychowanie fizyczne", std.getWeekTable().getDay(1).getLesson(1).getSubject()); Assert.assertEquals("Język angielski", full.getWeekTable().getDay(0).getLesson(1).getSubject()); + Assert.assertEquals("Wychowanie fizyczne", full.getWeekTable().getDay(0).getLesson(9).getSubject()); Assert.assertEquals("Wychowanie do życia w rodzinie", full.getWeekTable().getDay(2).getLesson(0).getSubject()); Assert.assertEquals("Wychowanie fizyczne", full.getWeekTable().getDay(3).getLesson(1).getSubject()); Assert.assertEquals("Uroczyste zakończenie roku szkolnego", full.getWeekTable().getDay(4).getLesson(0).getSubject()); Assert.assertEquals("Fizyka", full.getWeekTable().getDay(0).getLesson(0).getSubject()); Assert.assertEquals("Metodologia programowania", full.getWeekTable().getDay(1).getLesson(0).getSubject()); + Assert.assertEquals("Język niemiecki", full.getWeekTable().getDay(4).getLesson(2).getSubject()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getSubject()); } @@ -122,6 +124,7 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertEquals("Nowak Jadwiga", full.getWeekTable().getDay(2).getLesson(0).getTeacher()); Assert.assertEquals("Nowicka Irena", full.getWeekTable().getDay(3).getLesson(1).getTeacher()); Assert.assertEquals("Baran Małgorzata", full.getWeekTable().getDay(4).getLesson(0).getTeacher()); + Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getTeacher()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getTeacher()); } @@ -148,7 +151,13 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertEquals("zastępstwo (poprzednio: Religia)", full.getWeekTable().getDay(2).getLesson(0).getDescription()); Assert.assertEquals("zastępstwo (poprzednio: Wychowanie fizyczne)", full.getWeekTable().getDay(3).getLesson(1).getDescription()); Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(0).getDescription()); + Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getDescription()); + Assert.assertEquals("bez nawiasów (poprzednio: Religia)", full.getWeekTable().getDay(4).getLesson(3).getDescription()); + Assert.assertEquals("poprzednio: Wychowanie fizyczne", full.getWeekTable().getDay(4).getLesson(2).getDescription()); Assert.assertEquals("egzamin", full.getWeekTable().getDay(3).getLesson(0).getDescription()); + Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getDescription()); + Assert.assertEquals("poprzednio: Zajęcia z wychowawcą", full.getWeekTable().getDay(4).getLesson(5).getDescription()); + Assert.assertEquals("opis w uwadze bez klasy w spanie", full.getWeekTable().getDay(4).getLesson(4).getDescription()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getDescription()); } @@ -237,6 +246,8 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(2).isNewMovedInOrChanged()); Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(3).isNewMovedInOrChanged()); Assert.assertTrue(full.getWeekTable().getDay(3).getLesson(1).isNewMovedInOrChanged()); + Assert.assertFalse(full.getWeekTable().getDay(4).getLesson(1).isNewMovedInOrChanged()); + Assert.assertTrue(full.getWeekTable().getDay(4).getLesson(2).isNewMovedInOrChanged()); Assert.assertFalse(holidays.getWeekTable().getDay(3).getLesson(3).isNewMovedInOrChanged()); } } 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/Start-multi.html b/api/src/test/resources/io/github/wulkanowy/api/Start-multi.html new file mode 100644 index 00000000..98d385ee --- /dev/null +++ b/api/src/test/resources/io/github/wulkanowy/api/Start-multi.html @@ -0,0 +1,27 @@ + + + + + Uonet+ + + +
+
+
+ +
+
+
+ + diff --git a/api/src/test/resources/io/github/wulkanowy/api/Start.html b/api/src/test/resources/io/github/wulkanowy/api/Start-std.html similarity index 85% rename from api/src/test/resources/io/github/wulkanowy/api/Start.html rename to api/src/test/resources/io/github/wulkanowy/api/Start-std.html index 40e69161..574bd9b9 100644 --- a/api/src/test/resources/io/github/wulkanowy/api/Start.html +++ b/api/src/test/resources/io/github/wulkanowy/api/Start-std.html @@ -9,7 +9,7 @@
- + diff --git a/api/src/test/resources/io/github/wulkanowy/api/login/cert-no-symbols.xml b/api/src/test/resources/io/github/wulkanowy/api/login/cert-no-symbols.xml new file mode 100644 index 00000000..ca14bdf5 --- /dev/null +++ b/api/src/test/resources/io/github/wulkanowy/api/login/cert-no-symbols.xml @@ -0,0 +1,13 @@ + + + + + + + Default + + + + + + diff --git a/api/src/test/resources/io/github/wulkanowy/api/login/cert.xml b/api/src/test/resources/io/github/wulkanowy/api/login/cert-stock.xml similarity index 91% rename from api/src/test/resources/io/github/wulkanowy/api/login/cert.xml rename to api/src/test/resources/io/github/wulkanowy/api/login/cert-stock.xml index 549b2d42..31aa19b2 100644 --- a/api/src/test/resources/io/github/wulkanowy/api/login/cert.xml +++ b/api/src/test/resources/io/github/wulkanowy/api/login/cert-stock.xml @@ -1,9 +1,9 @@ - + - + Default demo12345 incorrect value diff --git a/api/src/test/resources/io/github/wulkanowy/api/mobile/DostepMobilny-filled.html b/api/src/test/resources/io/github/wulkanowy/api/mobile/DostepMobilny-filled.html new file mode 100644 index 00000000..9fbfd403 --- /dev/null +++ b/api/src/test/resources/io/github/wulkanowy/api/mobile/DostepMobilny-filled.html @@ -0,0 +1,44 @@ + + + + + Witryna ucznia i rodzica – dostęp mobilny + + + +
+

Dostęp mobilny

+ +
+

Zarejestrowane urządzenia

+
+ + + + + + + + + + + + + + + + + + + + +
UrządzenieData rejestracji
google Android SDK built for x86 (Android 8.1.0)20.01.2018 godz: 22:35:30 + Wyrejestruj +
google (Android SDK) built for x86 (Android 8.1.0)20.01.2018 godz: 22:35:30 + Wyrejestruj +
+
+ +
wersja: 18.01.0001.27311
+ + diff --git a/api/src/test/resources/io/github/wulkanowy/api/mobile/Rejestruj.html b/api/src/test/resources/io/github/wulkanowy/api/mobile/Rejestruj.html new file mode 100644 index 00000000..15d08d07 --- /dev/null +++ b/api/src/test/resources/io/github/wulkanowy/api/mobile/Rejestruj.html @@ -0,0 +1,26 @@ + + + + + Witryna ucznia i rodzica – Rejestracja urządzenia mobilnego + + + +
+

Rejestracja urządzenia mobilnego

+
+ Za pomocą aplikacji "Dzienniczek+" zeskanuj kod QR. + Kod QR + Token: 3S1A1B2C + Symbol: Default + PIN: 1234567 +
+
+
wersja: 18.01.0001.27311
+ + diff --git a/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html b/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html index 53eb0485..c3dbfebe 100644 --- a/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html +++ b/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html @@ -3,6 +3,40 @@ Witryna ucznia i rodzica – Plan lekcji +
@@ -82,13 +116,6 @@
-
- Metodologia programowania [zaw2] - - Baran Małgorzata - 36 - (zmiana organizacji zajęć) -
Wychowanie fizyczne [zaw2] @@ -96,6 +123,13 @@ G3 (przeniesiona z lekcji 7, 01.12.2017)
+
+ Metodologia programowania [zaw2] + + Baran Małgorzata + 36 + (zmiana organizacji zajęć) +
@@ -118,7 +152,18 @@ (zastępstwo)
- + +
+ Uroczyste rozpoczecie roku szkolnego 2017/2018 + + +
+
+ Uroczyste rozpoczecie roku szkolnego 2017/2018 + + +
+ 2 @@ -165,7 +210,20 @@
- + +
+ Język niemiecki [wf_grupa_2] + + + +
+
+ Wychowanie fizyczne [wf_grupa_2] + + Nauczycielel + 106 +
+ 3 @@ -214,7 +272,17 @@
- + +
+ Religia + Cyranka Krystian + 3 + Wychowanie do życia w rodzinie + Nowak Jadwiga + 3 + bez nawiasów +
+ 4 @@ -251,7 +319,16 @@ - + +
+ Język polski + + 16 + (oddział nieobecny) +
+ +
opis w uwadze bez klasy w spanie
+ 5 @@ -287,7 +364,20 @@ - + +
+ Tworzenie i administrowanie bazami danych [zaw2] + + + +
+
+ Zajęcia z wychowawcą + Małgorzata Kowal + 43 + (zmiana organizacji zajęć) +
+ 6 @@ -393,7 +483,18 @@ 9 14:50 15:35 - + +
+ Wychowanie fizyczne [zaw2] + + + G3 + (przeniesiona z lekcji 7, 01.12.2017) +
+
+ +
+
Język niemiecki [J1] diff --git a/android-sonarqube.gradle b/app/android-sonarqube.gradle similarity index 100% rename from android-sonarqube.gradle rename to app/android-sonarqube.gradle diff --git a/app/build.gradle b/app/build.gradle index d930afc0..dae3e0e7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,25 @@ +apply plugin: 'org.greenrobot.greendao' +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' // sync warning probably caused by bug https://issuetracker.google.com/issues/74537216 +apply plugin: 'io.fabric' +apply from: 'jacoco.gradle' +apply from: 'android-sonarqube.gradle' +apply plugin: 'com.google.gms.oss.licenses.plugin' +apply plugin: 'com.github.triplet.play' + buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } maven { url 'https://maven.fabric.io/public' } + google() } dependencies { - classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' - classpath 'io.fabric.tools:gradle:1.25.1' + classpath "org.greenrobot:greendao-gradle-plugin:$greenDaoGradle" + classpath "io.fabric.tools:gradle:$fabricGradle" + classpath "com.google.gms:oss-licenses:0.9.2" + classpath "com.github.triplet.gradle:play-publisher:$playPublisher" } } @@ -14,33 +27,46 @@ repositories { maven { url 'https://maven.fabric.io/public' } } -apply plugin: 'com.android.application' -apply plugin: 'org.greenrobot.greendao' -apply plugin: 'io.fabric' -apply from: '../jacoco.gradle' -apply from: '../android-sonarqube.gradle' - android { compileSdkVersion 27 - buildToolsVersion "27.0.3" + buildToolsVersion '27.0.3' + + playAccountConfigs { + defaultAccountConfig { + serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") + pk12File = file('key.p12') + } + } + defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" minSdkVersion 15 targetSdkVersion 27 - versionCode 3 - versionName "0.2.0" + versionCode 15 + versionName "0.5.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + playAccountConfig = playAccountConfigs.defaultAccountConfig manifestPlaceholders = [ fabricApiKey: System.getenv("FABRIC_API_KEY") ?: "null" ] } + signingConfigs { + release { + storeFile file("upload-key.jks") + storePassword System.getenv("PLAY_STORE_PASSWORD") + keyAlias System.getenv("PLAY_KEY_ALIAS") + keyPassword System.getenv("PLAY_KEY_PASSWORD") + } + } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } debug { applicationIdSuffix ".dev" @@ -61,49 +87,58 @@ android { } } +play { + track = 'alpha' + uploadImages = true +} + greendao { - schemaVersion 22 + schemaVersion 29 generateTests = true } +configurations.all { + resolutionStrategy.force "com.android.support:support-annotations:$supportVersion" +} + 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 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + 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.google.android.gms:play-services-oss-licenses:$ossLicenses" + implementation "com.jakewharton.timber:timber:$timber" + implementation "at.favre.lib:slf4j-timber:$slf4jTimber" - 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' + kapt "com.google.dagger:dagger-compiler:$dagger2" + kapt "com.google.dagger:dagger-android-processor:$dagger2" + kapt "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" - 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/jacoco.gradle b/app/jacoco.gradle new file mode 100644 index 00000000..f59b3857 --- /dev/null +++ b/app/jacoco.gradle @@ -0,0 +1,56 @@ +apply plugin: "jacoco" + +jacoco { + toolVersion "0.8.1" + reportsDir = file("$buildDir/reports") +} + +tasks.withType(Test) { + jacoco.includeNoLocationClasses = true +} + +// run ./gradlew clean createDebugCoverageReport jacocoTestReport +task jacocoTestReport(type: JacocoReport) { + + group = "Reporting" + description = "Generate Jacoco coverage reports" + + reports { + xml.enabled = true + html.enabled = true + } + + def excludes = [ + "**/R.class", + "**/R\$*.class", + "**/*\$ViewInjector*.*", + "**/BuildConfig.*", + "**/Manifest*.*", + "**/*Test*.*", + "android/**/*.*", + "**/*Fragment.*", + "**/*Activity.*" + ] + + // generated classes + classDirectories = fileTree( + // Java generated classes on Android project (debug build) + dir: "$buildDir/intermediates/classes/debug", + excludes: excludes + ) + fileTree( + // Kotlin generated classes on Android project (debug build) + dir: "$buildDir/tmp/kotlin-classes/debug", + excludes: excludes + ) + + // sources + sourceDirectories = files([ + android.sourceSets.main.java.srcDirs, + "src/main/kotlin" + ]) + + executionData = fileTree( + dir: project.projectDir, + includes: ["**/*.exec" , "**/*.ec"] + ) +} diff --git a/app/key-encrypted.p12 b/app/key-encrypted.p12 new file mode 100644 index 00000000..d9811213 Binary files /dev/null and b/app/key-encrypted.p12 differ 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..0aca371a 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() { @@ -15,13 +12,13 @@ public class AttendanceLessonTest extends AbstractDaoTestLongPk { protected Day createEntity(Long key) { Day entity = new Day(); entity.setId(key); - entity.setIsFreeDay(false); + entity.setFreeDay(false); return entity; } 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..2951aec8 --- /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.setCurrent(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/ExamTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/ExamTest.java new file mode 100644 index 00000000..271e811f --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/ExamTest.java @@ -0,0 +1,18 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class ExamTest extends AbstractDaoTestLongPk { + + public ExamTest() { + super(ExamDao.class); + } + + @Override + protected Exam createEntity(Long key) { + Exam entity = new Exam(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SchoolTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SchoolTest.java new file mode 100644 index 00000000..13f63e0d --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SchoolTest.java @@ -0,0 +1,22 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +import io.github.wulkanowy.data.db.dao.entities.School; +import io.github.wulkanowy.data.db.dao.entities.SchoolDao; + +public class SchoolTest extends AbstractDaoTestLongPk { + + public SchoolTest() { + super(SchoolDao.class); + } + + @Override + protected School createEntity(Long key) { + School entity = new School(); + entity.setId(key); + entity.setCurrent(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SemesterTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SemesterTest.java new file mode 100644 index 00000000..a18a7674 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SemesterTest.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.SemesterDao; + +public class SemesterTest extends AbstractDaoTestLongPk { + + public SemesterTest() { + super(SemesterDao.class); + } + + @Override + protected Semester createEntity(Long key) { + Semester entity = new Semester(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/StudentTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/StudentTest.java new file mode 100644 index 00000000..1ba8fd46 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/StudentTest.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +import io.github.wulkanowy.data.db.dao.entities.Student; +import io.github.wulkanowy.data.db.dao.entities.StudentDao; + +public class StudentTest extends AbstractDaoTestLongPk { + + public StudentTest() { + super(StudentDao.class); + } + + @Override + protected Student createEntity(Long key) { + Student entity = new Student(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SymbolTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SymbolTest.java new file mode 100644 index 00000000..e1bad201 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SymbolTest.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +import io.github.wulkanowy.data.db.dao.entities.Symbol; +import io.github.wulkanowy.data.db.dao.entities.SymbolDao; + +public class SymbolTest extends AbstractDaoTestLongPk { + + public SymbolTest() { + super(SymbolDao.class); + } + + @Override + protected Symbol createEntity(Long key) { + Symbol entity = new Symbol(); + entity.setId(key); + 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..190f09b7 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() { @@ -15,12 +12,12 @@ public class TimetableLessonTest extends AbstractDaoTestLongPk + android:label="@string/activity_dashboard_text" + android:launchMode="singleTop" + /> + + + + + + + + + + applicationInjector() { + return DaggerAppComponent.builder().create(this); } } 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..ac6a8b42 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Repository.java +++ b/app/src/main/java/io/github/wulkanowy/data/Repository.java @@ -1,159 +1,56 @@ package io.github.wulkanowy.data; -import java.io.IOException; -import java.text.ParseException; -import java.util.List; - import javax.inject.Inject; import javax.inject.Singleton; -import io.github.wulkanowy.api.VulcanException; -import io.github.wulkanowy.data.db.dao.entities.Account; -import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; -import io.github.wulkanowy.data.db.dao.entities.DaoSession; -import io.github.wulkanowy.data.db.dao.entities.Grade; -import io.github.wulkanowy.data.db.dao.entities.GradeDao; -import io.github.wulkanowy.data.db.dao.entities.Week; -import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.dao.DbContract; import io.github.wulkanowy.data.db.resources.ResourcesContract; import io.github.wulkanowy.data.db.shared.SharedPrefContract; import io.github.wulkanowy.data.sync.SyncContract; -import io.github.wulkanowy.data.sync.account.AccountSyncContract; -import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract; -import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; -import io.github.wulkanowy.di.annotations.SyncGrades; -import io.github.wulkanowy.di.annotations.SyncSubjects; -import io.github.wulkanowy.utils.security.CryptoException; @Singleton public class Repository implements RepositoryContract { - private final SharedPrefContract sharedPref; + private final DbContract database; private final ResourcesContract resources; - private final DaoSession daoSession; + private final SharedPrefContract sharedPref; - private final AccountSyncContract accountSync; - - private final AttendanceSyncContract attendanceSync; - - private final TimetableSyncContract timetableSync; - - private final SyncContract gradeSync; - - private final SyncContract subjectSync; + private final SyncContract synchronization; @Inject - Repository(SharedPrefContract sharedPref, - ResourcesContract resources, - DaoSession daoSession, - AccountSyncContract accountSync, - AttendanceSyncContract attendanceSync, - TimetableSyncContract timetableSync, - @SyncGrades SyncContract gradeSync, - @SyncSubjects SyncContract subjectSync) { - this.sharedPref = sharedPref; + Repository(DbContract database, ResourcesContract resources, SharedPrefContract sharedPref, + SyncContract synchronization) { + this.database = database; this.resources = resources; - this.daoSession = daoSession; - this.accountSync = accountSync; - this.attendanceSync = attendanceSync; - this.timetableSync = timetableSync; - this.gradeSync = gradeSync; - this.subjectSync = subjectSync; + this.sharedPref = sharedPref; + this.synchronization = synchronization; } @Override - public long getCurrentUserId() { - return sharedPref.getCurrentUserId(); + public SharedPrefContract getSharedRepo() { + return sharedPref; } @Override - public String[] getSymbolsKeysArray() { - return resources.getSymbolsKeysArray(); + public ResourcesContract getResRepo() { + return resources; } @Override - public String[] getSymbolsValuesArray() { - return resources.getSymbolsValuesArray(); + public DbContract getDbRepo() { + return database; } @Override - public String getErrorLoginMessage(Exception e) { - return resources.getErrorLoginMessage(e); + public SyncContract getSyncRepo() { + return synchronization; } @Override - public String getAttendanceLessonDescription(AttendanceLesson lesson) { - return resources.getAttendanceLessonDescription(lesson); - } - - @Override - public void registerUser(String email, String password, String symbol) throws VulcanException, - IOException, CryptoException { - accountSync.registerUser(email, password, symbol); - } - - @Override - public void initLastUser() throws VulcanException, IOException, CryptoException { - accountSync.initLastUser(); - } - - @Override - public void syncGrades() throws VulcanException, IOException, ParseException { - gradeSync.sync(); - } - - @Override - public void syncSubjects() throws VulcanException, IOException, ParseException { - subjectSync.sync(); - } - - @Override - public void syncAttendance() throws ParseException, IOException, VulcanException { - attendanceSync.syncAttendance(); - } - - @Override - public void syncAttendance(String date) throws ParseException, IOException, VulcanException { - attendanceSync.syncAttendance(date); - } - - @Override - public void syncTimetable() throws VulcanException, IOException, ParseException { - timetableSync.syncTimetable(); - } - - @Override - public void syncTimetable(String date) throws VulcanException, IOException, ParseException { - timetableSync.syncTimetable(date); - } - - @Override - public void syncAll() throws VulcanException, IOException, ParseException { - syncSubjects(); - syncGrades(); - syncAttendance(); - syncTimetable(); - } - - @Override - public Account getCurrentUser() { - return daoSession.getAccountDao().load(sharedPref.getCurrentUserId()); - } - - @Override - public Week getWeek(String date) { - return daoSession.getWeekDao().queryBuilder() - .where(WeekDao.Properties.StartDayDate.eq(date), - WeekDao.Properties.UserId.eq(getCurrentUserId())) - .unique(); - } - - @Override - public List getNewGrades() { - return daoSession.getGradeDao().queryBuilder() - .where(GradeDao.Properties.IsNew.eq(1)) - .list(); + public void cleanAllData() { + sharedPref.cleanSharedPref(); + database.recreateDatabase(); } } 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..e4dbd26b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java @@ -1,35 +1,22 @@ package io.github.wulkanowy.data; -import java.io.IOException; -import java.text.ParseException; -import java.util.List; - import javax.inject.Singleton; -import io.github.wulkanowy.api.VulcanException; -import io.github.wulkanowy.data.db.dao.entities.Account; -import io.github.wulkanowy.data.db.dao.entities.Grade; -import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.DbContract; import io.github.wulkanowy.data.db.resources.ResourcesContract; -import io.github.wulkanowy.data.sync.account.AccountSyncContract; -import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract; -import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; @Singleton -public interface RepositoryContract extends ResourcesContract, AccountSyncContract, - AttendanceSyncContract, TimetableSyncContract { +public interface RepositoryContract { - long getCurrentUserId(); + SharedPrefContract getSharedRepo(); - void syncGrades() throws VulcanException, IOException, ParseException; + ResourcesContract getResRepo(); - void syncSubjects() throws VulcanException, IOException, ParseException; + DbContract getDbRepo(); - void syncAll() throws VulcanException, IOException, ParseException; + SyncContract getSyncRepo(); - Account getCurrentUser(); - - Week getWeek(String date); - - List getNewGrades(); + void cleanAllData(); } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/DbContract.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbContract.java new file mode 100644 index 00000000..22eaa944 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbContract.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.data.db.dao; + +import java.util.List; + +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.Symbol; +import io.github.wulkanowy.data.db.dao.entities.Week; + +public interface DbContract { + + Week getWeek(String date); + + Week getWeek(long diaryId, String date); + + List getSubjectList(int semesterName); + + List getNewGrades(int semesterName); + + long getCurrentSchoolId(); + + long getCurrentStudentId(); + + long getCurrentSymbolId(); + + Symbol getCurrentSymbol(); + + long getCurrentDiaryId(); + + long getSemesterId(int name); + + long getCurrentSemesterId(); + + int getCurrentSemesterName(); + + void recreateDatabase(); +} 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..33b19311 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,59 +3,100 @@ 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.Named; 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.dao.migrations.Migration26; +import io.github.wulkanowy.data.db.dao.migrations.Migration27; +import io.github.wulkanowy.data.db.dao.migrations.Migration28; +import io.github.wulkanowy.data.db.dao.migrations.Migration29; import io.github.wulkanowy.data.db.shared.SharedPrefContract; -import io.github.wulkanowy.di.annotations.ApplicationContext; -import io.github.wulkanowy.di.annotations.DatabaseInfo; -import io.github.wulkanowy.utils.LogUtils; +import timber.log.Timber; @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) { + DbHelper(Context context, @Named("dbName") String dbName, + 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 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Timber.i("Cleaning user data oldVersion=%s newVersion=%s", oldVersion, newVersion); Database database = new StandardDatabase(db); - DaoMaster.dropAllTables(database, true); - onCreate(database); - sharedPref.setCurrentUserId(0); + recreateDatabase(database); + } - 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 { + Timber.i("Applying migration to db schema v%s...", migration.getVersion()); + migration.runMigration(db, sharedPref, vulcan); + Timber.i("Migration %s complete", migration.getVersion()); + } catch (Exception e) { + Timber.e(e, "Failed to apply migration"); + recreateDatabase(db); + break; + } + } + } + } + + private void recreateDatabase(Database db) { + Timber.i("Database is recreating..."); + sharedPref.setCurrentUserId(0); + DaoMaster.dropAllTables(db, true); + onCreate(db); + } + + private List getMigrations() { + List migrations = new ArrayList<>(); + migrations.add(new Migration23()); + migrations.add(new Migration26()); + migrations.add(new Migration27()); + migrations.add(new Migration28()); + migrations.add(new Migration29()); + + // 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/DbRepository.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbRepository.java new file mode 100644 index 00000000..d869bfe9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbRepository.java @@ -0,0 +1,132 @@ +package io.github.wulkanowy.data.db.dao; + +import org.greenrobot.greendao.database.Database; + +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.DiaryDao; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.GradeDao; +import io.github.wulkanowy.data.db.dao.entities.SchoolDao; +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.SemesterDao; +import io.github.wulkanowy.data.db.dao.entities.StudentDao; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.data.db.dao.entities.Symbol; +import io.github.wulkanowy.data.db.dao.entities.SymbolDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; + +public class DbRepository implements DbContract { + + private final DaoSession daoSession; + + private final SharedPrefContract sharedPref; + + @Inject + DbRepository(DaoSession daoSession, SharedPrefContract sharedPrefContract) { + this.daoSession = daoSession; + this.sharedPref = sharedPrefContract; + } + + + @Override + public Week getWeek(String date) { + return getWeek(getCurrentDiaryId(), date); + } + + @Override + public Week getWeek(long diaryId, String date) { + return daoSession.getWeekDao().queryBuilder().where( + WeekDao.Properties.StartDayDate.eq(date), + WeekDao.Properties.DiaryId.eq(diaryId) + ).unique(); + } + + @Override + public List getSubjectList(int semesterName) { + return daoSession.getSemesterDao().load(getSemesterId(semesterName)).getSubjectList(); + } + + @Override + public List getNewGrades(int semesterName) { + return daoSession.getGradeDao().queryBuilder().where( + GradeDao.Properties.IsNew.eq(1), + GradeDao.Properties.SemesterId.eq(getSemesterId(semesterName)) + ).list(); + } + + @Override + public Symbol getCurrentSymbol() { + return daoSession.getSymbolDao().queryBuilder().where( + SymbolDao.Properties.UserId.eq(sharedPref.getCurrentUserId()) + ).unique(); + } + + @Override + public long getCurrentSymbolId() { + return getCurrentSymbol().getId(); + } + + @Override + public long getCurrentSchoolId() { + return daoSession.getSchoolDao().queryBuilder().where( + SchoolDao.Properties.SymbolId.eq(getCurrentSymbolId()), + SchoolDao.Properties.Current.eq(true) + ).unique().getId(); + } + + @Override + public long getCurrentStudentId() { + return daoSession.getStudentDao().queryBuilder().where( + StudentDao.Properties.SchoolId.eq(getCurrentSchoolId()), + StudentDao.Properties.Current.eq(true) + ).unique().getId(); + } + + @Override + public long getCurrentDiaryId() { + return daoSession.getDiaryDao().queryBuilder().where( + DiaryDao.Properties.StudentId.eq(getCurrentStudentId()), + DiaryDao.Properties.Current.eq(true) + ).unique().getId(); + } + + @Override + public long getSemesterId(int name) { + return daoSession.getSemesterDao().queryBuilder().where( + SemesterDao.Properties.DiaryId.eq(getCurrentDiaryId()), + SemesterDao.Properties.Name.eq(String.valueOf(name)) + ).unique().getId(); + } + + @Override + public long getCurrentSemesterId() { + return getCurrentSemester().getId(); + } + + @Override + public int getCurrentSemesterName() { + return Integer.valueOf(getCurrentSemester().getName()); + } + + private Semester getCurrentSemester() { + return daoSession.getSemesterDao().queryBuilder().where( + SemesterDao.Properties.DiaryId.eq(getCurrentDiaryId()), + SemesterDao.Properties.Current.eq(true) + ).unique(); + } + + @Override + public void recreateDatabase() { + Database database = daoSession.getDatabase(); + + DaoMaster.dropAllTables(database, true); + DaoMaster.createAllTables(database, true); + } +} 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..78dff361 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 @@ -6,6 +6,7 @@ import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Property; import org.greenrobot.greendao.annotation.ToMany; +import org.greenrobot.greendao.annotation.Unique; import java.util.List; @@ -18,29 +19,15 @@ public class Account { @Id(autoincrement = true) private Long id; - @Property(nameInDb = "NAME") - private String name; - - @Property(nameInDb = "E-MAIL") + @Unique + @Property(nameInDb = "email") private String email; - @Property(nameInDb = "PASSWORD") + @Property(nameInDb = "password") private String password; - @Property(nameInDb = "SYMBOL") - private String symbol; - - @Property(nameInDb = "SNPID") - private String snpId; - @ToMany(referencedJoinProperty = "userId") - private List subjectList; - - @ToMany(referencedJoinProperty = "userId") - private List gradeList; - - @ToMany(referencedJoinProperty = "userId") - private List dayList; + private List symbolList; /** * Used to resolve relations @@ -54,15 +41,11 @@ 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 = 1104194311) + public Account(Long id, String email, String password) { this.id = id; - this.name = name; this.email = email; this.password = password; - this.symbol = symbol; - this.snpId = snpId; } @Generated(hash = 882125521) @@ -70,25 +53,15 @@ public class Account { } public Long getId() { - return id; + return this.id; } - public Account setId(Long id) { + public void setId(Long id) { this.id = id; - return this; - } - - public String getName() { - return name; - } - - public Account setName(String name) { - this.name = name; - return this; } public String getEmail() { - return email; + return this.email; } public Account setEmail(String email) { @@ -97,7 +70,7 @@ public class Account { } public String getPassword() { - return password; + return this.password; } public Account setPassword(String password) { @@ -105,82 +78,34 @@ public class Account { return this; } - public String getSymbol() { - return symbol; - } - - public Account setSymbol(String symbol) { - this.symbol = symbol; - return this; - } - - public String getSnpId() { - return this.snpId; - } - - public Account setSnpId(String snpId) { - this.snpId = snpId; - 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 = 1800750450) - public List getSubjectList() { - if (subjectList == null) { + @Generated(hash = 822972496) + public List getSymbolList() { + if (symbolList == null) { final DaoSession daoSession = this.daoSession; if (daoSession == null) { throw new DaoException("Entity is detached from DAO context"); } - SubjectDao targetDao = daoSession.getSubjectDao(); - List subjectListNew = targetDao._queryAccount_SubjectList(id); + SymbolDao targetDao = daoSession.getSymbolDao(); + List symbolListNew = targetDao._queryAccount_SymbolList(id); synchronized (this) { - if (subjectList == null) { - subjectList = subjectListNew; + if (symbolList == null) { + symbolList = symbolListNew; } } } - return subjectList; + return symbolList; } /** * Resets a to-many relationship, making the next get call to query for a fresh result. */ - @Generated(hash = 594294258) - public synchronized void resetSubjectList() { - subjectList = 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 = 1040074549) - public List getGradeList() { - if (gradeList == null) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - GradeDao targetDao = daoSession.getGradeDao(); - List gradeListNew = targetDao._queryAccount_GradeList(id); - synchronized (this) { - if (gradeList == null) { - gradeList = gradeListNew; - } - } - } - return gradeList; - } - - /** - * Resets a to-many relationship, making the next get call to query for a fresh result. - */ - @Generated(hash = 1939990047) - public synchronized void resetGradeList() { - gradeList = null; + @Generated(hash = 1716801695) + public synchronized void resetSymbolList() { + symbolList = null; } /** @@ -219,36 +144,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/AttendanceLesson.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLesson.java index 9250518a..81235e59 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLesson.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/AttendanceLesson.java @@ -20,38 +20,38 @@ public class AttendanceLesson implements Serializable { @Id(autoincrement = true) private Long id; - @Property(nameInDb = "DAY_ID") + @Property(nameInDb = "day_id") private Long dayId; - @Property(nameInDb = "DATE") + @Property(nameInDb = "date") private String date = ""; - @Property(nameInDb = "NUMBER_OF_LESSON") + @Property(nameInDb = "number_of_lesson") private int number = 0; - @Property(nameInDb = "SUBJECT_NAME") + @Property(nameInDb = "subject") private String subject = ""; - @Property(nameInDb = "IS_PRESENCE") - private boolean isPresence = false; + @Property(nameInDb = "presence") + private boolean presence = false; - @Property(nameInDb = "IS_ABSENCE_UNEXCUSED") - private boolean isAbsenceUnexcused = false; + @Property(nameInDb = "absence_unexcused") + private boolean absenceUnexcused = false; - @Property(nameInDb = "IS_ABSENCE_EXCUSED") - private boolean isAbsenceExcused = false; + @Property(nameInDb = "absence_excused") + private boolean absenceExcused = false; - @Property(nameInDb = "IS_UNEXCUSED_LATENESS") - private boolean isUnexcusedLateness = false; + @Property(nameInDb = "unexcused_lateness") + private boolean unexcusedLateness = false; - @Property(nameInDb = "IS_ABSENCE_FOR_SCHOOL_REASONS") - private boolean isAbsenceForSchoolReasons = false; + @Property(nameInDb = "absence_for_school_reasons") + private boolean absenceForSchoolReasons = false; - @Property(nameInDb = "IS_EXCUSED_LATENESS") - private boolean isExcusedLateness = false; + @Property(nameInDb = "excused_lateness") + private boolean excusedLateness = false; - @Property(nameInDb = "IS_EXEMPTION") - private boolean isExemption = false; + @Property(nameInDb = "exemption") + private boolean exemption = false; @Transient private String description = ""; @@ -70,24 +70,24 @@ public class AttendanceLesson implements Serializable { @Generated(hash = 1936953859) private transient AttendanceLessonDao myDao; - @Generated(hash = 1428129046) + @Generated(hash = 1741231228) public AttendanceLesson(Long id, Long dayId, String date, int number, - String subject, boolean isPresence, boolean isAbsenceUnexcused, - boolean isAbsenceExcused, boolean isUnexcusedLateness, - boolean isAbsenceForSchoolReasons, boolean isExcusedLateness, - boolean isExemption) { + String subject, boolean presence, boolean absenceUnexcused, + boolean absenceExcused, boolean unexcusedLateness, + boolean absenceForSchoolReasons, boolean excusedLateness, + boolean exemption) { this.id = id; this.dayId = dayId; this.date = date; this.number = number; this.subject = subject; - this.isPresence = isPresence; - this.isAbsenceUnexcused = isAbsenceUnexcused; - this.isAbsenceExcused = isAbsenceExcused; - this.isUnexcusedLateness = isUnexcusedLateness; - this.isAbsenceForSchoolReasons = isAbsenceForSchoolReasons; - this.isExcusedLateness = isExcusedLateness; - this.isExemption = isExemption; + this.presence = presence; + this.absenceUnexcused = absenceUnexcused; + this.absenceExcused = absenceExcused; + this.unexcusedLateness = unexcusedLateness; + this.absenceForSchoolReasons = absenceForSchoolReasons; + this.excusedLateness = excusedLateness; + this.exemption = exemption; } @Generated(hash = 921806575) @@ -137,66 +137,66 @@ public class AttendanceLesson implements Serializable { return this; } - public boolean getIsPresence() { - return this.isPresence; + public boolean getPresence() { + return this.presence; } - public AttendanceLesson setIsPresence(boolean isPresence) { - this.isPresence = isPresence; + public AttendanceLesson setPresence(boolean presence) { + this.presence = presence; return this; } - public boolean getIsAbsenceUnexcused() { - return this.isAbsenceUnexcused; + public boolean getAbsenceUnexcused() { + return this.absenceUnexcused; } - public AttendanceLesson setIsAbsenceUnexcused(boolean isAbsenceUnexcused) { - this.isAbsenceUnexcused = isAbsenceUnexcused; + public AttendanceLesson setAbsenceUnexcused(boolean absenceUnexcused) { + this.absenceUnexcused = absenceUnexcused; return this; } - public boolean getIsAbsenceExcused() { - return this.isAbsenceExcused; + public boolean getAbsenceExcused() { + return this.absenceExcused; } - public AttendanceLesson setIsAbsenceExcused(boolean isAbsenceExcused) { - this.isAbsenceExcused = isAbsenceExcused; + public AttendanceLesson setAbsenceExcused(boolean absenceExcused) { + this.absenceExcused = absenceExcused; return this; } - public boolean getIsUnexcusedLateness() { - return this.isUnexcusedLateness; + public boolean getUnexcusedLateness() { + return this.unexcusedLateness; } - public AttendanceLesson setIsUnexcusedLateness(boolean isUnexcusedLateness) { - this.isUnexcusedLateness = isUnexcusedLateness; + public AttendanceLesson setUnexcusedLateness(boolean unexcusedLateness) { + this.unexcusedLateness = unexcusedLateness; return this; } - public boolean getIsAbsenceForSchoolReasons() { - return this.isAbsenceForSchoolReasons; + public boolean getAbsenceForSchoolReasons() { + return this.absenceForSchoolReasons; } - public AttendanceLesson setIsAbsenceForSchoolReasons(boolean isAbsenceForSchoolReasons) { - this.isAbsenceForSchoolReasons = isAbsenceForSchoolReasons; + public AttendanceLesson setAbsenceForSchoolReasons(boolean absenceForSchoolReasons) { + this.absenceForSchoolReasons = absenceForSchoolReasons; return this; } - public boolean getIsExcusedLateness() { - return this.isExcusedLateness; + public boolean getExcusedLateness() { + return this.excusedLateness; } - public AttendanceLesson setIsExcusedLateness(boolean isExcusedLateness) { - this.isExcusedLateness = isExcusedLateness; + public AttendanceLesson setExcusedLateness(boolean excusedLateness) { + this.excusedLateness = excusedLateness; return this; } - public boolean getIsExemption() { - return this.isExemption; + public boolean getExemption() { + return this.exemption; } - public AttendanceLesson setIsExemption(boolean isExemption) { - this.isExemption = isExemption; + public AttendanceLesson setExemption(boolean exemption) { + this.exemption = exemption; return this; } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java index fb3ce414..c5472e98 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java @@ -14,59 +14,57 @@ import java.util.List; @Entity( nameInDb = "Days", active = true, - indexes = {@Index(value = "userId,weekId,date", unique = true)} + indexes = {@Index(value = "weekId,date", unique = true)} ) public class Day { @Id(autoincrement = true) private Long id; - @Property(nameInDb = "USER_ID") - private Long userId; - - @Property(nameInDb = "WEEK_ID") + @Property(nameInDb = "week_id") private Long weekId; - @Property(nameInDb = "DATE") + @Property(nameInDb = "date") private String date = ""; - @Property(nameInDb = "DAY_NAME") + @Property(nameInDb = "day_name") private String dayName = ""; - @Property(nameInDb = "IS_FREE_DAY") - private boolean isFreeDay = false; + @Property(nameInDb = "free_day") + private boolean freeDay = false; - @Property(nameInDb = "FREE_DAY_NAME") + @Property(nameInDb = "free_day_name") private String freeDayName = ""; + @OrderBy("number ASC") @ToMany(referencedJoinProperty = "dayId") private List timetableLessons; - @ToMany(referencedJoinProperty = "dayId") @OrderBy("number ASC") + @ToMany(referencedJoinProperty = "dayId") private List attendanceLessons; + @ToMany(referencedJoinProperty = "dayId") + private List exams; + /** * Used to resolve relations */ @Generated(hash = 2040040024) private transient DaoSession daoSession; - /** - * Used for active entity operations. - */ + /** Used for active entity operations. */ @Generated(hash = 312167767) private transient DayDao myDao; - @Generated(hash = 723729681) - public Day(Long id, Long userId, Long weekId, String date, String dayName, - boolean isFreeDay, String freeDayName) { + @Generated(hash = 523139020) + public Day(Long id, Long weekId, String date, String dayName, boolean freeDay, + String freeDayName) { this.id = id; - this.userId = userId; this.weekId = weekId; this.date = date; this.dayName = dayName; - this.isFreeDay = isFreeDay; + this.freeDay = freeDay; this.freeDayName = freeDayName; } @@ -82,26 +80,16 @@ public class Day { this.id = id; } - public Long getUserId() { - return userId; - } - public Long getWeekId() { - return weekId; + return this.weekId; } - public Day setWeekId(Long weekId) { + public void setWeekId(Long weekId) { this.weekId = weekId; - return this; - } - - public Day setUserId(Long userId) { - this.userId = userId; - return this; } public String getDate() { - return date; + return this.date; } public Day setDate(String date) { @@ -110,7 +98,7 @@ public class Day { } public String getDayName() { - return dayName; + return this.dayName; } public Day setDayName(String dayName) { @@ -118,17 +106,17 @@ public class Day { return this; } - public boolean getIsFreeDay() { - return this.isFreeDay; + public boolean getFreeDay() { + return this.freeDay; } - public Day setIsFreeDay(boolean isFreeDay) { - this.isFreeDay = isFreeDay; + public Day setFreeDay(boolean freeDay) { + this.freeDay = freeDay; return this; } public String getFreeDayName() { - return freeDayName; + return this.freeDayName; } public Day setFreeDayName(String freeDayName) { @@ -159,7 +147,9 @@ public class Day { return timetableLessons; } - /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ @Generated(hash = 1687683740) public synchronized void resetTimetableLessons() { timetableLessons = null; @@ -188,12 +178,44 @@ public class Day { return attendanceLessons; } - /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ @Generated(hash = 1343075564) public synchronized void resetAttendanceLessons() { attendanceLessons = 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 = 1231531946) + public List getExams() { + if (exams == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + ExamDao targetDao = daoSession.getExamDao(); + List examsNew = targetDao._queryDay_Exams(id); + synchronized (this) { + if (exams == null) { + exams = examsNew; + } + } + } + return exams; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 841969952) + public synchronized void resetExams() { + exams = null; + } + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. * Entity must attached to an entity context. @@ -236,4 +258,6 @@ public class Day { this.daoSession = daoSession; myDao = daoSession != null ? daoSession.getDayDao() : null; } + + } 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..b0b145da --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Diary.java @@ -0,0 +1,177 @@ +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; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Diaries", + active = true +) +public class Diary { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "student_id") + private Long studentId; + + @Property(nameInDb = "current") + private boolean current; + + @Property(nameInDb = "name") + private String name; + + @Property(nameInDb = "value") + private String value; + + @ToMany(referencedJoinProperty = "diaryId") + private List semesterList; + + /** + * 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 = 277096196) + public Diary(Long id, Long studentId, boolean current, String name, String value) { + this.id = id; + this.studentId = studentId; + this.current = current; + this.name = name; + this.value = value; + } + + @Generated(hash = 112123061) + public Diary() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getStudentId() { + return this.studentId; + } + + public Diary setStudentId(Long studentId) { + this.studentId = studentId; + return this; + } + + public String getName() { + return this.name; + } + + public Diary setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return this.value; + } + + public Diary setValue(String value) { + this.value = value; + return this; + } + + public boolean getCurrent() { + return this.current; + } + + public Diary setCurrent(boolean current) { + this.current = current; + 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 = 1738383053) + public List getSemesterList() { + if (semesterList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + SemesterDao targetDao = daoSession.getSemesterDao(); + List semesterListNew = targetDao._queryDiary_SemesterList(id); + synchronized (this) { + if (semesterList == null) { + semesterList = semesterListNew; + } + } + } + return semesterList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 995060657) + public synchronized void resetSemesterList() { + semesterList = null; + } + + /** + * 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/entities/Exam.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Exam.java new file mode 100644 index 00000000..543b6859 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Exam.java @@ -0,0 +1,177 @@ +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.Index; +import org.greenrobot.greendao.annotation.Property; + +import java.io.Serializable; + +@Entity( + nameInDb = "Exams", + active = true, + indexes = {@Index(value = "dayId,entryDate,subjectAndGroup,type,teacher", unique = true)} +) + +public class Exam implements Serializable { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "day_id") + private Long dayId; + + @Property(nameInDb = "subject_and_group") + private String subjectAndGroup = ""; + + @Property(nameInDb = "type") + private String type = ""; + + @Property(nameInDb = "description") + private String description = ""; + + @Property(nameInDb = "teacher") + private String teacher = ""; + + @Property(nameInDb = "entry_date") + private String entryDate = ""; + + private static final long serialVersionUID = 42L; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 973692038) + private transient ExamDao myDao; + + @Generated(hash = 998653360) + public Exam(Long id, Long dayId, String subjectAndGroup, String type, String description, + String teacher, String entryDate) { + this.id = id; + this.dayId = dayId; + this.subjectAndGroup = subjectAndGroup; + this.type = type; + this.description = description; + this.teacher = teacher; + this.entryDate = entryDate; + } + + @Generated(hash = 945526930) + public Exam() { + } + + public Long getId() { + return id; + } + + public Exam setId(Long id) { + this.id = id; + return this; + } + + public Long getDayId() { + return this.dayId; + } + + public Exam setDayId(Long dayId) { + this.dayId = dayId; + return this; + } + + public String getSubjectAndGroup() { + return subjectAndGroup; + } + + public Exam setSubjectAndGroup(String subjectAndGroup) { + this.subjectAndGroup = subjectAndGroup; + return this; + } + + public String getType() { + return type; + } + + public Exam setType(String type) { + this.type = type; + return this; + } + + public String getDescription() { + return description; + } + + public Exam setDescription(String description) { + this.description = description; + return this; + } + + public String getTeacher() { + return teacher; + } + + public Exam setTeacher(String teacher) { + this.teacher = teacher; + return this; + } + + public String getEntryDate() { + return entryDate; + } + + public Exam setEntryDate(String entryDate) { + this.entryDate = entryDate; + 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 = 1730563422) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getExamDao() : null; + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java index 632f9bcc..537b9e75 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java @@ -10,8 +10,6 @@ import org.greenrobot.greendao.annotation.Property; import java.io.Serializable; -import io.github.wulkanowy.R; - @Entity( nameInDb = "Grades", active = true @@ -21,72 +19,44 @@ public class Grade implements Serializable { @Id(autoincrement = true) protected Long id; - @Property(nameInDb = "SUBJECT_ID") + @Property(nameInDb = "semester_id") + private Long semesterId; + + @Property(nameInDb = "subject_id") private Long subjectId; - @Property(nameInDb = "USER_ID") - private Long userId; - - @Property(nameInDb = "SUBJECT") + @Property(nameInDb = "subject") private String subject = ""; - @Property(nameInDb = "VALUE") + @Property(nameInDb = "value") protected String value = ""; - @Property(nameInDb = "COLOR") - private String color = ""; - - @Property(nameInDb = "SYMBOL") - private String symbol = ""; - - @Property(nameInDb = "DESCRIPTION") - private String description = ""; - - @Property(nameInDb = "WEIGHT") + @Property(nameInDb = "weight") private String weight = ""; - @Property(nameInDb = "DATE") + @Property(nameInDb = "date") private String date = ""; - @Property(nameInDb = "TEACHER") + @Property(nameInDb = "symbol") + private String symbol = ""; + + @Property(nameInDb = "color") + private String color = ""; + + @Property(nameInDb = "description") + private String description = ""; + + @Property(nameInDb = "teacher") private String teacher = ""; - @Property(nameInDb = "SEMESTER") - private String semester = ""; - - @Property(nameInDb = "IS_NEW") + @Property(nameInDb = "is_new") private boolean isNew = false; - @Property(nameInDb = "READ") + @Property(nameInDb = "read") private boolean read = true; private static final long serialVersionUID = 42L; - @Generated(hash = 568899968) - public Grade(Long id, Long subjectId, Long userId, String subject, String value, - String color, String symbol, String description, String weight, - String date, String teacher, String semester, boolean isNew, - boolean read) { - this.id = id; - this.subjectId = subjectId; - this.userId = userId; - this.subject = subject; - this.value = value; - this.color = color; - this.symbol = symbol; - this.description = description; - this.weight = weight; - this.date = date; - this.teacher = teacher; - this.semester = semester; - this.isNew = isNew; - this.read = read; - } - - @Generated(hash = 2042976393) - public Grade() { - } - /** * Used to resolve relations */ @@ -99,29 +69,27 @@ public class Grade implements Serializable { @Generated(hash = 681281562) private transient GradeDao myDao; - public int getValueColor() { + @Generated(hash = 2042976393) + public Grade() { + } - String replacedString = value.replaceAll("[^0-9]", ""); - - if (!"".equals(replacedString)) { - switch (Integer.parseInt(replacedString)) { - case 6: - return R.color.six_grade; - case 5: - return R.color.five_grade; - case 4: - return R.color.four_grade; - case 3: - return R.color.three_grade; - case 2: - return R.color.two_grade; - case 1: - return R.color.one_grade; - default: - return R.color.default_grade; - } - } - return R.color.default_grade; + @Generated(hash = 619853992) + public Grade(Long id, Long semesterId, Long subjectId, String subject, String value, + String weight, String date, String symbol, String color, String description, + String teacher, boolean isNew, boolean read) { + this.id = id; + this.semesterId = semesterId; + this.subjectId = subjectId; + this.subject = subject; + this.value = value; + this.weight = weight; + this.date = date; + this.symbol = symbol; + this.color = color; + this.description = description; + this.teacher = teacher; + this.isNew = isNew; + this.read = read; } @Override @@ -133,6 +101,7 @@ public class Grade implements Serializable { Grade grade = (Grade) o; return new EqualsBuilder() + .append(semesterId, grade.semesterId) .append(subject, grade.subject) .append(value, grade.value) .append(color, grade.color) @@ -141,13 +110,13 @@ public class Grade implements Serializable { .append(weight, grade.weight) .append(date, grade.date) .append(teacher, grade.teacher) - .append(semester, grade.semester) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) + .append(semesterId) .append(subject) .append(value) .append(color) @@ -156,48 +125,19 @@ public class Grade implements Serializable { .append(weight) .append(date) .append(teacher) - .append(semester) .toHashCode(); } public Long getId() { - return id; + return this.id; } - public Grade setId(Long id) { + public void setId(Long id) { this.id = id; - return this; - } - - public Long getSubjectId() { - return subjectId; - } - - public Grade setSubjectId(Long subjectId) { - this.subjectId = subjectId; - return this; - } - - public Long getUserId() { - return userId; - } - - public Grade setUserId(Long userId) { - this.userId = userId; - return this; - } - - public String getSubject() { - return subject; - } - - public Grade setSubject(String subject) { - this.subject = subject; - return this; } public String getValue() { - return value; + return this.value; } public Grade setValue(String value) { @@ -205,8 +145,26 @@ public class Grade implements Serializable { return this; } + public Long getSemesterId() { + return this.semesterId; + } + + public Grade setSemesterId(Long semesterId) { + this.semesterId = semesterId; + return this; + } + + public String getSubject() { + return this.subject; + } + + public Grade setSubject(String subject) { + this.subject = subject; + return this; + } + public String getColor() { - return color; + return this.color; } public Grade setColor(String color) { @@ -215,7 +173,7 @@ public class Grade implements Serializable { } public String getSymbol() { - return symbol; + return this.symbol; } public Grade setSymbol(String symbol) { @@ -224,7 +182,7 @@ public class Grade implements Serializable { } public String getDescription() { - return description; + return this.description; } public Grade setDescription(String description) { @@ -233,7 +191,7 @@ public class Grade implements Serializable { } public String getWeight() { - return weight; + return this.weight; } public Grade setWeight(String weight) { @@ -242,7 +200,7 @@ public class Grade implements Serializable { } public String getDate() { - return date; + return this.date; } public Grade setDate(String date) { @@ -251,7 +209,7 @@ public class Grade implements Serializable { } public String getTeacher() { - return teacher; + return this.teacher; } public Grade setTeacher(String teacher) { @@ -259,22 +217,12 @@ public class Grade implements Serializable { return this; } - public String getSemester() { - return semester; - } - - public Grade setSemester(String semester) { - this.semester = semester; - return this; - } - public boolean getIsNew() { return this.isNew; } - public Grade setIsNew(boolean isNew) { + public void setIsNew(boolean isNew) { this.isNew = isNew; - return this; } public boolean getRead() { @@ -286,6 +234,17 @@ public class Grade implements Serializable { return this; } + + public Long getSubjectId() { + return this.subjectId; + } + + + public void setSubjectId(Long subjectId) { + this.subjectId = subjectId; + } + + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. * Entity must attached to an entity context. @@ -298,6 +257,7 @@ public class Grade implements Serializable { myDao.delete(this); } + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. * Entity must attached to an entity context. @@ -310,6 +270,7 @@ public class Grade implements Serializable { myDao.refresh(this); } + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. * Entity must attached to an entity context. @@ -322,6 +283,7 @@ public class Grade implements Serializable { myDao.update(this); } + /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 1187286414) public void __setDaoSession(DaoSession daoSession) { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/School.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/School.java new file mode 100644 index 00000000..59860e9e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/School.java @@ -0,0 +1,179 @@ +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; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Schools", + active = true +) +public class School { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "symbol_id") + private Long symbolId; + + @Property(nameInDb = "current") + private boolean current; + + @Property(nameInDb = "real_id") + private String realId; + + @Property(nameInDb = "name") + private String name; + + @ToMany(referencedJoinProperty = "schoolId") + private List studentList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 1796006707) + private transient SchoolDao myDao; + + @Generated(hash = 975562398) + public School(Long id, Long symbolId, boolean current, String realId, + String name) { + this.id = id; + this.symbolId = symbolId; + this.current = current; + this.realId = realId; + this.name = name; + } + + @Generated(hash = 1579966795) + public School() { + } + + public Long getId() { + return this.id; + } + + public School setId(Long id) { + this.id = id; + return this; + } + + public Long getSymbolId() { + return this.symbolId; + } + + public School setSymbolId(Long symbolId) { + this.symbolId = symbolId; + return this; + } + + public boolean getCurrent() { + return this.current; + } + + public School setCurrent(boolean current) { + this.current = current; + return this; + } + + public String getRealId() { + return this.realId; + } + + public School setRealId(String realId) { + this.realId = realId; + return this; + } + + public String getName() { + return this.name; + } + + public School setName(String name) { + this.name = name; + 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 = 180118651) + public List getStudentList() { + if (studentList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + StudentDao targetDao = daoSession.getStudentDao(); + List studentListNew = targetDao._querySchool_StudentList(id); + synchronized (this) { + if (studentList == null) { + studentList = studentListNew; + } + } + } + return studentList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1628625923) + public synchronized void resetStudentList() { + studentList = null; + } + + /** + * 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 = 234091322) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getSchoolDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Semester.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Semester.java new file mode 100644 index 00000000..c739688f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Semester.java @@ -0,0 +1,208 @@ +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; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Semesters", + active = true +) +public class Semester { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "diary_id") + private Long diaryId; + + @Property(nameInDb = "current") + private boolean current; + + @Property(nameInDb = "name") + private String name; + + @Property(nameInDb = "value") + private String value; + + @ToMany(referencedJoinProperty = "semesterId") + private List subjectList; + + @ToMany(referencedJoinProperty = "semesterId") + private List gradeList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 282930393) + private transient SemesterDao myDao; + + @Generated(hash = 1661077309) + public Semester(Long id, Long diaryId, boolean current, String name, String value) { + this.id = id; + this.diaryId = diaryId; + this.current = current; + this.name = name; + this.value = value; + } + + @Generated(hash = 58335877) + public Semester() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getDiaryId() { + return this.diaryId; + } + + public Semester setDiaryId(Long diaryId) { + this.diaryId = diaryId; + return this; + } + + public String getName() { + return this.name; + } + + public Semester setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return this.value; + } + + public Semester setValue(String value) { + this.value = value; + return this; + } + + public boolean getCurrent() { + return this.current; + } + + public Semester setCurrent(boolean current) { + this.current = current; + 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 = 723353662) + public List getSubjectList() { + if (subjectList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + SubjectDao targetDao = daoSession.getSubjectDao(); + List subjectListNew = targetDao._querySemester_SubjectList(id); + synchronized (this) { + if (subjectList == null) { + subjectList = subjectListNew; + } + } + } + return subjectList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 594294258) + public synchronized void resetSubjectList() { + subjectList = null; + } + + /** + * 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); + } + + /** + * 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 = 390330007) + public List getGradeList() { + if (gradeList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + GradeDao targetDao = daoSession.getGradeDao(); + List gradeListNew = targetDao._querySemester_GradeList(id); + synchronized (this) { + if (gradeList == null) { + gradeList = gradeListNew; + } + } + } + return gradeList; + } + + /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + @Generated(hash = 1939990047) + public synchronized void resetGradeList() { + gradeList = null; + } + + /** called by internal mechanisms, do not call yourself. */ + @Generated(hash = 676204164) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getSemesterDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Student.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Student.java new file mode 100644 index 00000000..1d545473 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Student.java @@ -0,0 +1,178 @@ +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; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Students", + active = true +) +public class Student { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "school_id") + private Long schoolId; + + @Property(nameInDb = "current") + private boolean current; + + @Property(nameInDb = "real_id") + private String realId; + + @Property(nameInDb = "name") + private String name; + + @ToMany(referencedJoinProperty = "studentId") + private List diaryList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 1943931642) + private transient StudentDao myDao; + + @Generated(hash = 470181623) + public Student(Long id, Long schoolId, boolean current, String realId, String name) { + this.id = id; + this.schoolId = schoolId; + this.current = current; + this.realId = realId; + this.name = name; + } + + @Generated(hash = 1556870573) + public Student() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSchoolId() { + return this.schoolId; + } + + public Student setSchoolId(Long schoolId) { + this.schoolId = schoolId; + return this; + } + + public String getRealId() { + return this.realId; + } + + public Student setRealId(String realId) { + this.realId = realId; + return this; + } + + public String getName() { + return this.name; + } + + public Student setName(String name) { + this.name = name; + return this; + } + + public boolean getCurrent() { + return this.current; + } + + public Student setCurrent(boolean current) { + this.current = current; + 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 = 508305571) + 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._queryStudent_DiaryList(id); + 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; + } + + /** + * 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 = 1701634981) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getStudentDao() : null; + } + +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java index 6bcce622..285cd7c0 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java @@ -18,21 +18,18 @@ public class Subject { @Id(autoincrement = true) private Long id; - @Property(nameInDb = "USER_ID") - private Long userId; + @Property(nameInDb = "semester_id") + private Long semesterId; - @Property(nameInDb = "NAME") + @Property(nameInDb = "name") private String name; - @Property(nameInDb = "PREDICTED_RATING") + @Property(nameInDb = "predicted_rating") private String predictedRating; - @Property(nameInDb = "FINAL_RATING") + @Property(nameInDb = "final_rating") private String finalRating; - @Property(nameInDb = "SEMESTER") - private String semester; - @ToMany(referencedJoinProperty = "subjectId") private List gradeList; @@ -48,15 +45,14 @@ public class Subject { @Generated(hash = 1644932788) private transient SubjectDao myDao; - @Generated(hash = 396325764) - public Subject(Long id, Long userId, String name, String predictedRating, - String finalRating, String semester) { + @Generated(hash = 1817932538) + public Subject(Long id, Long semesterId, String name, String predictedRating, + String finalRating) { this.id = id; - this.userId = userId; + this.semesterId = semesterId; this.name = name; this.predictedRating = predictedRating; this.finalRating = finalRating; - this.semester = semester; } @Generated(hash = 1617906264) @@ -64,25 +60,24 @@ public class Subject { } public Long getId() { - return id; + return this.id; } - public Subject setId(Long id) { + public void setId(Long id) { this.id = id; - return this; } - public Long getUserId() { - return userId; + public Long getSemesterId() { + return this.semesterId; } - public Subject setUserId(Long userId) { - this.userId = userId; + public Subject setSemesterId(Long semesterId) { + this.semesterId = semesterId; return this; } public String getName() { - return name; + return this.name; } public Subject setName(String name) { @@ -91,7 +86,7 @@ public class Subject { } public String getPredictedRating() { - return predictedRating; + return this.predictedRating; } public Subject setPredictedRating(String predictedRating) { @@ -100,7 +95,7 @@ public class Subject { } public String getFinalRating() { - return finalRating; + return this.finalRating; } public Subject setFinalRating(String finalRating) { @@ -108,45 +103,6 @@ public class Subject { return this; } - public String getSemester() { - return semester; - } - - public Subject setSemester(String semester) { - this.semester = semester; - 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 = 1358847893) - public List getGradeList() { - if (gradeList == null) { - final DaoSession daoSession = this.daoSession; - if (daoSession == null) { - throw new DaoException("Entity is detached from DAO context"); - } - GradeDao targetDao = daoSession.getGradeDao(); - List gradeListNew = targetDao._querySubject_GradeList(id); - synchronized (this) { - if (gradeList == null) { - gradeList = gradeListNew; - } - } - } - return gradeList; - } - - /** - * Resets a to-many relationship, making the next get call to query for a fresh result. - */ - @Generated(hash = 1939990047) - public synchronized void resetGradeList() { - gradeList = null; - } - /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. * Entity must attached to an entity context. @@ -183,6 +139,34 @@ public class Subject { 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 = 1358847893) + public List getGradeList() { + if (gradeList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + GradeDao targetDao = daoSession.getGradeDao(); + List gradeListNew = targetDao._querySubject_GradeList(id); + synchronized (this) { + if (gradeList == null) { + gradeList = gradeListNew; + } + } + } + return gradeList; + } + + /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + @Generated(hash = 1939990047) + public synchronized void resetGradeList() { + gradeList = null; + } + /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 937984622) public void __setDaoSession(DaoSession daoSession) { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Symbol.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Symbol.java new file mode 100644 index 00000000..7cc46111 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Symbol.java @@ -0,0 +1,175 @@ +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; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Symbols", + active = true +) +public class Symbol { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "user_id") + private Long userId; + + @Property(nameInDb = "host") + private String host; + + @Property(nameInDb = "symbol") + private String symbol; + + @Property(nameInDb = "type") + private String type; + + @ToMany(referencedJoinProperty = "symbolId") + private List schoolList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 684907977) + private transient SymbolDao myDao; + + @Generated(hash = 1034469460) + public Symbol(Long id, Long userId, String host, String symbol, String type) { + this.id = id; + this.userId = userId; + this.host = host; + this.symbol = symbol; + this.type = type; + } + + @Generated(hash = 460475327) + public Symbol() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return this.userId; + } + + public Symbol setUserId(Long userId) { + this.userId = userId; + return this; + } + + public String getHost() { + return this.host; + } + + public Symbol setHost(String host) { + this.host = host; + return this; + } + + public String getSymbol() { + return this.symbol; + } + + public Symbol setSymbol(String symbol) { + this.symbol = symbol; + return this; + } + + public String getType() { + return this.type; + } + + public Symbol setType(String type) { + this.type = type; + 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 = 1733082867) + public List getSchoolList() { + if (schoolList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + SchoolDao targetDao = daoSession.getSchoolDao(); + List schoolListNew = targetDao._querySymbol_SchoolList(id); + synchronized (this) { + if (schoolList == null) { + schoolList = schoolListNew; + } + } + } + return schoolList; + } + + /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + @Generated(hash = 1757777300) + public synchronized void resetSchoolList() { + schoolList = null; + } + + /** + * 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 = 632145708) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getSymbolDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/TimetableLesson.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/TimetableLesson.java index 390f679e..f3fdf6a8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/TimetableLesson.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/TimetableLesson.java @@ -1,5 +1,7 @@ package io.github.wulkanowy.data.db.dao.entities; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; import org.greenrobot.greendao.annotation.Generated; @@ -12,62 +14,62 @@ import java.io.Serializable; @Entity( nameInDb = "TimetableLessons", active = true, - indexes = {@Index(value = "dayId,date,startTime,endTime", unique = true)} + indexes = {@Index(value = "dayId,date,number,startTime,endTime", unique = true)} ) public class TimetableLesson implements Serializable { + private static final long serialVersionUID = 42L; + @Id(autoincrement = true) private Long id; - @Property(nameInDb = "DAY_ID") + @Property(nameInDb = "day_id") private Long dayId; - @Property(nameInDb = "NUMBER_OF_LESSON") - private String number; + @Property(nameInDb = "number") + private int number = 0; - @Property(nameInDb = "SUBJECT_NAME") + @Property(nameInDb = "subject") private String subject = ""; - @Property(nameInDb = "TEACHER") + @Property(nameInDb = "teacher") private String teacher = ""; - @Property(nameInDb = "ROOM") + @Property(nameInDb = "room") private String room = ""; - @Property(nameInDb = "DESCRIPTION") + @Property(nameInDb = "description") private String description = ""; - @Property(nameInDb = "GROUP_NAME") - private String groupName = ""; + @Property(nameInDb = "group") + private String group = ""; - @Property(nameInDb = "START_TIME") + @Property(nameInDb = "start_time") private String startTime = ""; - @Property(nameInDb = "END_TIME") + @Property(nameInDb = "end_time") private String endTime = ""; - @Property(nameInDb = "DATE") + @Property(nameInDb = "date") private String date = ""; - @Property(nameInDb = "IS_EMPTY") - private boolean isEmpty = false; + @Property(nameInDb = "empty") + private boolean empty = false; - @Property(nameInDb = "IS_DIVISION_INTO_GROUP") - private boolean isDivisionIntoGroups = false; + @Property(nameInDb = "division_into_groups") + private boolean divisionIntoGroups = false; - @Property(nameInDb = "IS_PLANNING") - private boolean isPlanning = false; + @Property(nameInDb = "planning") + private boolean planning = false; - @Property(nameInDb = "IS_REALIZED") - private boolean isRealized = false; + @Property(nameInDb = "realized") + private boolean realized = false; - @Property(nameInDb = "IS_MOVED_CANCELED") - private boolean isMovedOrCanceled = false; + @Property(nameInDb = "moved_canceled") + private boolean movedOrCanceled = false; - @Property(nameInDb = "IS_NEW_MOVED_IN_CANCELED") - private boolean isNewMovedInOrChanged = false; - - private static final long serialVersionUID = 42L; + @Property(nameInDb = "new_moved_in_canceled") + private boolean newMovedInOrChanged = false; /** * Used to resolve relations @@ -75,18 +77,15 @@ public class TimetableLesson implements Serializable { @Generated(hash = 2040040024) private transient DaoSession daoSession; - /** - * Used for active entity operations. - */ + /** Used for active entity operations. */ @Generated(hash = 1119360138) private transient TimetableLessonDao myDao; - @Generated(hash = 627457324) - public TimetableLesson(Long id, Long dayId, String number, String subject, - String teacher, String room, String description, String groupName, - String startTime, String endTime, String date, boolean isEmpty, - boolean isDivisionIntoGroups, boolean isPlanning, boolean isRealized, - boolean isMovedOrCanceled, boolean isNewMovedInOrChanged) { + @Generated(hash = 1665905034) + public TimetableLesson(Long id, Long dayId, int number, String subject, String teacher, + String room, String description, String group, String startTime, String endTime, + String date, boolean empty, boolean divisionIntoGroups, boolean planning, + boolean realized, boolean movedOrCanceled, boolean newMovedInOrChanged) { this.id = id; this.dayId = dayId; this.number = number; @@ -94,22 +93,22 @@ public class TimetableLesson implements Serializable { this.teacher = teacher; this.room = room; this.description = description; - this.groupName = groupName; + this.group = group; this.startTime = startTime; this.endTime = endTime; this.date = date; - this.isEmpty = isEmpty; - this.isDivisionIntoGroups = isDivisionIntoGroups; - this.isPlanning = isPlanning; - this.isRealized = isRealized; - this.isMovedOrCanceled = isMovedOrCanceled; - this.isNewMovedInOrChanged = isNewMovedInOrChanged; + this.empty = empty; + this.divisionIntoGroups = divisionIntoGroups; + this.planning = planning; + this.realized = realized; + this.movedOrCanceled = movedOrCanceled; + this.newMovedInOrChanged = newMovedInOrChanged; } @Generated(hash = 1878030142) public TimetableLesson() { } - + public Long getId() { return this.id; } @@ -122,15 +121,16 @@ public class TimetableLesson implements Serializable { return this.dayId; } - public void setDayId(Long dayId) { + public TimetableLesson setDayId(Long dayId) { this.dayId = dayId; + return this; } - public String getNumber() { + public int getNumber() { return this.number; } - public TimetableLesson setNumber(String number) { + public TimetableLesson setNumber(int number) { this.number = number; return this; } @@ -171,12 +171,12 @@ public class TimetableLesson implements Serializable { return this; } - public String getGroupName() { - return this.groupName; + public String getGroup() { + return this.group; } - public TimetableLesson setGroupName(String groupName) { - this.groupName = groupName; + public TimetableLesson setGroup(String group) { + this.group = group; return this; } @@ -207,60 +207,86 @@ public class TimetableLesson implements Serializable { return this; } - public boolean getIsEmpty() { - return this.isEmpty; + public boolean getEmpty() { + return this.empty; } - public TimetableLesson setEmpty(boolean isEmpty) { - this.isEmpty = isEmpty; + public TimetableLesson setEmpty(boolean empty) { + this.empty = empty; return this; } - public boolean getIsDivisionIntoGroups() { - return this.isDivisionIntoGroups; + public boolean getDivisionIntoGroups() { + return this.divisionIntoGroups; } - public TimetableLesson setDivisionIntoGroups(boolean isDivisionIntoGroups) { - this.isDivisionIntoGroups = isDivisionIntoGroups; + public TimetableLesson setDivisionIntoGroups(boolean divisionIntoGroups) { + this.divisionIntoGroups = divisionIntoGroups; return this; } - public boolean getIsPlanning() { - return this.isPlanning; + public boolean getPlanning() { + return this.planning; } - public TimetableLesson setPlanning(boolean isPlanning) { - this.isPlanning = isPlanning; + public TimetableLesson setPlanning(boolean planning) { + this.planning = planning; return this; } - public boolean getIsRealized() { - return this.isRealized; + public boolean getRealized() { + return this.realized; } - public TimetableLesson setRealized(boolean isRealized) { - this.isRealized = isRealized; + public TimetableLesson setRealized(boolean realized) { + this.realized = realized; return this; } - public boolean getIsMovedOrCanceled() { - return this.isMovedOrCanceled; + public boolean getMovedOrCanceled() { + return this.movedOrCanceled; } - public TimetableLesson setMovedOrCanceled(boolean isMovedOrCanceled) { - this.isMovedOrCanceled = isMovedOrCanceled; + public TimetableLesson setMovedOrCanceled(boolean movedOrCanceled) { + this.movedOrCanceled = movedOrCanceled; return this; } - public boolean getIsNewMovedInOrChanged() { - return this.isNewMovedInOrChanged; + public boolean getNewMovedInOrChanged() { + return this.newMovedInOrChanged; } - public TimetableLesson setNewMovedInOrChanged(boolean isNewMovedInOrChanged) { - this.isNewMovedInOrChanged = isNewMovedInOrChanged; + public TimetableLesson setNewMovedInOrChanged(boolean newMovedInOrChanged) { + this.newMovedInOrChanged = newMovedInOrChanged; return this; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + TimetableLesson lesson = (TimetableLesson) o; + + return new EqualsBuilder() + .append(number, lesson.number) + .append(startTime, lesson.startTime) + .append(endTime, lesson.endTime) + .append(date, lesson.date) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(number) + .append(startTime) + .append(endTime) + .append(date) + .toHashCode(); + } + /** * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. * Entity must attached to an entity context. @@ -297,30 +323,6 @@ public class TimetableLesson implements Serializable { myDao.update(this); } - public void setIsEmpty(boolean isEmpty) { - this.isEmpty = isEmpty; - } - - public void setIsDivisionIntoGroups(boolean isDivisionIntoGroups) { - this.isDivisionIntoGroups = isDivisionIntoGroups; - } - - public void setIsPlanning(boolean isPlanning) { - this.isPlanning = isPlanning; - } - - public void setIsRealized(boolean isRealized) { - this.isRealized = isRealized; - } - - public void setIsMovedOrCanceled(boolean isMovedOrCanceled) { - this.isMovedOrCanceled = isMovedOrCanceled; - } - - public void setIsNewMovedInOrChanged(boolean isNewMovedInOrChanged) { - this.isNewMovedInOrChanged = isNewMovedInOrChanged; - } - /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 1885258429) public void __setDaoSession(DaoSession daoSession) { diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java index 778f6ca3..1edfa245 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java @@ -13,44 +13,52 @@ import java.util.List; @Entity( nameInDb = "Weeks", active = true, - indexes ={@Index(value = "userId,startDayDate", unique = true)} + indexes = {@Index(value = "diaryId,startDayDate", unique = true)} ) public class Week { @Id(autoincrement = true) private Long id; - @Property(nameInDb = "USER_ID") - private Long userId; + @Property(nameInDb = "diary_id") + private Long diaryId; - @Property(nameInDb = "START_DATE") + @Property(nameInDb = "start_day_date") private String startDayDate = ""; - @Property(nameInDb = "IS_ATTENDANCE_SYNCED") - private boolean isAttendanceSynced = false; + @Property(nameInDb = "attendance_synced") + private boolean attendanceSynced = false; - @Property(nameInDb = "IS_TIMETABLE_SYNCED") - private boolean isTimetableSynced = false; + @Property(nameInDb = "timetable_synced") + private boolean timetableSynced = false; + + @Property(nameInDb = "exams_synced") + private boolean examsSynced = false; @ToMany(referencedJoinProperty = "weekId") private List dayList; - /** Used to resolve relations */ + /** + * Used to resolve relations + */ @Generated(hash = 2040040024) private transient DaoSession daoSession; - /** Used for active entity operations. */ + /** + * Used for active entity operations. + */ @Generated(hash = 1019310398) private transient WeekDao myDao; - @Generated(hash = 1745118398) - public Week(Long id, Long userId, String startDayDate, boolean isAttendanceSynced, - boolean isTimetableSynced) { + @Generated(hash = 23357599) + public Week(Long id, Long diaryId, String startDayDate, boolean attendanceSynced, + boolean timetableSynced, boolean examsSynced) { this.id = id; - this.userId = userId; + this.diaryId = diaryId; this.startDayDate = startDayDate; - this.isAttendanceSynced = isAttendanceSynced; - this.isTimetableSynced = isTimetableSynced; + this.attendanceSynced = attendanceSynced; + this.timetableSynced = timetableSynced; + this.examsSynced = examsSynced; } @Generated(hash = 2135529658) @@ -66,12 +74,12 @@ public class Week { return this; } - public Long getUserId() { - return userId; + public Long getDiaryId() { + return diaryId; } - public Week setUserId(Long userId) { - this.userId = userId; + public Week setDiaryId(Long diaryId) { + this.diaryId = diaryId; return this; } @@ -84,20 +92,31 @@ public class Week { return this; } - public boolean getIsAttendanceSynced() { - return this.isAttendanceSynced; + public boolean getAttendanceSynced() { + return this.attendanceSynced; } - public void setIsAttendanceSynced(boolean isAttendanceSynced) { - this.isAttendanceSynced = isAttendanceSynced; + public Week setAttendanceSynced(boolean attendanceSynced) { + this.attendanceSynced = attendanceSynced; + return this; } - public boolean getIsTimetableSynced() { - return this.isTimetableSynced; + public boolean getTimetableSynced() { + return this.timetableSynced; } - public void setIsTimetableSynced(boolean isTimetableSynced) { - this.isTimetableSynced = isTimetableSynced; + public Week setTimetableSynced(boolean timetableSynced) { + this.timetableSynced = timetableSynced; + return this; + } + + public Week setExamsSynced(boolean examsSynced) { + this.examsSynced = examsSynced; + return this; + } + + public boolean getExamsSynced() { + return examsSynced; } /** @@ -122,7 +141,9 @@ public class Week { return dayList; } - /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + /** + * 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; 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..b2a41ad1 --- /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.Vulcan; +import io.github.wulkanowy.api.generic.Diary; +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.getId() + "\"," + + "\"" + 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/dao/migrations/Migration26.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration26.java new file mode 100644 index 00000000..332f2dd2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration26.java @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.dao.migrations; + +import org.greenrobot.greendao.database.Database; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; + +public class Migration26 implements DbHelper.Migration { + + @Override + public Integer getVersion() { + return 26; + } + + @Override + public void runMigration(final Database db, final SharedPrefContract sharedPref, final Vulcan vulcan) throws Exception { + throw new Exception("No migrations"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration27.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration27.java new file mode 100644 index 00000000..922079f4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration27.java @@ -0,0 +1,24 @@ +package io.github.wulkanowy.data.db.dao.migrations; + +import org.greenrobot.greendao.database.Database; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.dao.entities.ExamDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; + +public class Migration27 implements DbHelper.Migration { + + @Override + public Integer getVersion() { + return 27; + } + + @Override + public void runMigration(Database db, SharedPrefContract sharedPref, Vulcan vulcan) { + ExamDao.dropTable(db, true); + ExamDao.createTable(db, true); + + db.execSQL("UPDATE Weeks SET exams_synced = 0"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration28.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration28.java new file mode 100644 index 00000000..3970df4c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration28.java @@ -0,0 +1,20 @@ +package io.github.wulkanowy.data.db.dao.migrations; + +import org.greenrobot.greendao.database.Database; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; + +public class Migration28 implements DbHelper.Migration { + + @Override + public Integer getVersion() { + return 28; + } + + @Override + public void runMigration(final Database db, final SharedPrefContract sharedPref, final Vulcan vulcan) throws Exception { + throw new Exception("No migrations"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration29.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration29.kt new file mode 100644 index 00000000..cb2fccb1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/migrations/Migration29.kt @@ -0,0 +1,60 @@ +package io.github.wulkanowy.data.db.dao.migrations + +import android.database.Cursor + +import org.greenrobot.greendao.database.Database + +import io.github.wulkanowy.api.Vulcan +import io.github.wulkanowy.data.db.dao.DbHelper +import io.github.wulkanowy.data.db.shared.SharedPrefContract + +class Migration29 : DbHelper.Migration { + + override fun getVersion(): Int? { + return 29 + } + + override fun runMigration(db: Database, sharedPref: SharedPrefContract, vulcan: Vulcan) { + createSchoolsTable(db) + modifyStudents(db) + insertSchool(db, getRealSchoolId(db)) + } + + private fun createSchoolsTable(db: Database) { + db.execSQL("DROP TABLE IF EXISTS \"Schools\";") + db.execSQL("CREATE TABLE IF NOT EXISTS \"Schools\" (" + // + "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id + "\"symbol_id\" INTEGER," + // 1: symbolId + "\"current\" INTEGER NOT NULL ," + // 2: current + "\"real_id\" TEXT," + // 3: realId + "\"name\" TEXT);") // 4: name + } + + private fun modifyStudents(db: Database) { + db.execSQL("ALTER TABLE Students ADD COLUMN school_id INTEGER") + db.execSQL("UPDATE Students SET (school_id) = ('1')") + } + + private fun getRealSchoolId(db: Database): String { + var cursor: Cursor? = null + try { + cursor = db.rawQuery("SELECT school_id FROM Symbols WHERE _id=?", arrayOf("1")) + + return if (cursor!!.count > 0) { + cursor.moveToFirst() + cursor.getString(cursor.getColumnIndex("school_id")) + } else "" + } finally { + cursor!!.close() + } + } + + private fun insertSchool(db: Database, realId: String) { + db.execSQL("INSERT INTO Schools(symbol_id, current, real_id, name) VALUES(" + + "\"1\"," + + "\"1\"," + + "\"" + realId + "\"," + + "\"Uczeń\"" + + ")") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java index 0ef45a76..0e654997 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java @@ -1,7 +1,10 @@ package io.github.wulkanowy.data.db.resources; +import javax.inject.Singleton; + import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson; +@Singleton public interface ResourcesContract { String[] getSymbolsKeysArray(); 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/ResourcesRepository.java similarity index 63% rename from app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java rename to app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesRepository.java index 349ac177..f8a1baaa 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/ResourcesRepository.java @@ -12,20 +12,18 @@ 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; -import io.github.wulkanowy.utils.LogUtils; import io.github.wulkanowy.utils.security.CryptoException; +import timber.log.Timber; @Singleton -public class AppResources implements ResourcesContract { +public class ResourcesRepository implements ResourcesContract { private Resources resources; @Inject - AppResources(@ApplicationContext Context context) { + ResourcesRepository(Context context) { resources = context.getResources(); } @@ -41,7 +39,7 @@ public class AppResources implements ResourcesContract { @Override public String getErrorLoginMessage(Exception exception) { - LogUtils.error(AppConstant.APP_NAME + " encountered a error", exception); + Timber.e(exception,"%s encountered a error", AppConstant.APP_NAME); if (exception instanceof CryptoException) { return resources.getString(R.string.encrypt_failed_text); @@ -50,9 +48,7 @@ public class AppResources implements ResourcesContract { } else if (exception instanceof SocketTimeoutException) { 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); + return resources.getString(R.string.login_failed_text); } else { return exception.getMessage(); } @@ -62,28 +58,28 @@ public class AppResources implements ResourcesContract { public String getAttendanceLessonDescription(AttendanceLesson lesson) { int id = R.string.attendance_present; - if (lesson.getIsAbsenceForSchoolReasons()) { + if (lesson.getAbsenceForSchoolReasons()) { id = R.string.attendance_absence_for_school_reasons; } - if (lesson.getIsAbsenceExcused()) { - id = R.string.attendance_absence_excused; + if (lesson.getAbsenceExcused()) { + id = R.string.attendance_absence_excused; } - if (lesson.getIsAbsenceUnexcused()) { - id = R.string.attendance_absence_unexcused; + if (lesson.getAbsenceUnexcused()) { + id = R.string.attendance_absence_unexcused; } - if (lesson.getIsExemption()) { - id = R.string.attendance_exemption; + if (lesson.getExemption()) { + id = R.string.attendance_exemption; } - if (lesson.getIsExcusedLateness()) { - id = R.string.attendance_excused_lateness; + if (lesson.getExcusedLateness()) { + id = R.string.attendance_excused_lateness; } - if (lesson.getIsUnexcusedLateness()) { - id = R.string.attendance_unexcused_lateness; + if (lesson.getUnexcusedLateness()) { + 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 deleted file mode 100644 index ba437f88..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.wulkanowy.data.db.shared; - -import android.content.Context; -import android.content.SharedPreferences; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import io.github.wulkanowy.di.annotations.ApplicationContext; -import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; - -@Singleton -public class SharedPref implements SharedPrefContract { - - private static final String SHARED_KEY_USER_ID = "USER_ID"; - - private final SharedPreferences sharedPreferences; - - @Inject - SharedPref(@ApplicationContext Context context, @SharedPreferencesInfo String sharedName) { - sharedPreferences = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); - } - - @Override - public long getCurrentUserId() { - return sharedPreferences.getLong(SHARED_KEY_USER_ID, 0); - } - - @Override - public void setCurrentUserId(long userId) { - sharedPreferences.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); - } -} 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..370f8dfe 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 @@ -1,8 +1,35 @@ package io.github.wulkanowy.data.db.shared; +import javax.inject.Singleton; + +@Singleton public interface SharedPrefContract { long getCurrentUserId(); + boolean isUserLoggedIn(); + void setCurrentUserId(long userId); + + void setTimetableWidgetState(boolean nextDay); + + boolean getTimetableWidgetState(); + + int getStartupTab(); + + boolean isShowGradesSummary(); + + boolean isShowAttendancePresent(); + + int getCurrentTheme(); + + int getServicesInterval(); + + boolean isMobileDisable(); + + boolean isServicesEnable(); + + boolean isNotifyEnable(); + + void cleanSharedPref(); } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefRepository.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefRepository.java new file mode 100644 index 00000000..adedbda4 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefRepository.java @@ -0,0 +1,102 @@ +package io.github.wulkanowy.data.db.shared; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import io.github.wulkanowy.ui.main.settings.SettingsFragment; + +@Singleton +public class SharedPrefRepository implements SharedPrefContract { + + private static final String SHARED_KEY_USER_ID = "USER_ID"; + + private static final String SHARED_KEY_TIMETABLE_WIDGET_STATE = "TIMETABLE_WIDGET_STATE"; + + private final SharedPreferences appSharedPref; + + private final SharedPreferences settingsSharedPref; + + @Inject + SharedPrefRepository(Context context, @Named("sharedPrefName") String sharedName) { + appSharedPref = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); + settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(context); + } + + @Override + public long getCurrentUserId() { + return appSharedPref.getLong(SHARED_KEY_USER_ID, 0); + } + + @Override + public boolean isUserLoggedIn() { + return getCurrentUserId() != 0; + } + + @Override + public void setCurrentUserId(long userId) { + appSharedPref.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); + } + + @SuppressLint("ApplySharedPref") + @Override + public void setTimetableWidgetState(boolean nextDay) { + appSharedPref.edit().putBoolean(SHARED_KEY_TIMETABLE_WIDGET_STATE, nextDay).commit(); + } + + @Override + public boolean getTimetableWidgetState() { + return appSharedPref.getBoolean(SHARED_KEY_TIMETABLE_WIDGET_STATE, false); + } + + @Override + public int getStartupTab() { + return Integer.parseInt(settingsSharedPref.getString(SettingsFragment.SHARED_KEY_START_TAB, "0")); + } + + @Override + public boolean isShowGradesSummary() { + return settingsSharedPref.getBoolean(SettingsFragment.SHARED_KEY_GRADES_SUMMARY, false); + } + + @Override + public boolean isShowAttendancePresent() { + return settingsSharedPref.getBoolean(SettingsFragment.SHARED_KEY_ATTENDANCE_PRESENT, false); + } + + @Override + public int getCurrentTheme() { + return Integer.parseInt(settingsSharedPref.getString(SettingsFragment.SHARED_KEY_THEME, "1")); + } + + @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); + } + + @Override + public void cleanSharedPref() { + appSharedPref.edit().clear().apply(); + settingsSharedPref.edit().clear().apply(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/AccountSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/AccountSync.java new file mode 100644 index 00000000..45f8c1f5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/AccountSync.java @@ -0,0 +1,197 @@ +package io.github.wulkanowy.data.sync; + +import android.content.Context; + +import org.greenrobot.greendao.database.Database; + +import java.io.IOException; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +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.DaoMaster; +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.dao.entities.School; +import io.github.wulkanowy.data.db.dao.entities.SchoolDao; +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.Student; +import io.github.wulkanowy.data.db.dao.entities.StudentDao; +import io.github.wulkanowy.data.db.dao.entities.Symbol; +import io.github.wulkanowy.data.db.dao.entities.SymbolDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.security.CryptoException; +import io.github.wulkanowy.utils.security.Scrambler; +import timber.log.Timber; + +@Singleton +public class AccountSync { + + private final DaoSession daoSession; + + private final SharedPrefContract sharedPref; + + private final Vulcan vulcan; + + private final Context context; + + @Inject + AccountSync(DaoSession daoSession, SharedPrefContract sharedPref, + Vulcan vulcan, Context context) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + this.context = context; + } + + public void registerUser(String email, String password, String symbol) + throws VulcanException, IOException, CryptoException { + + clearUserData(); + + vulcan.setCredentials(email, password, symbol, null, null, null); + + daoSession.getDatabase().beginTransaction(); + + Timber.i("Register start"); + + try { + Account account = insertAccount(email, password); + Symbol symbolEntity = insertSymbol(account); + School schoolEntity = insertSchools(symbolEntity); + Student student = insertStudents(schoolEntity); + Diary diary = insertDiaries(student); + insertSemesters(diary); + + sharedPref.setCurrentUserId(account.getId()); + + daoSession.getDatabase().setTransactionSuccessful(); + } finally { + daoSession.getDatabase().endTransaction(); + } + + Timber.i("Register end"); + } + + private Account insertAccount(String email, String password) throws CryptoException { + Timber.d("Register account"); + Account account = new Account() + .setEmail(email) + .setPassword(Scrambler.encrypt(email, password, context)); + daoSession.getAccountDao().insert(account); + return account; + } + + private Symbol insertSymbol(Account account) throws VulcanException, IOException { + vulcan.getSchools(); + Timber.d("Register symbol (%s)", vulcan.getSymbol()); + Symbol symbol = new Symbol() + .setUserId(account.getId()) + .setSymbol(vulcan.getSymbol()); + daoSession.getSymbolDao().insert(symbol); + + return symbol; + } + + private School insertSchools(Symbol symbol) throws VulcanException, IOException { + List schoolList = DataObjectConverter.schoolsToSchoolsEntities( + vulcan.getSchools(), + symbol.getId() + ); + Timber.d("Register schools (%s)", schoolList.size()); + daoSession.getSchoolDao().insertInTx(schoolList); + + return daoSession.getSchoolDao().queryBuilder().where( + SchoolDao.Properties.SymbolId.eq(symbol.getId()), + SchoolDao.Properties.Current.eq(true) + ).unique(); + } + + private Student insertStudents(School school) throws VulcanException, IOException { + List studentList = DataObjectConverter.studentsToStudentEntities( + vulcan.getStudentAndParent().getStudents(), + school.getId() + ); + Timber.d("Register students (%s)", studentList.size()); + daoSession.getStudentDao().insertInTx(studentList); + + return daoSession.getStudentDao().queryBuilder().where( + StudentDao.Properties.SchoolId.eq(school.getId()), + StudentDao.Properties.Current.eq(true) + ).unique(); + } + + private Diary insertDiaries(Student student) throws VulcanException, IOException { + List diaryList = DataObjectConverter.diariesToDiaryEntities( + vulcan.getStudentAndParent().getDiaries(), + student.getId() + ); + Timber.d("Register diaries (%s)", diaryList.size()); + daoSession.getDiaryDao().insertInTx(diaryList); + + return daoSession.getDiaryDao().queryBuilder().where( + DiaryDao.Properties.StudentId.eq(student.getId()), + DiaryDao.Properties.Current.eq(true) + ).unique(); + } + + private void insertSemesters(Diary diary) throws VulcanException, IOException { + List semesterList = DataObjectConverter.semestersToSemesterEntities( + vulcan.getStudentAndParent().getSemesters(), + diary.getId() + ); + Timber.d("Register semesters (%s)", semesterList.size()); + daoSession.getSemesterDao().insertInTx(semesterList); + } + + public void initLastUser() throws CryptoException { + + long userId = sharedPref.getCurrentUserId(); + + if (userId == 0) { + throw new NotRegisteredUserException("Can't find user id in SharedPreferences"); + } + + Timber.d("Init current user (%s)", userId); + + Account account = daoSession.getAccountDao().load(userId); + + Symbol symbol = daoSession.getSymbolDao().queryBuilder().where( + SymbolDao.Properties.UserId.eq(account.getId())).unique(); + + School school = daoSession.getSchoolDao().queryBuilder().where( + SchoolDao.Properties.SymbolId.eq(symbol.getId())).unique(); + + Student student = daoSession.getStudentDao().queryBuilder().where( + StudentDao.Properties.SchoolId.eq(school.getId()), + StudentDao.Properties.Current.eq(true) + ).unique(); + + Diary diary = daoSession.getDiaryDao().queryBuilder().where( + DiaryDao.Properties.StudentId.eq(student.getId()), + DiaryDao.Properties.Current.eq(true) + ).unique(); + + vulcan.setCredentials( + account.getEmail(), + Scrambler.decrypt(account.getEmail(), account.getPassword()), + symbol.getSymbol(), + school.getRealId(), + student.getRealId(), + diary.getValue() + ); + } + + private void clearUserData() { + Database database = daoSession.getDatabase(); + DaoMaster.dropAllTables(database, true); + DaoMaster.createAllTables(database, true); + sharedPref.setCurrentUserId(0); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/AttendanceSync.java similarity index 67% rename from app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSync.java rename to app/src/main/java/io/github/wulkanowy/data/sync/AttendanceSync.java index 325fff19..18769418 100644 --- a/app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSync.java +++ b/app/src/main/java/io/github/wulkanowy/data/sync/AttendanceSync.java @@ -1,7 +1,6 @@ -package io.github.wulkanowy.data.sync.attendance; +package io.github.wulkanowy.data.sync; import java.io.IOException; -import java.text.ParseException; import java.util.ArrayList; import java.util.List; @@ -18,39 +17,28 @@ import io.github.wulkanowy.data.db.dao.entities.Day; import io.github.wulkanowy.data.db.dao.entities.DayDao; import io.github.wulkanowy.data.db.dao.entities.Week; import io.github.wulkanowy.data.db.dao.entities.WeekDao; -import io.github.wulkanowy.data.db.shared.SharedPrefContract; import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.LogUtils; -import io.github.wulkanowy.utils.TimeUtils; +import timber.log.Timber; @Singleton -public class AttendanceSync implements AttendanceSyncContract { +public class AttendanceSync { private final DaoSession daoSession; - private final SharedPrefContract sharedPref; - private final Vulcan vulcan; - private long userId; + private long diaryId; @Inject - AttendanceSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + AttendanceSync(DaoSession daoSession, Vulcan vulcan) { this.daoSession = daoSession; - this.sharedPref = sharedPref; this.vulcan = vulcan; } - @Override - public void syncAttendance() throws IOException, ParseException, VulcanException { - syncAttendance(null); - } + public void syncAttendance(long diaryId, String date) throws IOException, VulcanException { + this.diaryId = diaryId; - @Override - public void syncAttendance(String date) throws IOException, ParseException, VulcanException { - this.userId = sharedPref.getCurrentUserId(); - - io.github.wulkanowy.api.generic.Week weekApi = getWeekFromApi(getNormalizedDate(date)); + io.github.wulkanowy.api.generic.Week weekApi = getWeekFromApi(date); Week weekDb = getWeekFromDb(weekApi.getStartDayDate()); long weekId = updateWeekInDb(weekDb, weekApi); @@ -59,35 +47,31 @@ public class AttendanceSync implements AttendanceSyncContract { daoSession.getAttendanceLessonDao().saveInTx(lessonList); - LogUtils.debug("Synchronization attendance lessons (amount = " + lessonList.size() + ")"); - } - - private String getNormalizedDate(String date) throws ParseException { - return null != date ? String.valueOf(TimeUtils.getNetTicks(date)) : ""; + Timber.d("Attendance synchronization complete (%s)", lessonList.size()); } private io.github.wulkanowy.api.generic.Week getWeekFromApi(String date) - throws IOException, ParseException, VulcanException { + throws IOException, VulcanException { return vulcan.getAttendanceTable().getWeekTable(date); } private Week getWeekFromDb(String date) { - return daoSession.getWeekDao() - .queryBuilder() - .where(WeekDao.Properties.UserId.eq(userId), WeekDao.Properties.StartDayDate.eq(date)) - .unique(); + return daoSession.getWeekDao().queryBuilder().where( + WeekDao.Properties.DiaryId.eq(diaryId), + WeekDao.Properties.StartDayDate.eq(date) + ).unique(); } private Long updateWeekInDb(Week dbWeekEntity, io.github.wulkanowy.api.generic.Week fromApi) { if (dbWeekEntity != null) { - dbWeekEntity.setIsAttendanceSynced(true); + dbWeekEntity.setAttendanceSynced(true); dbWeekEntity.update(); return dbWeekEntity.getId(); } - Week apiWeekEntity = DataObjectConverter.weekToWeekEntity(fromApi).setUserId(userId); - apiWeekEntity.setIsAttendanceSynced(true); + Week apiWeekEntity = DataObjectConverter.weekToWeekEntity(fromApi).setDiaryId(diaryId); + apiWeekEntity.setAttendanceSynced(true); return daoSession.getWeekDao().insert(apiWeekEntity); } @@ -97,7 +81,7 @@ public class AttendanceSync implements AttendanceSyncContract { for (io.github.wulkanowy.api.generic.Day dayFromApi : dayListFromApi) { - Day dbDayEntity = getDayFromDb(dayFromApi.getDate()); + Day dbDayEntity = getDayFromDb(dayFromApi.getDate(), weekId); Day apiDayEntity = DataObjectConverter.dayToDayEntity(dayFromApi); @@ -109,11 +93,12 @@ public class AttendanceSync implements AttendanceSyncContract { return updatedLessonList; } - private Day getDayFromDb(String date) { - return daoSession.getDayDao() - .queryBuilder() - .where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.Date.eq(date)) - .unique(); + private Day getDayFromDb(String date, long weekId) { + return daoSession.getDayDao().queryBuilder() + .where( + DayDao.Properties.WeekId.eq(weekId), + DayDao.Properties.Date.eq(date) + ).unique(); } private long updateDay(Day dbDayEntity, Day apiDayEntity, long weekId) { @@ -121,7 +106,6 @@ public class AttendanceSync implements AttendanceSyncContract { return dbDayEntity.getId(); } - apiDayEntity.setUserId(userId); apiDayEntity.setWeekId(weekId); return daoSession.getDayDao().insert(apiDayEntity); diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/ExamsSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/ExamsSync.java new file mode 100644 index 00000000..db473092 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/ExamsSync.java @@ -0,0 +1,135 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.VulcanException; +import io.github.wulkanowy.api.exams.ExamDay; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.DayDao; +import io.github.wulkanowy.data.db.dao.entities.Exam; +import io.github.wulkanowy.data.db.dao.entities.ExamDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.utils.DataObjectConverter; +import timber.log.Timber; + +public class ExamsSync { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private long diaryId; + + @Inject + ExamsSync(DaoSession daoSession, Vulcan vulcan) { + this.daoSession = daoSession; + this.vulcan = vulcan; + } + + public void syncExams(long diaryId, String date) throws IOException, VulcanException { + this.diaryId = diaryId; + + io.github.wulkanowy.api.generic.Week weekApi = getWeekFromApi(date); + Week weekDb = getWeekFromDb(weekApi.getStartDayDate()); + + long weekId = updateWeekInDb(weekDb, weekApi); + + List examList = getPreparedExams(weekApi.getDays(), weekId); + + daoSession.getExamDao().saveInTx(examList); + + Timber.d("Exams synchronization complete (%s)", examList.size()); + } + + private Week getWeekFromDb(String date) { + return daoSession.getWeekDao().queryBuilder().where( + WeekDao.Properties.DiaryId.eq(diaryId), + WeekDao.Properties.StartDayDate.eq(date) + ).unique(); + } + + private io.github.wulkanowy.api.generic.Week getWeekFromApi(String date) + throws VulcanException, IOException { + return vulcan.getExamsList().getWeek(date, true); + } + + private Long updateWeekInDb(Week weekDb, io.github.wulkanowy.api.generic.Week weekApi) { + if (weekDb != null) { + weekDb.setExamsSynced(true); + weekDb.update(); + + return weekDb.getId(); + } + + Week weekApiEntity = DataObjectConverter.weekToWeekEntity(weekApi).setDiaryId(diaryId); + weekApiEntity.setExamsSynced(true); + + return daoSession.getWeekDao().insert(weekApiEntity); + } + + private Day getDayFromDb(String date, long weekId) { + return daoSession.getDayDao().queryBuilder().where( + DayDao.Properties.WeekId.eq(weekId), + DayDao.Properties.Date.eq(date) + ).unique(); + } + + private List getPreparedExams(List dayListFromApi, + long weekId) { + List preparedExamList = new ArrayList<>(); + + for (ExamDay dayFromApi : dayListFromApi) { + + Day dayDb = getDayFromDb(dayFromApi.getDate(), weekId); + + Day dayApiEntity = DataObjectConverter.dayToDayEntity(dayFromApi); + + long dayId = updateDayInDb(dayDb, dayApiEntity, weekId); + + prepareExam(dayFromApi.getExamList(), preparedExamList, dayId); + } + return preparedExamList; + } + + private long updateDayInDb(Day dayDb, Day dayApi, long weekId) { + dayApi.setWeekId(weekId); + + if (null != dayDb) { + return dayDb.getId(); + } + return daoSession.getDayDao().insert(dayApi); + } + + private void prepareExam(List examList, + List preparedExams, long dayId) { + List examsApiEntity = DataObjectConverter.examsToExamsEntity(examList); + + for (Exam examApi : examsApiEntity) { + Exam examDb = getExamFromDb(examApi, dayId); + + examApi.setDayId(dayId); + + if (examDb != null) { + examApi.setId(examDb.getId()); + } + preparedExams.add(examApi); + } + } + + private Exam getExamFromDb(Exam examApi, long dayId) { + return daoSession.getExamDao().queryBuilder() + .where(ExamDao.Properties.DayId.eq(dayId), + ExamDao.Properties.EntryDate.eq(examApi.getEntryDate()), + ExamDao.Properties.SubjectAndGroup.eq(examApi.getSubjectAndGroup()), + ExamDao.Properties.Type.eq(examApi.getType()), + ExamDao.Properties.Teacher.eq(examApi.getTeacher())) + .unique(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/GradeSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/GradeSync.java new file mode 100644 index 00000000..59a71845 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/GradeSync.java @@ -0,0 +1,81 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.VulcanException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.EntitiesCompare; +import timber.log.Timber; + +@Singleton +public class GradeSync { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private long semesterId; + + @Inject + GradeSync(DaoSession daoSession, Vulcan vulcan) { + this.daoSession = daoSession; + this.vulcan = vulcan; + } + + public void sync(long semesterId) throws IOException, VulcanException { + this.semesterId = semesterId; + + Semester semester = daoSession.getSemesterDao().load(semesterId); + resetSemesterRelations(semester); + + List lastList = getUpdatedList(getComparedList(semester)); + + daoSession.getGradeDao().deleteInTx(semester.getGradeList()); + daoSession.getGradeDao().insertInTx(lastList); + + Timber.d("Grades synchronization complete (%s)", lastList.size()); + } + + private void resetSemesterRelations(Semester semester) { + semester.resetSubjectList(); + semester.resetGradeList(); + } + + private List getUpdatedList(List comparedList) { + List updatedList = new ArrayList<>(); + + for (Grade grade : comparedList) { + grade.setSemesterId(semesterId); + grade.setSubjectId(getSubjectId(grade.getSubject())); + updatedList.add(grade); + } + + return updatedList; + } + + private List getComparedList(Semester semester) throws IOException, VulcanException { + List gradesFromNet = DataObjectConverter.gradesToGradeEntities( + vulcan.getGradesList().getAll(semester.getValue()), semesterId); + + List gradesFromDb = semester.getGradeList(); + + return EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb); + } + + private Long getSubjectId(String subjectName) { + return daoSession.getSubjectDao().queryBuilder().where( + SubjectDao.Properties.Name.eq(subjectName), + SubjectDao.Properties.SemesterId.eq(semesterId) + ).build().uniqueOrThrow().getId(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/NotRegisteredUserException.java b/app/src/main/java/io/github/wulkanowy/data/sync/NotRegisteredUserException.java new file mode 100644 index 00000000..ddba1a21 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/NotRegisteredUserException.java @@ -0,0 +1,8 @@ +package io.github.wulkanowy.data.sync; + +public class NotRegisteredUserException extends RuntimeException { + + public NotRegisteredUserException(String message) { + super(message); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/SubjectSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/SubjectSync.java new file mode 100644 index 00000000..1d0300d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/SubjectSync.java @@ -0,0 +1,66 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.VulcanException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.utils.DataObjectConverter; +import timber.log.Timber; + +@Singleton +public class SubjectSync { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private long semesterId; + + @Inject + SubjectSync(DaoSession daoSession, Vulcan vulcan) { + this.daoSession = daoSession; + this.vulcan = vulcan; + } + + public void sync(long semesterId) throws VulcanException, IOException { + this.semesterId = semesterId; + + Semester semester = daoSession.getSemesterDao().load(semesterId); + + List lastList = getUpdatedList(getSubjectsFromNet(semester)); + + daoSession.getSubjectDao().deleteInTx(getSubjectsFromDb()); + daoSession.getSubjectDao().insertInTx(lastList); + + Timber.d("Subjects synchronization complete (%s)", lastList.size()); + } + + private List getSubjectsFromNet(Semester semester) throws VulcanException, IOException { + return DataObjectConverter.subjectsToSubjectEntities( + vulcan.getSubjectsList().getAll(semester.getValue()), semesterId); + } + + private List getSubjectsFromDb() { + Semester semester = daoSession.getSemesterDao().load(semesterId); + semester.resetSubjectList(); + return semester.getSubjectList(); + } + + private List getUpdatedList(List subjectsFromNet) { + List updatedList = new ArrayList<>(); + + for (Subject subject : subjectsFromNet) { + subject.setSemesterId(semesterId); + updatedList.add(subject); + } + return updatedList; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java index d9850ab1..67797ad1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java +++ b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java @@ -3,9 +3,38 @@ package io.github.wulkanowy.data.sync; import java.io.IOException; import java.text.ParseException; -import io.github.wulkanowy.api.VulcanException; +import javax.inject.Singleton; +import io.github.wulkanowy.api.VulcanException; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton public interface SyncContract { - void sync() throws VulcanException, IOException, ParseException; + void registerUser(String email, String password, String symbol) throws VulcanException, + IOException, CryptoException; + + void initLastUser() throws IOException, CryptoException; + + void syncGrades(int semesterName) throws VulcanException, IOException, ParseException; + + void syncGrades() throws VulcanException, IOException, ParseException; + + void syncSubjects(int semesterName) throws VulcanException, IOException; + + void syncSubjects() throws VulcanException, IOException; + + void syncAttendance() throws ParseException, IOException, VulcanException; + + void syncAttendance(long diaryId, String date) throws ParseException, IOException, VulcanException; + + void syncTimetable() throws VulcanException, IOException, ParseException; + + void syncTimetable(long diaryId, String date) throws VulcanException, IOException, ParseException; + + void syncExams() throws VulcanException, IOException, ParseException; + + void syncExams(long diaryId, String date) throws VulcanException, IOException, ParseException; + + void syncAll() throws VulcanException, IOException, ParseException; } diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/SyncRepository.java b/app/src/main/java/io/github/wulkanowy/data/sync/SyncRepository.java new file mode 100644 index 00000000..05dd14fd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/SyncRepository.java @@ -0,0 +1,123 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.VulcanException; +import io.github.wulkanowy.data.db.dao.DbContract; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton +public class SyncRepository implements SyncContract { + + private final GradeSync gradeSync; + + private final SubjectSync subjectSync; + + private final AttendanceSync attendanceSync; + + private final TimetableSync timetableSync; + + private final AccountSync accountSync; + + private final ExamsSync examsSync; + + private final DbContract database; + + @Inject + SyncRepository(GradeSync gradeSync, SubjectSync subjectSync, AttendanceSync attendanceSync, + TimetableSync timetableSync, AccountSync accountSync, ExamsSync examsSync, + DbContract database) { + this.gradeSync = gradeSync; + this.subjectSync = subjectSync; + this.attendanceSync = attendanceSync; + this.timetableSync = timetableSync; + this.accountSync = accountSync; + this.examsSync = examsSync; + this.database = database; + } + + @Override + public void registerUser(String email, String password, String symbol) throws VulcanException, + IOException, CryptoException { + accountSync.registerUser(email, password, symbol); + } + + @Override + public void initLastUser() throws CryptoException { + accountSync.initLastUser(); + } + + @Override + public void syncGrades(int semesterName) throws VulcanException, IOException { + gradeSync.sync(semesterName); + } + + @Override + public void syncGrades() throws VulcanException, IOException { + gradeSync.sync(database.getCurrentSemesterId()); + } + + @Override + public void syncSubjects(int semesterName) throws VulcanException, IOException { + subjectSync.sync(semesterName); + } + + @Override + public void syncSubjects() throws VulcanException, IOException { + subjectSync.sync(database.getCurrentSemesterId()); + } + + @Override + public void syncAttendance() throws IOException, VulcanException { + attendanceSync.syncAttendance(database.getCurrentDiaryId(), null); + } + + @Override + public void syncAttendance(long diaryId, String date) throws IOException, VulcanException { + if (diaryId != 0) { + attendanceSync.syncAttendance(diaryId, date); + } else { + attendanceSync.syncAttendance(database.getCurrentDiaryId(), date); + } + } + + @Override + public void syncTimetable() throws VulcanException, IOException { + timetableSync.syncTimetable(database.getCurrentDiaryId(), null); + } + + @Override + public void syncTimetable(long diaryId, String date) throws VulcanException, IOException { + if (diaryId != 0) { + timetableSync.syncTimetable(diaryId, date); + } else { + timetableSync.syncTimetable(database.getCurrentDiaryId(), date); + } + } + + @Override + public void syncExams() throws VulcanException, IOException { + examsSync.syncExams(database.getCurrentDiaryId(), null); + } + + @Override + public void syncExams(long diaryId, String date) throws VulcanException, IOException { + if (diaryId != 0) { + examsSync.syncExams(diaryId, date); + } else { + examsSync.syncExams(database.getCurrentDiaryId(), date); + } + } + + @Override + public void syncAll() throws VulcanException, IOException { + syncSubjects(); + syncGrades(); + syncAttendance(); + syncTimetable(); + syncExams(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/TimetableSync.java similarity index 64% rename from app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java rename to app/src/main/java/io/github/wulkanowy/data/sync/TimetableSync.java index da6b143f..1ea2fa48 100644 --- a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java +++ b/app/src/main/java/io/github/wulkanowy/data/sync/TimetableSync.java @@ -1,7 +1,8 @@ -package io.github.wulkanowy.data.sync.timetable; +package io.github.wulkanowy.data.sync; + +import org.apache.commons.collections4.CollectionUtils; import java.io.IOException; -import java.text.ParseException; import java.util.ArrayList; import java.util.List; @@ -18,39 +19,28 @@ import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; import io.github.wulkanowy.data.db.dao.entities.TimetableLessonDao; import io.github.wulkanowy.data.db.dao.entities.Week; import io.github.wulkanowy.data.db.dao.entities.WeekDao; -import io.github.wulkanowy.data.db.shared.SharedPrefContract; import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.LogUtils; -import io.github.wulkanowy.utils.TimeUtils; +import timber.log.Timber; @Singleton -public class TimetableSync implements TimetableSyncContract { +public class TimetableSync { private final DaoSession daoSession; - private final SharedPrefContract sharedPref; - private final Vulcan vulcan; - private long userId; + private long diaryId; @Inject - TimetableSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + TimetableSync(DaoSession daoSession, Vulcan vulcan) { this.daoSession = daoSession; - this.sharedPref = sharedPref; this.vulcan = vulcan; } - @Override - public void syncTimetable() throws IOException, ParseException, VulcanException { - syncTimetable(null); - } + public void syncTimetable(long diaryId, String date) throws IOException, VulcanException { + this.diaryId = diaryId; - @Override - public void syncTimetable(String date) throws IOException, ParseException, VulcanException { - this.userId = sharedPref.getCurrentUserId(); - - io.github.wulkanowy.api.generic.Week weekApi = getWeekFromApi(getNormalizedDate(date)); + io.github.wulkanowy.api.generic.Week weekApi = getWeekFromApi(date); Week weekDb = getWeekFromDb(weekApi.getStartDayDate()); long weekId = updateWeekInDb(weekDb, weekApi); @@ -59,47 +49,43 @@ public class TimetableSync implements TimetableSyncContract { daoSession.getTimetableLessonDao().saveInTx(lessonList); - LogUtils.debug("Synchronization timetable lessons (amount = " + lessonList.size() + ")"); + Timber.d("Timetable synchronization complete (%s)", lessonList.size()); } - private String getNormalizedDate(String date) throws ParseException { - return null != date ? String.valueOf(TimeUtils.getNetTicks(date)) : ""; - } - - private io.github.wulkanowy.api.generic.Week getWeekFromApi(String date) - throws IOException, VulcanException, ParseException { + private io.github.wulkanowy.api.generic.Week getWeekFromApi(String date) + throws IOException, VulcanException { return vulcan.getTimetable().getWeekTable(date); } private Week getWeekFromDb(String date) { - return daoSession.getWeekDao() - .queryBuilder() - .where(WeekDao.Properties.UserId.eq(userId), WeekDao.Properties.StartDayDate.eq(date)) - .unique(); + return daoSession.getWeekDao().queryBuilder().where( + WeekDao.Properties.DiaryId.eq(diaryId), + WeekDao.Properties.StartDayDate.eq(date) + ).unique(); } private Long updateWeekInDb(Week dbEntity, io.github.wulkanowy.api.generic.Week fromApi) { if (dbEntity != null) { - dbEntity.setIsTimetableSynced(true); + dbEntity.setTimetableSynced(true); dbEntity.update(); return dbEntity.getId(); } - Week apiEntity = DataObjectConverter.weekToWeekEntity(fromApi).setUserId(userId); - apiEntity.setIsTimetableSynced(true); + Week apiEntity = DataObjectConverter.weekToWeekEntity(fromApi).setDiaryId(diaryId); + apiEntity.setTimetableSynced(true); return daoSession.getWeekDao().insert(apiEntity); } - private List updateDays(List dayListFromApi, long weekId) { + private List updateDays(List dayListFromApi, long weekId) { List updatedLessonList = new ArrayList<>(); - for (io.github.wulkanowy.api.generic.Day dayFromApi : dayListFromApi) { + for (io.github.wulkanowy.api.timetable.TimetableDay dayFromApi : dayListFromApi) { - Day dbDayEntity = getDayFromDb(dayFromApi.getDate()); + Day dbDayEntity = getDayFromDb(dayFromApi.getDate(), weekId); - Day apiDayEntity = DataObjectConverter.dayToDayEntity(dayFromApi); + Day apiDayEntity = DataObjectConverter.timetableDayToDayEntity(dayFromApi); long dayId = updateDay(dbDayEntity, apiDayEntity, weekId); @@ -109,15 +95,14 @@ public class TimetableSync implements TimetableSyncContract { return updatedLessonList; } - private Day getDayFromDb(String date) { - return daoSession.getDayDao() - .queryBuilder() - .where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.Date.eq(date)) - .unique(); + private Day getDayFromDb(String date, long weekId) { + return daoSession.getDayDao().queryBuilder().where( + DayDao.Properties.WeekId.eq(weekId), + DayDao.Properties.Date.eq(date) + ).unique(); } private long updateDay(Day dayFromDb, Day apiDayEntity, long weekId) { - apiDayEntity.setUserId(userId); apiDayEntity.setWeekId(weekId); if (null != dayFromDb) { @@ -136,6 +121,16 @@ public class TimetableSync implements TimetableSyncContract { List lessonsFromApiEntities = DataObjectConverter .lessonsToTimetableLessonsEntities(lessons); + List lessonsFromDbEntities = getLessonsFromDb(dayId); + + if (!lessonsFromDbEntities.isEmpty()) { + List lessonToRemove = new ArrayList<>(CollectionUtils.removeAll(lessonsFromDbEntities, lessonsFromApiEntities)); + + for (TimetableLesson timetableLesson : lessonToRemove) { + daoSession.getTimetableLessonDao().delete(timetableLesson); + } + } + for (TimetableLesson apiLessonEntity : lessonsFromApiEntities) { TimetableLesson lessonFromDb = getLessonFromDb(apiLessonEntity, dayId); @@ -159,4 +154,8 @@ public class TimetableSync implements TimetableSyncContract { TimetableLessonDao.Properties.EndTime.eq(apiEntity.getEndTime())) .unique(); } + + private List getLessonsFromDb(long dayId) { + return daoSession.getDayDao().load(dayId).getTimetableLessons(); + } } 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 deleted file mode 100644 index d56f9990..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSync.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.wulkanowy.data.sync.account; - -import android.content.Context; - -import java.io.IOException; - -import javax.inject.Inject; -import javax.inject.Singleton; - -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.shared.SharedPrefContract; -import io.github.wulkanowy.di.annotations.ApplicationContext; -import io.github.wulkanowy.utils.LogUtils; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Scrambler; - -@Singleton -public class AccountSync implements AccountSyncContract { - - private final DaoSession daoSession; - - private final SharedPrefContract sharedPref; - - private final Vulcan vulcan; - - private final Context context; - - @Inject - AccountSync(DaoSession daoSession, SharedPrefContract sharedPref, - Vulcan vulcan, @ApplicationContext Context context) { - this.daoSession = daoSession; - this.sharedPref = sharedPref; - this.vulcan = vulcan; - this.context = context; - } - - @Override - public void registerUser(String email, String password, String symbol) - throws VulcanException, IOException, CryptoException { - - LogUtils.debug("Register new user email=" + email); - - vulcan.setCredentials(email, password, symbol, 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()); - - daoSession.getAccountDao().insert(account); - - sharedPref.setCurrentUserId(account.getId()); - } - - @Override - public void initLastUser() throws VulcanException, IOException, CryptoException { - - long userId = sharedPref.getCurrentUserId(); - - if (userId == 0) { - throw new IOException("Can't find saved user"); - } - - LogUtils.debug("Initialization current user id=" + userId); - - Account account = daoSession.getAccountDao().load(userId); - - vulcan.setCredentials(account.getEmail(), - Scrambler.decrypt(account.getEmail(), account.getPassword()), - account.getSymbol(), - account.getSnpId()); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSyncContract.java deleted file mode 100644 index 799d320e..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/account/AccountSyncContract.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.wulkanowy.data.sync.account; - -import java.io.IOException; - -import io.github.wulkanowy.api.VulcanException; -import io.github.wulkanowy.utils.security.CryptoException; - -public interface AccountSyncContract { - - void registerUser(String email, String password, String symbol) - throws VulcanException, IOException, - CryptoException; - - void initLastUser() throws VulcanException, IOException, - CryptoException; -} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSyncContract.java deleted file mode 100644 index dfb4c1b4..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/attendance/AttendanceSyncContract.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.wulkanowy.data.sync.attendance; - -import java.io.IOException; -import java.text.ParseException; - -import io.github.wulkanowy.api.VulcanException; - -public interface AttendanceSyncContract { - - void syncAttendance(String date) throws IOException, ParseException, VulcanException; - - void syncAttendance() throws IOException, ParseException, VulcanException; -} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java deleted file mode 100644 index b132ac1b..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.github.wulkanowy.data.sync.grades; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -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.Grade; -import io.github.wulkanowy.data.db.dao.entities.SubjectDao; -import io.github.wulkanowy.data.db.shared.SharedPrefContract; -import io.github.wulkanowy.data.sync.SyncContract; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.EntitiesCompare; -import io.github.wulkanowy.utils.LogUtils; - -@Singleton -public class GradeSync implements SyncContract { - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - private final SharedPrefContract sharedPref; - - private Long userId; - - @Inject - GradeSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { - this.daoSession = daoSession; - this.sharedPref = sharedPref; - this.vulcan = vulcan; - } - - @Override - public void sync() throws IOException, VulcanException, ParseException { - - userId = sharedPref.getCurrentUserId(); - - Account account = daoSession.getAccountDao().load(userId); - resetAccountRelations(account); - - List lastList = getUpdatedList(getComparedList(account)); - - daoSession.getGradeDao().deleteInTx(account.getGradeList()); - daoSession.getGradeDao().insertInTx(lastList); - - LogUtils.debug("Synchronization grades (amount = " + lastList.size() + ")"); - } - - private void resetAccountRelations(Account account) { - account.resetSubjectList(); - account.resetGradeList(); - } - - private List getUpdatedList(List comparedList) { - List updatedList = new ArrayList<>(); - - for (Grade grade : comparedList) { - grade.setUserId(userId); - grade.setSubjectId(getSubjectId(grade.getSubject())); - updatedList.add(grade); - } - return updatedList; - } - - private List getComparedList(Account account) throws IOException, VulcanException, - ParseException { - List gradesFromNet = DataObjectConverter - .gradesToGradeEntities(vulcan.getGradesList().getAll()); - - List gradesFromDb = account.getGradeList(); - - return EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb); - } - - private Long getSubjectId(String subjectName) { - return daoSession.getSubjectDao().queryBuilder() - .where(SubjectDao.Properties.Name.eq(subjectName), - SubjectDao.Properties.UserId.eq(userId)) - .build() - .uniqueOrThrow() - .getId(); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java deleted file mode 100644 index 1c5bfc71..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.wulkanowy.data.sync.subjects; - -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -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.Subject; -import io.github.wulkanowy.data.db.shared.SharedPrefContract; -import io.github.wulkanowy.data.sync.SyncContract; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.LogUtils; - -@Singleton -public class SubjectSync implements SyncContract { - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - private final SharedPrefContract sharedPref; - - private Long userId; - - @Inject - SubjectSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { - this.daoSession = daoSession; - this.sharedPref = sharedPref; - this.vulcan = vulcan; - } - - @Override - public void sync() throws VulcanException, IOException, ParseException { - - userId = sharedPref.getCurrentUserId(); - - List lastList = getUpdatedList(getSubjectsFromNet()); - - daoSession.getSubjectDao().deleteInTx(getSubjectsFromDb()); - daoSession.getSubjectDao().insertInTx(lastList); - - LogUtils.debug("Synchronization subjects (amount = " + lastList.size() + ")"); - } - - private List getSubjectsFromNet() throws VulcanException, IOException { - return DataObjectConverter.subjectsToSubjectEntities(vulcan.getSubjectsList().getAll()); - } - - private List getSubjectsFromDb() { - Account account = daoSession.getAccountDao().load(userId); - account.resetSubjectList(); - return account.getSubjectList(); - } - - private List getUpdatedList(List subjectsFromNet) { - List updatedList = new ArrayList<>(); - - for (Subject subject : subjectsFromNet) { - subject.setUserId(userId); - updatedList.add(subject); - } - return updatedList; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java deleted file mode 100644 index 9bba209a..00000000 --- a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.wulkanowy.data.sync.timetable; - -import java.io.IOException; -import java.text.ParseException; - -import io.github.wulkanowy.api.VulcanException; - -public interface TimetableSyncContract { - - void syncTimetable(String date) throws VulcanException, IOException, ParseException; - - void syncTimetable() throws VulcanException, IOException, ParseException; -} diff --git a/app/src/main/java/io/github/wulkanowy/di/AppComponent.java b/app/src/main/java/io/github/wulkanowy/di/AppComponent.java new file mode 100644 index 00000000..92f5d9a1 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/AppComponent.java @@ -0,0 +1,20 @@ +package io.github.wulkanowy.di; + +import javax.inject.Singleton; + +import dagger.Component; +import dagger.android.AndroidInjector; +import dagger.android.support.AndroidSupportInjectionModule; +import io.github.wulkanowy.WulkanowyApp; + +@Singleton +@Component(modules = { + AndroidSupportInjectionModule.class, + AppModule.class, + BuilderModule.class +}) +public interface AppComponent extends AndroidInjector { + @Component.Builder + abstract class Builder extends AndroidInjector.Builder { + } +} diff --git a/app/src/main/java/io/github/wulkanowy/di/AppModule.java b/app/src/main/java/io/github/wulkanowy/di/AppModule.java new file mode 100644 index 00000000..d6d4dfa2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/AppModule.java @@ -0,0 +1,78 @@ +package io.github.wulkanowy.di; + +import android.content.Context; + +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.Repository; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.DbContract; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.dao.DbRepository; +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.db.resources.ResourcesRepository; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.db.shared.SharedPrefRepository; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.data.sync.SyncRepository; +import io.github.wulkanowy.utils.AppConstant; + +@Module +public abstract class AppModule { + + @Binds + abstract Context provideContext(WulkanowyApp app); + + @Singleton + @Binds + abstract RepositoryContract provideRepository(Repository repository); + + @Singleton + @Binds + abstract DbContract provideDatabse(DbRepository dbRepository); + + @Singleton + @Binds + abstract SharedPrefContract provideSharedPref(SharedPrefRepository sharedPrefRepository); + + @Singleton + @Binds + abstract SyncContract provideSync(SyncRepository syncRepository); + + @Singleton + @Binds + abstract ResourcesContract provideResources(ResourcesRepository resourcesRepository); + + @Singleton + @Provides + static DaoSession provideDaoSession(DbHelper dbHelper) { + return new DaoMaster(dbHelper.getWritableDb()).newSession(); + } + + @Singleton + @Provides + static Vulcan provideVulcan() { + return new Vulcan(); + } + + @Provides + @Named("dbName") + static String provideDbName() { + return AppConstant.DATABASE_NAME; + } + + @Provides + @Named("sharedPrefName") + static String provideSharedPrefName() { + return AppConstant.SHARED_PREFERENCES_NAME; + } + +} diff --git a/app/src/main/java/io/github/wulkanowy/di/BuilderModule.java b/app/src/main/java/io/github/wulkanowy/di/BuilderModule.java new file mode 100644 index 00000000..74b77d52 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/BuilderModule.java @@ -0,0 +1,39 @@ +package io.github.wulkanowy.di; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; +import io.github.wulkanowy.di.scopes.PerActivity; +import io.github.wulkanowy.services.jobs.SyncJob; +import io.github.wulkanowy.services.widgets.TimetableWidgetServices; +import io.github.wulkanowy.ui.login.LoginActivity; +import io.github.wulkanowy.ui.login.LoginModule; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.ui.main.MainModule; +import io.github.wulkanowy.ui.splash.SplashActivity; +import io.github.wulkanowy.ui.splash.SplashModule; +import io.github.wulkanowy.ui.widgets.TimetableWidgetProvider; + +@Module +abstract class BuilderModule { + + @PerActivity + @ContributesAndroidInjector(modules = SplashModule.class) + abstract SplashActivity bindSplashActivity(); + + @PerActivity + @ContributesAndroidInjector(modules = LoginModule.class) + abstract LoginActivity bindLoginActivity(); + + @PerActivity + @ContributesAndroidInjector(modules = MainModule.class) + abstract MainActivity bindMainActivity(); + + @ContributesAndroidInjector + abstract SyncJob bindSyncJob(); + + @ContributesAndroidInjector + abstract TimetableWidgetServices bindTimetableWidgetServices(); + + @ContributesAndroidInjector + abstract TimetableWidgetProvider bindTimetableWidgetProvider(); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java deleted file mode 100644 index 2a74c32d..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.di.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ActivityContext { -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java deleted file mode 100644 index 04139d99..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.di.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface ApplicationContext { -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java deleted file mode 100644 index fabcefba..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.di.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface DatabaseInfo { -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java deleted file mode 100644 index 919c77a0..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.di.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface SharedPreferencesInfo { -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java deleted file mode 100644 index 81d351be..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.wulkanowy.di.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import javax.inject.Qualifier; - -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface SyncSubjects { -} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java deleted file mode 100644 index 3365a317..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.di.component; - -import dagger.Component; -import io.github.wulkanowy.di.annotations.PerActivity; -import io.github.wulkanowy.di.modules.ActivityModule; -import io.github.wulkanowy.ui.login.LoginActivity; -import io.github.wulkanowy.ui.main.MainActivity; -import io.github.wulkanowy.ui.splash.SplashActivity; - -@PerActivity -@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) -public interface ActivityComponent { - - void inject(SplashActivity splashActivity); - - void inject(LoginActivity loginActivity); - - void inject(MainActivity mainActivity); -} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java deleted file mode 100644 index 3439a949..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.wulkanowy.di.component; - -import android.content.Context; - -import javax.inject.Singleton; - -import dagger.Component; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.data.RepositoryContract; -import io.github.wulkanowy.di.annotations.ApplicationContext; -import io.github.wulkanowy.di.modules.ApplicationModule; -import io.github.wulkanowy.services.SyncJob; - -@Singleton -@Component(modules = ApplicationModule.class) -public interface ApplicationComponent { - - @ApplicationContext - Context getContext(); - - RepositoryContract getRepository(); - - void inject(WulkanowyApp wulkanowyApp); - - void inject(SyncJob syncJob); -} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java deleted file mode 100644 index b26abaaf..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.wulkanowy.di.component; - -import dagger.Component; -import io.github.wulkanowy.di.annotations.PerFragment; -import io.github.wulkanowy.di.modules.FragmentModule; -import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; -import io.github.wulkanowy.ui.main.attendance.AttendanceTabFragment; -import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; -import io.github.wulkanowy.ui.main.grades.GradesFragment; -import io.github.wulkanowy.ui.main.timetable.TimetableFragment; -import io.github.wulkanowy.ui.main.timetable.TimetableTabFragment; - -@PerFragment -@Component(dependencies = ApplicationComponent.class, modules = FragmentModule.class) -public interface FragmentComponent { - - void inject(GradesFragment gradesFragment); - - void inject(AttendanceFragment attendanceFragment); - - void inject(AttendanceTabFragment attendanceTabFragment); - - void inject(DashboardFragment dashboardFragment); - - void inject(TimetableFragment timetableFragment); - - void inject(TimetableTabFragment timetableTabFragment); -} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java deleted file mode 100644 index 53dc1086..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.di.modules; - - -import android.content.Context; -import android.support.v7.app.AppCompatActivity; - -import dagger.Module; -import dagger.Provides; -import io.github.wulkanowy.di.annotations.ActivityContext; -import io.github.wulkanowy.di.annotations.PerActivity; -import io.github.wulkanowy.ui.login.LoginContract; -import io.github.wulkanowy.ui.login.LoginPresenter; -import io.github.wulkanowy.ui.main.MainContract; -import io.github.wulkanowy.ui.main.MainPagerAdapter; -import io.github.wulkanowy.ui.main.MainPresenter; -import io.github.wulkanowy.ui.splash.SplashContract; -import io.github.wulkanowy.ui.splash.SplashPresenter; - -@Module -public class ActivityModule { - - private AppCompatActivity activity; - - public ActivityModule(AppCompatActivity activity) { - this.activity = activity; - } - - @ActivityContext - @Provides - Context provideContext() { - return activity; - } - - @Provides - AppCompatActivity provideActivity() { - return activity; - } - - @PerActivity - @Provides - SplashContract.Presenter provideSplashPresenter - (SplashPresenter splashPresenter) { - return splashPresenter; - } - - @PerActivity - @Provides - LoginContract.Presenter provideLoginPresenter - (LoginPresenter loginPresenter) { - return loginPresenter; - } - - @PerActivity - @Provides - MainContract.Presenter provideMainPresenter - (MainPresenter mainPresenter) { - return mainPresenter; - } - - @Provides - MainPagerAdapter provideMainPagerAdapter() { - return new MainPagerAdapter(activity.getSupportFragmentManager()); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java deleted file mode 100644 index e7d2bcc6..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.github.wulkanowy.di.modules; - -import android.app.Application; -import android.content.Context; - -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.GooglePlayDriver; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.data.Repository; -import io.github.wulkanowy.data.RepositoryContract; -import io.github.wulkanowy.data.db.dao.DbHelper; -import io.github.wulkanowy.data.db.dao.entities.DaoMaster; -import io.github.wulkanowy.data.db.dao.entities.DaoSession; -import io.github.wulkanowy.data.db.resources.AppResources; -import io.github.wulkanowy.data.db.resources.ResourcesContract; -import io.github.wulkanowy.data.db.shared.SharedPref; -import io.github.wulkanowy.data.db.shared.SharedPrefContract; -import io.github.wulkanowy.data.sync.SyncContract; -import io.github.wulkanowy.data.sync.account.AccountSync; -import io.github.wulkanowy.data.sync.account.AccountSyncContract; -import io.github.wulkanowy.data.sync.attendance.AttendanceSync; -import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract; -import io.github.wulkanowy.data.sync.grades.GradeSync; -import io.github.wulkanowy.data.sync.subjects.SubjectSync; -import io.github.wulkanowy.data.sync.timetable.TimetableSync; -import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; -import io.github.wulkanowy.di.annotations.ApplicationContext; -import io.github.wulkanowy.di.annotations.DatabaseInfo; -import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; -import io.github.wulkanowy.di.annotations.SyncGrades; -import io.github.wulkanowy.di.annotations.SyncSubjects; -import io.github.wulkanowy.utils.AppConstant; - -@Module -public class ApplicationModule { - - private final Application application; - - public ApplicationModule(Application application) { - this.application = application; - } - - @Provides - Application provideApplication() { - return application; - } - - @ApplicationContext - @Provides - Context provideAppContext() { - return application; - } - - @DatabaseInfo - @Provides - String provideDatabaseName() { - return AppConstant.DATABASE_NAME; - } - - @SharedPreferencesInfo - @Provides - String provideSharedPreferencesName() { - return AppConstant.SHARED_PREFERENCES_NAME; - } - - @Singleton - @Provides - DaoSession provideDaoSession(DbHelper dbHelper) { - return new DaoMaster(dbHelper.getWritableDb()).newSession(); - } - - @Singleton - @Provides - Vulcan provideVulcan() { - return new Vulcan(); - } - - @Singleton - @Provides - RepositoryContract provideRepository(Repository repository) { - return repository; - } - - @Singleton - @Provides - SharedPrefContract provideSharedPref(SharedPref sharedPref) { - return sharedPref; - } - - @Singleton - @Provides - ResourcesContract provideAppResources(AppResources appResources) { - return appResources; - } - - @Singleton - @Provides - AccountSyncContract provideLoginSync(AccountSync accountSync) { - return accountSync; - } - - @SyncGrades - @Singleton - @Provides - SyncContract provideGradesSync(GradeSync gradeSync) { - return gradeSync; - } - - @SyncSubjects - @Singleton - @Provides - SyncContract provideSubjectSync(SubjectSync subjectSync) { - return subjectSync; - } - - @Singleton - @Provides - TimetableSyncContract provideTimetableSync(TimetableSync timetableSync) { - return timetableSync; - } - - @Singleton - @Provides - AttendanceSyncContract provideAttendanceSync(AttendanceSync attendanceSync) { - return attendanceSync; - } - - @Provides - FirebaseJobDispatcher provideDispatcher() { - return new FirebaseJobDispatcher(new GooglePlayDriver(application)); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java deleted file mode 100644 index 3479a8c4..00000000 --- a/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.wulkanowy.di.modules; - -import android.support.v4.app.Fragment; - -import dagger.Module; -import dagger.Provides; -import eu.davidea.flexibleadapter.FlexibleAdapter; -import io.github.wulkanowy.di.annotations.PerFragment; -import io.github.wulkanowy.ui.main.attendance.AttendanceContract; -import io.github.wulkanowy.ui.main.attendance.AttendanceHeaderItem; -import io.github.wulkanowy.ui.main.attendance.AttendancePagerAdapter; -import io.github.wulkanowy.ui.main.attendance.AttendancePresenter; -import io.github.wulkanowy.ui.main.attendance.AttendanceTabContract; -import io.github.wulkanowy.ui.main.attendance.AttendanceTabPresenter; -import io.github.wulkanowy.ui.main.dashboard.DashboardContract; -import io.github.wulkanowy.ui.main.dashboard.DashboardPresenter; -import io.github.wulkanowy.ui.main.grades.GradeHeaderItem; -import io.github.wulkanowy.ui.main.grades.GradesContract; -import io.github.wulkanowy.ui.main.grades.GradesPresenter; -import io.github.wulkanowy.ui.main.timetable.TimetableContract; -import io.github.wulkanowy.ui.main.timetable.TimetableHeaderItem; -import io.github.wulkanowy.ui.main.timetable.TimetablePagerAdapter; -import io.github.wulkanowy.ui.main.timetable.TimetablePresenter; -import io.github.wulkanowy.ui.main.timetable.TimetableTabContract; -import io.github.wulkanowy.ui.main.timetable.TimetableTabPresenter; - -@Module -public class FragmentModule { - - private final Fragment fragment; - - public FragmentModule(Fragment fragment) { - this.fragment = fragment; - } - - @PerFragment - @Provides - GradesContract.Presenter provideGradesPresenter(GradesPresenter gradesPresenter) { - return gradesPresenter; - } - - @PerFragment - @Provides - AttendanceContract.Presenter provideAttendancePresenter(AttendancePresenter attendancePresenter) { - return attendancePresenter; - } - - @PerFragment - @Provides - DashboardContract.Presenter provideDashboardPresenter(DashboardPresenter dashboardPresenter) { - return dashboardPresenter; - } - - @PerFragment - @Provides - AttendanceTabContract.Presenter provideAttendanceTabPresenter(AttendanceTabPresenter timetableTabPresenter) { - return timetableTabPresenter; - } - - @Provides - AttendancePagerAdapter provideAttendancePagerAdapter() { - return new AttendancePagerAdapter(fragment.getChildFragmentManager()); - } - - @Provides - FlexibleAdapter provideAttendanceTabAdapter() { - return new FlexibleAdapter<>(null); - } - - @PerFragment - @Provides - TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter) { - return timetablePresenter; - } - - @PerFragment - @Provides - TimetableTabContract.Presenter provideTimetableTabPresenter(TimetableTabPresenter timetableTabPresenter) { - return timetableTabPresenter; - } - - @Provides - TimetablePagerAdapter provideTimetablePagerAdapter() { - return new TimetablePagerAdapter(fragment.getChildFragmentManager()); - } - - @Provides - FlexibleAdapter provideTimetableTabAdapter() { - return new FlexibleAdapter<>(null); - } - - @Provides - FlexibleAdapter provideGradesAdapter() { - return new FlexibleAdapter<>(null); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java b/app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.java similarity index 81% rename from app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java rename to app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.java index f103994a..c9fc8a5b 100644 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java +++ b/app/src/main/java/io/github/wulkanowy/di/scopes/PerActivity.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.di.annotations; +package io.github.wulkanowy.di.scopes; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java b/app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.java similarity index 51% rename from app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java rename to app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.java index 90e6c02f..67a9f820 100644 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java +++ b/app/src/main/java/io/github/wulkanowy/di/scopes/PerChildFragment.java @@ -1,11 +1,11 @@ -package io.github.wulkanowy.di.annotations; +package io.github.wulkanowy.di.scopes; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; +import javax.inject.Scope; -@Qualifier +@Scope @Retention(RetentionPolicy.RUNTIME) -public @interface SyncGrades { +public @interface PerChildFragment { } diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java b/app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.java similarity index 81% rename from app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java rename to app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.java index 98f364f7..a4d37c38 100644 --- a/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java +++ b/app/src/main/java/io/github/wulkanowy/di/scopes/PerFragment.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.di.annotations; +package io.github.wulkanowy.di.scopes; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java deleted file mode 100644 index ab721f46..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java +++ /dev/null @@ -1,55 +0,0 @@ -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.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"; - - private NotificationManager manager; - - private Context context; - - NotificationService(Context context) { - this.context = context; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createChannel(); - } - } - - void notify(Notification notification) { - getManager().notify(new Random().nextInt(1000), notification); - } - - NotificationCompat.Builder notificationBuilder() { - return new NotificationCompat.Builder(context, CHANNEL_ID); - } - - @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); - } - - private NotificationManager getManager() { - if (manager == null) { - manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - } - return manager; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/SyncJob.java similarity index 53% rename from app/src/main/java/io/github/wulkanowy/services/SyncJob.java rename to app/src/main/java/io/github/wulkanowy/services/jobs/SyncJob.java index e7b0908f..9854666e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/SyncJob.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.services; +package io.github.wulkanowy.services.jobs; import android.app.PendingIntent; import android.content.Context; @@ -14,83 +14,112 @@ 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; +import dagger.android.AndroidInjection; import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.sync.NotRegisteredUserException; +import io.github.wulkanowy.services.notifies.GradeNotify; import io.github.wulkanowy.ui.main.MainActivity; -import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.FabricUtils; +import timber.log.Timber; + +import static io.github.wulkanowy.utils.TimeUtilsKt.isHolidays; public class SyncJob extends SimpleJobService { - private static final int DEFAULT_INTERVAL_START = 60 * 50; + public static final String JOB_TAG = "SyncJob"; - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); - - public static final String EXTRA_INTENT_KEY = "cardId"; - - private List gradeList; + 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(); - ((WulkanowyApp) getApplication()).getApplicationComponent().inject(this); + AndroidInjection.inject(this); } @Override public int onRunJob(JobParameters job) { + if (isHolidays()) { + stop(getApplicationContext()); + + return JobService.RESULT_FAIL_NORETRY; + } + try { - repository.initLastUser(); - repository.syncAll(); + repository.getSyncRepo().initLastUser(); + repository.getSyncRepo().syncAll(); - gradeList = repository.getNewGrades(); + gradeList = repository.getDbRepo().getNewGrades(repository.getDbRepo().getCurrentSemesterName()); - if (!gradeList.isEmpty()) { + if (!gradeList.isEmpty() && repository.getSharedRepo().isNotifyEnable()) { showNotification(); } + + FabricUtils.logLogin("Background", true); + return JobService.RESULT_SUCCESS; + } catch (NotRegisteredUserException e) { + logError(e); + stop(getApplicationContext()); + + return JobService.RESULT_FAIL_NORETRY; + } catch (BadCredentialsException e) { + logError(e); + repository.cleanAllData(); + stop(getApplicationContext()); + + return JobService.RESULT_FAIL_NORETRY; } catch (Exception e) { - LogUtils.error("During background synchronization an error occurred", e); + logError(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) .setColor(getResources().getColor(R.color.colorPrimary)) .setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, - MainActivity.getStartIntent(getApplicationContext()).putExtra(EXTRA_INTENT_KEY, 0) - , 0 + MainActivity.getStartIntent(getApplicationContext()) + .putExtra(MainActivity.EXTRA_CARD_ID_KEY, 0) + , PendingIntent.FLAG_UPDATE_CURRENT )) .build()); } @@ -111,4 +140,9 @@ public class SyncJob extends SimpleJobService { gradeList.size(), gradeList.size()); } } + + private void logError(Exception e) { + FabricUtils.logLogin("Background", false); + Timber.e(e, "During background synchronization an error occurred"); + } } diff --git a/app/src/main/java/io/github/wulkanowy/services/notifies/GradeNotify.java b/app/src/main/java/io/github/wulkanowy/services/notifies/GradeNotify.java new file mode 100644 index 00000000..a554bea3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/notifies/GradeNotify.java @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services.notifies; + +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; + +public class GradeNotify extends NotificationService { + + private static final String CHANNEL_ID = "Grade_Notify"; + + public 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/notifies/NotificationService.java b/app/src/main/java/io/github/wulkanowy/services/notifies/NotificationService.java new file mode 100644 index 00000000..156de2bd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/notifies/NotificationService.java @@ -0,0 +1,57 @@ +package io.github.wulkanowy.services.notifies; + + +import android.annotation.TargetApi; +import android.app.Notification; +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; + +public class NotificationService { + + private NotificationManager manager; + + private Context context; + + public NotificationService(Context context) { + this.context = context; + } + + public void notify(Notification notification) { + getManager().notify(new Random().nextInt(1000), notification); + } + + public NotificationCompat.Builder notificationBuilder() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + return new NotificationCompat.Builder(context, getChannelId()); + } + + public void cancelAll() { + getManager().cancelAll(); + } + + 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/widgets/TimetableWidgetServices.java b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetServices.java new file mode 100644 index 00000000..f442542e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetServices.java @@ -0,0 +1,22 @@ +package io.github.wulkanowy.services.widgets; + +import android.content.Intent; +import android.widget.RemoteViewsService; + +import javax.inject.Inject; + +import dagger.android.AndroidInjection; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.widgets.TimetableWidgetFactory; + +public class TimetableWidgetServices extends RemoteViewsService { + + @Inject + RepositoryContract repository; + + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + AndroidInjection.inject(this); + return new TimetableWidgetFactory(getApplicationContext(), repository); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java index c5c66df0..82ab9d19 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java @@ -1,35 +1,51 @@ package io.github.wulkanowy.ui.base; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; +import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatDelegate; -import android.widget.Toast; +import android.view.View; +import butterknife.ButterKnife; import butterknife.Unbinder; +import dagger.android.support.DaggerAppCompatActivity; import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.di.component.ActivityComponent; -import io.github.wulkanowy.di.component.DaggerActivityComponent; -import io.github.wulkanowy.di.modules.ActivityModule; import io.github.wulkanowy.utils.NetworkUtils; -public abstract class BaseActivity extends AppCompatActivity implements BaseContract.View { - - private ActivityComponent activityComponent; +public abstract class BaseActivity extends DaggerAppCompatActivity implements BaseContract.View { private Unbinder unbinder; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); + } - activityComponent = DaggerActivityComponent.builder() - .activityModule(new ActivityModule(this)) - .applicationComponent(((WulkanowyApp) getApplication()).getApplicationComponent()) - .build(); + protected void injectViews() { + unbinder = ButterKnife.bind(this); + } + + @Override + public void showMessage(@NonNull String text) { + if (getMessageView() != null) { + Snackbar.make(getMessageView(), text, Snackbar.LENGTH_LONG).show(); + } + } + + @Override + public void showNoNetworkMessage() { + showMessage(getString(R.string.noInternet_text)); + } + + @Override + public boolean isNetworkConnected() { + return NetworkUtils.isOnline(getApplicationContext()); + } + + protected View getMessageView() { + return null; } @Override @@ -38,33 +54,6 @@ public abstract class BaseActivity extends AppCompatActivity implements BaseCont if (unbinder != null) { unbinder.unbind(); } - } - - @Override - public void onError(int resId) { - onError(getString(resId)); - } - - @Override - public void onError(String message) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - @Override - public void onNoNetworkError() { - onError(R.string.noInternet_text); - } - - @Override - public boolean isNetworkConnected() { - return NetworkUtils.isOnline(getApplicationContext()); - } - - public ActivityComponent getActivityComponent() { - return activityComponent; - } - - public void setButterKnife(Unbinder unbinder) { - this.unbinder = unbinder; + invalidateOptionsMenu(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java index 2a4dc569..3bfa40d1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java @@ -1,27 +1,22 @@ package io.github.wulkanowy.ui.base; -import android.support.annotation.StringRes; - -import io.github.wulkanowy.di.annotations.PerActivity; +import android.support.annotation.NonNull; public interface BaseContract { interface View { - void onError(@StringRes int resId); + void showMessage(@NonNull String text); - void onError(String message); - - void onNoNetworkError(); + void showNoNetworkMessage(); boolean isNetworkConnected(); } - @PerActivity interface Presenter { - void onStart(V view); + void attachView(@NonNull V view); - void onDestroy(); + void detachView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java index efb9d61a..e2a5a9e0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java @@ -1,56 +1,20 @@ package io.github.wulkanowy.ui.base; -import android.content.Context; -import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; +import android.support.annotation.StringRes; import android.view.View; +import butterknife.ButterKnife; import butterknife.Unbinder; -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.di.component.DaggerFragmentComponent; -import io.github.wulkanowy.di.component.FragmentComponent; -import io.github.wulkanowy.di.modules.FragmentModule; +import dagger.android.support.DaggerFragment; +import io.github.wulkanowy.utils.NetworkUtils; -public abstract class BaseFragment extends Fragment implements BaseContract.View { - - private BaseActivity activity; +public abstract class BaseFragment extends DaggerFragment implements BaseContract.View { private Unbinder unbinder; - private FragmentComponent fragmentComponent; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof BaseActivity) { - activity = (BaseActivity) context; - } - - fragmentComponent = DaggerFragmentComponent.builder() - .fragmentModule(new FragmentModule(this)) - .applicationComponent(((WulkanowyApp) activity.getApplication()).getApplicationComponent()) - .build(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setUpOnViewCreated(view); - } - - @Override - public void onDetach() { - activity = null; - super.onDetach(); + protected void injectViews(@NonNull View view) { + unbinder = ButterKnife.bind(this, view); } @Override @@ -61,44 +25,32 @@ public abstract class BaseFragment extends Fragment implements BaseContract.View super.onDestroyView(); } - @Override - public void onError(int resId) { - onError(getString(resId)); - } - - @Override - public void onError(String message) { - if (activity != null) { - activity.onError(message); + public void setTitle(String title) { + if (getActivity() != null) { + getActivity().setTitle(title); } } @Override - public void onNoNetworkError() { - onError(R.string.noInternet_text); + public void showMessage(@NonNull String text) { + if (getActivity() != null) { + ((BaseActivity) getActivity()).showMessage(text); + } + } + + public void showMessage(@StringRes int stringId) { + showMessage(getString(stringId)); + } + + @Override + public void showNoNetworkMessage() { + if (getActivity() != null) { + ((BaseActivity) getActivity()).showNoNetworkMessage(); + } } @Override public boolean isNetworkConnected() { - return activity != null && activity.isNetworkConnected(); - } - - public void setButterKnife(Unbinder unbinder) { - this.unbinder = unbinder; - } - - public void setTitle(String title) { - if (activity != null) { - activity.setTitle(title); - } - } - - public FragmentComponent getFragmentComponent() { - return fragmentComponent; - } - - - protected void setUpOnViewCreated(View fragmentView) { - // do something on view created + return NetworkUtils.isOnline(getContext()); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.java new file mode 100644 index 00000000..178f0110 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePagerAdapter.java @@ -0,0 +1,47 @@ +package io.github.wulkanowy.ui.base; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class BasePagerAdapter extends FragmentStatePagerAdapter { + + private List fragmentList = new ArrayList<>(); + + private List titleList = new ArrayList<>(); + + public BasePagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); + } + + public void addFragment(@NonNull Fragment fragment, @NonNull String title) { + fragmentList.add(fragment); + titleList.add(title); + } + + public void addFragment(@NonNull Fragment fragment) { + fragmentList.add(fragment); + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + @Override + public int getCount() { + return fragmentList.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + if (!titleList.isEmpty()) { + return titleList.get(position); + } + return null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java index 611b7a29..8f9b8af0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.base; +import android.support.annotation.NonNull; + import javax.inject.Inject; import io.github.wulkanowy.data.RepositoryContract; @@ -16,12 +18,12 @@ public class BasePresenter implements BaseContract. } @Override - public void onStart(V view) { + public void attachView(@NonNull V view) { this.view = view; } @Override - public void onDestroy() { + public void detachView() { view = null; } 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..a0960af2 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 @@ -5,19 +5,20 @@ import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.design.widget.Snackbar; +import android.support.annotation.NonNull; 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; import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.TextView; +import android.widget.Toast; import javax.inject.Inject; import butterknife.BindView; -import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnEditorAction; import io.github.wulkanowy.R; @@ -63,14 +64,10 @@ public class LoginActivity extends BaseActivity implements LoginContract.View { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); + injectViews(); - setButterKnife(ButterKnife.bind(this)); - getActivityComponent().inject(this); - - presenter.onStart(this); - + presenter.attachView(this); setUpOnCreate(); - } protected void setUpOnCreate() { @@ -164,12 +161,6 @@ public class LoginActivity extends BaseActivity implements LoginContract.View { KeyboardUtils.hideSoftInput(this); } - @Override - public void onError(String message) { - Snackbar.make(findViewById(R.id.login_activity_container), message, - Snackbar.LENGTH_LONG).show(); - } - @Override public void setStepOneLoginProgress() { onLoginProgressUpdate("1", getString(R.string.step_login)); @@ -210,12 +201,36 @@ public class LoginActivity extends BaseActivity implements LoginContract.View { } @Override - public void onDestroy() { - super.onDestroy(); - presenter.onDestroy(); + public void showActionBar(boolean show) { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + if (show) { + actionBar.show(); + } else { + actionBar.hide(); + } + } + } + + @NonNull + @Override + protected View getMessageView() { + return findViewById(R.id.login_activity_container); + } + + + @Override + public void onSyncFailed() { + Toast.makeText(getApplicationContext(), R.string.login_sync_error, Toast.LENGTH_LONG).show(); } private void onLoginProgressUpdate(String step, String message) { loginProgressText.setText(String.format("%1$s/2 - %2$s...", step, message)); } + + @Override + public void onDestroy() { + presenter.detachView(); + 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..96bb49d6 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 @@ -1,7 +1,5 @@ package io.github.wulkanowy.ui.login; -import io.github.wulkanowy.data.RepositoryContract; -import io.github.wulkanowy.di.annotations.PerActivity; import io.github.wulkanowy.ui.base.BaseContract; public interface LoginContract { @@ -33,9 +31,12 @@ public interface LoginContract { void hideSoftInput(); + void showActionBar(boolean show); + + void onSyncFailed(); + } - @PerActivity interface Presenter extends BaseContract.Presenter { void attemptLogin(String email, String password, String symbol); @@ -46,10 +47,8 @@ public interface LoginContract { void onLoginProgress(int step); - void onEndAsync(boolean success, Exception exception); + void onEndAsync(int success, Exception exception); void onCanceledAsync(); - - RepositoryContract getRepository(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.java new file mode 100644 index 00000000..0cb26c49 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginModule.java @@ -0,0 +1,13 @@ +package io.github.wulkanowy.ui.login; + +import dagger.Binds; +import dagger.Module; +import io.github.wulkanowy.di.scopes.PerActivity; + +@Module +public abstract class LoginModule { + + @PerActivity + @Binds + abstract LoginContract.Presenter provideLoginPresenter(LoginPresenter loginPresenter); +} 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..d8461bf3 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 @@ -11,6 +11,7 @@ import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.ui.base.BasePresenter; import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.FabricUtils; public class LoginPresenter extends BasePresenter implements LoginContract.Presenter { @@ -46,7 +47,7 @@ public class LoginPresenter extends BasePresenter loginAsync.execute(); } else { - getView().onNoNetworkError(); + getView().showNoNetworkMessage(); } getView().hideSoftInput(); @@ -55,6 +56,7 @@ public class LoginPresenter extends BasePresenter @Override public void onStartAsync() { if (isViewAttached()) { + getView().showActionBar(false); getView().showLoginProgress(true); } } @@ -63,10 +65,10 @@ public class LoginPresenter extends BasePresenter public void onDoInBackground(int stepNumber) throws Exception { switch (stepNumber) { case 1: - getRepository().registerUser(email, password, symbol); + getRepository().getSyncRepo().registerUser(email, password, symbol); break; case 2: - getRepository().syncAll(); + getRepository().getSyncRepo().syncAll(); break; } } @@ -81,27 +83,38 @@ public class LoginPresenter extends BasePresenter } @Override - public void onEndAsync(boolean success, Exception exception) { - if (success) { - getView().openMainActivity(); - } 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); + public void onEndAsync(int success, Exception exception) { + switch (success) { + case LoginTask.LOGIN_AND_SYNC_SUCCESS: + FabricUtils.logRegister(true, getRepository().getDbRepo().getCurrentSymbol().getSymbol(), "Success"); + getView().openMainActivity(); + return; + case LoginTask.SYNC_FAILED: + FabricUtils.logRegister(true, symbol, exception.getMessage()); + getView().onSyncFailed(); + getView().openMainActivity(); + return; + case LoginTask.LOGIN_FAILED: + if (exception instanceof BadCredentialsException) { + getView().setErrorPassIncorrect(); + getView().showSoftInput(); + } else if (exception instanceof AccountPermissionException) { + getView().setErrorSymbolRequired(); + getView().showSoftInput(); + } else { + FabricUtils.logRegister(false, symbol, exception.getMessage()); + getView().showMessage(getRepository().getResRepo().getErrorLoginMessage(exception)); + } + break; } - + getView().showActionBar(true); + getView().showLoginProgress(false); } @Override public void onCanceledAsync() { if (isViewAttached()) { + getView().showActionBar(true); getView().showLoginProgress(false); } } @@ -119,8 +132,8 @@ public class LoginPresenter extends BasePresenter return AppConstant.DEFAULT_SYMBOL; } - String[] keys = getRepository().getSymbolsKeysArray(); - String[] values = getRepository().getSymbolsValuesArray(); + String[] keys = getRepository().getResRepo().getSymbolsKeysArray(); + String[] values = getRepository().getResRepo().getSymbolsValuesArray(); LinkedHashMap map = new LinkedHashMap<>(); for (int i = 0; i < Math.min(keys.length, values.length); ++i) { @@ -155,11 +168,11 @@ public class LoginPresenter extends BasePresenter } @Override - public void onDestroy() { + public void detachView() { if (loginAsync != null) { loginAsync.cancel(true); loginAsync = null; } - super.onDestroy(); + super.detachView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java index b22194da..2938a836 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java @@ -2,7 +2,13 @@ package io.github.wulkanowy.ui.login; import android.os.AsyncTask; -public class LoginTask extends AsyncTask { +public class LoginTask extends AsyncTask { + + public final static int LOGIN_AND_SYNC_SUCCESS = 1; + + public final static int LOGIN_FAILED = -1; + + public final static int SYNC_FAILED = 2; private LoginContract.Presenter presenter; @@ -18,18 +24,23 @@ public class LoginTask extends AsyncTask { } @Override - protected Boolean doInBackground(Void... params) { + protected Integer doInBackground(Void... params) { try { publishProgress(1); presenter.onDoInBackground(1); + } catch (Exception e) { + exception = e; + return LOGIN_FAILED; + } + try { publishProgress(2); presenter.onDoInBackground(2); } catch (Exception e) { exception = e; - return false; + return SYNC_FAILED; } - return true; + return LOGIN_AND_SYNC_SUCCESS; } @Override @@ -38,7 +49,7 @@ public class LoginTask extends AsyncTask { } @Override - protected void onPostExecute(Boolean success) { + protected void onPostExecute(Integer success) { presenter.onEndAsync(success, exception); } 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..b739ae98 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 @@ -2,9 +2,11 @@ package io.github.wulkanowy.ui.main; import android.content.Context; import android.content.Intent; -import android.graphics.Color; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; import android.view.View; import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; @@ -12,21 +14,25 @@ import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; import com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager; import javax.inject.Inject; +import javax.inject.Named; import butterknife.BindView; -import butterknife.ButterKnife; import io.github.wulkanowy.R; -import io.github.wulkanowy.services.SyncJob; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.services.jobs.SyncJob; import io.github.wulkanowy.ui.base.BaseActivity; +import io.github.wulkanowy.ui.base.BasePagerAdapter; import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; -import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; +import io.github.wulkanowy.ui.main.exams.ExamsFragment; import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.settings.SettingsFragment; import io.github.wulkanowy.ui.main.timetable.TimetableFragment; +import io.github.wulkanowy.utils.CommonUtils; public class MainActivity extends BaseActivity implements MainContract.View, AHBottomNavigation.OnTabSelectedListener, OnFragmentIsReadyListener { - private int initTabPosition = 0; + public static final String EXTRA_CARD_ID_KEY = "cardId"; @BindView(R.id.main_activity_nav) AHBottomNavigation bottomNavigation; @@ -37,12 +43,19 @@ public class MainActivity extends BaseActivity implements MainContract.View, @BindView(R.id.main_activity_progress_bar) View progressBar; + @BindView(R.id.main_activity_appbar) + AppBarLayout appBar; + + @Named("Main") @Inject - MainPagerAdapter pagerAdapter; + BasePagerAdapter pagerAdapter; @Inject MainContract.Presenter presenter; + @Inject + RepositoryContract repository; + public static Intent getStartIntent(Context context) { return new Intent(context, MainActivity.class); } @@ -51,16 +64,10 @@ public class MainActivity extends BaseActivity implements MainContract.View, public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + setSupportActionBar((Toolbar) findViewById(R.id.main_activity_toolbar)); + injectViews(); - initTabPosition = getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, initTabPosition); - - getActivityComponent().inject(this); - setButterKnife(ButterKnife.bind(this)); - - presenter.onStart(this); - - initiationViewPager(); - initiationBottomNav(); + presenter.attachView(this, getIntent().getIntExtra(EXTRA_CARD_ID_KEY, -1)); } @Override @@ -89,6 +96,8 @@ public class MainActivity extends BaseActivity implements MainContract.View, @Override public boolean onTabSelected(int position, boolean wasSelected) { presenter.onTabSelected(position, wasSelected); + appBar.setExpanded(true, true); + invalidateOptionsMenu(); return true; } @@ -102,53 +111,60 @@ 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.exams_text), + R.drawable.ic_menu_exams_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.setInactiveColor(CommonUtils.getThemeAttrColor(this, android.R.attr.textColorTertiary)); + bottomNavigation.setDefaultBackgroundColor(CommonUtils.getThemeAttrColor(this, R.attr.bottomNavBackground)); 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 ExamsFragment()); 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 + public void startSyncService(int interval, boolean useOnlyWifi) { + SyncJob.start(getApplicationContext(), interval, useOnlyWifi); + } + + @NonNull + @Override + protected View getMessageView() { + return findViewById(R.id.main_activity_view_pager); } @Override protected void onDestroy() { + presenter.detachView(); super.onDestroy(); - presenter.onDestroy(); } } 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..cb4e29e4 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 @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.main; -import io.github.wulkanowy.di.annotations.PerActivity; +import android.support.annotation.NonNull; + import io.github.wulkanowy.ui.base.BaseContract; public interface MainContract { @@ -14,11 +15,18 @@ public interface MainContract { void showActionBar(); void hideActionBar(); + + void initiationViewPager(int tabPosition); + + void initiationBottomNav(int tabPosition); + + void startSyncService(int interval, boolean useOnlyWifi); } - @PerActivity interface Presenter extends BaseContract.Presenter { + void attachView(@NonNull View view, int initTabId); + void onTabSelected(int position, boolean wasSelected); void onFragmentIsReady(); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.java new file mode 100644 index 00000000..ff3fed67 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainModule.java @@ -0,0 +1,50 @@ +package io.github.wulkanowy.ui.main; + +import javax.inject.Named; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.android.ContributesAndroidInjector; +import io.github.wulkanowy.di.scopes.PerActivity; +import io.github.wulkanowy.di.scopes.PerFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; +import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; +import io.github.wulkanowy.ui.main.attendance.AttendanceModule; +import io.github.wulkanowy.ui.main.exams.ExamsFragment; +import io.github.wulkanowy.ui.main.exams.ExamsModule; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.grades.GradesModule; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableModule; + +@Module +public abstract class MainModule { + + @PerActivity + @Binds + abstract MainContract.Presenter provideMainPresenter(MainPresenter mainPresenter); + + @Named("Main") + @PerActivity + @Provides + static BasePagerAdapter provideAdapter(MainActivity activity) { + return new BasePagerAdapter(activity.getSupportFragmentManager()); + } + + @PerFragment + @ContributesAndroidInjector(modules = GradesModule.class) + abstract GradesFragment bindsGradesFragment(); + + @PerFragment + @ContributesAndroidInjector(modules = TimetableModule.class) + abstract TimetableFragment bindTimetableFragment(); + + @PerFragment + @ContributesAndroidInjector(modules = ExamsModule.class) + abstract ExamsFragment bindExamsFragment(); + + @PerFragment + @ContributesAndroidInjector(modules = AttendanceModule.class) + abstract AttendanceFragment bindAttendanceFramgnet(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java deleted file mode 100644 index fcc51fb5..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class MainPagerAdapter extends FragmentStatePagerAdapter { - - private List fragmentList = new ArrayList<>(); - - public MainPagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); - } - - void addFragment(Fragment fragment) { - fragmentList.add(fragment); - } - - @Override - public Fragment getItem(int position) { - return fragmentList.get(position); - } - - @Override - public int getCount() { - return fragmentList.size(); - } -} 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..2ca9eefa 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 @@ -1,11 +1,14 @@ package io.github.wulkanowy.ui.main; +import android.support.annotation.NonNull; import javax.inject.Inject; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.ui.base.BasePresenter; +import static io.github.wulkanowy.utils.TimeUtilsKt.isHolidays; + public class MainPresenter extends BasePresenter implements MainContract.Presenter { @@ -17,10 +20,26 @@ public class MainPresenter extends BasePresenter } @Override - public void onStart(MainContract.View view) { - super.onStart(view); + public void attachView(@NonNull MainContract.View view, int initTabId) { + super.attachView(view); getView().showProgressBar(true); getView().hideActionBar(); + + int tabPosition; + + if (initTabId != -1) { + tabPosition = initTabId; + } else { + tabPosition = getRepository().getSharedRepo().getStartupTab(); + } + + getView().initiationBottomNav(tabPosition); + getView().initiationViewPager(tabPosition); + + if (getRepository().getSharedRepo().isServicesEnable() && !isHolidays()) { + getView().startSyncService(getRepository().getSharedRepo().getServicesInterval(), + getRepository().getSharedRepo().isMobileDisable()); + } } @Override @@ -32,11 +51,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/TabsData.java b/app/src/main/java/io/github/wulkanowy/ui/main/TabsData.java deleted file mode 100644 index 3af29534..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/TabsData.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.support.v4.app.Fragment; - -import java.util.ArrayList; -import java.util.List; - -public class TabsData { - - private List fragments = new ArrayList<>(); - - private List titles = new ArrayList<>(); - - public Fragment getFragment(int index) { - return fragments.get(index); - } - - public void addFragment(Fragment fragment) { - if (fragment != null) { - fragments.add(fragment); - } - } - - public int getFragmentsCount() { - return fragments.size(); - } - - public String getTitle(int index) { - return titles.get(index); - } - - public void addTitle(String title) { - if (title != null) { - titles.add(title); - } - } -} 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..1ad66787 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 @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.main.attendance; -import io.github.wulkanowy.di.annotations.PerActivity; +import android.support.annotation.NonNull; + import io.github.wulkanowy.ui.base.BaseContract; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; public interface AttendanceContract { @@ -13,24 +13,21 @@ public interface AttendanceContract { void scrollViewPagerToPosition(int position); - void setTabDataToAdapter(TabsData tabsData); + void setTabDataToAdapter(String date); void setAdapterWithTabLayout(); - void setChildFragmentSelected(int position, boolean selected); - boolean isMenuVisible(); + + void setThemeForTab(int position); } - @PerActivity interface Presenter extends BaseContract.Presenter { - void onFragmentVisible(boolean isVisible); + void onFragmentActivated(boolean isVisible); - void onTabSelected(int position); + void attachView(@NonNull View view, OnFragmentIsReadyListener listener); - void onTabUnselected(int position); - - void onStart(View view, OnFragmentIsReadyListener listener); + void setRestoredPosition(int position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialogFragment.java index f5bbcb41..ea38e51d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialogFragment.java @@ -77,8 +77,8 @@ public class AttendanceDialogFragment extends DialogFragment { description.setText(lesson.getDescription()); - if (lesson.getIsAbsenceUnexcused()) { - description.setTextColor(getResources().getColor(R.color.colorPrimaryDark)); + if (lesson.getAbsenceUnexcused()) { + description.setTextColor(getResources().getColor(R.color.colorPrimary)); } return view; 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..732806db 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 @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.main.attendance; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; @@ -11,16 +10,18 @@ import android.view.View; import android.view.ViewGroup; import javax.inject.Inject; +import javax.inject.Named; import butterknife.BindView; -import butterknife.ButterKnife; import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; +import io.github.wulkanowy.ui.main.attendance.tab.AttendanceTabFragment; -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; @@ -29,7 +30,8 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra TabLayout tabLayout; @Inject - AttendancePagerAdapter pagerAdapter; + @Named("Attendance") + BasePagerAdapter pagerAdapter; @Inject AttendanceContract.Presenter presenter; @@ -39,14 +41,13 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_attendance, container, false); + injectViews(view); - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + presenter.attachView(this, (OnFragmentIsReadyListener) getActivity()); + + if (savedInstanceState != null) { + presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY)); } - return view; } @@ -54,41 +55,19 @@ 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); + public void setTabDataToAdapter(String date) { + pagerAdapter.addFragment(AttendanceTabFragment.newInstance(date), date); } @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 @@ -96,22 +75,28 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra viewPager.setCurrentItem(position, false); } + @Override + public void setThemeForTab(int position) { + TabLayout.Tab tab = tabLayout.getTabAt(position); + if (tab != null) { + tab.setCustomView(R.layout.current_week_tab); + } + } + @Override public void setActivityTitle() { setTitle(getString(R.string.attendance_text)); } @Override - public void onError(String message) { - if (getActivity() != null) { - Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), - message, Snackbar.LENGTH_LONG).show(); - } + public void onSaveInstanceState(Bundle outState) { + outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem()); + super.onSaveInstanceState(outState); } @Override public void onDestroyView() { - presenter.onDestroy(); + presenter.detachView(); super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceModule.java new file mode 100644 index 00000000..8679cf10 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceModule.java @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.main.attendance; + +import javax.inject.Named; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.android.ContributesAndroidInjector; +import io.github.wulkanowy.di.scopes.PerChildFragment; +import io.github.wulkanowy.di.scopes.PerFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; +import io.github.wulkanowy.ui.main.attendance.tab.AttendanceTabFragment; +import io.github.wulkanowy.ui.main.attendance.tab.AttendanceTabModule; + +@Module +public abstract class AttendanceModule { + + @PerFragment + @Binds + abstract AttendanceContract.Presenter provideAttendancePresenter(AttendancePresenter attendancePresenter); + + @PerFragment + @Named("Attendance") + @Provides + static BasePagerAdapter providePagerAdapter(AttendanceFragment fragment) { + return new BasePagerAdapter(fragment.getChildFragmentManager()); + } + + @PerChildFragment + @ContributesAndroidInjector(modules = AttendanceTabModule.class) + abstract AttendanceTabFragment bindAttendanceTabFragment(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePagerAdapter.java deleted file mode 100644 index 3cabff71..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePagerAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.ui.main.attendance; - -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; - -import io.github.wulkanowy.ui.main.TabsData; - -public class AttendancePagerAdapter extends FragmentStatePagerAdapter { - - private TabsData tabsData; - - public AttendancePagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); - } - - void setTabsData(TabsData tabsData) { - this.tabsData = tabsData; - } - - @Override - public Fragment getItem(int position) { - return tabsData.getFragment(position); - } - - - @Override - public int getCount() { - return tabsData.getFragmentsCount(); - } - - @Nullable - @Override - public CharSequence getPageTitle(int position) { - return tabsData.getTitle(position); - } -} 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..73a2837c 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 @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.main.attendance; +import android.support.annotation.NonNull; + import java.util.ArrayList; import java.util.List; @@ -8,11 +10,12 @@ import javax.inject.Inject; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.ui.base.BasePresenter; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; -import io.github.wulkanowy.utils.TimeUtils; import io.github.wulkanowy.utils.async.AbstractTask; import io.github.wulkanowy.utils.async.AsyncListeners; +import static io.github.wulkanowy.utils.TimeUtilsKt.getFirstDayOfCurrentWeek; +import static io.github.wulkanowy.utils.TimeUtilsKt.getMondaysFromCurrentSchoolYear; + public class AttendancePresenter extends BasePresenter implements AttendanceContract.Presenter, AsyncListeners.OnFirstLoadingListener { @@ -20,11 +23,9 @@ public class AttendancePresenter extends BasePresenter private List dates = new ArrayList<>(); - private TabsData tabsData = new TabsData(); - private OnFragmentIsReadyListener listener; - private int positionToScroll; + private int positionToScroll = 0; private boolean isFirstSight = false; @@ -34,8 +35,8 @@ public class AttendancePresenter extends BasePresenter } @Override - public void onStart(AttendanceContract.View view, OnFragmentIsReadyListener listener) { - super.onStart(view); + public void attachView(@NonNull AttendanceContract.View view, OnFragmentIsReadyListener listener) { + super.attachView(view); this.listener = listener; if (getView().isMenuVisible()) { @@ -43,9 +44,12 @@ public class AttendancePresenter extends BasePresenter } if (dates.isEmpty()) { - dates = TimeUtils.getMondaysFromCurrentSchoolYear(); + dates = getMondaysFromCurrentSchoolYear(); + } + + if (positionToScroll == 0) { + positionToScroll = dates.indexOf(getFirstDayOfCurrentWeek()); } - positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); if (!isFirstSight) { isFirstSight = true; @@ -57,27 +61,16 @@ 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 { + public void onDoInBackgroundLoading() { for (String date : dates) { - tabsData.addTitle(date); - tabsData.addFragment(AttendanceTabFragment.newInstance(date)); + getView().setTabDataToAdapter(date); } } @@ -90,22 +83,26 @@ public class AttendancePresenter extends BasePresenter @Override public void onEndLoadingAsync(boolean result, Exception exception) { if (result) { - getView().setTabDataToAdapter(tabsData); getView().setAdapterWithTabLayout(); + getView().setThemeForTab(positionToScroll); getView().scrollViewPagerToPosition(positionToScroll); listener.onFragmentIsReady(); } } @Override - public void onDestroy() { + public void setRestoredPosition(int position) { + this.positionToScroll = position; + } + + @Override + public void detachView() { isFirstSight = false; if (loadingTask != null) { loadingTask.cancel(true); loadingTask = null; } - - super.onDestroy(); + super.detachView(); } } 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/tab/AttendanceHeader.java similarity index 62% rename from app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceHeaderItem.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceHeader.java index b103a007..86128109 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/tab/AttendanceHeader.java @@ -1,5 +1,8 @@ -package io.github.wulkanowy.ui.main.attendance; +package io.github.wulkanowy.ui.main.attendance.tab; +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; @@ -11,21 +14,22 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.List; -import butterknife.BindColor; 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; +import io.github.wulkanowy.utils.CommonUtils; -public class AttendanceHeaderItem - extends AbstractExpandableHeaderItem { +public class AttendanceHeader + extends AbstractExpandableHeaderItem { private Day day; - AttendanceHeaderItem(Day day) { + AttendanceHeader(Day day) { this.day = day; } @@ -35,7 +39,7 @@ public class AttendanceHeaderItem if (o == null || getClass() != o.getClass()) return false; - AttendanceHeaderItem that = (AttendanceHeaderItem) o; + AttendanceHeader that = (AttendanceHeader) o; return new EqualsBuilder() .append(day, that.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()); } @@ -81,16 +86,13 @@ public class AttendanceHeaderItem @BindView(R.id.attendance_header_free_name) TextView freeName; - @BindColor(R.color.secondary_text) - int secondaryColor; - - @BindColor(R.color.free_day) - int backgroundFreeDay; + 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,32 +105,46 @@ 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); - getContentView().setBackgroundColor(backgroundFreeDay); - dayName.setTextColor(secondaryColor); + private void setInactiveHeader(boolean inactive) { + ((FrameLayout) getContentView()).setForeground(inactive ? null : getSelectableDrawable()); + dayName.setTextColor(CommonUtils.getThemeAttrColor(context, + inactive ? android.R.attr.textColorSecondary : android.R.attr.textColorPrimary)); + + if (inactive) { + getContentView().setBackgroundColor(CommonUtils.getThemeAttrColor(context, R.attr.colorControlHighlight)); + } 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) { - if (subItem.getLesson().getIsAbsenceUnexcused()) { + if (subItem.getLesson().getAbsenceUnexcused()) { i++; } } - return i; } private boolean isSubItemsHasChanges(List subItems) { for (AttendanceSubItem subItem : subItems) { - if (subItem.getLesson().getIsAbsenceUnexcused() || subItem.getLesson().getIsUnexcusedLateness()) { + if (subItem.getLesson().getAbsenceUnexcused() || subItem.getLesson() + .getUnexcusedLateness()) { return true; } } - return 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/tab/AttendanceSubItem.java similarity index 81% rename from app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceSubItem.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceSubItem.java index 6da35555..65c8329e 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/tab/AttendanceSubItem.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.main.attendance; +package io.github.wulkanowy.ui.main.attendance.tab; import android.content.Context; import android.support.v4.app.DialogFragment; @@ -16,16 +16,18 @@ 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; +import io.github.wulkanowy.ui.main.attendance.AttendanceDialogFragment; class AttendanceSubItem - extends AbstractSectionableItem { + extends AbstractSectionableItem { private AttendanceLesson lesson; - AttendanceSubItem(AttendanceHeaderItem header, AttendanceLesson lesson) { + AttendanceSubItem(AttendanceHeader header, AttendanceLesson lesson) { super(header); this.lesson = lesson; } @@ -60,12 +62,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); } @@ -100,7 +103,7 @@ class AttendanceSubItem lessonName.setText(lesson.getSubject()); lessonNumber.setText((String.valueOf(lesson.getNumber()))); lessonDescription.setText(lesson.getDescription()); - alert.setVisibility(lesson.getIsAbsenceUnexcused() || lesson.getIsUnexcusedLateness() + alert.setVisibility(lesson.getAbsenceUnexcused() || lesson.getUnexcusedLateness() ? View.VISIBLE : View.INVISIBLE); } 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/tab/AttendanceTabContract.java similarity index 66% rename from app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabContract.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabContract.java index 1bac005d..a83e67e8 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/tab/AttendanceTabContract.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.main.attendance; +package io.github.wulkanowy.ui.main.attendance.tab; import java.util.List; @@ -8,7 +8,7 @@ public interface AttendanceTabContract { interface View extends BaseContract.View { - void updateAdapterList(List headerItems); + void updateAdapterList(List headerItems); void onRefreshSuccess(); @@ -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/tab/AttendanceTabFragment.java similarity index 60% rename from app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabFragment.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabFragment.java index 90313daa..8de47469 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/tab/AttendanceTabFragment.java @@ -1,9 +1,8 @@ -package io.github.wulkanowy.ui.main.attendance; +package io.github.wulkanowy.ui.main.attendance.tab; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -15,11 +14,9 @@ import java.util.List; import javax.inject.Inject; import butterknife.BindView; -import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; import io.github.wulkanowy.ui.base.BaseFragment; public class AttendanceTabFragment extends BaseFragment implements AttendanceTabContract.View, @@ -27,12 +24,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; @@ -49,7 +40,9 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab AttendanceTabContract.Presenter presenter; @Inject - FlexibleAdapter adapter; + FlexibleAdapter adapter; + + private boolean isFragmentVisible = false; public static AttendanceTabFragment newInstance(String date) { AttendanceTabFragment fragmentTab = new AttendanceTabFragment(); @@ -61,60 +54,45 @@ 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) { View view = inflater.inflate(R.layout.fragment_attendance_tab, container, false); + injectViews(view); - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - - if (getArguments() != null) { - presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); - } - - presenter.onStart(this, isPrimary); + if (getArguments() != null) { + presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); } + + presenter.attachView(this); + presenter.onFragmentActivated(isFragmentVisible); return view; } @Override - protected void setUpOnViewCreated(View fragmentView) { + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { adapter.setAutoCollapseOnExpand(true); adapter.setAutoScrollOnExpand(true); adapter.expandItemsAtStartUp(); - recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(view.getContext())); recyclerView.setAdapter(adapter); refreshLayout.setColorSchemeResources(android.R.color.black); refreshLayout.setOnRefreshListener(this); - } @Override - public void updateAdapterList(List headerItems) { + public void updateAdapterList(List headerItems) { adapter.updateDataSet(headerItems); } @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 +103,7 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab @Override public void onRefreshSuccess() { - onError(R.string.timetable_refresh_success); + showMessage(R.string.sync_completed); } @Override @@ -143,28 +121,9 @@ 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) { - Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), - message, Snackbar.LENGTH_LONG).show(); - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(SAVED_KEY, isSelected); - super.onSaveInstanceState(outState); - } - @Override public void onDestroyView() { - isPrimary = false; - presenter.onDestroy(); + presenter.detachView(); super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabModule.java new file mode 100644 index 00000000..380a221f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabModule.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.main.attendance.tab; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import io.github.wulkanowy.di.scopes.PerChildFragment; + +@Module +public abstract class AttendanceTabModule { + + @PerChildFragment + @Binds + abstract AttendanceTabContract.Presenter provideAttendanceTabPresenter(AttendanceTabPresenter attendanceTabPresenter); + + @PerChildFragment + @Provides + static FlexibleAdapter provideAdapter() { + return new FlexibleAdapter<>(null); + } +} 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/tab/AttendanceTabPresenter.java similarity index 70% rename from app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceTabPresenter.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/tab/AttendanceTabPresenter.java index d0d448ca..7d0c26e5 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/tab/AttendanceTabPresenter.java @@ -1,4 +1,6 @@ -package io.github.wulkanowy.ui.main.attendance; +package io.github.wulkanowy.ui.main.attendance.tab; + +import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.List; @@ -10,6 +12,7 @@ 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.Week; import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.FabricUtils; import io.github.wulkanowy.utils.async.AbstractTask; import io.github.wulkanowy.utils.async.AsyncListeners; @@ -21,7 +24,7 @@ public class AttendanceTabPresenter extends BasePresenter headerItems = new ArrayList<>(); + private List headerItems = new ArrayList<>(); private String date; @@ -33,21 +36,23 @@ public class AttendanceTabPresenter extends BasePresenter dayList = week.getDayList(); headerItems = new ArrayList<>(); @@ -105,7 +114,8 @@ public class AttendanceTabPresenter extends BasePresenter subItems = new ArrayList<>(); for (AttendanceLesson lesson : lessonList) { - lesson.setDescription(getRepository().getAttendanceLessonDescription(lesson)); + if (!isShowPresent && lesson.getPresence()) { + continue; + } + + lesson.setDescription(getRepository().getResRepo().getAttendanceLessonDescription(lesson)); subItems.add(new AttendanceSubItem(headerItem, lesson)); } + if (!isShowPresent && subItems.isEmpty()) { + continue; + } + headerItem.setSubItems(subItems); headerItem.setExpanded(false); headerItems.add(headerItem); @@ -153,13 +171,10 @@ public class AttendanceTabPresenter extends BasePresenter { - - void onStart(View view, OnFragmentIsReadyListener listener); - - void onFragmentVisible(boolean isVisible); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java deleted file mode 100644 index 0832b5fb..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.wulkanowy.ui.main.dashboard; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import javax.inject.Inject; - -import butterknife.ButterKnife; -import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; -import io.github.wulkanowy.ui.base.BaseFragment; -import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; - -public class DashboardFragment extends BaseFragment implements DashboardContract.View { - - @Inject - DashboardContract.Presenter presenter; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_board, container, false); - - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); - } - return view; - } - - @Override - public void setMenuVisibility(boolean menuVisible) { - super.setMenuVisibility(menuVisible); - if (presenter != null) { - presenter.onFragmentVisible(menuVisible); - } - } - - @Override - public void setActivityTitle() { - setTitle(getString(R.string.dashboard_text)); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java deleted file mode 100644 index 58d89b39..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.wulkanowy.ui.main.dashboard; - -import javax.inject.Inject; - -import io.github.wulkanowy.data.RepositoryContract; -import io.github.wulkanowy.ui.base.BasePresenter; -import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; - -public class DashboardPresenter extends BasePresenter - implements DashboardContract.Presenter { - - private OnFragmentIsReadyListener listener; - - @Inject - DashboardPresenter(RepositoryContract repository) { - super(repository); - } - - @Override - public void onStart(DashboardContract.View view, OnFragmentIsReadyListener listener) { - super.onStart(view); - this.listener = listener; - - if (getView().isMenuVisible()) { - getView().setActivityTitle(); - } - - this.listener.onFragmentIsReady(); - } - - @Override - public void onFragmentVisible(boolean isVisible) { - if (isVisible) { - getView().setActivityTitle(); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsContract.java new file mode 100644 index 00000000..aeeecea8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsContract.java @@ -0,0 +1,33 @@ +package io.github.wulkanowy.ui.main.exams; + +import android.support.annotation.NonNull; + +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface ExamsContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + boolean isMenuVisible(); + + void scrollViewPagerToPosition(int position); + + void setTabDataToAdapter(String date); + + void setAdapterWithTabLayout(); + + void setThemeForTab(int position); + } + + interface Presenter extends BaseContract.Presenter { + + void attachView(@NonNull View view, OnFragmentIsReadyListener listener); + + void onFragmentActivated(boolean isVisible); + + void setRestoredPosition(int position); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsDialogFragment.java new file mode 100644 index 00000000..8c4fafcd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsDialogFragment.java @@ -0,0 +1,81 @@ +package io.github.wulkanowy.ui.main.exams; + + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Exam; + +public class ExamsDialogFragment extends DialogFragment { + + private static final String ARGUMENT_KEY = "Item"; + + private Exam exam; + + @BindView(R.id.exams_dialog_subject_value) + TextView subject; + + @BindView(R.id.exams_dialog_type_value) + TextView type; + + @BindView(R.id.exams_dialog_teacher_value) + TextView teacher; + + @BindView(R.id.exams_dialog_date_value) + TextView entryDate; + + @BindView(R.id.exams_dialog_description_value) + TextView description; + + public static ExamsDialogFragment newInstance(Exam exam) { + ExamsDialogFragment dialogFragment = new ExamsDialogFragment(); + + Bundle bundle = new Bundle(); + bundle.putSerializable(ARGUMENT_KEY, exam); + + dialogFragment.setArguments(bundle); + + return dialogFragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + exam = (Exam) getArguments().getSerializable(ARGUMENT_KEY); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.exams_dialog, container, false); + + ButterKnife.bind(this, view); + + subject.setText(exam.getSubjectAndGroup()); + teacher.setText(exam.getTeacher()); + type.setText(exam.getType()); + entryDate.setText(exam.getEntryDate()); + + if (!exam.getDescription().isEmpty()) { + description.setText(exam.getDescription()); + } + + return view; + } + + @OnClick(R.id.exams_dialog_close) + void onClickCloseButton() { + dismiss(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsFragment.java new file mode 100644 index 00000000..05557b81 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsFragment.java @@ -0,0 +1,100 @@ +package io.github.wulkanowy.ui.main.exams; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import io.github.wulkanowy.R; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.ui.main.exams.tab.ExamsTabFragment; + +public class ExamsFragment extends BaseFragment implements ExamsContract.View { + + private static final String CURRENT_ITEM_KEY = "CurrentItem"; + + @BindView(R.id.exams_fragment_viewpager) + ViewPager viewPager; + + @BindView(R.id.exams_fragment_tab_layout) + TabLayout tabLayout; + + @Inject + @Named("Exams") + BasePagerAdapter pagerAdapter; + + @Inject + ExamsContract.Presenter presenter; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_exams, container, false); + injectViews(view); + + presenter.attachView(this, (OnFragmentIsReadyListener) getActivity()); + + if (savedInstanceState != null) { + presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY)); + } + return view; + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentActivated(menuVisible); + } + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.exams_text)); + } + + @Override + public void scrollViewPagerToPosition(int position) { + viewPager.setCurrentItem(position, false); + } + + @Override + public void setThemeForTab(int position) { + TabLayout.Tab tab = tabLayout.getTabAt(position); + if (tab != null) { + tab.setCustomView(R.layout.current_week_tab); + } + } + + @Override + public void setTabDataToAdapter(String date) { + pagerAdapter.addFragment(ExamsTabFragment.newInstance(date), date); + } + + @Override + public void setAdapterWithTabLayout() { + viewPager.setAdapter(pagerAdapter); + tabLayout.setupWithViewPager(viewPager); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem()); + super.onSaveInstanceState(outState); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + presenter.detachView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsModule.java new file mode 100644 index 00000000..8d56cf23 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsModule.java @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.main.exams; + +import javax.inject.Named; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.android.ContributesAndroidInjector; +import io.github.wulkanowy.di.scopes.PerChildFragment; +import io.github.wulkanowy.di.scopes.PerFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; +import io.github.wulkanowy.ui.main.exams.tab.ExamsTabFragment; +import io.github.wulkanowy.ui.main.exams.tab.ExamsTabModule; + +@Module +public abstract class ExamsModule { + + @PerFragment + @Binds + abstract ExamsContract.Presenter provideExamsPresneter(ExamsPresenter examsPresenter); + + @Named("Exams") + @PerFragment + @Provides + static BasePagerAdapter providePagerAdapter(ExamsFragment fragment) { + return new BasePagerAdapter(fragment.getChildFragmentManager()); + } + + @PerChildFragment + @ContributesAndroidInjector(modules = ExamsTabModule.class) + abstract ExamsTabFragment bindExamsTabFragment(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsPresenter.java new file mode 100644 index 00000000..20c0fe2e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/ExamsPresenter.java @@ -0,0 +1,107 @@ +package io.github.wulkanowy.ui.main.exams; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +import static io.github.wulkanowy.utils.TimeUtilsKt.getFirstDayOfCurrentWeek; +import static io.github.wulkanowy.utils.TimeUtilsKt.getMondaysFromCurrentSchoolYear; + +public class ExamsPresenter extends BasePresenter + implements ExamsContract.Presenter, AsyncListeners.OnFirstLoadingListener { + + private AbstractTask loadingTask; + + private List dates = new ArrayList<>(); + + private OnFragmentIsReadyListener listener; + + private int positionToScroll = 0; + + private boolean isFirstSight = false; + + @Inject + ExamsPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void attachView(@NonNull ExamsContract.View view, OnFragmentIsReadyListener listener) { + super.attachView(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + if (dates.isEmpty()) { + dates = getMondaysFromCurrentSchoolYear(); + } + + if (positionToScroll == 0) { + positionToScroll = dates.indexOf(getFirstDayOfCurrentWeek()); + } + + if (!isFirstSight) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onFragmentActivated(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } + + @Override + public void setRestoredPosition(int position) { + this.positionToScroll = position; + } + + @Override + public void onDoInBackgroundLoading() { + for (String date : dates) { + getView().setTabDataToAdapter(date); + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (result) { + getView().setAdapterWithTabLayout(); + getView().setThemeForTab(positionToScroll); + getView().scrollViewPagerToPosition(positionToScroll); + listener.onFragmentIsReady(); + } + } + + @Override + public void detachView() { + isFirstSight = false; + + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + super.detachView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsHeader.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsHeader.java new file mode 100644 index 00000000..6c0c364e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsHeader.java @@ -0,0 +1,82 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractHeaderItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Day; + +public class ExamsHeader extends AbstractHeaderItem { + + private Day day; + + ExamsHeader(Day day) { + this.day = day; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ExamsHeader that = (ExamsHeader) o; + + return new EqualsBuilder() + .append(day, that.day) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(day) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.exams_header; + } + + @Override + public HeaderVieHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderVieHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderVieHolder holder, int position, List payloads) { + holder.onBind(day); + } + + static class HeaderVieHolder extends FlexibleViewHolder { + + @BindView(R.id.exams_header_name) + TextView name; + + @BindView(R.id.exams_header_date) + TextView date; + + HeaderVieHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + } + + void onBind(Day item) { + name.setText(StringUtils.capitalize(item.getDayName())); + date.setText(item.getDate()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsSubItem.java new file mode 100644 index 00000000..9ff10ebc --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsSubItem.java @@ -0,0 +1,109 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +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.Exam; +import io.github.wulkanowy.ui.main.exams.ExamsDialogFragment; + +public class ExamsSubItem + extends AbstractSectionableItem { + + private Exam exam; + + ExamsSubItem(ExamsHeader header, Exam exam) { + super(header); + this.exam = exam; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + ExamsSubItem that = (ExamsSubItem) o; + + return new EqualsBuilder() + .append(exam, that.exam) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(exam) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.exams_subitem; + } + + @Override + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + holder.onBind(exam); + } + + static class SubItemViewHolder extends FlexibleViewHolder { + + @BindView(R.id.exams_subitem_subject) + TextView subject; + + @BindView(R.id.exams_subitems_teacher) + TextView teacher; + + @BindView(R.id.exams_subitems_type) + TextView type; + + private Exam item; + + SubItemViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + view.setOnClickListener(this); + } + + void onBind(Exam exam) { + item = exam; + + subject.setText(item.getSubjectAndGroup()); + teacher.setText(item.getTeacher()); + type.setText(item.getType()); + } + + @Override + public void onClick(View view) { + super.onClick(view); + showDialog(); + + } + + private void showDialog() { + ExamsDialogFragment dialogFragment = ExamsDialogFragment.newInstance(item); + dialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); + dialogFragment.show(((FragmentActivity) getContentView().getContext()).getSupportFragmentManager(), + item.toString()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabContract.java new file mode 100644 index 00000000..4e0785bb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabContract.java @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import java.util.List; + +import io.github.wulkanowy.ui.base.BaseContract; + +public interface ExamsTabContract { + + interface View extends BaseContract.View { + + void onRefreshSuccess(); + + void hideRefreshingBar(); + + void showNoItem(boolean show); + + void showProgressBar(boolean show); + + void updateAdapterList(List headerItems); + } + + interface Presenter extends BaseContract.Presenter { + + void onFragmentActivated(boolean isSelected); + + void setArgumentDate(String date); + + void onRefresh(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabFragment.java new file mode 100644 index 00000000..1b6f361a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabFragment.java @@ -0,0 +1,126 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; +import io.github.wulkanowy.R; +import io.github.wulkanowy.ui.base.BaseFragment; + +public class ExamsTabFragment extends BaseFragment implements ExamsTabContract.View, + SwipeRefreshLayout.OnRefreshListener { + + private static final String ARGUMENT_KEY = "date"; + + @BindView(R.id.exams_tab_fragment_recycler) + RecyclerView recyclerView; + + @BindView(R.id.exams_tab_fragment_swipe_refresh) + SwipeRefreshLayout refreshLayout; + + @BindView(R.id.exams_tab_fragment_progress_bar) + View progressBar; + + @BindView(R.id.exams_tab_fragment_no_item_container) + View noItemView; + + @Inject + ExamsTabContract.Presenter presenter; + + @Inject + FlexibleAdapter adapter; + + private boolean isFragmentVisible = false; + + public static ExamsTabFragment newInstance(String date) { + ExamsTabFragment tabFragment = new ExamsTabFragment(); + + Bundle bundle = new Bundle(); + bundle.putString(ARGUMENT_KEY, date); + tabFragment.setArguments(bundle); + + return tabFragment; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_exams_tab, container, false); + injectViews(view); + + if (getArguments() != null) { + presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); + } + presenter.attachView(this); + presenter.onFragmentActivated(isFragmentVisible); + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + adapter.setDisplayHeadersAtStartUp(true); + + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(view.getContext())); + recyclerView.setAdapter(adapter); + + refreshLayout.setColorSchemeResources(android.R.color.black); + refreshLayout.setOnRefreshListener(this); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + isFragmentVisible = menuVisible; + if (presenter != null) { + presenter.onFragmentActivated(menuVisible); + } + } + + @Override + public void updateAdapterList(List headerItems) { + adapter.updateDataSet(headerItems); + } + + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void onRefreshSuccess() { + showMessage(R.string.sync_completed); + } + + @Override + public void hideRefreshingBar() { + refreshLayout.setRefreshing(false); + } + + @Override + public void showNoItem(boolean show) { + noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void showProgressBar(boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + + @Override + public void onDestroyView() { + presenter.detachView(); + super.onDestroyView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabModule.java new file mode 100644 index 00000000..fc9a7595 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabModule.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import io.github.wulkanowy.di.scopes.PerChildFragment; + +@Module +public abstract class ExamsTabModule { + + @PerChildFragment + @Binds + abstract ExamsTabContract.Presenter provideExamsTabPresenter(ExamsTabPresenter examsTabPresenter); + + @PerChildFragment + @Provides + static FlexibleAdapter provideAdapter() { + return new FlexibleAdapter<>(null); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabPresenter.java new file mode 100644 index 00000000..b60a4607 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exams/tab/ExamsTabPresenter.java @@ -0,0 +1,167 @@ +package io.github.wulkanowy.ui.main.exams.tab; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Exam; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.FabricUtils; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class ExamsTabPresenter extends BasePresenter + implements ExamsTabContract.Presenter, AsyncListeners.OnFirstLoadingListener, + AsyncListeners.OnRefreshListener { + + private AbstractTask refreshTask; + + private AbstractTask loadingTask; + + private List subItems = new ArrayList<>(); + + private String date; + + private boolean isFirstSight = false; + + @Inject + ExamsTabPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void attachView(@NonNull ExamsTabContract.View view) { + super.attachView(view); + getView().showProgressBar(true); + getView().showNoItem(false); + } + + @Override + public void onFragmentActivated(boolean isSelected) { + if (!isFirstSight && isSelected && isViewAttached()) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } else if (!isSelected) { + cancelAsyncTasks(); + } + } + + @Override + public void setArgumentDate(String date) { + this.date = date; + } + + @Override + public void onRefresh() { + if (getView().isNetworkConnected()) { + refreshTask = new AbstractTask(); + refreshTask.setOnRefreshListener(this); + refreshTask.execute(); + } else { + getView().showNoNetworkMessage(); + getView().hideRefreshingBar(); + } + } + + @Override + public void onDoInBackgroundRefresh() throws Exception { + syncData(); + } + + @Override + public void onCanceledRefreshAsync() { + if (isViewAttached()) { + getView().hideRefreshingBar(); + } + } + + @Override + public void onEndRefreshAsync(boolean result, Exception exception) { + if (result) { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + + getView().onRefreshSuccess(); + } else { + getView().showMessage(getRepository().getResRepo().getErrorLoginMessage(exception)); + } + getView().hideRefreshingBar(); + + FabricUtils.logRefresh("Exams", result, date); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + Week week = getRepository().getDbRepo().getWeek(date); + + if (week == null || !week.getExamsSynced()) { + syncData(); + week = getRepository().getDbRepo().getWeek(date); + } + + week.resetDayList(); + List dayList = week.getDayList(); + + subItems = new ArrayList<>(); + + for (Day day : dayList) { + day.resetExams(); + ExamsHeader headerItem = new ExamsHeader(day); + + List examList = day.getExams(); + + for (Exam exam : examList) { + subItems.add(new ExamsSubItem(headerItem, exam)); + } + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (subItems.isEmpty()) { + getView().showNoItem(true); + getView().updateAdapterList(null); + } else { + getView().updateAdapterList(subItems); + getView().showNoItem(false); + } + getView().showProgressBar(false); + } + + private void syncData() throws Exception { + getRepository().getSyncRepo().syncExams(0, date); + } + + private void cancelAsyncTasks() { + if (refreshTask != null) { + refreshTask.cancel(true); + refreshTask = null; + } + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } + + @Override + public void detachView() { + isFirstSight = false; + cancelAsyncTasks(); + super.detachView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java deleted file mode 100644 index a66be29b..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.github.wulkanowy.ui.main.grades; - - -import android.content.res.Resources; -import android.view.View; -import android.widget.TextView; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; -import eu.davidea.viewholders.ExpandableViewHolder; -import io.github.wulkanowy.R; -import io.github.wulkanowy.data.db.dao.entities.Subject; -import io.github.wulkanowy.utils.AverageCalculator; - -public class GradeHeaderItem - extends AbstractExpandableHeaderItem { - - private Subject subject; - - GradeHeaderItem(Subject subject) { - this.subject = subject; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - GradeHeaderItem that = (GradeHeaderItem) o; - - return new EqualsBuilder() - .append(subject, that.subject) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(subject) - .toHashCode(); - } - - @Override - public int getLayoutRes() { - return R.layout.grade_header; - } - - @Override - public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { - return new HeaderViewHolder(view, adapter); - } - - @Override - public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { - holder.onBind(subject, getSubItems()); - } - - static class HeaderViewHolder extends ExpandableViewHolder { - - @BindView(R.id.grade_header_subject_text) - TextView subjectName; - - @BindView(R.id.grade_header_average_text) - TextView averageText; - - @BindView(R.id.grade_header_number_of_grade_text) - TextView numberText; - - @BindView(R.id.grade_header_alert_image) - View alertImage; - - Resources resources; - - HeaderViewHolder(View view, FlexibleAdapter adapter) { - super(view, adapter); - ButterKnife.bind(this, view); - resources = view.getResources(); - view.setOnClickListener(this); - } - - void onBind(Subject item, List subItems) { - subjectName.setText(item.getName()); - numberText.setText(resources.getQuantityString(R.plurals.numberOfGradesPlurals, - subItems.size(), subItems.size())); - averageText.setText(getGradesAverageString(item)); - alertImage.setVisibility(isSubItemsReadAndSaveAlertView(subItems) - ? View.INVISIBLE : View.VISIBLE); - } - - private boolean isSubItemsReadAndSaveAlertView(List subItems) { - boolean isRead = true; - - for (GradesSubItem item : subItems) { - isRead = item.getGrade().getRead(); - item.setSubjectAlertImage(alertImage); - } - return isRead; - } - - private String getGradesAverageString(Subject item) { - float average = AverageCalculator.calculate(item.getGradeList()); - - if (average < 0) { - return resources.getString(R.string.info_no_average); - } else { - return resources.getString(R.string.info_average_grades, average); - } - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java index 7deabb5e..d2423ec8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.main.grades; +import android.support.annotation.NonNull; import android.support.v4.widget.SwipeRefreshLayout; import java.util.List; -import io.github.wulkanowy.di.annotations.PerActivity; import io.github.wulkanowy.ui.base.BaseContract; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; @@ -12,7 +12,9 @@ public interface GradesContract { interface View extends BaseContract.View, SwipeRefreshLayout.OnRefreshListener { - void updateAdapterList(List headerItems); + void updateAdapterList(List headerItems); + + void updateSummaryAdapterList(List summarySubItems); void showNoItem(boolean show); @@ -24,17 +26,24 @@ public interface GradesContract { void setActivityTitle(); + void setCurrentSemester(int semester); + boolean isMenuVisible(); + void setSummaryAverages(String calculatedValue, String predictedValue, String finalValue ); + } - @PerActivity interface Presenter extends BaseContract.Presenter { void onFragmentVisible(boolean isVisible); void onRefresh(); - void onStart(View view, OnFragmentIsReadyListener listener); + void attachView(@NonNull View view, OnFragmentIsReadyListener listener); + + void onSemesterChange(int which); + + void onSemesterSwitchActive(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java index 0d9a699f..17ebc6e6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java @@ -16,6 +16,7 @@ import butterknife.OnClick; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.Grade; import io.github.wulkanowy.utils.CommonUtils; +import io.github.wulkanowy.utils.GradeUtils; public class GradesDialogFragment extends DialogFragment { @@ -70,13 +71,13 @@ public class GradesDialogFragment extends DialogFragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.grade_dialog, container, false); + View view = inflater.inflate(R.layout.grades_dialog, container, false); ButterKnife.bind(this, view); subject.setText(grade.getSubject()); value.setText(grade.getValue()); - value.setBackgroundResource(grade.getValueColor()); + value.setBackgroundResource(GradeUtils.getValueColor(grade.getValue())); weight.setText(grade.getWeight()); date.setText(grade.getDate()); color.setText(CommonUtils.colorHexToColorName(grade.getColor())); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java index bd44f298..ca1b949c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java @@ -1,45 +1,71 @@ package io.github.wulkanowy.ui.main.grades; +import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import java.util.List; import javax.inject.Inject; import butterknife.BindView; -import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; import io.github.wulkanowy.ui.base.BaseFragment; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; public class GradesFragment extends BaseFragment implements GradesContract.View { + @BindView(R.id.grade_fragment_summary_container) + View summary; + + @BindView(R.id.grade_fragment_details_container) + View details; + @BindView(R.id.grade_fragment_recycler) RecyclerView recyclerView; + @BindView(R.id.grade_fragment_summary_recycler) + RecyclerView summaryRecyclerView; + @BindView(R.id.grade_fragment_no_item_container) View noItemView; @BindView(R.id.grade_fragment_swipe_refresh) SwipeRefreshLayout refreshLayout; + @BindView(R.id.grade_fragment_summary_predicted_average) + TextView predictedAverage; + + @BindView(R.id.grade_fragment_summary_calculated_average) + TextView calculatedAverage; + + @BindView(R.id.grade_fragment_summary_final_average) + TextView finalAverage; + @Inject - FlexibleAdapter adapter; + FlexibleAdapter adapter; + + @Inject + FlexibleAdapter summaryAdapter; @Inject GradesContract.Presenter presenter; + int currentSemester = -1; + public GradesFragment() { // empty constructor for fragment } @@ -48,27 +74,73 @@ public class GradesFragment extends BaseFragment implements GradesContract.View @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_grades, container, false); + injectViews(view); - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); - } - + presenter.attachView(this, (OnFragmentIsReadyListener) getActivity()); return view; } @Override - protected void setUpOnViewCreated(View fragmentView) { + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.clear(); + inflater.inflate(R.menu.grades_action_menu, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_semester_switch: + presenter.onSemesterSwitchActive(); + CharSequence[] items = new CharSequence[]{ + getResources().getString(R.string.semester_text, 1), + getResources().getString(R.string.semester_text, 2), + }; + new AlertDialog.Builder(getContext()) + .setTitle(R.string.switch_semester) + .setNegativeButton(R.string.cancel, null) + .setSingleChoiceItems(items, this.currentSemester, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + presenter.onSemesterChange(which); + dialog.cancel(); + } + }).show(); + return true; + case R.id.action_summary_switch: + boolean isDetailsVisible = details.getVisibility() == View.VISIBLE; + + item.setTitle(isDetailsVisible ? R.string.action_title_details : R.string.action_title_summary); + details.setVisibility(isDetailsVisible ? View.INVISIBLE : View.VISIBLE); + summary.setVisibility(isDetailsVisible ? View.VISIBLE : View.INVISIBLE); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { noItemView.setVisibility(View.GONE); + summary.setVisibility(View.INVISIBLE); + details.setVisibility(View.VISIBLE); adapter.setAutoCollapseOnExpand(true); adapter.setAutoScrollOnExpand(true); adapter.expandItemsAtStartUp(); + summaryAdapter.setDisplayHeadersAtStartUp(true); - recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(view.getContext())); recyclerView.setAdapter(adapter); + summaryRecyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(view.getContext())); + summaryRecyclerView.setAdapter(summaryAdapter); + summaryRecyclerView.setNestedScrollingEnabled(false); refreshLayout.setColorSchemeResources(android.R.color.black); refreshLayout.setOnRefreshListener(this); @@ -82,11 +154,22 @@ public class GradesFragment extends BaseFragment implements GradesContract.View } } + @Override + public void setSummaryAverages(String calculatedValue, String predictedValue, String finalValue) { + calculatedAverage.setText(calculatedValue); + predictedAverage.setText(predictedValue); + finalAverage.setText(finalValue); + } + @Override public void setActivityTitle() { setTitle(getString(R.string.grades_text)); } + public void setCurrentSemester(int currentSemester) { + this.currentSemester = currentSemester; + } + @Override public void onRefresh() { presenter.onRefresh(); @@ -103,31 +186,28 @@ public class GradesFragment extends BaseFragment implements GradesContract.View } @Override - public void updateAdapterList(List headerItems) { + public void updateAdapterList(List headerItems) { adapter.updateDataSet(headerItems); } + @Override + public void updateSummaryAdapterList(List summarySubItems) { + summaryAdapter.updateDataSet(summarySubItems); + } + @Override public void onRefreshSuccessNoGrade() { - onError(R.string.snackbar_no_grades); + showMessage(R.string.snackbar_no_grades); } @Override public void onRefreshSuccess(int number) { - onError(getString(R.string.snackbar_new_grade, number)); - } - - @Override - public void onError(String message) { - if (getActivity() != null) { - Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), - message, Snackbar.LENGTH_LONG).show(); - } + showMessage(getString(R.string.snackbar_new_grade, number)); } @Override public void onDestroyView() { - presenter.onDestroy(); + presenter.detachView(); super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesHeader.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesHeader.java new file mode 100644 index 00000000..03f8564a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesHeader.java @@ -0,0 +1,199 @@ +package io.github.wulkanowy.ui.main.grades; + + +import android.content.res.Resources; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.viewholders.ExpandableViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.utils.AnimationUtils; +import io.github.wulkanowy.utils.GradeUtils; + +public class GradesHeader + extends AbstractExpandableHeaderItem { + + private Subject subject; + + private final boolean isShowSummary; + + GradesHeader(Subject subject, boolean isShowSummary) { + this.subject = subject; + this.isShowSummary = isShowSummary; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradesHeader that = (GradesHeader) o; + + return new EqualsBuilder() + .append(subject, that.subject) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grades_header; + } + + @Override + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderViewHolder(view, adapter, isShowSummary); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + holder.onBind(subject, getSubItems()); + } + + static class HeaderViewHolder extends ExpandableViewHolder { + + @BindView(R.id.grade_header_subject_text) + TextView subjectName; + + @BindView(R.id.grade_header_average_text) + TextView averageText; + + @BindView(R.id.grade_header_number_of_grade_text) + TextView numberText; + + @BindView(R.id.grade_header_predicted_rating_text) + TextView predictedText; + + @BindView(R.id.grade_header_final_rating_text) + TextView finalText; + + @BindView(R.id.grade_header_alert_image) + View alertImage; + + private Resources resources; + + private Subject item; + + private FlexibleAdapter adapter; + + private boolean isShowSummary; + + HeaderViewHolder(View view, FlexibleAdapter adapter, boolean isShowSummary) { + super(view, adapter); + ButterKnife.bind(this, view); + resources = view.getResources(); + view.setOnClickListener(this); + this.isShowSummary = isShowSummary; + this.adapter = adapter; + } + + void onBind(Subject item, List subItems) { + this.item = item; + + subjectName.setText(item.getName()); + numberText.setText(resources.getQuantityString(R.plurals.numberOfGradesPlurals, + subItems.size(), subItems.size())); + averageText.setText(getGradesAverageString()); + + predictedText.setText(resources.getString(R.string.info_grades_predicted_rating, + GradeUtils.getShortGradeValue(item.getPredictedRating()))); + finalText.setText(resources.getString(R.string.info_grades_final_rating, + GradeUtils.getShortGradeValue(item.getFinalRating()))); + + resetViews(); + toggleSubjectText(); + toggleSummary(); + + alertImage.setVisibility(isSubItemsReadAndSaveAlertView(subItems) + ? View.INVISIBLE : View.VISIBLE); + } + + private String getGradesAverageString() { + float average = GradeUtils.calculateWeightedAverage(item.getGradeList()); + + if (average < 0) { + return resources.getString(R.string.info_no_average); + } + + return resources.getString(R.string.info_average_grades, average); + } + + @Override + public void onClick(View view) { + super.onClick(view); + toggleSubjectText(); + toggleSummary(); + } + + private void resetViews() { + subjectName.setMaxLines(1); + setDefaultSummaryVisibility(predictedText, item.getPredictedRating()); + setDefaultSummaryVisibility(finalText, item.getFinalRating()); + } + + private void setDefaultSummaryVisibility(View view, String value) { + if (!"-".equals(value) && isShowSummary) { + view.setVisibility(View.VISIBLE); + } else { + view.setVisibility(View.GONE); + } + } + + private void toggleSubjectText() { + if (isExpand()) { + subjectName.setMaxLines(3); + } else { + subjectName.setMaxLines(1); + } + } + + private void toggleSummary() { + toggleSummaryView(predictedText, item.getPredictedRating(), isExpand()); + toggleSummaryView(finalText, item.getFinalRating(), isExpand()); + } + + private boolean isExpand() { + return adapter.isExpanded(getFlexibleAdapterPosition()); + } + + private void toggleSummaryView(View view, String value, boolean expand) { + if ("-".equals(value) || isShowSummary) { + return; + } + + if (expand) { + AnimationUtils.slideDown(view); + } else { + AnimationUtils.slideUp(view); + } + } + + private boolean isSubItemsReadAndSaveAlertView(List subItems) { + boolean isRead = true; + + for (GradesSubItem gradesSubItem : subItems) { + isRead = gradesSubItem.getGrade().getRead(); + gradesSubItem.setSubjectAlertImage(alertImage); + } + + return isRead; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesModule.java new file mode 100644 index 00000000..25eeef48 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesModule.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.main.grades; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; + +@Module +public abstract class GradesModule { + + @Binds + abstract GradesContract.Presenter provideGradesPresenter(GradesPresenter gradesPresenter); + + @Provides + static FlexibleAdapter provideGradesAdapter() { + return new FlexibleAdapter<>(null); + } + + @Provides + static FlexibleAdapter provideGradesSummaryAdapter() { + return new FlexibleAdapter<>(null); + } +} 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..0f4d363c 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 @@ -1,7 +1,15 @@ package io.github.wulkanowy.ui.main.grades; +import android.support.annotation.NonNull; + +import com.crashlytics.android.answers.Answers; +import com.crashlytics.android.answers.CustomEvent; + +import org.threeten.bp.LocalDate; + import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.inject.Inject; @@ -10,6 +18,8 @@ import io.github.wulkanowy.data.db.dao.entities.Grade; import io.github.wulkanowy.data.db.dao.entities.Subject; import io.github.wulkanowy.ui.base.BasePresenter; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.FabricUtils; +import io.github.wulkanowy.utils.GradeUtils; import io.github.wulkanowy.utils.async.AbstractTask; import io.github.wulkanowy.utils.async.AsyncListeners; @@ -23,33 +33,58 @@ public class GradesPresenter extends BasePresenter private OnFragmentIsReadyListener listener; - private List headerItems = new ArrayList<>(); + private List headerItems = new ArrayList<>(); + + private List summarySubItems = new ArrayList<>(); private boolean isFirstSight = false; + private int semesterName; + + private float finalAverage; + + private float predictedAverage; + + private float calculatedAverage; + @Inject GradesPresenter(RepositoryContract repository) { super(repository); } @Override - public void onStart(GradesContract.View view, OnFragmentIsReadyListener listener) { - super.onStart(view); + public void attachView(@NonNull GradesContract.View view, OnFragmentIsReadyListener listener) { + super.attachView(view); this.listener = listener; if (getView().isMenuVisible()) { getView().setActivityTitle(); } + semesterName = getRepository().getDbRepo().getCurrentSemesterName(); + getView().setCurrentSemester(semesterName - 1); + if (!isFirstSight) { isFirstSight = true; - - loadingTask = new AbstractTask(); - loadingTask.setOnFirstLoadingListener(this); - loadingTask.execute(); + reloadGrades(); } } + @Override + public void onSemesterSwitchActive() { + cancelAsyncTasks(); + } + + @Override + public void onSemesterChange(int which) { + semesterName = which + 1; + getView().setCurrentSemester(which); + reloadGrades(); + + Answers.getInstance().logCustom(new CustomEvent("Semester change") + .putCustomAttribute("Name", semesterName)); + } + @Override public void onFragmentVisible(boolean isVisible) { if (isVisible) { @@ -64,15 +99,15 @@ public class GradesPresenter extends BasePresenter refreshTask.setOnRefreshListener(this); refreshTask.execute(); } else { - getView().onNoNetworkError(); + getView().showNoNetworkMessage(); getView().hideRefreshingBar(); } } @Override public void onDoInBackgroundRefresh() throws Exception { - getRepository().syncSubjects(); - getRepository().syncGrades(); + getRepository().getSyncRepo().syncSubjects(semesterName); + getRepository().getSyncRepo().syncGrades(semesterName); } @Override @@ -83,13 +118,11 @@ public class GradesPresenter extends BasePresenter } @Override - public void onEndRefreshAsync(boolean success, Exception exception) { - if (success) { - loadingTask = new AbstractTask(); - loadingTask.setOnFirstLoadingListener(this); - loadingTask.execute(); + public void onEndRefreshAsync(boolean result, Exception exception) { + if (result) { + reloadGrades(); - int numberOfNewGrades = getRepository().getNewGrades().size(); + int numberOfNewGrades = getRepository().getDbRepo().getNewGrades(semesterName).size(); if (numberOfNewGrades <= 0) { getView().onRefreshSuccessNoGrade(); @@ -97,22 +130,30 @@ public class GradesPresenter extends BasePresenter getView().onRefreshSuccess(numberOfNewGrades); } } else { - getView().onError(getRepository().getErrorLoginMessage(exception)); + getView().showMessage(getRepository().getResRepo().getErrorLoginMessage(exception)); } getView().hideRefreshingBar(); + + FabricUtils.logRefresh("Grades", result, LocalDate.now().toString()); } @Override - public void onDoInBackgroundLoading() throws Exception { - List subjectList = getRepository().getCurrentUser().getSubjectList(); + public void onDoInBackgroundLoading() { + List subjectList = getRepository().getDbRepo().getSubjectList(semesterName); + boolean isShowSummary = getRepository().getSharedRepo().isShowGradesSummary(); headerItems = new ArrayList<>(); + summarySubItems = new ArrayList<>(); for (Subject subject : subjectList) { + subject.resetGradeList(); List gradeList = subject.getGradeList(); + GradesSummaryHeader summaryHeader = new GradesSummaryHeader(subject, GradeUtils.calculateWeightedAverage(gradeList)); + summarySubItems.add(new GradesSummarySubItem(summaryHeader, subject)); + if (!gradeList.isEmpty()) { - GradeHeaderItem headerItem = new GradeHeaderItem(subject); + GradesHeader headerItem = new GradesHeader(subject, isShowSummary); List subItems = new ArrayList<>(); @@ -125,6 +166,10 @@ public class GradesPresenter extends BasePresenter headerItems.add(headerItem); } } + + finalAverage = GradeUtils.calculateSubjectsAverage(subjectList, false); + predictedAverage = GradeUtils.calculateSubjectsAverage(subjectList, true); + calculatedAverage = GradeUtils.calculateDetailedSubjectsAverage(subjectList); } @Override @@ -134,19 +179,38 @@ public class GradesPresenter extends BasePresenter @Override public void onEndLoadingAsync(boolean result, Exception exception) { - if (headerItems.isEmpty()) { - getView().showNoItem(true); - } else { - getView().updateAdapterList(headerItems); - getView().showNoItem(false); - } + getView().showNoItem(headerItems.isEmpty()); + getView().updateAdapterList(headerItems); + + setSummaryAverages(); + getView().updateSummaryAdapterList(summarySubItems); + listener.onFragmentIsReady(); } - @Override - public void onDestroy() { - isFirstSight = false; + private void setSummaryAverages() { + getView().setSummaryAverages( + getFormattedAverage(calculatedAverage), + getFormattedAverage(predictedAverage), + getFormattedAverage(finalAverage) + ); + } + private String getFormattedAverage(float average) { + if (-1.0f == average) { + return "-- --"; + } + + return String.format(Locale.FRANCE, "%.2f", average); + } + + private void reloadGrades() { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + + private void cancelAsyncTasks() { if (refreshTask != null) { refreshTask.cancel(true); refreshTask = null; @@ -155,6 +219,12 @@ public class GradesPresenter extends BasePresenter loadingTask.cancel(true); loadingTask = null; } - super.onDestroy(); + } + + @Override + public void detachView() { + isFirstSight = false; + cancelAsyncTasks(); + super.detachView(); } } 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..ee995040 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 @@ -18,9 +18,10 @@ import eu.davidea.flexibleadapter.items.AbstractSectionableItem; import eu.davidea.viewholders.FlexibleViewHolder; import io.github.wulkanowy.R; import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.utils.GradeUtils; public class GradesSubItem - extends AbstractSectionableItem { + extends AbstractSectionableItem { private Grade grade; @@ -28,7 +29,7 @@ public class GradesSubItem private View subjectAlertImage; - GradesSubItem(GradeHeaderItem header, Grade grade) { + GradesSubItem(GradesHeader header, Grade grade) { super(header); this.grade = grade; } @@ -37,7 +38,7 @@ public class GradesSubItem return grade; } - public void setSubjectAlertImage(View subjectAlertImage) { + void setSubjectAlertImage(View subjectAlertImage) { this.subjectAlertImage = subjectAlertImage; } @@ -63,7 +64,7 @@ public class GradesSubItem @Override public int getLayoutRes() { - return R.layout.grade_subitem; + return R.layout.grades_subitem; } @Override @@ -87,6 +88,9 @@ public class GradesSubItem @BindView(R.id.grade_subitem_date) TextView date; + @BindView(R.id.grade_subitem_weight) + TextView weight; + @BindView(R.id.grade_subitem_alert_image) View alert; @@ -108,9 +112,10 @@ public class GradesSubItem this.subjectAlertImage = subjectAlertImage; value.setText(item.getValue()); - value.setBackgroundResource(item.getValueColor()); - date.setText(item.getDate()); + value.setBackgroundResource(GradeUtils.getValueColor(item.getValue())); description.setText(getDescriptionString()); + date.setText(item.getDate()); + weight.setText(String.format("%s: %s", context.getResources().getString(R.string.grade_weight_text), item.getWeight())); alert.setVisibility(item.getRead() ? View.INVISIBLE : View.VISIBLE); if (!item.getRead()) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummaryHeader.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummaryHeader.java new file mode 100644 index 00000000..a06fa0d5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummaryHeader.java @@ -0,0 +1,87 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractHeaderItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Subject; + +class GradesSummaryHeader extends AbstractHeaderItem { + + private Subject subject; + + private String average; + + GradesSummaryHeader(Subject subject, float average) { + this.subject = subject; + this.average = String.format(Locale.FRANCE, "%.2f", average); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradesSummaryHeader that = (GradesSummaryHeader) o; + + return new EqualsBuilder() + .append(subject, that.subject) + .append(average, that.average) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .append(average) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grades_summary_header; + } + + @Override + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + holder.onBind(subject, average); + } + + static class HeaderViewHolder extends FlexibleViewHolder { + + @BindView(R.id.grades_summary_header_name) + TextView name; + + @BindView(R.id.grades_summary_header_average) + TextView average; + + HeaderViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + } + + void onBind(Subject item, String value) { + name.setText(item.getName()); + average.setText("-1,00".equals(value) ? "" : value); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummarySubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummarySubItem.java new file mode 100644 index 00000000..58f418aa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSummarySubItem.java @@ -0,0 +1,84 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +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.Subject; +import io.github.wulkanowy.utils.GradeUtils; + +public class GradesSummarySubItem + extends AbstractSectionableItem { + + private Subject subject; + + public GradesSummarySubItem(GradesSummaryHeader header, Subject subject) { + super(header); + this.subject = subject; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradesSummarySubItem that = (GradesSummarySubItem) o; + + return new EqualsBuilder() + .append(subject, that.subject) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grades_summary_subitem; + } + + @Override + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + holder.onBind(subject); + } + + static class SubItemViewHolder extends FlexibleViewHolder { + + @BindView(R.id.grades_summary_subitem_final_grade) + TextView finalGrade; + + @BindView(R.id.grades_summary_subitem_predicted_grade) + TextView predictedGrade; + + SubItemViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + } + + void onBind(Subject item) { + predictedGrade.setText(GradeUtils.getShortGradeValue(item.getPredictedRating())); + finalGrade.setText(GradeUtils.getShortGradeValue(item.getFinalRating())); + } + } +} 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..0f2009be --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/settings/SettingsFragment.java @@ -0,0 +1,181 @@ +package io.github.wulkanowy.ui.main.settings; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.widget.Toast; + +import com.google.android.gms.oss.licenses.OssLicensesMenuActivity; + +import io.github.wulkanowy.BuildConfig; +import io.github.wulkanowy.R; +import io.github.wulkanowy.services.jobs.SyncJob; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.utils.AppConstant; + +import static io.github.wulkanowy.utils.TimeUtilsKt.isHolidays; + +public class SettingsFragment extends PreferenceFragmentCompat + implements SharedPreferences.OnSharedPreferenceChangeListener { + + public static final String SHARED_KEY_START_TAB = "startup_tab"; + + public static final String SHARED_KEY_GRADES_SUMMARY = "grades_summary"; + + public static final String SHARED_KEY_ATTENDANCE_PRESENT = "attendance_present"; + + public static final String SHARED_KEY_THEME = "theme"; + + 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"; + + public static final String SHARED_KEY_ABOUT_VERSION = "about_version"; + + public static final String SHARED_KEY_ABOUT_LICENSES = "about_osl"; + + public static final String SHARED_KEY_ABOUT_REPO = "about_repo"; + + private boolean isStarted; + + private boolean isVisible; + + private Preference.OnPreferenceClickListener onProgrammerListener = new Preference.OnPreferenceClickListener() { + private int clicks = 0; + + @Override + public boolean onPreferenceClick(Preference preference) { + Toast.makeText(getActivity(), getVersionToast(clicks++), Toast.LENGTH_SHORT).show(); + return true; + } + + private int getVersionToast(int click) { + if (0 == click) { + return R.string.about_programmer_step1; + } + + if (1 == click) { + return R.string.about_programmer_step2; + } + + if (9 > click) { + return R.string.about_programmer_step3; + } + + return R.string.about_programmer; + } + }; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences); + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + findPreference(SHARED_KEY_ABOUT_VERSION).setSummary(BuildConfig.VERSION_NAME); + findPreference(SHARED_KEY_ABOUT_VERSION).setOnPreferenceClickListener(onProgrammerListener); + findPreference(SHARED_KEY_ABOUT_REPO).setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(AppConstant.REPO_URL))); + findPreference(SHARED_KEY_ABOUT_LICENSES).setIntent(new Intent(getActivity(), OssLicensesMenuActivity.class) + .putExtra("title", R.string.pref_about_osl)); + + if (isHolidays()) { + Preference services = findPreference(SHARED_KEY_SERVICES_ENABLE); + services.setSummary(R.string.pref_services_suspended_on_holidays); + services.setEnabled(false); + } + } + + @Override + public Fragment getCallbackFragment() { + return this; + } + + @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); + } + + if (key.equals(SHARED_KEY_THEME)) { + setCurrentTheme(sharedPreferences); + } + } + + private void setCurrentTheme(SharedPreferences sharedPreferences) { + AppCompatDelegate.setDefaultNightMode(Integer.parseInt(sharedPreferences.getString(SHARED_KEY_THEME, "1"))); + getActivity().finish(); + startActivity(MainActivity + .getStartIntent(getContext()) + .putExtra(MainActivity.EXTRA_CARD_ID_KEY, 4) + ); + getActivity().overridePendingTransition(0, 0); + } + + 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()); + } + } + + private void setTitle() { + getActivity().setTitle(R.string.settings_text); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + isVisible = isVisibleToUser; + if (isVisible && isStarted) { + setTitle(); + } + } + + @Override + public void onStart() { + super.onStart(); + isStarted = true; + if (isVisible) { + setTitle(); + } + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onStop() { + super.onStop(); + isStarted = false; + } +} 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..8344ad76 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 @@ -1,9 +1,9 @@ package io.github.wulkanowy.ui.main.timetable; -import io.github.wulkanowy.di.annotations.PerActivity; +import android.support.annotation.NonNull; + import io.github.wulkanowy.ui.base.BaseContract; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; public interface TimetableContract { @@ -13,24 +13,21 @@ public interface TimetableContract { void scrollViewPagerToPosition(int position); - void setTabDataToAdapter(TabsData tabsData); + void setTabDataToAdapter(String date); void setAdapterWithTabLayout(); - void setChildFragmentSelected(int position, boolean selected); - boolean isMenuVisible(); + + void setThemeForTab(int position); } - @PerActivity interface Presenter extends BaseContract.Presenter { - void onFragmentVisible(boolean isVisible); + void onFragmentActivated(boolean isVisible); - void onTabSelected(int position); + void attachView(@NonNull View view, OnFragmentIsReadyListener listener); - void onTabUnselected(int position); - - void onStart(View view, OnFragmentIsReadyListener listener); + void setRestoredPosition(int position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java index b9390195..cc668dc2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java @@ -91,8 +91,8 @@ public class TimetableDialogFragment extends DialogFragment { teacherLabel.setVisibility(View.GONE); } - if (!lesson.getGroupName().isEmpty()) { - group.setText(lesson.getGroupName()); + if (!lesson.getGroup().isEmpty()) { + group.setText(lesson.getGroup()); } else { group.setVisibility(View.GONE); groupLabel.setVisibility(View.GONE); 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..db3cc83c 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 @@ -3,7 +3,6 @@ package io.github.wulkanowy.ui.main.timetable; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; @@ -11,16 +10,18 @@ import android.view.View; import android.view.ViewGroup; import javax.inject.Inject; +import javax.inject.Named; import butterknife.BindView; -import butterknife.ButterKnife; import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; +import io.github.wulkanowy.ui.main.timetable.tab.TimetableTabFragment; -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; @@ -28,8 +29,9 @@ public class TimetableFragment extends BaseFragment implements TimetableContract @BindView(R.id.timetable_fragment_tab_layout) TabLayout tabLayout; + @Named("Timetable") @Inject - TimetablePagerAdapter pagerAdapter; + BasePagerAdapter pagerAdapter; @Inject TimetableContract.Presenter presenter; @@ -38,12 +40,12 @@ public class TimetableFragment extends BaseFragment implements TimetableContract @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_timetable, container, false); + injectViews(view); - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + presenter.attachView(this, (OnFragmentIsReadyListener) getActivity()); + + if (savedInstanceState != null) { + presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY)); } return view; } @@ -52,41 +54,19 @@ 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); + public void setTabDataToAdapter(String date) { + pagerAdapter.addFragment(TimetableTabFragment.newInstance(date), date); } @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 @@ -95,21 +75,27 @@ public class TimetableFragment extends BaseFragment implements TimetableContract } @Override - public void setActivityTitle() { - setTitle(getString(R.string.lessonplan_text)); - } - - @Override - public void onError(String message) { - if (getActivity() != null) { - Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), - message, Snackbar.LENGTH_LONG).show(); + public void setThemeForTab(int position) { + TabLayout.Tab tab = tabLayout.getTabAt(position); + if (tab != null) { + tab.setCustomView(R.layout.current_week_tab); } } + @Override + public void setActivityTitle() { + setTitle(getString(R.string.timetable_text)); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem()); + super.onSaveInstanceState(outState); + } + @Override public void onDestroyView() { - presenter.onDestroy(); + presenter.detachView(); super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableModule.java new file mode 100644 index 00000000..9ad1ee60 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableModule.java @@ -0,0 +1,32 @@ +package io.github.wulkanowy.ui.main.timetable; + +import javax.inject.Named; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.android.ContributesAndroidInjector; +import io.github.wulkanowy.di.scopes.PerChildFragment; +import io.github.wulkanowy.di.scopes.PerFragment; +import io.github.wulkanowy.ui.base.BasePagerAdapter; +import io.github.wulkanowy.ui.main.timetable.tab.TimetableTabFragment; +import io.github.wulkanowy.ui.main.timetable.tab.TimetableTabModule; + +@Module +public abstract class TimetableModule { + + @Named("Timetable") + @PerFragment + @Provides + static BasePagerAdapter providePagerAdapter(TimetableFragment fragment) { + return new BasePagerAdapter(fragment.getChildFragmentManager()); + } + + @PerFragment + @Binds + abstract TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter); + + @PerChildFragment + @ContributesAndroidInjector(modules = TimetableTabModule.class) + abstract TimetableTabFragment bindTimetableTabFragment(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java deleted file mode 100644 index 0b852342..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.ui.main.timetable; - -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; - -import io.github.wulkanowy.ui.main.TabsData; - -public class TimetablePagerAdapter extends FragmentStatePagerAdapter { - - private TabsData tabsData; - - public TimetablePagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); - } - - void setTabsData(TabsData tabsData) { - this.tabsData = tabsData; - } - - @Override - public Fragment getItem(int position) { - return tabsData.getFragment(position); - } - - - @Override - public int getCount() { - return tabsData.getFragmentsCount(); - } - - @Nullable - @Override - public CharSequence getPageTitle(int position) { - return tabsData.getTitle(position); - } -} 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..1e9cf0b8 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 @@ -1,5 +1,7 @@ package io.github.wulkanowy.ui.main.timetable; +import android.support.annotation.NonNull; + import java.util.ArrayList; import java.util.List; @@ -8,11 +10,12 @@ import javax.inject.Inject; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.ui.base.BasePresenter; import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -import io.github.wulkanowy.ui.main.TabsData; -import io.github.wulkanowy.utils.TimeUtils; import io.github.wulkanowy.utils.async.AbstractTask; import io.github.wulkanowy.utils.async.AsyncListeners; +import static io.github.wulkanowy.utils.TimeUtilsKt.getFirstDayOfCurrentWeek; +import static io.github.wulkanowy.utils.TimeUtilsKt.getMondaysFromCurrentSchoolYear; + public class TimetablePresenter extends BasePresenter implements TimetableContract.Presenter, AsyncListeners.OnFirstLoadingListener { @@ -20,11 +23,9 @@ public class TimetablePresenter extends BasePresenter private List dates = new ArrayList<>(); - private TabsData tabsData = new TabsData(); - private OnFragmentIsReadyListener listener; - private int positionToScroll; + private int positionToScroll = 0; private boolean isFirstSight = false; @@ -34,8 +35,8 @@ public class TimetablePresenter extends BasePresenter } @Override - public void onStart(TimetableContract.View view, OnFragmentIsReadyListener listener) { - super.onStart(view); + public void attachView(@NonNull TimetableContract.View view, OnFragmentIsReadyListener listener) { + super.attachView(view); this.listener = listener; if (getView().isMenuVisible()) { @@ -43,9 +44,12 @@ public class TimetablePresenter extends BasePresenter } if (dates.isEmpty()) { - dates = TimeUtils.getMondaysFromCurrentSchoolYear(); + dates = getMondaysFromCurrentSchoolYear(); + } + + if (positionToScroll == 0) { + positionToScroll = dates.indexOf(getFirstDayOfCurrentWeek()); } - positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); if (!isFirstSight) { isFirstSight = true; @@ -57,54 +61,47 @@ 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 { + public void onDoInBackgroundLoading() { for (String date : dates) { - tabsData.addTitle(date); - tabsData.addFragment(TimetableTabFragment.newInstance(date)); + getView().setTabDataToAdapter(date); } } @Override public void onCanceledLoadingAsync() { //do nothing - } @Override public void onEndLoadingAsync(boolean result, Exception exception) { if (result) { - getView().setTabDataToAdapter(tabsData); getView().setAdapterWithTabLayout(); + getView().setThemeForTab(positionToScroll); getView().scrollViewPagerToPosition(positionToScroll); listener.onFragmentIsReady(); } } @Override - public void onDestroy() { + public void setRestoredPosition(int position) { + this.positionToScroll = position; + } + + @Override + public void detachView() { isFirstSight = false; if (loadingTask != null) { loadingTask.cancel(true); loadingTask = null; } - super.onDestroy(); + super.detachView(); } } 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/tab/TimetableHeader.java similarity index 57% rename from app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java rename to app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableHeader.java index 39a8f012..cb877387 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/tab/TimetableHeader.java @@ -1,5 +1,8 @@ -package io.github.wulkanowy.ui.main.timetable; +package io.github.wulkanowy.ui.main.timetable.tab; +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; @@ -11,21 +14,22 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.List; -import butterknife.BindColor; 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; +import io.github.wulkanowy.utils.CommonUtils; -public class TimetableHeaderItem - extends AbstractExpandableHeaderItem { +public class TimetableHeader + extends AbstractExpandableHeaderItem { private Day day; - TimetableHeaderItem(Day day) { + TimetableHeader(Day day) { this.day = day; } @@ -35,7 +39,7 @@ public class TimetableHeaderItem if (o == null || getClass() != o.getClass()) return false; - TimetableHeaderItem that = (TimetableHeaderItem) o; + TimetableHeader that = (TimetableHeader) o; return new EqualsBuilder() .append(day, that.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()); } @@ -78,38 +83,50 @@ public class TimetableHeaderItem @BindView(R.id.timetable_header_free_name) TextView freeName; - @BindColor(R.color.secondary_text) - int secondaryColor; - - @BindColor(R.color.free_day) - int backgroundFreeDay; + 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) { dayName.setText(StringUtils.capitalize(item.getDayName())); date.setText(item.getDate()); alert.setVisibility(isSubItemNewMovedInOrChanged(subItems) ? View.VISIBLE : View.INVISIBLE); - freeName.setVisibility(item.getIsFreeDay() ? View.VISIBLE : View.INVISIBLE); + freeName.setVisibility(item.getFreeDay() ? View.VISIBLE : View.INVISIBLE); freeName.setText(item.getFreeDayName()); + setInactiveHeader(item.getFreeDay()); + } - if (item.getIsFreeDay()) { - ((FrameLayout) getContentView()).setForeground(null); - getContentView().setBackgroundColor(backgroundFreeDay); - dayName.setTextColor(secondaryColor); + private void setInactiveHeader(boolean inactive) { + ((FrameLayout) getContentView()).setForeground(inactive ? null : getSelectableDrawable()); + dayName.setTextColor(CommonUtils.getThemeAttrColor(context, + inactive ? android.R.attr.textColorSecondary : android.R.attr.textColorPrimary)); + + if (inactive) { + getContentView().setBackgroundColor(CommonUtils.getThemeAttrColor(context, R.attr.colorControlHighlight)); + } 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; for (TimetableSubItem subItem : subItems) { - if (subItem.getLesson().getIsMovedOrCanceled() || - subItem.getLesson().getIsNewMovedInOrChanged()) { + if (subItem.getLesson().getMovedOrCanceled() || + subItem.getLesson().getNewMovedInOrChanged()) { isAlertActive = true; } } 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/tab/TimetableSubItem.java similarity index 84% rename from app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java rename to app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableSubItem.java index ca82f783..2bf47b33 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/tab/TimetableSubItem.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.main.timetable; +package io.github.wulkanowy.ui.main.timetable.tab; import android.content.Context; import android.graphics.Paint; @@ -17,17 +17,19 @@ 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; +import io.github.wulkanowy.ui.main.timetable.TimetableDialogFragment; public class TimetableSubItem - extends AbstractSectionableItem { + extends AbstractSectionableItem { private TimetableLesson lesson; - public TimetableSubItem(TimetableHeaderItem header, TimetableLesson lesson) { + TimetableSubItem(TimetableHeader header, TimetableLesson lesson) { super(header); this.lesson = lesson; } @@ -62,12 +64,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); } @@ -104,11 +107,11 @@ public class TimetableSubItem lessonName.setText(lesson.getSubject()); lessonTime.setText(getLessonTimeString()); - numberOfLesson.setText(lesson.getNumber()); + numberOfLesson.setText(String.valueOf(lesson.getNumber())); room.setText(getRoomString()); - alert.setVisibility(lesson.getIsMovedOrCanceled() || lesson.getIsNewMovedInOrChanged() + alert.setVisibility(lesson.getMovedOrCanceled() || lesson.getNewMovedInOrChanged() ? View.VISIBLE : View.INVISIBLE); - lessonName.setPaintFlags(lesson.getIsMovedOrCanceled() ? lessonName.getPaintFlags() + lessonName.setPaintFlags(lesson.getMovedOrCanceled() ? lessonName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG : lessonName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); room.setText(getRoomString()); 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/tab/TimetableTabContract.java similarity index 69% rename from app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabContract.java rename to app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabContract.java index d17e6e55..0814ff3c 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/tab/TimetableTabContract.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.ui.main.timetable; +package io.github.wulkanowy.ui.main.timetable.tab; import java.util.List; @@ -8,7 +8,9 @@ public interface TimetableTabContract { interface View extends BaseContract.View { - void updateAdapterList(List headerItems); + void updateAdapterList(List headerItems); + + void expandItem(int item); void onRefreshSuccess(); @@ -23,12 +25,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/tab/TimetableTabFragment.java similarity index 62% rename from app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java rename to app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabFragment.java index 65841247..87db5195 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/tab/TimetableTabFragment.java @@ -1,9 +1,8 @@ -package io.github.wulkanowy.ui.main.timetable; +package io.github.wulkanowy.ui.main.timetable.tab; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; @@ -16,11 +15,9 @@ import java.util.List; import javax.inject.Inject; import butterknife.BindView; -import butterknife.ButterKnife; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import io.github.wulkanowy.R; -import io.github.wulkanowy.di.component.FragmentComponent; import io.github.wulkanowy.ui.base.BaseFragment; public class TimetableTabFragment extends BaseFragment implements TimetableTabContract.View, @@ -28,12 +25,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; @@ -53,7 +44,9 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo TimetableTabContract.Presenter presenter; @Inject - FlexibleAdapter adapter; + FlexibleAdapter adapter; + + private boolean isFragmentVisible = false; public static TimetableTabFragment newInstance(String date) { TimetableTabFragment fragmentTab = new TimetableTabFragment(); @@ -65,59 +58,50 @@ 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) { View view = inflater.inflate(R.layout.fragment_timetable_tab, container, false); + injectViews(view); - FragmentComponent component = getFragmentComponent(); - if (component != null) { - component.inject(this); - setButterKnife(ButterKnife.bind(this, view)); - - if (getArguments() != null) { - presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); - } - - presenter.onStart(this, isPrimary); + if (getArguments() != null) { + presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); } + presenter.attachView(this); + presenter.onFragmentActivated(isFragmentVisible); return view; } @Override - protected void setUpOnViewCreated(View fragmentView) { + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { adapter.setAutoCollapseOnExpand(true); adapter.setAutoScrollOnExpand(true); adapter.expandItemsAtStartUp(); - recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(view.getContext())); recyclerView.setAdapter(adapter); refreshLayout.setColorSchemeResources(android.R.color.black); refreshLayout.setOnRefreshListener(this); - } @Override - public void updateAdapterList(List headerItems) { + public void updateAdapterList(List headerItems) { adapter.updateDataSet(headerItems); } + @Override + public void expandItem(int position) { + adapter.expand(adapter.getItem(position), true); + recyclerView.scrollToPosition(position); + } + @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 +117,7 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo @Override public void onRefreshSuccess() { - onError(R.string.timetable_refresh_success); + showMessage(R.string.sync_completed); } @Override @@ -151,28 +135,9 @@ 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) { - Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), - message, Snackbar.LENGTH_LONG).show(); - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(SAVED_KEY, isSelected); - super.onSaveInstanceState(outState); - } - @Override public void onDestroyView() { - isPrimary = false; - presenter.onDestroy(); + presenter.detachView(); super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabModule.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabModule.java new file mode 100644 index 00000000..1afb055f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabModule.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.main.timetable.tab; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import io.github.wulkanowy.di.scopes.PerChildFragment; + +@Module +public abstract class TimetableTabModule { + + @PerChildFragment + @Binds + abstract TimetableTabContract.Presenter provideTimetableTabPresneter(TimetableTabPresenter timetableTabPresenter); + + @PerChildFragment + @Provides + static FlexibleAdapter provideTimetableAdapter() { + return new FlexibleAdapter<>(null); + } +} 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/tab/TimetableTabPresenter.java similarity index 66% rename from app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java rename to app/src/main/java/io/github/wulkanowy/ui/main/timetable/tab/TimetableTabPresenter.java index e5eb499f..f30dd804 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/tab/TimetableTabPresenter.java @@ -1,5 +1,8 @@ -package io.github.wulkanowy.ui.main.timetable; +package io.github.wulkanowy.ui.main.timetable.tab; +import android.support.annotation.NonNull; + +import org.threeten.bp.LocalDate; import java.util.ArrayList; import java.util.List; @@ -11,9 +14,14 @@ import io.github.wulkanowy.data.db.dao.entities.Day; import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; import io.github.wulkanowy.data.db.dao.entities.Week; import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.FabricUtils; import io.github.wulkanowy.utils.async.AbstractTask; import io.github.wulkanowy.utils.async.AsyncListeners; +import static io.github.wulkanowy.utils.TimeUtilsKt.getParsedDate; +import static io.github.wulkanowy.utils.TimeUtilsKt.isDateInWeek; + public class TimetableTabPresenter extends BasePresenter implements TimetableTabContract.Presenter, AsyncListeners.OnRefreshListener, AsyncListeners.OnFirstLoadingListener { @@ -22,7 +30,7 @@ public class TimetableTabPresenter extends BasePresenter headerItems = new ArrayList<>(); + private List headerItems = new ArrayList<>(); private String date; @@ -36,21 +44,20 @@ public class TimetableTabPresenter extends BasePresenter dayList = week.getDayList(); headerItems = new ArrayList<>(); @@ -108,10 +118,11 @@ public class TimetableTabPresenter extends BasePresenter lessonList = day.getTimetableLessons(); @@ -140,15 +151,24 @@ public class TimetableTabPresenter extends BasePresenter { } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashModule.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashModule.java new file mode 100644 index 00000000..838b411d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashModule.java @@ -0,0 +1,13 @@ +package io.github.wulkanowy.ui.splash; + +import dagger.Binds; +import dagger.Module; +import io.github.wulkanowy.di.scopes.PerActivity; + +@Module +public abstract class SplashModule { + + @PerActivity + @Binds + abstract SplashContract.Presenter provideSplashPresenter(SplashPresenter splashPresenter); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java index d617b4af..69423348 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java @@ -6,7 +6,6 @@ import javax.inject.Inject; import io.github.wulkanowy.data.RepositoryContract; import io.github.wulkanowy.ui.base.BasePresenter; -import io.github.wulkanowy.utils.LogUtils; public class SplashPresenter extends BasePresenter implements SplashContract.Presenter { @@ -17,19 +16,15 @@ public class SplashPresenter extends BasePresenter } @Override - public void onStart(@NonNull SplashContract.View activity) { - super.onStart(activity); - getView().startSyncService(); + public void attachView(@NonNull SplashContract.View view) { + super.attachView(view); + getView().setCurrentThemeMode(getRepository().getSharedRepo().getCurrentTheme()); + getView().cancelNotifications(); - if (getRepository().getCurrentUserId() == 0) { - getView().openLoginActivity(); + if (getRepository().getSharedRepo().isUserLoggedIn()) { + getView().openMainActivity(); } else { - try { - getRepository().initLastUser(); - getView().openMainActivity(); - } catch (Exception e) { - LogUtils.error("An error occurred when the application was started", e); - } + getView().openLoginActivity(); } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetFactory.java b/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetFactory.java new file mode 100644 index 00000000..9e632396 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetFactory.java @@ -0,0 +1,140 @@ +package io.github.wulkanowy.ui.widgets; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Paint; +import android.view.View; +import android.widget.AdapterView; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; +import io.github.wulkanowy.data.db.dao.entities.Week; + +import static io.github.wulkanowy.utils.TimeUtilsKt.getFirstDayOfCurrentWeek; +import static io.github.wulkanowy.utils.TimeUtilsKt.getTodayOrNextDayOrder; + +public class TimetableWidgetFactory implements RemoteViewsService.RemoteViewsFactory { + + private final Context context; + + private List lessonList = new ArrayList<>(); + + private final RepositoryContract repository; + + public TimetableWidgetFactory(Context context, RepositoryContract repository) { + this.context = context; + this.repository = repository; + } + + + @Override + public void onCreate() { + // do nothing + } + + @Override + public void onDataSetChanged() { + lessonList = new ArrayList<>(); + + if (repository.getSharedRepo().isUserLoggedIn()) { + + Week week = repository.getDbRepo().getWeek(getFirstDayOfCurrentWeek()); + int valueOfDay = getTodayOrNextDayOrder(repository.getSharedRepo().getTimetableWidgetState()); + + if (valueOfDay != 5 && valueOfDay != 6 && week != null) { + week.resetDayList(); + lessonList = week.getDayList().get(valueOfDay).getTimetableLessons(); + } + } + } + + @Override + public void onDestroy() { + // do nothing + } + + @Override + public int getCount() { + return lessonList.size(); + } + + @Override + public RemoteViews getViewAt(int position) { + if (position == AdapterView.INVALID_POSITION) { + return null; + } + + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.timetable_widget_item); + views.setTextViewText(R.id.timetable_widget_item_subject, getSubjectName(position)); + views.setTextViewText(R.id.timetable_widget_item_time, getTimeText(position)); + views.setTextViewText(R.id.timetable_widget_item_room, getRoomText(position)); + + if (!getDescriptionText(position).isEmpty()) { + views.setViewVisibility(R.id.timetable_widget_item_description, View.VISIBLE); + views.setTextViewText(R.id.timetable_widget_item_description, getDescriptionText(position)); + } else { + views.setViewVisibility(R.id.timetable_widget_item_description, View.GONE); + } + + if (lessonList.get(position).getMovedOrCanceled()) { + views.setInt(R.id.timetable_widget_item_subject, "setPaintFlags", + Paint.STRIKE_THRU_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); + } else { + views.setInt(R.id.timetable_widget_item_subject, "setPaintFlags", + Paint.ANTI_ALIAS_FLAG); + } + + views.setOnClickFillInIntent(R.id.timetable_widget_item_container, new Intent()); + return views; + } + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } + + private String getSubjectName(int position) { + return lessonList.get(position).getSubject(); + } + + private String getRoomText(int position) { + TimetableLesson lesson = lessonList.get(position); + if (!lesson.getRoom().isEmpty()) { + return context.getString(R.string.timetable_dialog_room) + " " + lesson.getRoom(); + } + return lesson.getRoom(); + } + + private String getTimeText(int position) { + TimetableLesson lesson = lessonList.get(position); + return lesson.getStartTime() + " - " + lesson.getEndTime(); + } + + private String getDescriptionText(int position) { + return StringUtils.capitalize(lessonList.get(position).getDescription()); + } + +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetProvider.java b/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetProvider.java new file mode 100644 index 00000000..aa164e29 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/widgets/TimetableWidgetProvider.java @@ -0,0 +1,112 @@ +package io.github.wulkanowy.ui.widgets; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.TaskStackBuilder; +import android.widget.RemoteViews; + +import javax.inject.Inject; + +import dagger.android.AndroidInjection; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.services.widgets.TimetableWidgetServices; +import io.github.wulkanowy.ui.main.MainActivity; + +import static io.github.wulkanowy.utils.TimeUtilsKt.getTodayOrNextDay; + +public class TimetableWidgetProvider extends AppWidgetProvider { + + private static final String ACTION_TIMETABLE_TOGGLE = "timetable_toggle"; + + @Inject + RepositoryContract repository; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + inject(context); + + for (int appWidgetId : appWidgetIds) { + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.timetable_widget); + + setViews(views, context, appWidgetId); + setToggleIntent(views, context); + setTemplateIntent(views, context); + updateWidget(views, appWidgetManager, appWidgetId); + } + super.onUpdate(context, appWidgetManager, appWidgetIds); + } + + @Override + public void onReceive(final Context context, Intent intent) { + super.onReceive(context, intent); + inject(context); + + if (ACTION_TIMETABLE_TOGGLE.equals(intent.getAction())) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + ComponentName thisWidget = new ComponentName(context.getPackageName(), + TimetableWidgetProvider.class.getName()); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); + + repository.getSharedRepo().setTimetableWidgetState(!repository.getSharedRepo().getTimetableWidgetState()); + onUpdate(context, appWidgetManager, appWidgetIds); + } + } + + @Override + public void onDisabled(Context context) { + super.onDisabled(context); + inject(context); + repository.getSharedRepo().setTimetableWidgetState(false); + } + + private void setToggleIntent(RemoteViews views, Context context) { + Intent refreshIntent = new Intent(context, TimetableWidgetProvider.class); + refreshIntent.setAction(ACTION_TIMETABLE_TOGGLE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, + refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT); + views.setOnClickPendingIntent(R.id.timetable_widget_toggle, pendingIntent); + } + + private void setTemplateIntent(RemoteViews views, Context context) { + Intent intent = MainActivity.getStartIntent(context); + intent.putExtra(MainActivity.EXTRA_CARD_ID_KEY, 3); + + PendingIntent pendingIntent = TaskStackBuilder.create(context) + .addNextIntentWithParentStack(intent) + .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + views.setPendingIntentTemplate(R.id.timetable_widget_list, pendingIntent); + } + + private void setViews(RemoteViews views, Context context, int appWidgetId) { + Intent intent = new Intent(context, TimetableWidgetServices.class); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + + views.setRemoteAdapter(appWidgetId, R.id.timetable_widget_list, intent); + views.setEmptyView(R.id.timetable_widget_list, R.id.timetable_widget_empty); + + boolean nextDay = repository.getSharedRepo().getTimetableWidgetState(); + + String toggleText = context.getString(nextDay ? R.string.widget_timetable_tomorrow + : R.string.widget_timetable_today); + + views.setTextViewText(R.id.timetable_widget_toggle, toggleText); + views.setTextViewText(R.id.timetable_widget_date, getTodayOrNextDay(nextDay)); + } + + private void updateWidget(RemoteViews views, AppWidgetManager appWidgetManager, int appWidgetId) { + appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetable_widget_list); + appWidgetManager.updateAppWidget(appWidgetId, views); + } + + private void inject(Context context) { + if (repository == null) { + AndroidInjection.inject(this, context); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java b/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java new file mode 100644 index 00000000..04116423 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AnimationUtils.java @@ -0,0 +1,40 @@ +package io.github.wulkanowy.utils; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.View; + +public final class AnimationUtils { + + public static void slideDown(final View view) { + view.setVisibility(View.VISIBLE); + view.setAlpha(0.f); + + view.setTranslationY(-(view.getHeight() / 2)); + view.animate() + .translationY(0) + .alpha(1.f) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.VISIBLE); + view.setAlpha(1.f); + } + }); + } + + public static void slideUp(final View view) { + view.animate() + .translationY(-(view.getHeight() / 2)) + .alpha(0.f) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // superfluous restoration + view.setVisibility(View.GONE); + view.setAlpha(1.f); + view.setTranslationY(0.f); + } + }); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java index 27d76e75..2499974d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java +++ b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java @@ -19,6 +19,8 @@ public final class AppConstant { public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String REPO_URL = "https://github.com/wulkanowy/wulkanowy"; + private AppConstant() { throw new IllegalStateException("Utility class"); } diff --git a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java b/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java deleted file mode 100644 index cc33e65a..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.wulkanowy.utils; - -import java.util.List; - -import io.github.wulkanowy.data.db.dao.entities.Grade; - -public final class AverageCalculator { - - private AverageCalculator() { - throw new IllegalStateException("Utility class"); - } - - public static float calculate(List gradeList) { - - float counter = 0f; - float denominator = 0f; - - for (Grade grade : gradeList) { - int integerWeight = getIntegerForWeightOfGrade(grade.getWeight()); - float floatValue = getMathematicalValueOfGrade(grade.getValue()); - - if (floatValue != -1f) { - counter += floatValue * integerWeight; - denominator += integerWeight; - } - } - - if (counter == 0f) { - return -1f; - } else { - return counter / denominator; - } - } - - private static float getMathematicalValueOfGrade(String valueOfGrade) { - if (valueOfGrade.matches("[-|+|=]{0,2}[0-6]") - || valueOfGrade.matches("[0-6][-|+|=]{0,2}")) { - if (valueOfGrade.matches("[-][0-6]") - || valueOfGrade.matches("[0-6][-]")) { - String replacedValue = valueOfGrade.replaceAll("[-]", ""); - return Float.valueOf(replacedValue) - 0.33f; - } else if (valueOfGrade.matches("[+][0-6]") - || valueOfGrade.matches("[0-6][+]")) { - String replacedValue = valueOfGrade.replaceAll("[+]", ""); - return Float.valueOf((replacedValue)) + 0.33f; - } else if (valueOfGrade.matches("[-|=]{1,2}[0-6]") - || valueOfGrade.matches("[0-6][-|=]{1,2}")) { - String replacedValue = valueOfGrade.replaceAll("[-|=]{1,2}", ""); - return Float.valueOf((replacedValue)) - 0.5f; - } else { - return Float.valueOf(valueOfGrade); - } - } else { - return -1; - } - } - - private static int getIntegerForWeightOfGrade(String weightOfGrade) { - return Integer.valueOf(weightOfGrade.substring(0, weightOfGrade.length() - 3)); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java index 8ec73faf..9c7b35fb 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java +++ b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java @@ -1,7 +1,11 @@ package io.github.wulkanowy.utils; import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; import android.net.Uri; +import android.support.annotation.AttrRes; +import android.support.annotation.ColorInt; import android.support.customtabs.CustomTabsIntent; import io.github.wulkanowy.R; @@ -37,4 +41,14 @@ public final class CommonUtils { return R.string.noColor_text; } } + + @ColorInt + public static int getThemeAttrColor(Context context, @AttrRes int colorAttr) { + final TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr}); + try { + return array.getColor(0, 0); + } finally { + array.recycle(); + } + } } 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..cd4490d6 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java +++ b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java @@ -6,7 +6,12 @@ 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.Exam; import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.School; +import io.github.wulkanowy.data.db.dao.entities.Semester; +import io.github.wulkanowy.data.db.dao.entities.Student; import io.github.wulkanowy.data.db.dao.entities.Subject; import io.github.wulkanowy.data.db.dao.entities.TimetableLesson; import io.github.wulkanowy.data.db.dao.entities.Week; @@ -17,12 +22,71 @@ public final class DataObjectConverter { throw new IllegalStateException("Utility class"); } - public static List subjectsToSubjectEntities(List subjectList) { + public static List schoolsToSchoolsEntities(List schools, Long symbolId) { + List studentList = new ArrayList<>(); + for (io.github.wulkanowy.api.generic.School school : schools) { + studentList.add(new School() + .setName(school.getName()) + .setCurrent(school.getCurrent()) + .setRealId(school.getId()) + .setSymbolId(symbolId) + ); + } + + return studentList; + } + + public static List studentsToStudentEntities(List students, Long schoolId) { + List studentList = new ArrayList<>(); + + for (io.github.wulkanowy.api.generic.Student student : students) { + studentList.add(new Student() + .setName(student.getName()) + .setCurrent(student.isCurrent()) + .setRealId(student.getId()) + .setSchoolId(schoolId) + ); + } + + return studentList; + } + + public static List diariesToDiaryEntities(List diaryList, Long studentId) { + List diaryEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.generic.Diary diary : diaryList) { + diaryEntityList.add(new Diary() + .setStudentId(studentId) + .setValue(diary.getId()) + .setName(diary.getName()) + .setCurrent(diary.isCurrent())); + } + + return diaryEntityList; + } + + public static List semestersToSemesterEntities(List semesters, long diaryId) { + List semesterList = new ArrayList<>(); + + for (io.github.wulkanowy.api.generic.Semester semester : semesters) { + semesterList.add(new Semester() + .setDiaryId(diaryId) + .setName(semester.getName()) + .setCurrent(semester.isCurrent()) + .setValue(semester.getId()) + ); + } + + return semesterList; + } + + public static List subjectsToSubjectEntities(List subjectList, long semesterId) { List subjectEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.grades.Subject subject : subjectList) { Subject subjectEntity = new Subject() + .setSemesterId(semesterId) .setName(subject.getName()) .setPredictedRating(subject.getPredictedRating()) .setFinalRating(subject.getFinalRating()); @@ -32,12 +96,11 @@ public final class DataObjectConverter { return subjectEntityList; } - public static List gradesToGradeEntities(List gradeList) { - + public static List gradesToGradeEntities(List gradeList, long semesterId) { List gradeEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.grades.Grade grade : gradeList) { - Grade gradeEntity = new Grade() + gradeEntityList.add(new Grade() .setSubject(grade.getSubject()) .setValue(grade.getValue()) .setColor(grade.getColor()) @@ -46,10 +109,9 @@ public final class DataObjectConverter { .setWeight(grade.getWeight()) .setDate(grade.getDate()) .setTeacher(grade.getTeacher()) - .setSemester(grade.getSemester()); - - gradeEntityList.add(gradeEntity); + .setSemesterId(semesterId)); } + return gradeEntityList; } @@ -58,16 +120,20 @@ public final class DataObjectConverter { } public static Day dayToDayEntity(io.github.wulkanowy.api.generic.Day day) { + return new Day() + .setDate(day.getDate()) + .setDayName(day.getDayName()); + } + + public static Day timetableDayToDayEntity(io.github.wulkanowy.api.timetable.TimetableDay day) { return new Day() .setDate(day.getDate()) .setDayName(day.getDayName()) - .setIsFreeDay(day.isFreeDay()) + .setFreeDay(day.isFreeDay()) .setFreeDayName(day.getFreeDayName()); } - public static List daysToDaysEntities(List dayList) { - List dayEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Day day : dayList) { @@ -76,56 +142,61 @@ public final class DataObjectConverter { return dayEntityList; } - public static TimetableLesson lessonToTimetableLessonEntity(io.github.wulkanowy.api.generic.Lesson lesson) { - return new TimetableLesson() - .setNumber(lesson.getNumber()) - .setSubject(lesson.getSubject()) - .setTeacher(lesson.getTeacher()) - .setRoom(lesson.getRoom()) - .setDescription(lesson.getDescription()) - .setGroupName(lesson.getGroupName()) - .setStartTime(lesson.getStartTime()) - .setEndTime(lesson.getEndTime()) - .setDate(lesson.getDate()) - .setEmpty(lesson.isEmpty()) - .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) - .setPlanning(lesson.isPlanning()) - .setRealized(lesson.isRealized()) - .setMovedOrCanceled(lesson.isMovedOrCanceled()) - .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); - } - - public static AttendanceLesson lessonToAttendanceLessonEntity(io.github.wulkanowy.api.generic.Lesson lesson) { - return new AttendanceLesson() - .setNumber(Integer.valueOf(lesson.getNumber())) - .setSubject(lesson.getSubject()) - .setDate(lesson.getDate()) - .setIsPresence(lesson.isPresence()) - .setIsAbsenceUnexcused(lesson.isAbsenceUnexcused()) - .setIsAbsenceExcused(lesson.isAbsenceExcused()) - .setIsUnexcusedLateness(lesson.isUnexcusedLateness()) - .setIsAbsenceForSchoolReasons(lesson.isAbsenceForSchoolReasons()) - .setIsExcusedLateness(lesson.isExcusedLateness()) - .setIsExemption(lesson.isExemption()); - } - public static List lessonsToTimetableLessonsEntities(List lessonList) { - List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) { - lessonEntityList.add(lessonToTimetableLessonEntity(lesson)); + lessonEntityList.add(new TimetableLesson() + .setNumber(lesson.getNumber()) + .setSubject(lesson.getSubject()) + .setTeacher(lesson.getTeacher()) + .setRoom(lesson.getRoom()) + .setDescription(lesson.getDescription()) + .setGroup(lesson.getGroupName()) + .setStartTime(lesson.getStartTime()) + .setEndTime(lesson.getEndTime()) + .setDate(lesson.getDate()) + .setEmpty(lesson.isEmpty()) + .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) + .setPlanning(lesson.isPlanning()) + .setRealized(lesson.isRealized()) + .setMovedOrCanceled(lesson.isMovedOrCanceled()) + .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged())); } + return lessonEntityList; } public static List lessonsToAttendanceLessonsEntities(List lessonList) { - List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) { - lessonEntityList.add(lessonToAttendanceLessonEntity(lesson)); + lessonEntityList.add(new AttendanceLesson() + .setNumber(lesson.getNumber()) + .setSubject(lesson.getSubject()) + .setDate(lesson.getDate()) + .setPresence(lesson.isPresence()) + .setAbsenceUnexcused(lesson.isAbsenceUnexcused()) + .setAbsenceExcused(lesson.isAbsenceExcused()) + .setUnexcusedLateness(lesson.isUnexcusedLateness()) + .setAbsenceForSchoolReasons(lesson.isAbsenceForSchoolReasons()) + .setExcusedLateness(lesson.isExcusedLateness()) + .setExemption(lesson.isExemption())); } return lessonEntityList; } + + public static List examsToExamsEntity(List examList) { + List examEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.exams.Exam exam : examList) { + examEntityList.add(new Exam() + .setDescription(exam.getDescription()) + .setEntryDate(exam.getEntryDate()) + .setSubjectAndGroup(exam.getSubjectAndGroup()) + .setTeacher(exam.getTeacher()) + .setType(exam.getType())); + } + return examEntityList; + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java b/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java new file mode 100644 index 00000000..69897caa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.utils; + +import com.crashlytics.android.answers.Answers; +import com.crashlytics.android.answers.CustomEvent; +import com.crashlytics.android.answers.LoginEvent; +import com.crashlytics.android.answers.SignUpEvent; + +public final class FabricUtils { + + private FabricUtils() { + throw new IllegalStateException("Utility class"); + } + + public static void logLogin(String method, boolean result) { + Answers.getInstance().logLogin(new LoginEvent() + .putMethod(method) + .putSuccess(result) + ); + } + + public static void logRegister(boolean result, String symbol, String message) { + Answers.getInstance().logSignUp(new SignUpEvent() + .putMethod("Login activity") + .putSuccess(result) + .putCustomAttribute("symbol", symbol) + .putCustomAttribute("message", message) + ); + } + + public static void logRefresh(String name, boolean result, String date) { + Answers.getInstance().logCustom( + new CustomEvent(name + " refresh") + .putCustomAttribute("Success", result ? "true" : "false") + .putCustomAttribute("Date", date) + ); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java new file mode 100644 index 00000000..7b6ba193 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/GradeUtils.java @@ -0,0 +1,172 @@ +package io.github.wulkanowy.utils; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Subject; + +public final class GradeUtils { + + private final static Pattern validGradePattern = Pattern.compile("^(\\++|-|--|=)?[0-6](\\++|-|--|=)?$"); + private final static Pattern simpleGradeValuePattern = Pattern.compile("([0-6])"); + + private GradeUtils() { + throw new IllegalStateException("Utility class"); + } + + public static float calculateWeightedAverage(List gradeList) { + + float counter = 0f; + float denominator = 0f; + + for (Grade grade : gradeList) { + int weight = getWeightValue(grade.getWeight()); + float value = getWeightedGradeValue(grade.getValue()); + + if (value != -1.0f) { + counter += value * weight; + denominator += weight; + } + } + + if (counter == 0f) { + return -1.0f; + } + return counter / denominator; + } + + public static float calculateSubjectsAverage(List subjectList, boolean usePredicted) { + return calculateSubjectsAverage(subjectList, usePredicted, false); + } + + public static float calculateDetailedSubjectsAverage(List subjectList) { + return calculateSubjectsAverage(subjectList, false, true); + } + + public static int getValueColor(String value) { + Matcher m1 = validGradePattern.matcher(value); + if (!m1.find()) { + return R.color.grade_default; + } + + Matcher m2 = simpleGradeValuePattern.matcher(m1.group()); + if (!m2.find()) { + return R.color.grade_default; + } + + switch (Integer.parseInt(m2.group())) { + case 6: + return R.color.grade_six; + case 5: + return R.color.grade_five; + case 4: + return R.color.grade_four; + case 3: + return R.color.grade_three; + case 2: + return R.color.grade_two; + case 1: + return R.color.grade_one; + default: + return R.color.grade_default; + } + } + + private static float calculateSubjectsAverage(List subjectList, boolean usePredicted, boolean useSubjectsAverages) { + float counter = 0f; + float denominator = 0f; + + for (Subject subject : subjectList) { + float value; + + if (useSubjectsAverages) { + value = calculateWeightedAverage(subject.getGradeList()); + } else { + value = getGradeValue(usePredicted ? subject.getPredictedRating() : subject.getFinalRating()); + } + + if (value != -1.0f) { + counter += Math.round(value); + denominator++; + } + } + + if (counter == 0) { + return -1.0f; + } + + return counter / denominator; + } + + public static float getGradeValue(String grade) { + if (validGradePattern.matcher(grade).matches()) { + return getWeightedGradeValue(grade); + } + + return getVerbalGradeValue(grade); + } + + private static float getVerbalGradeValue(String grade) { + switch (grade) { + case "celujący": + return 6f; + case "bardzo dobry": + return 5f; + case "dobry": + return 4f; + case "dostateczny": + return 3f; + case "dopuszczający": + return 2f; + case "niedostateczny": + return 1f; + default: + return -1f; + } + } + + public static String getShortGradeValue(String grade) { + switch (grade) { + case "celujący": + return "6"; + case "bardzo dobry": + return "5"; + case "dobry": + return "4"; + case "dostateczny": + return "3"; + case "dopuszczający": + return "2"; + case "niedostateczny": + return "1"; + default: + return grade; + } + } + + private static float getWeightedGradeValue(String value) { + if (validGradePattern.matcher(value).matches()) { + if (value.matches("[-][0-6]") || value.matches("[0-6][-]")) { + String replacedValue = value.replaceAll("[-]", ""); + return Float.valueOf(replacedValue) - 0.33f; + } else if (value.matches("[+][0-6]") || value.matches("[0-6][+]")) { + String replacedValue = value.replaceAll("[+]", ""); + return Float.valueOf((replacedValue)) + 0.33f; + } else if (value.matches("[-|=]{1,2}[0-6]") || value.matches("[0-6][-|=]{1,2}")) { + String replacedValue = value.replaceAll("[-|=]{1,2}", ""); + return Float.valueOf((replacedValue)) - 0.5f; + } else { + return Float.valueOf(value); + } + } else { + return -1; + } + } + + private static int getWeightValue(String weightOfGrade) { + return Integer.valueOf(weightOfGrade.substring(0, weightOfGrade.length() - 3)); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java b/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java deleted file mode 100644 index f59bbf64..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.wulkanowy.utils; - -import android.util.Log; - -public final class LogUtils { - - private LogUtils() { - throw new IllegalStateException("Utility class"); - } - - public static void debug(String message) { - Log.d(AppConstant.APP_NAME, message); - } - - public static void error(String message, Throwable throwable) { - Log.e(AppConstant.APP_NAME, message, throwable); - } - - public static void error(String message) { - Log.e(AppConstant.APP_NAME, message); - } - - public static void info(String message) { - Log.i(AppConstant.APP_NAME, message); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java new file mode 100644 index 00000000..6e6c701b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/LoggerUtils.java @@ -0,0 +1,47 @@ +package io.github.wulkanowy.utils; + +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.crashlytics.android.Crashlytics; + +import timber.log.Timber; + +public final class LoggerUtils { + + public static class CrashlyticsTree extends Timber.Tree { + + @Override + protected void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable t) { + Crashlytics.setInt("priority", priority); + Crashlytics.setString("tag", tag); + + if (t == null) { + Crashlytics.log(message); + } else { + Crashlytics.setString("message", message); + Crashlytics.logException(t); + } + } + } + + public static class DebugLogTree extends Timber.DebugTree { + + @Override + protected void log(int priority, String tag, @NonNull String message, Throwable t) { + if ("HUAWEI".equals(Build.MANUFACTURER) || "samsung".equals(Build.MANUFACTURER)) { + if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { + priority = Log.ERROR; + } + } + super.log(priority, AppConstant.APP_NAME, message, t); + } + + @Override + protected String createStackElementTag(@NonNull StackTraceElement element) { + return super.createStackElementTag(element) + " - " + element.getLineNumber(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/RootChecker.java b/app/src/main/java/io/github/wulkanowy/utils/RootChecker.java deleted file mode 100644 index 9c23c217..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/RootChecker.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.wulkanowy.utils; - -import android.os.Build; - -import java.io.File; - -public final class RootChecker { - - private RootChecker() { - throw new IllegalStateException("Utility class"); - } - - public static boolean isRooted() { - return checkOne() || checkTwo() || checkThree(); - } - - private static boolean checkOne() { - return Build.TAGS != null && Build.TAGS.contains("test-keys"); - } - - private static boolean checkTwo() { - return new File("/system/app/Superuser.apk").exists(); - } - - private static boolean checkThree() { - String[] commands = {"/system/xbin/which su", "/system/bin/which su", "which su"}; - for (String command : commands) { - try { - Runtime.getRuntime().exec(command); - return true; - } catch (Exception e) { - // ignore - } - } - return false; - } - -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java deleted file mode 100644 index 717ed42a..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.wulkanowy.utils; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; -import org.joda.time.LocalDate; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -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 TimeUtils() { - throw new IllegalStateException("Utility class"); - } - - public static long getNetTicks(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - - return (calendar.getTimeInMillis() * TICKS_PER_MILLISECOND) + TICKS_AT_EPOCH; - } - - public static long getNetTicks(String dateString) throws ParseException { - return getNetTicks(dateString, DATE_PATTERN); - } - - public static long getNetTicks(String dateString, String dateFormat) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat(dateFormat, Locale.ROOT); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - Date dateObject = format.parse(dateString); - - return getNetTicks(dateObject); - } - - public static Date getDate(long netTicks) { - return new Date((netTicks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND); - } - - public static List getMondaysFromCurrentSchoolYear() { - LocalDate startDate = new LocalDate(getCurrentSchoolYear(), 9, 1); - LocalDate endDate = new LocalDate(getCurrentSchoolYear() + 1, 8, 31); - - List dateList = new ArrayList<>(); - - LocalDate thisMonday = startDate.withDayOfWeek(DateTimeConstants.MONDAY); - - if (startDate.isAfter(thisMonday)) { - startDate = thisMonday.plusWeeks(1); - } else { - startDate = thisMonday; - } - - while (startDate.isBefore(endDate)) { - dateList.add(startDate.toString(DATE_PATTERN)); - startDate = startDate.plusWeeks(1); - } - return dateList; - } - - public static int getCurrentSchoolYear() { - DateTime dateTime = new DateTime(); - return dateTime.getMonthOfYear() <= 8 ? dateTime.getYear() - 1 : dateTime.getYear(); - } - - public static String getDateOfCurrentMonday(boolean normalize) { - DateTime currentDate = new DateTime(); - - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY && normalize) { - currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY && normalize) { - currentDate = currentDate.plusDays(1); - } else { - currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); - } - return currentDate.toString(DATE_PATTERN); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.kt new file mode 100644 index 00000000..c61c7e0d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.kt @@ -0,0 +1,97 @@ +package io.github.wulkanowy.utils + +import org.threeten.bp.DayOfWeek.* +import org.threeten.bp.LocalDate +import org.threeten.bp.Year +import org.threeten.bp.format.DateTimeFormatter +import org.threeten.bp.temporal.TemporalAdjusters +import java.util.* + +private val formatter = DateTimeFormatter.ofPattern(AppConstant.DATE_PATTERN) + +fun getParsedDate(dateString: String, dateFormat: String): LocalDate { + return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(dateFormat)) +} + +fun getMondaysFromCurrentSchoolYear() = getMondaysFromCurrentSchoolYear(LocalDate.now()) + +fun getMondaysFromCurrentSchoolYear(date: LocalDate): List { + val startDate = getFirstSchoolDay(getSchoolYearForDate(date)) + ?.with(TemporalAdjusters.previousOrSame(MONDAY)) + val endDate = getFirstSchoolDay(getSchoolYearForDate(date) + 1) + ?.with(TemporalAdjusters.previousOrSame(MONDAY)) + + val dateList = ArrayList() + var monday = startDate as LocalDate + while (monday.isBefore(endDate)) { + dateList.add(monday.format(formatter)) + monday = monday.plusWeeks(1) + } + + return dateList +} + +fun getSchoolYearForDate(date: LocalDate): Int { + return if (date.monthValue <= 8) date.year - 1 else date.year +} + +fun getFirstDayOfCurrentWeek(): String = getFirstDayOfCurrentWeek(LocalDate.now()) + +fun getFirstDayOfCurrentWeek(date: LocalDate): String { + return when (date.dayOfWeek) { + SATURDAY -> date.plusDays(2) + SUNDAY -> date.plusDays(1) + else -> date.with(MONDAY) + }.format(formatter) +} + +fun getTodayOrNextDayOrder(next: Boolean): Int = getTodayOrNextDayOrder(next, LocalDate.now()) + +fun getTodayOrNextDayOrder(next: Boolean, date: LocalDate): Int { + val day = date.dayOfWeek + return if (next) { + if (day == SUNDAY) { + 0 + } else day.value + } else day.value - 1 +} + +fun getTodayOrNextDay(next: Boolean): String? = getTodayOrNextDay(next, LocalDate.now()) + +fun getTodayOrNextDay(next: Boolean, date: LocalDate): String? { + return (if (next) { + date.plusDays(1) + } else date).format(formatter) +} + +fun isDateInWeek(firstWeekDay: LocalDate, date: LocalDate): Boolean { + return date.isAfter(firstWeekDay.minusDays(1)) && date.isBefore(firstWeekDay.plusDays(5)) +} + +/** + * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) + */ +fun isHolidays(): Boolean = isHolidays(LocalDate.now(), Year.now().value) + +fun isHolidays(day: LocalDate, year: Int): Boolean { + return day.isAfter(getLastSchoolDay(year)) && day.isBefore(getFirstSchoolDay(year)) +} + +fun getFirstSchoolDay(year: Int): LocalDate? { + val firstSeptember = LocalDate.of(year, 9, 1) + + return when (firstSeptember.dayOfWeek) { + FRIDAY, + SATURDAY, + SUNDAY -> firstSeptember.with(TemporalAdjusters.firstInMonth(MONDAY)) + else -> { + firstSeptember + } + } +} + +fun getLastSchoolDay(year: Int): LocalDate? { + return LocalDate + .of(year, 6, 20) + .with(TemporalAdjusters.next(FRIDAY)) +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java index 5106d641..44721416 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java +++ b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java @@ -2,7 +2,7 @@ package io.github.wulkanowy.utils.async; import android.os.AsyncTask; -import io.github.wulkanowy.utils.LogUtils; +import timber.log.Timber; public class AbstractTask extends AsyncTask { @@ -28,7 +28,7 @@ public class AbstractTask extends AsyncTask { } else if (onRefreshListener != null) { onRefreshListener.onDoInBackgroundRefresh(); } else { - LogUtils.error("AbstractTask does not have a listener assigned"); + Timber.e("AbstractTask does not have a listener assigned"); } return true; } catch (Exception e) { @@ -45,7 +45,7 @@ public class AbstractTask extends AsyncTask { } else if (onRefreshListener != null) { onRefreshListener.onCanceledRefreshAsync(); } else { - LogUtils.error("AbstractTask does not have a listener assigned"); + Timber.e("AbstractTask does not have a listener assigned"); } } @@ -57,7 +57,7 @@ public class AbstractTask extends AsyncTask { } else if (onRefreshListener != null) { onRefreshListener.onEndRefreshAsync(result, exception); } else { - LogUtils.error("AbstractTask does not have a listener assigned"); + Timber.e("AbstractTask does not have a listener assigned"); } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java index 93da1d0b..8e425a89 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java @@ -26,8 +26,7 @@ import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.security.auth.x500.X500Principal; -import io.github.wulkanowy.utils.LogUtils; -import io.github.wulkanowy.utils.RootChecker; +import timber.log.Timber; public final class Scrambler { @@ -46,23 +45,16 @@ public final class Scrambler { loadKeyStore(); generateNewKey(email, context); return encryptString(email, plainText); - } else { - if (RootChecker.isRooted()) { - return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); - } else { - throw new UnsupportedOperationException("Stored data in this devices " + - "isn't safe because android is rooted"); - } } + return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); } public static String decrypt(String email, String encryptedText) throws CryptoException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { loadKeyStore(); return decryptString(email, encryptedText); - } else { - return new String(Base64.decode(encryptedText, Base64.DEFAULT)); } + return new String(Base64.decode(encryptedText, Base64.DEFAULT)); } private static void loadKeyStore() throws CryptoException { @@ -119,7 +111,7 @@ public final class Scrambler { throw new CryptoException("GenerateNewKey - String is empty"); } - LogUtils.debug("Key pair are create"); + Timber.d("Key pair are create"); } diff --git a/app/src/main/play/contactEmail b/app/src/main/play/contactEmail new file mode 100644 index 00000000..851521eb --- /dev/null +++ b/app/src/main/play/contactEmail @@ -0,0 +1 @@ +wulkanowyinc@gmail.com diff --git a/app/src/main/play/contactPhone b/app/src/main/play/contactPhone new file mode 100644 index 00000000..552e53a5 --- /dev/null +++ b/app/src/main/play/contactPhone @@ -0,0 +1 @@ ++48733393622 diff --git a/app/src/main/play/contactWebsite b/app/src/main/play/contactWebsite new file mode 100644 index 00000000..53f7bee6 --- /dev/null +++ b/app/src/main/play/contactWebsite @@ -0,0 +1 @@ +https://wulkanowy.github.io/ diff --git a/app/src/main/play/defaultLanguage b/app/src/main/play/defaultLanguage new file mode 100644 index 00000000..1134c4d5 --- /dev/null +++ b/app/src/main/play/defaultLanguage @@ -0,0 +1 @@ +pl-PL diff --git a/app/src/main/play/pl-PL/listing/featureGraphic/feature.png b/app/src/main/play/pl-PL/listing/featureGraphic/feature.png new file mode 100644 index 00000000..15e6ebbf Binary files /dev/null and b/app/src/main/play/pl-PL/listing/featureGraphic/feature.png differ diff --git a/app/src/main/play/pl-PL/listing/fulldescription b/app/src/main/play/pl-PL/listing/fulldescription new file mode 100644 index 00000000..4af6238a --- /dev/null +++ b/app/src/main/play/pl-PL/listing/fulldescription @@ -0,0 +1,10 @@ +Aplikacja jest we wczesnej fazie rozwoju, ciągle pracujemy nad kolejnymi funkcjami. + +Wyróżnione cechy i funkcje: +- Całkowicie darmowa i otwarta (brak jakichkolwiek reklam i mikropłatności) +- Powiadomienia +- Tryb offline +- Aktywne wsparcie i rozwój + +GitHub: https://github.com/wulkanowy/wulkanowy +Discord: https://discord.gg/vccAQBr diff --git a/app/src/main/play/pl-PL/listing/icon/icon.png b/app/src/main/play/pl-PL/listing/icon/icon.png new file mode 100644 index 00000000..7cad5c2f Binary files /dev/null and b/app/src/main/play/pl-PL/listing/icon/icon.png differ diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-dialog.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-dialog.png new file mode 100644 index 00000000..3f4ceb7e Binary files /dev/null and b/app/src/main/play/pl-PL/listing/phoneScreenshots/attendance-dialog.png differ diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/exams.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/exams.png new file mode 100644 index 00000000..5a055ff4 Binary files /dev/null and b/app/src/main/play/pl-PL/listing/phoneScreenshots/exams.png differ diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/grades.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/grades.png new file mode 100644 index 00000000..58c22c51 Binary files /dev/null and b/app/src/main/play/pl-PL/listing/phoneScreenshots/grades.png differ diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-widget.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-widget.png new file mode 100644 index 00000000..832db0b6 Binary files /dev/null and b/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable-widget.png differ diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable.png new file mode 100644 index 00000000..e9e66bbb Binary files /dev/null and b/app/src/main/play/pl-PL/listing/phoneScreenshots/timetable.png differ diff --git a/app/src/main/play/pl-PL/listing/shortdescription b/app/src/main/play/pl-PL/listing/shortdescription new file mode 100644 index 00000000..b5fc7491 --- /dev/null +++ b/app/src/main/play/pl-PL/listing/shortdescription @@ -0,0 +1 @@ +Nieoficjalna aplikacja dla dziennika VULCAN UONET+. diff --git a/app/src/main/play/pl-PL/listing/title b/app/src/main/play/pl-PL/listing/title new file mode 100644 index 00000000..e5160837 --- /dev/null +++ b/app/src/main/play/pl-PL/listing/title @@ -0,0 +1 @@ +Wulkanowy Dziennik UONET+ diff --git a/app/src/main/play/pl-PL/listing/video b/app/src/main/play/pl-PL/listing/video new file mode 100644 index 00000000..e69de29b diff --git a/app/src/main/play/pl-PL/whatsnew b/app/src/main/play/pl-PL/whatsnew new file mode 100644 index 00000000..ff412b22 --- /dev/null +++ b/app/src/main/play/pl-PL/whatsnew @@ -0,0 +1,5 @@ +Wersja 0.5.1: +- dodano wagę ocen na liście +- naprawiono błędy podczas logowania +- zmieniono nasycenie kolorów ocen +- wyłączenie synchronizacji podczas wakacji 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-night/ic_border.xml b/app/src/main/res/drawable-night/ic_border.xml new file mode 100644 index 00000000..ca44eb29 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_border.xml @@ -0,0 +1,9 @@ + + + + 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/drawable/ic_menu_dashboard_24dp.xml b/app/src/main/res/drawable/ic_action_menu_semester.xml similarity index 57% rename from app/src/main/res/drawable/ic_menu_dashboard_24dp.xml rename to app/src/main/res/drawable/ic_action_menu_semester.xml index 50726abd..b7acb9df 100644 --- a/app/src/main/res/drawable/ic_menu_dashboard_24dp.xml +++ b/app/src/main/res/drawable/ic_action_menu_semester.xml @@ -1,9 +1,10 @@ + android:fillColor="#FF000000" + android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" /> diff --git a/app/src/main/res/drawable/ic_action_menu_summary.xml b/app/src/main/res/drawable/ic_action_menu_summary.xml new file mode 100644 index 00000000..1b2a93b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_menu_summary.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_border.xml b/app/src/main/res/drawable/ic_border.xml index dc33b8aa..f46b3ad7 100644 --- a/app/src/main/res/drawable/ic_border.xml +++ b/app/src/main/res/drawable/ic_border.xml @@ -6,4 +6,4 @@ android:centerColor="@android:color/transparent" android:centerX="0.01" android:startColor="#60606060" /> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_exclamation_24dp.xml b/app/src/main/res/drawable/ic_exclamation_24dp.xml index 4d3c91cf..14950458 100644 --- a/app/src/main/res/drawable/ic_exclamation_24dp.xml +++ b/app/src/main/res/drawable/ic_exclamation_24dp.xml @@ -1,4 +1,4 @@ - + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_menu_exams_24dp.xml b/app/src/main/res/drawable/ic_menu_exams_24dp.xml new file mode 100644 index 00000000..13179b7c --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_exams_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_grade_26dp.xml b/app/src/main/res/drawable/ic_menu_grade_26dp.xml index 61506dad..3866cd3c 100644 --- a/app/src/main/res/drawable/ic_menu_grade_26dp.xml +++ b/app/src/main/res/drawable/ic_menu_grade_26dp.xml @@ -4,8 +4,8 @@ android:viewportHeight="26" android:viewportWidth="26"> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_menu_other_24dp.xml b/app/src/main/res/drawable/ic_menu_other_24dp.xml index 9fed586b..a0774724 100644 --- a/app/src/main/res/drawable/ic_menu_other_24dp.xml +++ b/app/src/main/res/drawable/ic_menu_other_24dp.xml @@ -5,7 +5,7 @@ android:viewportHeight="24" android:viewportWidth="24"> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_menu_timetable_24dp.xml b/app/src/main/res/drawable/ic_menu_timetable_24dp.xml index a689dc06..63880977 100644 --- a/app/src/main/res/drawable/ic_menu_timetable_24dp.xml +++ b/app/src/main/res/drawable/ic_menu_timetable_24dp.xml @@ -5,7 +5,7 @@ android:viewportHeight="24" android:viewportWidth="24"> - \ No newline at end of file + diff --git a/app/src/main/res/drawable/ic_wrench_24dp.xml b/app/src/main/res/drawable/ic_wrench_24dp.xml deleted file mode 100644 index 8f3a86fa..00000000 --- a/app/src/main/res/drawable/ic_wrench_24dp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_timetable_preview.png b/app/src/main/res/drawable/widget_timetable_preview.png new file mode 100644 index 00000000..d9f3538c Binary files /dev/null and b/app/src/main/res/drawable/widget_timetable_preview.png differ diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 67095982..4a6f08b2 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -25,7 +25,7 @@ style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentTop="true" + android:layout_below="@id/login_activity_progress_text" android:layout_centerHorizontal="true" android:indeterminate="true" android:minHeight="30dp" @@ -35,9 +35,9 @@ android:id="@+id/login_activity_progress_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignBottom="@+id/login_activity_progress_bar" android:layout_centerHorizontal="true" - android:layout_marginBottom="42dp" /> + android:layout_marginBottom="15dp" + android:text="@string/app_name" /> @@ -76,6 +76,7 @@ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a39a3b57..3f8473d8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,33 +1,53 @@ - + android:baselineAligned="false" + android:weightSum="1"> - + android:layout_weight="1"> - + + + + + + + + + + - + android:layout_marginBottom="@dimen/bottom_navigation_height" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - + - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/attendance_dialog.xml b/app/src/main/res/layout/attendance_dialog.xml index 916c993c..4b0f1ff9 100644 --- a/app/src/main/res/layout/attendance_dialog.xml +++ b/app/src/main/res/layout/attendance_dialog.xml @@ -144,6 +144,7 @@ android:background="?attr/selectableItemBackground" android:focusable="true" android:text="@string/generic_dialog_close" + android:textColor="?android:attr/android:textColorSecondary" android:textAllCaps="true" android:textSize="15sp" /> diff --git a/app/src/main/res/layout/attendance_header.xml b/app/src/main/res/layout/attendance_header.xml index 61dac631..350b2452 100644 --- a/app/src/main/res/layout/attendance_header.xml +++ b/app/src/main/res/layout/attendance_header.xml @@ -39,7 +39,7 @@ android:layout_marginTop="5dp" android:maxLines="1" android:text="@string/app_name" - android:textColor="@color/secondary_text" + android:textColor="?android:attr/android:textColorSecondary" android:textSize="14sp" /> - + diff --git a/app/src/main/res/layout/exams_dialog.xml b/app/src/main/res/layout/exams_dialog.xml new file mode 100644 index 00000000..72d4b8dd --- /dev/null +++ b/app/src/main/res/layout/exams_dialog.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +