1
0

Compare commits

...

31 Commits
0.4.2 ... 0.5.0

Author SHA1 Message Date
dab1bd4ac6 Version 0.5.0 2018-06-14 14:45:32 +02:00
11578aa735 Add dark theme (#133) 2018-06-14 11:40:46 +02:00
b63e28f9a9 Separate synchronization from login (#137) 2018-06-13 18:53:42 +02:00
072c504d2b Clean data user on bad user credentials (#138) 2018-06-13 03:07:34 +02:00
7b7be1eef1 API fixes (#136) 2018-06-10 19:46:34 +02:00
81d177c270 Hide actionbar on scroll (#135) 2018-06-09 00:59:39 +02:00
8d014ab7e9 Fix displaying the replacements in widget (#134) 2018-06-07 08:13:49 +02:00
a06d114127 Add logger (#131) 2018-06-06 19:38:54 +02:00
0e16519baf Add a summary of grades (#127) 2018-06-04 21:47:46 +02:00
dde5775a41 Move ticks converter to api (#130) 2018-06-01 12:52:03 +02:00
e2003e2538 Expand current day on startup (#129) 2018-05-31 23:54:59 +02:00
228f680e5d Hide empty fields in summary (#128) 2018-05-31 23:01:52 +02:00
306092ce45 Add mobile access API (#126) 2018-05-30 11:57:29 +02:00
d4b172e022 Version 0.4.5 2018-05-28 11:32:39 +02:00
b4c765b482 [API] Fix timetable endpoint (#125) 2018-05-28 11:29:43 +02:00
3f1fff6d96 Implementation of a new Dagger injection (#103) 2018-05-26 20:44:06 +02:00
b59008a90f Version 0.4.4 2018-05-25 20:33:45 +02:00
74c0dda999 Fix lesson description from warning (#124) 2018-05-25 20:14:16 +02:00
2288ceffb8 Stop refreshing while semester switching window open (#123) 2018-05-25 19:10:34 +02:00
ffe8511e3f Add some fabric answers (#120) 2018-05-25 16:35:12 +02:00
34205e4a8b Fix autofill on api lvl 26 (#122) 2018-05-25 11:49:53 +02:00
ef648c7f8b Clear db before register (#121) 2018-05-24 22:25:24 +02:00
3592946e6f Fix api login(#119) 2018-05-23 19:21:35 +02:00
859f8dc319 Avoid null if cell second div not contain span (#118) 2018-05-23 18:44:04 +02:00
9c97962e89 Version 0.4.3 2018-05-19 23:37:32 +02:00
a4445a8a97 Fix api login error (#115) 2018-05-19 23:02:54 +02:00
3f54d13b6b Add condition to exam query (#116) 2018-05-19 21:11:04 +02:00
5685e73e46 Fix text alignment <= lv l20 (#117) 2018-05-19 19:58:33 +02:00
e9b357e92d Remove the root check (#114) 2018-05-19 12:48:12 +02:00
62bc00cd68 Delete non-existing lessons on sync (#112) 2018-05-19 12:30:17 +02:00
54e6aee82e Stop SyncJob if user is not registered in app (#113) 2018-05-16 20:43:29 +02:00
199 changed files with 2971 additions and 1856 deletions

View File

@ -6,7 +6,7 @@
[![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/)
[![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/JMG2rhJ)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[Pobierz wersję beta](https://play.google.com/store/apps/details?id=io.github.wulkanowy&amp;utm_source=vcs)

View File

@ -1,4 +1,5 @@
apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'org.sonarqube'
apply plugin: 'jacoco'
apply plugin: 'com.jfrog.bintray'
@ -31,6 +32,9 @@ dependencies {
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"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testImplementation "junit:junit:$junit"
testImplementation "org.mockito:mockito-core:$mockito"
@ -115,3 +119,29 @@ artifacts {
archives sourcesJar
archives javadocJar
}
buildscript {
ext.kotlin_version = '1.2.41'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}

View File

@ -3,6 +3,8 @@ 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;
@ -27,6 +29,8 @@ public class Client {
private Cookies cookies = new Cookies();
private static final Logger logger = LoggerFactory.getLogger(Client.class);
Client(String email, String password, String symbol) {
this.email = email;
this.password = password;
@ -58,12 +62,17 @@ public class Client {
return;
}
clearCookies();
this.symbol = new Login(this).login(email, password, symbol);
logger.info("Login successful on {} at {}", getHost(), new Date());
}
private boolean isLoggedIn() {
logger.debug("Last success request: {}", lastSuccessRequest);
logger.debug("Cookies: {}", getCookies().size());
return getCookies().size() > 0 && lastSuccessRequest != null &&
29 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime());
5 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime());
}
@ -75,14 +84,14 @@ public class Client {
this.symbol = symbol;
}
public void addCookies(Map<String, String> items) {
cookies.addItems(items);
}
private Map<String, String> getCookies() {
return cookies.getItems();
}
public void clearCookies() {
cookies = new Cookies();
}
String getHost() {
return host;
}
@ -111,14 +120,18 @@ public class Client {
this.cookies.addItems(cookies);
}
Connection.Response response = Jsoup.connect(getFilledUrl(url))
url = getFilledUrl(url);
logger.debug("GET {}", url);
Connection.Response response = Jsoup.connect(url)
.followRedirects(true)
.cookies(getCookies())
.execute();
this.cookies.addItems(response.cookies());
Document doc = checkForErrors(response.parse());
Document doc = checkForErrors(response.parse(), response.statusCode());
if (loginBefore) {
lastSuccessRequest = new Date();
@ -128,7 +141,11 @@ public class Client {
}
public synchronized Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException {
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]);
@ -142,15 +159,19 @@ public class Client {
this.cookies.addItems(response.cookies());
response.bufferUp(); // fixes cert parsing issues
response.bufferUp(); // fixes cert parsing issues #109
return checkForErrors(response.parse());
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())
@ -164,7 +185,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]);
@ -182,7 +207,7 @@ public class Client {
return response.body();
}
Document checkForErrors(Document doc) throws VulcanException {
Document checkForErrors(Document doc, int code) throws VulcanException {
lastSuccessRequest = null;
String title = doc.select("title").text();
@ -196,7 +221,7 @@ public class Client {
}
if ("Błąd strony".equals(title)) {
throw new VulcanException("Nieznany błąd");
throw new NotLoggedInErrorException(title + " " + doc.body() + ", status: " + code);
}
return doc;

View File

@ -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)
}

View File

@ -6,6 +6,11 @@ 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 getSchoolID();

View File

@ -3,6 +3,8 @@ 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;
@ -11,6 +13,11 @@ 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";
@ -27,6 +34,8 @@ public class StudentAndParent implements SnP {
private String diaryID;
private static final Logger logger = LoggerFactory.getLogger(StudentAndParent.class);
StudentAndParent(Client client, String schoolID, String studentID, String diaryID) {
this.client = client;
this.schoolID = schoolID;
@ -38,6 +47,11 @@ public class StudentAndParent implements SnP {
if (null == getStudentID() || "".equals(getStudentID())) {
Document doc = client.getPageByUrl(getSnpHomePageUrl());
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();
@ -65,26 +79,29 @@ public class StudentAndParent implements SnP {
return getBaseUrl();
}
// get url to uonetplus-opiekun.vulcan.net.pl
// get url to uonetplus-opiekun.fakelog.cf
Document startPage = client.getPageByUrl(START_PAGE_URL);
Element studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a").first();
Elements studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a");
if (null == studentTileLink) {
throw new NotLoggedInErrorException("You are probably not logged in. Force login");
logger.debug("studentTileLink: {}", studentTileLink.size());
if (studentTileLink.isEmpty()) {
throw new VulcanException("Na pewno używasz konta z dostępem do Witryny ucznia i rodzica?");
}
String snpPageUrl = studentTileLink.attr("href");
String snpPageUrl = studentTileLink.last().attr("href");
this.schoolID = getExtractedIdFromUrl(snpPageUrl);
return snpPageUrl;
}
String getExtractedIdFromUrl(String snpPageUrl) throws NotLoggedInErrorException {
String getExtractedIdFromUrl(String snpPageUrl) throws VulcanException {
String[] path = snpPageUrl.split(client.getHost())[1].split("/");
if (5 != path.length) {
throw new NotLoggedInErrorException("You are probably not logged in");
logger.error("Expected snp url, got {}", snpPageUrl);
throw new VulcanException("Na pewno używasz konta z dostępem do Witryny ucznia i rodzica?");
}
return path[2];
@ -102,12 +119,12 @@ public class StudentAndParent implements SnP {
Map<String, String> cookies = new HashMap<>();
cookies.put("idBiezacyDziennik", diaryID);
cookies.put("idBiezacyUczen", studentID);
client.addCookies(cookies);
Document doc = client.getPageByUrl(getBaseUrl() + url, true, cookies);
if (!doc.title().startsWith("Witryna ucznia i rodzica")) {
throw new VulcanException("Expected SnP page, got page with title: " + doc.title());
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")) {

View File

@ -1,5 +1,8 @@
package io.github.wulkanowy.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import io.github.wulkanowy.api.attendance.AttendanceStatistics;
@ -8,6 +11,8 @@ import io.github.wulkanowy.api.exams.ExamsWeek;
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;
@ -28,17 +33,21 @@ public class Vulcan {
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.schoolId = schoolId;
this.studentId = studentId;
this.diaryId = diaryId;
client = new Client(email, password, symbol);
logger.debug("Client created with symbol " + symbol);
}
public Client getClient() throws NotLoggedInErrorException {
if (null == client) {
throw new NotLoggedInErrorException("Use setCredentials() method first");
throw new NotLoggedInErrorException("Vulcan must be initialized by calling setCredentials() prior to fetch data");
}
return client;
@ -108,6 +117,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());
}

View File

@ -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<Day> getWeekTable() throws IOException, ParseException, VulcanException {
public Week<Day> getWeekTable() throws IOException, VulcanException {
return getWeekTable("");
}
public Week<Day> getWeekTable(String tick) throws IOException, ParseException, VulcanException {
Element table = snp.getSnPPageDocument(ATTENDANCE_PAGE_URL + tick)
public Week<Day> 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("<br>");
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));

View File

@ -6,17 +6,16 @@ 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;
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=";
@ -27,12 +26,12 @@ public class ExamsWeek {
this.snp = snp;
}
public Week<ExamDay> getCurrent() throws IOException, VulcanException, ParseException {
public Week<ExamDay> getCurrent() throws IOException, VulcanException {
return getWeek("", true);
}
public Week<ExamDay> getWeek(String tick, final boolean onlyNotEmpty) throws IOException, VulcanException, ParseException {
Document examsPage = snp.getSnPPageDocument(EXAMS_PAGE_URL + tick);
public Week<ExamDay> 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<ExamDay> days = new ArrayList<>();
@ -71,11 +70,4 @@ public class ExamsWeek {
.first().text().split(" ")[1]))
.setDays(days);
}
private String getFormattedDate(String date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT);
Date d = sdf.parse(date);
sdf.applyPattern("yyyy-MM-dd");
return sdf.format(d);
}
}

View File

@ -37,7 +37,8 @@ public class Day {
return dayName;
}
public void setDayName(String dayName) {
public Day setDayName(String dayName) {
this.dayName = dayName;
return this;
}
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.api;
package io.github.wulkanowy.api.generic;
public class Diary implements ParamItem {

View File

@ -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() {

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.api;
package io.github.wulkanowy.api.generic;
interface ParamItem {
public interface ParamItem {
ParamItem setId(String id);

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.api;
package io.github.wulkanowy.api.generic;
public class Semester implements ParamItem {

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.api;
package io.github.wulkanowy.api.generic;
public class Student implements ParamItem {

View File

@ -5,42 +5,32 @@ 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.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;
private List<Grade> grades = new ArrayList<>();
public GradesList(SnP snp) {
this.snp = snp;
}
private String getGradesPageUrl() {
return GRADES_PAGE_URL;
}
public List<Grade> getAll() throws IOException, ParseException, VulcanException {
return getAll("");
}
public List<Grade> getAll(String semester) throws IOException, ParseException, VulcanException {
Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester);
public List<Grade> getAll(String semester) throws IOException, VulcanException {
Document gradesPage = snp.getSnPPageDocument(GRADES_PAGE_URL + semester);
Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr");
List<Grade> grades = new ArrayList<>();
for (Element row : gradesRows) {
if ("Brak ocen".equals(row.select("td:nth-child(2)").text())) {
continue;
@ -52,13 +42,13 @@ public class GradesList {
return grades;
}
private Grade getGrade(Element row) throws ParseException {
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 = formatDate(row.select("td:nth-child(5)").text());
String date = getFormattedDate(row.select("td:nth-child(5)").text());
return new Grade()
.setSubject(row.select("td:nth-child(1)").text())
@ -82,12 +72,4 @@ public class GradesList {
return color;
}
private String formatDate(String date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT);
Date d = sdf.parse(date);
sdf.applyPattern("yyyy-MM-dd");
return sdf.format(d);
}
}

View File

@ -5,10 +5,13 @@ 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 {
@ -20,6 +23,8 @@ public class Login {
private Client client;
private static final Logger logger = LoggerFactory.getLogger(Login.class);
public Login(Client client) {
this.client = client;
}
@ -27,6 +32,11 @@ public class Login {
public String login(String email, String password, String symbol) throws VulcanException, IOException {
Document certDoc = sendCredentials(email, password);
if ("Błąd".equals(certDoc.title())) {
client.clearCookies();
throw new NotLoggedInErrorException(certDoc.body().text());
}
return sendCertificate(certDoc, symbol);
}
@ -36,29 +46,29 @@ public class Login {
{"Password", password}
};
String nextUrl = LOGIN_PAGE_URL;
Document loginPage = client.getPageByUrl(nextUrl, false);
Document nextDoc = sendCredentialsData(credentials, LOGIN_PAGE_URL);
Element formFirst = loginPage.select("#form1").first();
if (null != formFirst) { // on adfs login
Document formSecond = client.postPageByUrl(
formFirst.attr("abs:action"),
getFormStateParams(formFirst, "", "")
);
credentials = getFormStateParams(formSecond, email, password);
nextUrl = formSecond.select("#form1").first().attr("abs:action");
} else if (!"Logowanie".equals(loginPage.select("#h1Default").text())) {
throw new VulcanException("Expected login page, got page with title: " + loginPage.title());
}
Document html = client.postPageByUrl(nextUrl, credentials);
Element errorMessage = html.select(".ErrorMessage, #ErrorTextLabel").first();
Element errorMessage = nextDoc.selectFirst(".ErrorMessage, #ErrorTextLabel");
if (null != errorMessage) {
throw new BadCredentialsException(errorMessage.text());
}
return html;
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) {
@ -77,18 +87,13 @@ public class Login {
}
String sendCertificate(Document doc, String defaultSymbol) throws IOException, VulcanException {
String certificate = doc.select("input[name=wresult]").val();
if ("".equals(certificate)) {
throw new VulcanException("Expected certificate, got empty string. Page title: " + doc.title());
}
client.setSymbol(findSymbol(defaultSymbol, certificate));
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();
}
@ -97,6 +102,7 @@ public class Login {
}
if (!"Uonet+".equals(title)) {
logger.debug("Login failed. Body: {}", targetDoc.body());
throw new LoginErrorException("Expected page title `UONET+`, got " + title);
}
@ -106,10 +112,6 @@ public class Login {
private Document sendCertData(Document doc) throws IOException, VulcanException {
String url = doc.select("form[name=hiddenform]").attr("action");
if (!doc.title().equals("Working...")) {
throw new VulcanException("Expected certificate page, got page with title: " + doc.title());
}
return client.postPageByUrl(url.replaceFirst("Default", "{symbol}"), new String[][]{
{"wa", "wsignin1.0"},
{"wresult", doc.select("input[name=wresult]").val()},

View File

@ -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()
}
}

View File

@ -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<Device> {
val items = snp.getSnPPageDocument(DEVICES_LIST_URL).select("table tbody tr")
val devices: MutableList<Device> = 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
}
}

View File

@ -14,9 +14,7 @@ public class AchievementsList {
private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie";
private SnP snp = null;
private List<String> 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<String> achievements = new ArrayList<>();
for (Element item : items) {
achievements.add(item.text());
}

View File

@ -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<Note> 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<Note> 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))

View File

@ -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;

View File

@ -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;

View File

@ -3,36 +3,39 @@ 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.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<TimetableDay> getWeekTable() throws IOException, ParseException, VulcanException {
public Week<TimetableDay> getWeekTable() throws IOException, VulcanException {
return getWeekTable("");
}
public Week<TimetableDay> getWeekTable(final String tick) throws IOException, ParseException, VulcanException {
Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + tick)
public Week<TimetableDay> getWeekTable(final String date) throws IOException, VulcanException {
Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + getDateAsTick(date))
.select(".mainContainer .presentData").first();
List<TimetableDay> days = getDays(table.select("thead th"));
@ -44,19 +47,20 @@ public class Timetable {
.setDays(days);
}
private List<TimetableDay> getDays(Elements tableHeaderCells) throws ParseException {
private List<TimetableDay> getDays(Elements tableHeaderCells) {
List<TimetableDay> 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("<br>");
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT);
Date d = sdf.parse(dayHeaderCell[1].trim());
sdf.applyPattern("yyyy-MM-dd");
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);
@ -81,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"));
@ -98,8 +102,10 @@ public class Timetable {
addLessonInfoFromElement(lesson, e.first());
break;
case 2:
Element span = e.last().select("span").first();
if (span.hasClass(LessonTypes.CLASS_MOVED_OR_CANCELED)) {
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());
@ -120,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);
}
}
@ -128,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);
@ -214,6 +227,10 @@ 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];

View File

@ -25,7 +25,7 @@ public class ClientTest {
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)
@ -34,7 +34,7 @@ public class ClientTest {
Document doc = Jsoup.parse(getFixtureAsString("login/PrzerwaTechniczna.html"));
client.checkForErrors(doc);
client.checkForErrors(doc, 200);
}
@Test(expected = NotLoggedInErrorException.class)
@ -43,7 +43,7 @@ public class ClientTest {
Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-notLoggedIn.html"));
client.checkForErrors(doc);
client.checkForErrors(doc, 200);
}
@Test

View File

@ -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))
}
}

View File

@ -11,6 +11,8 @@ 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;
@ -53,7 +55,7 @@ public class StudentAndParentTest {
snp.getSnpHomePageUrl());
}
@Test(expected = NotLoggedInErrorException.class)
@Test(expected = VulcanException.class)
public void getSnpPageUrlWithWrongPage() throws Exception {
Document wrongPageDocument = Jsoup.parse(
FixtureHelper.getAsString(getClass().getResourceAsStream("OcenyWszystkie-semester.html"))
@ -81,7 +83,7 @@ public class StudentAndParentTest {
snp.getExtractedIdFromUrl("https://uonetplus-opiekun.vulcan.net.pl/demoupowiat/demo12345/Start/Index/"));
}
@Test(expected = NotLoggedInErrorException.class)
@Test(expected = VulcanException.class)
public void getExtractedIDNotLoggedTest() throws Exception {
Mockito.when(client.getHost()).thenReturn("vulcan.net.pl");
StudentAndParent snp = new StudentAndParent(client, "symbol", null, null);
@ -129,8 +131,8 @@ public class StudentAndParentTest {
@Test
public void getDiariesAndStudentTest() throws IOException, VulcanException {
Document snpHome = Jsoup.parse(FixtureHelper.getAsString(
getClass().getResourceAsStream("StudentAndParent.html")));
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);

View File

@ -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 {

View File

@ -19,12 +19,12 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getAllTest() throws Exception {
Assert.assertEquals(7, filled.getAll().size()); // 2 items are skipped
Assert.assertEquals(7, filled.getAll("").size()); // 2 items are skipped
}
@Test
public void getSubjectTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> 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<Grade> list = filled.getAll();
List<Grade> 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<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("000000", list.get(0).getColor());
Assert.assertEquals("1289F7", list.get(3).getColor());
@ -54,7 +54,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getSymbolTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("A1", list.get(0).getSymbol());
Assert.assertEquals("BW3", list.get(3).getSymbol());
@ -65,7 +65,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getDescriptionTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("Dzień Kobiet w naszej klasie", list.get(0).getDescription());
Assert.assertEquals("Writing", list.get(3).getDescription());
@ -76,7 +76,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getWeightTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("1,00", list.get(0).getWeight());
Assert.assertEquals("3,00", list.get(3).getWeight());
@ -86,7 +86,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getDateTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("2017-03-21", list.get(0).getDate());
Assert.assertEquals("2017-06-02", list.get(3).getDate());
@ -96,7 +96,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getTeacherTest() throws Exception {
List<Grade> list = filled.getAll();
List<Grade> list = filled.getAll("");
Assert.assertEquals("Patryk Maciejewski", list.get(0).getTeacher());
Assert.assertEquals("Oliwia Woźniak", list.get(3).getTeacher());

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -30,8 +30,8 @@ public class NotesListTest extends StudentAndParentTestCase {
public void getDateTest() throws Exception {
List<Note> 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

View File

@ -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,6 +105,7 @@ 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());
@ -155,6 +156,8 @@ public class TimetableTest extends StudentAndParentTestCase {
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());
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>Witryna ucznia i rodzica dostęp mobilny</title>
</head>
<body>
<main class="mainContainer">
<h1>Dostęp mobilny</h1>
<article>
<h2>Zarejestrowane urządzenia</h2>
</article>
<table>
<thead>
<tr>
<th>Urządzenie</th>
<th>Data rejestracji</th>
<th></th>
</tr>
</thead>
<tbody>
<tr class="text-center">
<td>google Android SDK built for x86 (Android 8.1.0)</td>
<td>20.01.2018 godz: 22:35:30</td>
<td class="cellWithButton">
<a href="/Default/123456/DostepMobilny.mvc/Wyrejestruj/321" class="button">Wyrejestruj</a>
</td>
</tr>
<tr class="text-center">
<td>google (Android SDK) built for x86 (Android 8.1.0)</td>
<td>20.01.2018 godz: 22:35:30</td>
<td class="cellWithButton">
<a href="/Default/123456/DostepMobilny.mvc/Wyrejestruj/213" class="button">Wyrejestruj</a>
</td>
</tr>
</tbody>
</table>
</main>
<footer>wersja: 18.01.0001.27311</footer>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>Witryna ucznia i rodzica Rejestracja urządzenia mobilnego</title>
<style>
.blockElement {
display: block;
}
</style>
</head>
<body>
<main class="mainContainer">
<h1>Rejestracja urządzenia mobilnego</h1>
<article class="text-center" id="rejestracja-formularz">
<span class="blockElement">Za pomocą aplikacji &quot;Dzienniczek+&quot; zeskanuj kod QR.</span>
<img src=" data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAYAAACAvzbMAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gUdCTohm9rlswAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAFKklEQVR42u3dYYqDQAyA0abk/lfO/l+6LLZYksx7J3C09SPgaFRVPQDgoqdTAICAACAgAAgIAAICAAICgIAAICAACAgAAgIAAgKAgAAgIAAICAACAgACAoCAACAgAAgIAAICAAICgIAAICAACAgAAgIAAgKAgAAgIAAICAACAgACAoCAACAgAAgIAAICAFdltwOKiNUnvKqOXj/4/9y3fhMIACMICAACAoCAACAgAAgIAAgIAJ/IaQfc7Tno3+5+Dr37+sH/p+/6TSAACAgAAgKAgACAgAAgIAAICADj5bYF2YcBuH+YQAAQEAAEBAAEBAABAUBAABAQAAQEAAQEAAEBQEAAEBAABAQABAQAAQFAQAAQEAAEBAAEBAABAUBAABAQAAQEAP6R2xZUVa4q4P5hAgFAQAAQEAAQEAAEBAABAUBAANho3D6QiHDVAPcPEwgAAgKAgACAgAAgIAAICAACAsCJ2u0D8T5+wP3DBAKAgACAgAAgIAAICAACAoCAAMBL7faBeF8/4P7wWrd9LiYQAAQEAAEBQEAAEBAAEBAABASAr8tpB+x9/7DX6f/vaftcTCAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIA4BQAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAgACAoCAACAgAAgIAAICAAICgIAAICAACAgAAgIAAgKAgAAgIAAICAACAgACAoCAACAgAAgIAAICAAICgIAAICAACAgAAgIAAgKAgAAgIAAICAACAgACAoCAANBWTjvgiDj6glWVXy1gAgFAQAAQEAAQEAAEBAABAUBAuOb0x5CBPdrtAzl9n4PAACYQAAQEAAQEAAEBQEAAEBAABAQA/tBuH8j2fRC+5wGYQAAQEAAQEAAEBAABAUBAAEBAAHhTTjvg7vsofM8DMIEAgIAAICAACAgAAgKAgACAgABwk9y2oLv3YfieB4AJBAABAUBAABAQAAQEAAQEAAEBQEAAEBAABAQABAQAAQFAQAAQEAAEBAAEBAABAUBAABAQAAQEAAQEAAEBQEAAGC+3LaiqXFUAEwgAAgKAgACAgAAgIAAICAACAsBG4/aBRMTRF+z09QMmEAAEBAABAQABAUBAABAQAAQEgINF+YAGACYQAAQEAAEBQEAAQEAAEBAABAQAAQFAQABAQAAQEAAEBAABAUBAAEBAABAQAAQEAAEBQEAAQEAAEBAABAQAAQFAQABAQAAQEAAEBAABAUBAAEBAABAQAAQEAAEBQEAAQEAA+IYf8fZKNX0RrMQAAAAASUVORK5CYII="
alt="Kod QR" title="Kod QR" height="400" width="400"/>
<span class="blockElement">Token: 3S1A1B2C</span>
<span class="blockElement">Symbol: Default</span>
<span class="blockElement">PIN: 1234567</span>
</article>
</main>
<footer>wersja: 18.01.0001.27311</footer>
</body>
</html>

View File

@ -319,7 +319,16 @@
<span></span>
</div>
</td>
<td></td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-inv">Język polski</span>
<span class="x-treelabel-ppl x-treelabel-inv"> </span>
<span class="x-treelabel-ppl x-treelabel-inv">16</span>
<span class="random-class">(oddział nieobecny)</span>
</div>
<button type="button" class="uwaga-btn">Uwaga</button>
<div class="uwaga-panel">opis w uwadze bez klasy w spanie</div>
</td>
</tr>
<tr>
<td>5</td>
@ -355,7 +364,20 @@
<span></span>
</div>
</td>
<td></td>
<td>
<div>
<span class="">Tworzenie i administrowanie bazami danych [zaw2]</span>
<span class=""></span>
<span class=""></span>
<span class=""></span>
</div>
<div>
<span class="x-treelabel-ppl x-treelabel-inv">Zajęcia z wychowawcą</span>
<span class="x-treelabel-ppl x-treelabel-inv">Małgorzata Kowal</span>
<span class="x-treelabel-ppl x-treelabel-inv">43</span>
<span class="x-treelabel-rlz">(zmiana organizacji zajęć)</span>
</div>
</td>
</tr>
<tr>
<td>6</td>
@ -461,7 +483,18 @@
<tr>
<td>9</td>
<td>14:50 15:35</td>
<td></td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-zas">Wychowanie fizyczne [zaw2]</span>
<span class="x-treelabel-ppl x-treelabel-zas"></span>
<span class="x-treelabel-ppl x-treelabel-zas"></span>
<span class="x-treelabel-ppl x-treelabel-zas">G3</span>
<span class="x-treelabel-rlz">(przeniesiona z lekcji 7, 01.12.2017)</span>
</div>
<div>
<!--<span>WTF</span>-->
</div>
</td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-inv">Język niemiecki [J1]</span>

View File

@ -9,7 +9,7 @@ buildscript {
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:1.2.0'
classpath "com.github.triplet.gradle:play-publisher:$playPublisher"
}
}
@ -41,8 +41,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 26
versionCode 10
versionName "0.4.2"
versionCode 14
versionName "0.5.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
@ -91,7 +91,7 @@ play {
}
greendao {
schemaVersion 27
schemaVersion 28
generateTests = true
}
@ -118,6 +118,9 @@ dependencies {
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:$crashlyticsSdk@aar") {
transitive = true
}

View File

@ -14,7 +14,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/WulkanowyTheme">
<activity
android:name=".ui.splash.SplashActivity"
@ -31,20 +30,20 @@
android:name=".ui.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:label="@string/title_activity_login"
android:theme="@style/WulkanowyTheme.LoginTheme"
android:theme="@style/WulkanowyTheme.DarkActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.main.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/activity_dashboard_text"
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme" />
/>
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/WulkanowyTheme.LoginTheme" />
android:theme="@style/WulkanowyTheme.DarkActionBar" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/WulkanowyTheme.LoginTheme" />
android:theme="@style/WulkanowyTheme.DarkActionBar" />
<service
android:name=".services.jobs.SyncJob"

View File

@ -1,8 +1,7 @@
package io.github.wulkanowy;
import android.app.Application;
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.core.CrashlyticsCore;
import com.jakewharton.threetenabp.AndroidThreeTen;
@ -10,18 +9,17 @@ import org.greenrobot.greendao.query.QueryBuilder;
import javax.inject.Inject;
import dagger.android.AndroidInjector;
import dagger.android.support.DaggerApplication;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.utils.Log;
import io.fabric.sdk.android.Fabric;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.di.component.ApplicationComponent;
import io.github.wulkanowy.di.component.DaggerApplicationComponent;
import io.github.wulkanowy.di.modules.ApplicationModule;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.di.DaggerAppComponent;
import io.github.wulkanowy.utils.FabricUtils;
import io.github.wulkanowy.utils.LoggerUtils;
import timber.log.Timber;
public class WulkanowyApp extends Application {
protected ApplicationComponent applicationComponent;
public class WulkanowyApp extends DaggerApplication {
@Inject
RepositoryContract repository;
@ -31,12 +29,6 @@ public class WulkanowyApp extends Application {
super.onCreate();
AndroidThreeTen.init(this);
applicationComponent = DaggerApplicationComponent
.builder()
.applicationModule(new ApplicationModule(this))
.build();
applicationComponent.inject(this);
if (BuildConfig.DEBUG) {
enableDebugLog();
}
@ -48,27 +40,35 @@ public class WulkanowyApp extends Application {
if (repository.getSharedRepo().isUserLoggedIn()) {
try {
repository.getSyncRepo().initLastUser();
FabricUtils.logLogin("Open app", true);
} catch (Exception e) {
LogUtils.error("An error occurred when the application was started", e);
FabricUtils.logLogin("Open app", false);
Timber.e(e, "An error occurred when the application was started");
}
}
}
private void enableDebugLog() {
QueryBuilder.LOG_VALUES = true;
FlexibleAdapter.enableLogs(Log.Level.DEBUG);
FlexibleAdapter.enableLogs(eu.davidea.flexibleadapter.utils.Log.Level.DEBUG);
Timber.plant(new LoggerUtils.DebugLogTree());
}
private void initializeFabric() {
Fabric.with(new Fabric.Builder(this)
.kits(new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build())
.kits(
new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build(),
new Answers()
)
.debuggable(BuildConfig.DEBUG)
.build());
Timber.plant(new LoggerUtils.CrashlyticsTree());
}
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}

View File

@ -47,4 +47,10 @@ public class Repository implements RepositoryContract {
public SyncContract getSyncRepo() {
return synchronization;
}
@Override
public void cleanAllData() {
sharedPref.cleanSharedPref();
database.recreateDatabase();
}
}

View File

@ -17,4 +17,6 @@ public interface RepositoryContract {
DbContract getDbRepo();
SyncContract getSyncRepo();
void cleanAllData();
}

View File

@ -4,6 +4,7 @@ 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 {
@ -20,6 +21,8 @@ public interface DbContract {
long getCurrentSymbolId();
Symbol getCurrentSymbol();
long getCurrentDiaryId();
long getSemesterId(int name);
@ -27,4 +30,6 @@ public interface DbContract {
long getCurrentSemesterId();
int getCurrentSemesterName();
void recreateDatabase();
}

View File

@ -12,6 +12,7 @@ import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
@ -19,10 +20,9 @@ import io.github.wulkanowy.data.db.dao.entities.DaoMaster;
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.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 {
@ -32,7 +32,7 @@ public class DbHelper extends DaoMaster.OpenHelper {
private final Vulcan vulcan;
@Inject
DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName,
DbHelper(Context context, @Named("dbName") String dbName,
SharedPrefContract sharedPref, Vulcan vulcan) {
super(context, dbName);
this.sharedPref = sharedPref;
@ -41,7 +41,7 @@ public class DbHelper extends DaoMaster.OpenHelper {
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LogUtils.info("Cleaning user data oldVersion=" + oldVersion + " newVersion=" + newVersion);
Timber.i("Cleaning user data oldVersion=%s newVersion=%s", oldVersion, newVersion);
Database database = new StandardDatabase(db);
recreateDatabase(database);
}
@ -54,11 +54,11 @@ public class DbHelper extends DaoMaster.OpenHelper {
for (Migration migration : migrations) {
if (oldVersion < migration.getVersion()) {
try {
LogUtils.info("Applying migration to db schema v" + migration.getVersion() + "...");
Timber.i("Applying migration to db schema v%s...", migration.getVersion());
migration.runMigration(db, sharedPref, vulcan);
LogUtils.info("Migration " + migration.getVersion() + " complete");
Timber.i("Migration %s complete", migration.getVersion());
} catch (Exception e) {
e.printStackTrace();
Timber.e(e, "Failed to apply migration");
recreateDatabase(db);
break;
}
@ -67,7 +67,7 @@ public class DbHelper extends DaoMaster.OpenHelper {
}
private void recreateDatabase(Database db) {
LogUtils.info("Database is recreating...");
Timber.i("Database is recreating...");
sharedPref.setCurrentUserId(0);
DaoMaster.dropAllTables(db, true);
onCreate(db);
@ -78,6 +78,7 @@ public class DbHelper extends DaoMaster.OpenHelper {
migrations.add(new Migration23());
migrations.add(new Migration26());
migrations.add(new Migration27());
migrations.add(new Migration28());
// Sorting just to be safe, in case other people add migrations in the wrong order.
Comparator<Migration> migrationComparator = new Comparator<Migration>() {

View File

@ -1,9 +1,12 @@
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;
@ -12,6 +15,7 @@ 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;
@ -57,10 +61,15 @@ public class DbRepository implements DbContract {
}
@Override
public long getCurrentSymbolId() {
public Symbol getCurrentSymbol() {
return daoSession.getSymbolDao().queryBuilder().where(
SymbolDao.Properties.UserId.eq(sharedPref.getCurrentUserId())
).unique().getId();
).unique();
}
@Override
public long getCurrentSymbolId() {
return getCurrentSymbol().getId();
}
@Override
@ -103,4 +112,12 @@ public class DbRepository implements DbContract {
SemesterDao.Properties.Current.eq(true)
).unique();
}
@Override
public void recreateDatabase() {
Database database = daoSession.getDatabase();
DaoMaster.dropAllTables(database, true);
DaoMaster.createAllTables(database, true);
}
}

View File

@ -5,6 +5,7 @@ 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.OrderBy;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.ToMany;
@ -35,9 +36,11 @@ public class Day {
@Property(nameInDb = "free_day_name")
private String freeDayName = "";
@OrderBy("number ASC")
@ToMany(referencedJoinProperty = "dayId")
private List<TimetableLesson> timetableLessons;
@OrderBy("number ASC")
@ToMany(referencedJoinProperty = "dayId")
private List<AttendanceLesson> attendanceLessons;
@ -50,9 +53,7 @@ public class Day {
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/**
* Used for active entity operations.
*/
/** Used for active entity operations. */
@Generated(hash = 312167767)
private transient DayDao myDao;
@ -185,6 +186,36 @@ public class Day {
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<Exam> 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<Exam> 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.
@ -221,40 +252,12 @@ public class Day {
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 = 1231531946)
public List<Exam> 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<Exam> 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;
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 1409317752)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getDayDao() : null;
}
}

View File

@ -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,7 +14,7 @@ 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 {
@ -25,7 +27,7 @@ public class TimetableLesson implements Serializable {
private Long dayId;
@Property(nameInDb = "number")
private String number;
private int number = 0;
@Property(nameInDb = "subject")
private String subject = "";
@ -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 = 1955911128)
public TimetableLesson(Long id, Long dayId, String 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) {
@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;
@ -109,7 +108,7 @@ public class TimetableLesson implements Serializable {
@Generated(hash = 1878030142)
public TimetableLesson() {
}
public Long getId() {
return this.id;
}
@ -127,11 +126,11 @@ public class TimetableLesson implements Serializable {
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;
}
@ -262,6 +261,32 @@ public class TimetableLesson implements Serializable {
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.

View File

@ -9,8 +9,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.github.wulkanowy.api.Diary;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.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;

View File

@ -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");
}
}

View File

@ -3,8 +3,6 @@ package io.github.wulkanowy.data.db.resources;
import android.content.Context;
import android.content.res.Resources;
import com.crashlytics.android.Crashlytics;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@ -15,10 +13,9 @@ import javax.inject.Singleton;
import io.github.wulkanowy.R;
import io.github.wulkanowy.api.NotLoggedInErrorException;
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 ResourcesRepository implements ResourcesContract {
@ -26,7 +23,7 @@ public class ResourcesRepository implements ResourcesContract {
private Resources resources;
@Inject
ResourcesRepository(@ApplicationContext Context context) {
ResourcesRepository(Context context) {
resources = context.getResources();
}
@ -42,8 +39,7 @@ public class ResourcesRepository implements ResourcesContract {
@Override
public String getErrorLoginMessage(Exception exception) {
LogUtils.error(AppConstant.APP_NAME + " encountered a error", exception);
Crashlytics.logException(exception);
Timber.e(exception,"%s encountered a error", AppConstant.APP_NAME);
if (exception instanceof CryptoException) {
return resources.getString(R.string.encrypt_failed_text);

View File

@ -21,6 +21,8 @@ public interface SharedPrefContract {
boolean isShowAttendancePresent();
int getCurrentTheme();
int getServicesInterval();
boolean isMobileDisable();
@ -28,4 +30,6 @@ public interface SharedPrefContract {
boolean isServicesEnable();
boolean isNotifyEnable();
void cleanSharedPref();
}

View File

@ -6,10 +6,9 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import io.github.wulkanowy.di.annotations.ApplicationContext;
import io.github.wulkanowy.di.annotations.SharedPreferencesInfo;
import io.github.wulkanowy.ui.main.settings.SettingsFragment;
@Singleton
@ -24,7 +23,7 @@ public class SharedPrefRepository implements SharedPrefContract {
private final SharedPreferences settingsSharedPref;
@Inject
SharedPrefRepository(@ApplicationContext Context context, @SharedPreferencesInfo String sharedName) {
SharedPrefRepository(Context context, @Named("sharedPrefName") String sharedName) {
appSharedPref = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE);
settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(context);
}
@ -70,6 +69,11 @@ public class SharedPrefRepository implements SharedPrefContract {
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"));
@ -89,4 +93,10 @@ public class SharedPrefRepository implements SharedPrefContract {
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();
}
}

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.data.sync;
import android.content.Context;
import org.greenrobot.greendao.database.Database;
import java.io.IOException;
import java.util.List;
@ -11,6 +13,7 @@ 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;
@ -20,11 +23,10 @@ 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.di.annotations.ApplicationContext;
import io.github.wulkanowy.utils.DataObjectConverter;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.security.CryptoException;
import io.github.wulkanowy.utils.security.Scrambler;
import timber.log.Timber;
@Singleton
public class AccountSync {
@ -39,7 +41,7 @@ public class AccountSync {
@Inject
AccountSync(DaoSession daoSession, SharedPrefContract sharedPref,
Vulcan vulcan, @ApplicationContext Context context) {
Vulcan vulcan, Context context) {
this.daoSession = daoSession;
this.sharedPref = sharedPref;
this.vulcan = vulcan;
@ -49,6 +51,8 @@ public class AccountSync {
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();
@ -69,7 +73,7 @@ public class AccountSync {
}
private Account insertAccount(String email, String password) throws CryptoException {
LogUtils.debug("Register account: " + email);
Timber.d("Register account");
Account account = new Account()
.setEmail(email)
.setPassword(Scrambler.encrypt(email, password, context));
@ -78,10 +82,11 @@ public class AccountSync {
}
private Symbol insertSymbol(Account account) throws VulcanException, IOException {
LogUtils.debug("Register symbol: " + vulcan.getSymbol());
String schoolId = vulcan.getStudentAndParent().getSchoolID();
Timber.d("Register symbol %s", vulcan.getSymbol());
Symbol symbol = new Symbol()
.setUserId(account.getId())
.setSchoolId(vulcan.getStudentAndParent().getSchoolID())
.setSchoolId(schoolId)
.setSymbol(vulcan.getSymbol());
daoSession.getSymbolDao().insert(symbol);
@ -93,7 +98,7 @@ public class AccountSync {
vulcan.getStudentAndParent().getStudents(),
symbol.getId()
);
LogUtils.debug("Register students: " + studentList.size());
Timber.d("Register students %s", studentList.size());
daoSession.getStudentDao().insertInTx(studentList);
}
@ -104,7 +109,7 @@ public class AccountSync {
StudentDao.Properties.SymbolId.eq(symbolEntity.getId()),
StudentDao.Properties.Current.eq(true)
).unique().getId());
LogUtils.debug("Register diaries: " + diaryList.size());
Timber.d("Register diaries %s", diaryList.size());
daoSession.getDiaryDao().insertInTx(diaryList);
}
@ -114,19 +119,19 @@ public class AccountSync {
daoSession.getDiaryDao().queryBuilder().where(
DiaryDao.Properties.Current.eq(true)
).unique().getId());
LogUtils.debug("Register semesters: " + semesterList.size());
Timber.d("Register semesters %s", semesterList.size());
daoSession.getSemesterDao().insertInTx(semesterList);
}
public void initLastUser() throws IOException, CryptoException {
public void initLastUser() throws CryptoException {
long userId = sharedPref.getCurrentUserId();
if (userId == 0) {
throw new IOException("Can't find saved user");
throw new NotRegisteredUserException("Can't find user id in SharedPreferences");
}
LogUtils.debug("Initialization current user id=" + userId);
Timber.d("Init current user (%s)", userId);
Account account = daoSession.getAccountDao().load(userId);
@ -152,4 +157,11 @@ public class AccountSync {
diary.getValue()
);
}
private void clearUserData() {
Database database = daoSession.getDatabase();
DaoMaster.dropAllTables(database, true);
DaoMaster.createAllTables(database, true);
sharedPref.setCurrentUserId(0);
}
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.sync;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@ -19,8 +18,7 @@ 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.utils.DataObjectConverter;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.TimeUtils;
import timber.log.Timber;
@Singleton
public class AttendanceSync {
@ -37,10 +35,10 @@ public class AttendanceSync {
this.vulcan = vulcan;
}
public void syncAttendance(long diaryId, String date) throws IOException, ParseException, VulcanException {
public void syncAttendance(long diaryId, String date) throws IOException, VulcanException {
this.diaryId = diaryId;
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> weekApi = getWeekFromApi(getNormalizedDate(date));
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> weekApi = getWeekFromApi(date);
Week weekDb = getWeekFromDb(weekApi.getStartDayDate());
long weekId = updateWeekInDb(weekDb, weekApi);
@ -49,15 +47,11 @@ public class AttendanceSync {
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<io.github.wulkanowy.api.generic.Day> getWeekFromApi(String date)
throws IOException, ParseException, VulcanException {
throws IOException, VulcanException {
return vulcan.getAttendanceTable().getWeekTable(date);
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.sync;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@ -18,8 +17,7 @@ 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 io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.TimeUtils;
import timber.log.Timber;
public class ExamsSync {
@ -35,11 +33,10 @@ public class ExamsSync {
this.vulcan = vulcan;
}
public void syncExams(long diaryId, String date) throws IOException, VulcanException,
ParseException {
public void syncExams(long diaryId, String date) throws IOException, VulcanException {
this.diaryId = diaryId;
io.github.wulkanowy.api.generic.Week<ExamDay> weekApi = getWeekFromApi(getNormalizedDate(date));
io.github.wulkanowy.api.generic.Week<ExamDay> weekApi = getWeekFromApi(date);
Week weekDb = getWeekFromDb(weekApi.getStartDayDate());
long weekId = updateWeekInDb(weekDb, weekApi);
@ -48,7 +45,7 @@ public class ExamsSync {
daoSession.getExamDao().saveInTx(examList);
LogUtils.debug("Synchronization exams (amount = " + examList.size() + ")");
Timber.d("Exams synchronization complete (%s)", examList.size());
}
private Week getWeekFromDb(String date) {
@ -59,7 +56,7 @@ public class ExamsSync {
}
private io.github.wulkanowy.api.generic.Week<ExamDay> getWeekFromApi(String date)
throws VulcanException, IOException, ParseException {
throws VulcanException, IOException {
return vulcan.getExamsList().getWeek(date, true);
}
@ -77,10 +74,6 @@ public class ExamsSync {
return daoSession.getWeekDao().insert(weekApiEntity);
}
private String getNormalizedDate(String date) throws ParseException {
return null != date ? String.valueOf(TimeUtils.getNetTicks(date)) : "";
}
private Day getDayFromDb(String date, long weekId) {
return daoSession.getDayDao().queryBuilder().where(
DayDao.Properties.WeekId.eq(weekId),
@ -135,6 +128,7 @@ public class ExamsSync {
.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();
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.sync;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@ -16,7 +15,7 @@ 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 io.github.wulkanowy.utils.LogUtils;
import timber.log.Timber;
@Singleton
public class GradeSync {
@ -33,7 +32,7 @@ public class GradeSync {
this.vulcan = vulcan;
}
public void sync(long semesterId) throws IOException, VulcanException, ParseException {
public void sync(long semesterId) throws IOException, VulcanException {
this.semesterId = semesterId;
Semester semester = daoSession.getSemesterDao().load(semesterId);
@ -44,7 +43,7 @@ public class GradeSync {
daoSession.getGradeDao().deleteInTx(semester.getGradeList());
daoSession.getGradeDao().insertInTx(lastList);
LogUtils.debug("Synchronization grades (amount = " + lastList.size() + ")");
Timber.d("Grades synchronization complete (%s)", lastList.size());
}
private void resetSemesterRelations(Semester semester) {
@ -64,7 +63,7 @@ public class GradeSync {
return updatedList;
}
private List<Grade> getComparedList(Semester semester) throws IOException, VulcanException, ParseException {
private List<Grade> getComparedList(Semester semester) throws IOException, VulcanException {
List<Grade> gradesFromNet = DataObjectConverter.gradesToGradeEntities(
vulcan.getGradesList().getAll(semester.getValue()), semesterId);

View File

@ -0,0 +1,8 @@
package io.github.wulkanowy.data.sync;
public class NotRegisteredUserException extends RuntimeException {
public NotRegisteredUserException(String message) {
super(message);
}
}

View File

@ -13,7 +13,7 @@ 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 io.github.wulkanowy.utils.LogUtils;
import timber.log.Timber;
@Singleton
public class SubjectSync {
@ -40,7 +40,7 @@ public class SubjectSync {
daoSession.getSubjectDao().deleteInTx(getSubjectsFromDb());
daoSession.getSubjectDao().insertInTx(lastList);
LogUtils.debug("Synchronization subjects (amount = " + lastList.size() + ")");
Timber.d("Subjects synchronization complete (%s)", lastList.size());
}
private List<Subject> getSubjectsFromNet(Semester semester) throws VulcanException, IOException {

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.data.sync;
import java.io.IOException;
import java.text.ParseException;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -47,17 +46,17 @@ public class SyncRepository implements SyncContract {
}
@Override
public void initLastUser() throws IOException, CryptoException {
public void initLastUser() throws CryptoException {
accountSync.initLastUser();
}
@Override
public void syncGrades(int semesterName) throws VulcanException, IOException, ParseException {
public void syncGrades(int semesterName) throws VulcanException, IOException {
gradeSync.sync(semesterName);
}
@Override
public void syncGrades() throws VulcanException, IOException, ParseException {
public void syncGrades() throws VulcanException, IOException {
gradeSync.sync(database.getCurrentSemesterId());
}
@ -72,12 +71,12 @@ public class SyncRepository implements SyncContract {
}
@Override
public void syncAttendance() throws ParseException, IOException, VulcanException {
public void syncAttendance() throws IOException, VulcanException {
attendanceSync.syncAttendance(database.getCurrentDiaryId(), null);
}
@Override
public void syncAttendance(long diaryId, String date) throws ParseException, IOException, VulcanException {
public void syncAttendance(long diaryId, String date) throws IOException, VulcanException {
if (diaryId != 0) {
attendanceSync.syncAttendance(diaryId, date);
} else {
@ -86,12 +85,12 @@ public class SyncRepository implements SyncContract {
}
@Override
public void syncTimetable() throws VulcanException, IOException, ParseException {
public void syncTimetable() throws VulcanException, IOException {
timetableSync.syncTimetable(database.getCurrentDiaryId(), null);
}
@Override
public void syncTimetable(long diaryId, String date) throws VulcanException, IOException, ParseException {
public void syncTimetable(long diaryId, String date) throws VulcanException, IOException {
if (diaryId != 0) {
timetableSync.syncTimetable(diaryId, date);
} else {
@ -100,12 +99,12 @@ public class SyncRepository implements SyncContract {
}
@Override
public void syncExams() throws VulcanException, IOException, ParseException {
public void syncExams() throws VulcanException, IOException {
examsSync.syncExams(database.getCurrentDiaryId(), null);
}
@Override
public void syncExams(long diaryId, String date) throws VulcanException, IOException, ParseException {
public void syncExams(long diaryId, String date) throws VulcanException, IOException {
if (diaryId != 0) {
examsSync.syncExams(diaryId, date);
} else {
@ -114,7 +113,7 @@ public class SyncRepository implements SyncContract {
}
@Override
public void syncAll() throws VulcanException, IOException, ParseException {
public void syncAll() throws VulcanException, IOException {
syncSubjects();
syncGrades();
syncAttendance();

View File

@ -1,7 +1,8 @@
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;
@ -19,8 +20,7 @@ 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.utils.DataObjectConverter;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.TimeUtils;
import timber.log.Timber;
@Singleton
public class TimetableSync {
@ -37,10 +37,10 @@ public class TimetableSync {
this.vulcan = vulcan;
}
public void syncTimetable(long diaryId, String date) throws IOException, ParseException, VulcanException {
public void syncTimetable(long diaryId, String date) throws IOException, VulcanException {
this.diaryId = diaryId;
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.timetable.TimetableDay> weekApi = getWeekFromApi(getNormalizedDate(date));
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.timetable.TimetableDay> weekApi = getWeekFromApi(date);
Week weekDb = getWeekFromDb(weekApi.getStartDayDate());
long weekId = updateWeekInDb(weekDb, weekApi);
@ -49,15 +49,11 @@ public class TimetableSync {
daoSession.getTimetableLessonDao().saveInTx(lessonList);
LogUtils.debug("Synchronization timetable lessons (amount = " + lessonList.size() + ")");
}
private String getNormalizedDate(String date) throws ParseException {
return null != date ? String.valueOf(TimeUtils.getNetTicks(date)) : "";
Timber.d("Timetable synchronization complete (%s)", lessonList.size());
}
private io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.timetable.TimetableDay> getWeekFromApi(String date)
throws IOException, ParseException, VulcanException {
throws IOException, VulcanException {
return vulcan.getTimetable().getWeekTable(date);
}
@ -125,6 +121,16 @@ public class TimetableSync {
List<TimetableLesson> lessonsFromApiEntities = DataObjectConverter
.lessonsToTimetableLessonsEntities(lessons);
List<TimetableLesson> lessonsFromDbEntities = getLessonsFromDb(dayId);
if (!lessonsFromDbEntities.isEmpty()) {
List<TimetableLesson> lessonToRemove = new ArrayList<>(CollectionUtils.removeAll(lessonsFromDbEntities, lessonsFromApiEntities));
for (TimetableLesson timetableLesson : lessonToRemove) {
daoSession.getTimetableLessonDao().delete(timetableLesson);
}
}
for (TimetableLesson apiLessonEntity : lessonsFromApiEntities) {
TimetableLesson lessonFromDb = getLessonFromDb(apiLessonEntity, dayId);
@ -148,4 +154,8 @@ public class TimetableSync {
TimetableLessonDao.Properties.EndTime.eq(apiEntity.getEndTime()))
.unique();
}
private List<TimetableLesson> getLessonsFromDb(long dayId) {
return daoSession.getDayDao().load(dayId).getTimetableLessons();
}
}

View File

@ -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<WulkanowyApp> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<WulkanowyApp> {
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

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

View File

@ -1,32 +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.jobs.SyncJob;
import io.github.wulkanowy.ui.widgets.TimetableWidgetFactory;
import io.github.wulkanowy.ui.widgets.TimetableWidgetProvider;
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
@ApplicationContext
Context getContext();
RepositoryContract getRepository();
void inject(WulkanowyApp wulkanowyApp);
void inject(SyncJob syncJob);
void inject(TimetableWidgetFactory timetableWidgetFactory);
void inject(TimetableWidgetProvider timetableWidgetProvider);
}

View File

@ -1,31 +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.exams.ExamsFragment;
import io.github.wulkanowy.ui.main.exams.ExamsTabFragment;
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(ExamsFragment examsFragment);
void inject(ExamsTabFragment examsTabFragment);
void inject(TimetableFragment timetableFragment);
void inject(TimetableTabFragment timetableTabFragment);
}

View File

@ -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.base.BasePagerAdapter;
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.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
BasePagerAdapter provideMainPagerAdapter() {
return new BasePagerAdapter(activity.getSupportFragmentManager());
}
}

View File

@ -1,111 +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.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.di.annotations.ApplicationContext;
import io.github.wulkanowy.di.annotations.DatabaseInfo;
import io.github.wulkanowy.di.annotations.SharedPreferencesInfo;
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(SharedPrefRepository sharedPrefRepository) {
return sharedPrefRepository;
}
@Singleton
@Provides
ResourcesContract provideAppResources(ResourcesRepository resourcesRepository) {
return resourcesRepository;
}
@Singleton
@Provides
DbContract provideDatabase(DbRepository dbRepository) {
return dbRepository;
}
@Singleton
@Provides
SyncContract provideSync(SyncRepository syncRepository) {
return syncRepository;
}
@Provides
FirebaseJobDispatcher provideDispatcher() {
return new FirebaseJobDispatcher(new GooglePlayDriver(application));
}
}

View File

@ -1,103 +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.base.BasePagerAdapter;
import io.github.wulkanowy.ui.main.attendance.AttendanceContract;
import io.github.wulkanowy.ui.main.attendance.AttendanceHeaderItem;
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.exams.ExamsContract;
import io.github.wulkanowy.ui.main.exams.ExamsPresenter;
import io.github.wulkanowy.ui.main.exams.ExamsSubItem;
import io.github.wulkanowy.ui.main.exams.ExamsTabContract;
import io.github.wulkanowy.ui.main.exams.ExamsTabPresenter;
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.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
ExamsContract.Presenter provideDashboardPresenter(ExamsPresenter examsPresenter) {
return examsPresenter;
}
@PerFragment
@Provides
AttendanceTabContract.Presenter provideAttendanceTabPresenter(AttendanceTabPresenter timetableTabPresenter) {
return timetableTabPresenter;
}
@Provides
BasePagerAdapter provideBasePagerAdapter() {
return new BasePagerAdapter(fragment.getChildFragmentManager());
}
@Provides
FlexibleAdapter<AttendanceHeaderItem> provideAttendanceTabAdapter() {
return new FlexibleAdapter<>(null);
}
@Provides
FlexibleAdapter<TimetableHeaderItem> provideTimetableTabAdapter() {
return new FlexibleAdapter<>(null);
}
@Provides
FlexibleAdapter<GradeHeaderItem> provideGradesAdapter() {
return new FlexibleAdapter<>(null);
}
@Provides
FlexibleAdapter<ExamsSubItem> provideExamAdapter() {
return new FlexibleAdapter<>(null);
}
@PerFragment
@Provides
TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter) {
return timetablePresenter;
}
@PerFragment
@Provides
TimetableTabContract.Presenter provideTimetableTabPresenter(TimetableTabPresenter timetableTabPresenter) {
return timetableTabPresenter;
}
@Provides
ExamsTabContract.Presenter provideExamsTabPresenter(ExamsTabPresenter examsTabPresenter) {
return examsTabPresenter;
}
}

View File

@ -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;

View File

@ -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 DatabaseInfo {
public @interface PerChildFragment {
}

View File

@ -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;

View File

@ -4,7 +4,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import com.crashlytics.android.Crashlytics;
import com.firebase.jobdispatcher.Constraint;
import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver;
@ -20,13 +19,16 @@ 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;
public class SyncJob extends SimpleJobService {
@ -59,7 +61,7 @@ public class SyncJob extends SimpleJobService {
@Override
public void onCreate() {
super.onCreate();
((WulkanowyApp) getApplication()).getApplicationComponent().inject(this);
AndroidInjection.inject(this);
}
@Override
@ -73,10 +75,24 @@ public class SyncJob extends SimpleJobService {
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) {
Crashlytics.logException(e);
LogUtils.error("During background synchronization an error occurred", e);
logError(e);
return JobService.RESULT_FAIL_RETRY;
}
}
@ -116,4 +132,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");
}
}

View File

@ -3,12 +3,20 @@ 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) {
return new TimetableWidgetFactory(getApplicationContext());
AndroidInjection.inject(this);
return new TimetableWidgetFactory(getApplicationContext(), repository);
}
}

View File

@ -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();
}
}

View File

@ -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<V extends View> {
void onStart(V view);
void attachView(@NonNull V view);
void onDestroy();
void detachView();
}
}

View File

@ -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());
}
}

View File

@ -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<V extends BaseContract.View> 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;
}

View File

@ -5,7 +5,7 @@ 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;
@ -14,11 +14,11 @@ 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;
@ -64,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() {
@ -165,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));
@ -222,13 +212,25 @@ public class LoginActivity extends BaseActivity implements LoginContract.View {
}
}
@NonNull
@Override
public void onDestroy() {
super.onDestroy();
presenter.onDestroy();
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();
}
}

View File

@ -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 {
@ -35,9 +33,10 @@ public interface LoginContract {
void showActionBar(boolean show);
void onSyncFailed();
}
@PerActivity
interface Presenter extends BaseContract.Presenter<View> {
void attemptLogin(String email, String password, String symbol);
@ -48,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();
}
}

View File

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

View File

@ -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<LoginContract.View>
implements LoginContract.Presenter {
@ -46,7 +47,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
loginAsync.execute();
} else {
getView().onNoNetworkError();
getView().showNoNetworkMessage();
}
getView().hideSoftInput();
@ -82,20 +83,30 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
}
@Override
public void onEndAsync(boolean success, Exception exception) {
if (success) {
getView().openMainActivity();
return;
} else if (exception instanceof BadCredentialsException) {
getView().setErrorPassIncorrect();
getView().showSoftInput();
} else if (exception instanceof AccountPermissionException) {
getView().setErrorSymbolRequired();
getView().showSoftInput();
} else {
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
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);
}
@ -157,11 +168,11 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
}
@Override
public void onDestroy() {
public void detachView() {
if (loginAsync != null) {
loginAsync.cancel(true);
loginAsync = null;
}
super.onDestroy();
super.detachView();
}
}

View File

@ -2,7 +2,13 @@ package io.github.wulkanowy.ui.login;
import android.os.AsyncTask;
public class LoginTask extends AsyncTask<Void, Integer, Boolean> {
public class LoginTask extends AsyncTask<Void, Integer, Integer> {
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<Void, Integer, Boolean> {
}
@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<Void, Integer, Boolean> {
}
@Override
protected void onPostExecute(Boolean success) {
protected void onPostExecute(Integer success) {
presenter.onEndAsync(success, exception);
}

View File

@ -2,8 +2,9 @@ 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;
@ -13,10 +14,11 @@ 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.data.RepositoryContract;
import io.github.wulkanowy.services.jobs.SyncJob;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.base.BasePagerAdapter;
@ -25,6 +27,7 @@ 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 {
@ -40,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
BasePagerAdapter pagerAdapter;
@Inject
MainContract.Presenter presenter;
@Inject
RepositoryContract repository;
public static Intent getStartIntent(Context context) {
return new Intent(context, MainActivity.class);
}
@ -54,12 +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.toolbar));
setSupportActionBar((Toolbar) findViewById(R.id.main_activity_toolbar));
injectViews();
getActivityComponent().inject(this);
setButterKnife(ButterKnife.bind(this));
presenter.onStart(this, getIntent().getIntExtra(EXTRA_CARD_ID_KEY, -1));
presenter.attachView(this, getIntent().getIntExtra(EXTRA_CARD_ID_KEY, -1));
}
@Override
@ -88,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;
}
@ -119,8 +129,8 @@ public class MainActivity extends BaseActivity implements MainContract.View,
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(tabPosition);
@ -146,9 +156,15 @@ public class MainActivity extends BaseActivity implements MainContract.View,
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();
}
}

View File

@ -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 {
@ -22,10 +23,9 @@ public interface MainContract {
void startSyncService(int interval, boolean useOnlyWifi);
}
@PerActivity
interface Presenter extends BaseContract.Presenter<View> {
void onStart(View view, int tabPositionIntent);
void attachView(@NonNull View view, int initTabId);
void onTabSelected(int position, boolean wasSelected);

View File

@ -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();
}

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy.ui.main;
import android.support.annotation.NonNull;
import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
@ -17,15 +19,15 @@ public class MainPresenter extends BasePresenter<MainContract.View>
}
@Override
public void onStart(MainContract.View view, int tabPositionIntent) {
super.onStart(view);
public void attachView(@NonNull MainContract.View view, int initTabId) {
super.attachView(view);
getView().showProgressBar(true);
getView().hideActionBar();
int tabPosition;
if (tabPositionIntent != -1) {
tabPosition = tabPositionIntent;
if (initTabId != -1) {
tabPosition = initTabId;
} else {
tabPosition = getRepository().getSharedRepo().getStartupTab();
}

View File

@ -1,6 +1,7 @@
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;
@ -21,12 +22,11 @@ public interface AttendanceContract {
void setThemeForTab(int position);
}
@PerActivity
interface Presenter extends BaseContract.Presenter<View> {
void onFragmentActivated(boolean isVisible);
void onStart(View view, OnFragmentIsReadyListener listener);
void attachView(@NonNull View view, OnFragmentIsReadyListener listener);
void setRestoredPosition(int position);
}

View File

@ -78,7 +78,7 @@ public class AttendanceDialogFragment extends DialogFragment {
description.setText(lesson.getDescription());
if (lesson.getAbsenceUnexcused()) {
description.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
description.setTextColor(getResources().getColor(R.color.colorPrimary));
}
return view;

View File

@ -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,14 +10,14 @@ 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.attendance.tab.AttendanceTabFragment;
public class AttendanceFragment extends BaseFragment implements AttendanceContract.View {
@ -31,6 +30,7 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra
TabLayout tabLayout;
@Inject
@Named("Attendance")
BasePagerAdapter pagerAdapter;
@Inject
@ -41,18 +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));
}
if (savedInstanceState != null) {
presenter.setRestoredPosition(savedInstanceState.getInt(CURRENT_ITEM_KEY));
}
return view;
}
@ -93,14 +88,6 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra
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();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(CURRENT_ITEM_KEY, viewPager.getCurrentItem());
@ -109,7 +96,7 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra
@Override
public void onDestroyView() {
presenter.onDestroy();
presenter.detachView();
super.onDestroyView();
}
}

View File

@ -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();
}

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.ui.main.attendance;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
@ -31,8 +33,8 @@ public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
}
@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()) {
@ -64,7 +66,7 @@ public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
}
@Override
public void onDoInBackgroundLoading() throws Exception {
public void onDoInBackgroundLoading() {
for (String date : dates) {
getView().setTabDataToAdapter(date);
}
@ -92,14 +94,13 @@ public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
}
@Override
public void onDestroy() {
public void detachView() {
isFirstSight = false;
if (loadingTask != null) {
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
super.detachView();
}
}

View File

@ -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.content.res.TypedArray;
@ -14,7 +14,6 @@ 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;
@ -23,13 +22,14 @@ 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<AttendanceHeaderItem.HeaderViewHolder, AttendanceSubItem> {
public class AttendanceHeader
extends AbstractExpandableHeaderItem<AttendanceHeader.HeaderViewHolder, AttendanceSubItem> {
private Day day;
AttendanceHeaderItem(Day day) {
AttendanceHeader(Day day) {
this.day = day;
}
@ -39,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)
@ -86,15 +86,6 @@ 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;
@BindColor(android.R.color.black)
int black;
private Context context;
HeaderViewHolder(View view, FlexibleAdapter adapter) {
@ -117,16 +108,15 @@ public class AttendanceHeaderItem
setInactiveHeader(item.getAttendanceLessons().isEmpty());
}
private void setInactiveHeader(boolean inactive) {
((FrameLayout) getContentView()).setForeground(inactive ? null : getSelectableDrawable());
dayName.setTextColor(inactive ? secondaryColor : black);
dayName.setTextColor(CommonUtils.getThemeAttrColor(context,
inactive ? android.R.attr.textColorSecondary : android.R.attr.textColorPrimary));
if (inactive) {
getContentView().setBackgroundColor(backgroundFreeDay);
getContentView().setBackgroundColor(CommonUtils.getThemeAttrColor(context, R.attr.colorControlHighlight));
} else {
getContentView().setBackgroundDrawable(context.getResources()
.getDrawable(R.drawable.ic_border));
getContentView().setBackgroundDrawable(context.getResources().getDrawable(R.drawable.ic_border));
}
}

View File

@ -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;
@ -20,13 +20,14 @@ 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<AttendanceSubItem.SubItemViewHolder, AttendanceHeaderItem> {
extends AbstractSectionableItem<AttendanceSubItem.SubItemViewHolder, AttendanceHeader> {
private AttendanceLesson lesson;
AttendanceSubItem(AttendanceHeaderItem header, AttendanceLesson lesson) {
AttendanceSubItem(AttendanceHeader header, AttendanceLesson lesson) {
super(header);
this.lesson = lesson;
}

View File

@ -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<AttendanceHeaderItem> headerItems);
void updateAdapterList(List<AttendanceHeader> headerItems);
void onRefreshSuccess();

View File

@ -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,
@ -43,7 +40,7 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab
AttendanceTabContract.Presenter presenter;
@Inject
FlexibleAdapter<AttendanceHeaderItem> adapter;
FlexibleAdapter<AttendanceHeader> adapter;
private boolean isFragmentVisible = false;
@ -61,38 +58,32 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab
@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);
presenter.onFragmentActivated(isFragmentVisible);
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<AttendanceHeaderItem> headerItems) {
public void updateAdapterList(List<AttendanceHeader> headerItems) {
adapter.updateDataSet(headerItems);
}
@ -112,7 +103,7 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab
@Override
public void onRefreshSuccess() {
onError(R.string.sync_completed);
showMessage(R.string.sync_completed);
}
@Override
@ -130,17 +121,9 @@ public class AttendanceTabFragment extends BaseFragment implements AttendanceTab
noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@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 onDestroyView() {
presenter.onDestroy();
presenter.detachView();
super.onDestroyView();
}
}

View File

@ -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<AttendanceHeader> provideAdapter() {
return new FlexibleAdapter<>(null);
}
}

Some files were not shown because too many files have changed in this diff Show More