Compare commits

...

10 Commits
0.1.1 ... 0.2.0

Author SHA1 Message Date
83b4f359bc Version 0.2.0 2018-03-11 20:01:45 +01:00
a094d4f0d9 Fix crash in grades on some devices (#66) 2018-03-11 19:31:56 +01:00
3aca34340d Optimize session handling (#63)
* [APP] Change way the Vulcan is configured (#65)
2018-03-11 19:16:20 +01:00
a0313827ce [DB] Add database migrations (#64) 2018-03-11 18:18:33 +01:00
3799fa910b Add attendance (#58) 2018-03-10 15:55:34 +01:00
828e76821b Replace versioneye with snyk (#62) 2018-03-10 14:04:00 +01:00
e050982de2 Add application id suffix in debug builds (#61) 2018-03-10 13:08:01 +01:00
69fc4bf874 Add cleaning user data on upgrade and downgrade (#60) 2018-03-06 20:14:54 +01:00
c3803b1c96 Add a view of free days to the timetable (#57)
* Fix Timetable freeze when app was killed
* Add views for free days
2018-03-06 00:57:06 +01:00
e274949257 Add remove user id in db upgrade (#59) 2018-03-06 00:25:12 +01:00
112 changed files with 3097 additions and 823 deletions

View File

@ -1,12 +1,12 @@
# Wulkanowy
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Dependency Status](https://www.versioneye.com/user/projects/5969ff0b0fb24f004f8c711b/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/5969ff0b0fb24f004f8c711b)
[![CircleCI](https://img.shields.io/circleci/project/github/wulkanowy/wulkanowy.svg?style=flat-square)](https://circleci.com/gh/wulkanowy/wulkanowy)
[![CircleCI](https://img.shields.io/circleci/project/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://circleci.com/gh/wulkanowy/wulkanowy)
[![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/)
[![Known Vulnerabilities](https://snyk.io/test/github/wulkanowy/wulkanowy/badge.svg?targetFile=app%2Fbuild.gradle&style=flat-square)](https://snyk.io/test/github/wulkanowy/wulkanowy?targetFile=app%2Fbuild.gradle)
[![Bintray](https://img.shields.io/bintray/v/wulkanowy/wulkanowy/api.svg?style=flat-square)](https://bintray.com/wulkanowy/wulkanowy/api)
[Pobierz wersję rozwojową](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/app-release-bitrise-signed.apk)
[Pobierz wersję rozwojową](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/app-debug-bitrise-signed.apk)
Wulkanowy to projekt aplikacji na androida ułatwiającej używanie dziennika VULCANa.
Wulkanowy to aplikacja na androida polepszająca wygodę używania dziennika UONET+.

View File

@ -5,44 +5,102 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.github.wulkanowy.api.login.Login;
public class Client {
private String protocol;
private String protocol = "https";
private String host;
private String host = "vulcan.net.pl";
private String symbol;
private String email;
private String password;
private String symbol = "Default";
private Login login;
private Date lastSuccessRequest = new Date();
private Cookies cookies = new Cookies();
Client(String protocol, String host, String symbol) {
this.protocol = protocol;
this.host = host;
Client(String email, String password, String symbol) {
this.email = email;
this.password = password;
this.symbol = symbol;
setFullEndpointInfo(email);
}
String getHost() {
return host;
private void setFullEndpointInfo(String info) {
String[] creds = info.split("\\\\");
email = info;
if (creds.length > 2) {
String[] url = creds[0].split("://");
protocol = url[0];
host = url[1];
email = creds[2];
}
}
private void login() throws IOException, VulcanException {
if (isLoggedIn()) {
return;
}
this.symbol = getLogin().login(email, password, symbol);
}
private boolean isLoggedIn() {
return getCookies().size() > 0 &&
29 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime());
}
Login getLogin() {
if (null != login) {
return login;
}
login = new Login(this);
return login;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
Map<String, String> getCookies() {
private Map<String, String> getCookies() {
return cookies.getItems();
}
private String getFilledUrl(String url) {
String getHost() {
return host;
}
String getFilledUrl(String url) {
return url
.replace("{schema}", protocol)
.replace("{host}", host.replace(":", "%253A"))
.replace("{symbol}", symbol == null ? "Default" : symbol);
.replace("{symbol}", symbol);
}
Document getPageByUrl(String url) throws IOException {
Document getPageByUrl(String url) throws IOException, VulcanException {
login();
Connection.Response response = Jsoup.connect(getFilledUrl(url))
.followRedirects(true)
.cookies(getCookies())
@ -50,10 +108,10 @@ public class Client {
this.cookies.addItems(response.cookies());
return response.parse();
return checkForErrors(response.parse());
}
public Document postPageByUrl(String url, String[][] params) throws IOException {
public Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException {
Connection connection = Jsoup.connect(getFilledUrl(url));
for (String[] data : params) {
@ -68,10 +126,12 @@ public class Client {
this.cookies.addItems(response.cookies());
return response.parse();
return checkForErrors(response.parse());
}
public String getJsonStringByUrl(String url) throws IOException {
public String getJsonStringByUrl(String url) throws IOException, VulcanException {
login();
Connection.Response response = Jsoup.connect(getFilledUrl(url))
.followRedirects(true)
.ignoreContentType(true)
@ -83,7 +143,9 @@ public class Client {
return response.body();
}
public String postJsonStringByUrl(String url, String[][] params) throws IOException {
public String postJsonStringByUrl(String url, String[][] params) throws IOException, VulcanException {
login();
Connection connection = Jsoup.connect(getFilledUrl(url));
for (String[] data : params) {
@ -101,4 +163,18 @@ public class Client {
return response.body();
}
Document checkForErrors(Document doc) throws VulcanException {
if ("Przerwa techniczna".equals(doc.select("title").text())) {
throw new VulcanOfflineException();
}
if ("Zaloguj się".equals(doc.select(".loginButton").text())) {
throw new NotLoggedInErrorException();
}
lastSuccessRequest = new Date();
return doc;
}
}

View File

@ -3,21 +3,15 @@ package io.github.wulkanowy.api;
import java.util.HashMap;
import java.util.Map;
public class Cookies {
class Cookies {
private Map<String, String> jar = new HashMap<>();
public Map<String, String> getItems() {
Map<String, String> getItems() {
return jar;
}
public Cookies setItems(Map<String, String> items) {
this.jar = items;
return this;
}
public Cookies addItems(Map<String, String> items) {
this.jar.putAll(items);
return this;
void addItems(Map<String, String> items) {
jar.putAll(items);
}
}

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.api;
public class NotLoggedInErrorException extends VulcanException {
}

View File

@ -6,19 +6,17 @@ import org.jsoup.nodes.Element;
import java.io.IOException;
import java.util.List;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public interface SnP {
String getId();
void storeContextCookies() throws IOException, NotLoggedInErrorException;
StudentAndParent storeContextCookies() throws IOException, VulcanException;
String getRowDataChildValue(Element e, int index);
Document getSnPPageDocument(String url) throws IOException;
Document getSnPPageDocument(String url) throws IOException, VulcanException;
List<Semester> getSemesters() throws IOException;
List<Semester> getSemesters() throws IOException, VulcanException;
List<Semester> getSemesters(Document gradesPage);

View File

@ -8,8 +8,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public class StudentAndParent implements SnP {
private static final String START_PAGE_URL = "{schema}://uonetplus.{host}/{symbol}/Start.mvc/Index";
@ -22,12 +20,8 @@ public class StudentAndParent implements SnP {
private String id;
StudentAndParent(Client client) {
this.client = client;
}
StudentAndParent(Client client, String id) {
this(client);
this.client = client;
this.id = id;
}
@ -39,11 +33,12 @@ public class StudentAndParent implements SnP {
return id;
}
public void storeContextCookies() throws IOException, NotLoggedInErrorException {
public StudentAndParent storeContextCookies() throws IOException, VulcanException {
client.getPageByUrl(getSnpHomePageUrl());
return this;
}
String getSnpHomePageUrl() throws IOException, NotLoggedInErrorException {
String getSnpHomePageUrl() throws IOException, VulcanException {
if (null != getId()) {
return getBaseUrl();
}
@ -77,11 +72,11 @@ public class StudentAndParent implements SnP {
return e.select(".daneWiersz .wartosc").get(index - 1).text();
}
public Document getSnPPageDocument(String url) throws IOException {
public Document getSnPPageDocument(String url) throws IOException, VulcanException {
return client.getPageByUrl(getBaseUrl() + url);
}
public List<Semester> getSemesters() throws IOException {
public List<Semester> getSemesters() throws IOException, VulcanException {
return getSemesters(getSnPPageDocument(GRADES_PAGE_URL));
}

View File

@ -7,12 +7,6 @@ import io.github.wulkanowy.api.attendance.AttendanceTable;
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.login.AccountPermissionException;
import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.Login;
import io.github.wulkanowy.api.login.LoginErrorException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.api.messages.Messages;
import io.github.wulkanowy.api.notes.AchievementsList;
import io.github.wulkanowy.api.notes.NotesList;
@ -26,171 +20,92 @@ public class Vulcan {
private String id;
private String symbol;
private SnP snp;
private String protocolSchema = "https";
private String logHost = "vulcan.net.pl";
private String email;
private Client client;
private Login login;
public void setClient(Client client) {
this.client = client;
}
public void setLogin(Login login) {
this.login = login;
}
public void login(String email, String password, String symbol)
throws BadCredentialsException, AccountPermissionException,
LoginErrorException, IOException, VulcanOfflineException {
setFullEndpointInfo(email);
login = getLogin();
this.symbol = login.login(this.email, password, symbol);
}
public Vulcan login(String email, String password, String symbol, String id)
throws BadCredentialsException, AccountPermissionException,
LoginErrorException, IOException, VulcanOfflineException {
login(email, password, symbol);
public void setCredentials(String email, String password, String symbol, String id) {
client = new Client(email, password, symbol);
this.id = id;
return this;
}
String getProtocolSchema() {
return protocolSchema;
}
String getLogHost() {
return logHost;
}
public String getEmail() {
return email;
}
public String getSymbol() {
return symbol;
}
private void setFullEndpointInfo(String email) {
String[] creds = email.split("\\\\");
this.email = email;
if (creds.length >= 2) {
String[] url = creds[0].split("://");
this.protocolSchema = url[0];
this.logHost = url[1];
this.email = creds[2];
public Client getClient() throws NotLoggedInErrorException {
if (null == client) {
throw new NotLoggedInErrorException();
}
}
protected Client getClient() {
if (null != client) {
return client;
}
client = new Client(getProtocolSchema(), getLogHost(), symbol);
return client;
}
protected Login getLogin() {
if (null != login) {
return login;
}
public String getSymbol() throws NotLoggedInErrorException {
return getClient().getSymbol();
login = new Login(getClient());
return login;
}
public SnP getStudentAndParent() throws IOException, NotLoggedInErrorException {
if (0 == getClient().getCookies().size()) {
throw new NotLoggedInErrorException();
public SnP getStudentAndParent() throws IOException, VulcanException {
if (null != this.snp) {
return this.snp;
}
if (null != snp) {
return snp;
}
this.snp = new StudentAndParent(getClient(), id).storeContextCookies();
snp = createSnp(getClient(), id);
snp.storeContextCookies();
return snp;
return this.snp;
}
SnP createSnp(Client client, String id) {
if (null == id) {
return new StudentAndParent(client);
}
return new StudentAndParent(client, id);
public String getId() throws IOException, VulcanException {
return getStudentAndParent().getId();
}
public AttendanceStatistics getAttendanceStatistics() throws IOException, NotLoggedInErrorException {
return new AttendanceStatistics(getStudentAndParent());
}
public AttendanceTable getAttendanceTable() throws IOException, NotLoggedInErrorException {
public AttendanceTable getAttendanceTable() throws IOException, VulcanException {
return new AttendanceTable(getStudentAndParent());
}
public ExamsWeek getExamsList() throws IOException, NotLoggedInErrorException {
public AttendanceStatistics getAttendanceStatistics() throws IOException, VulcanException {
return new AttendanceStatistics(getStudentAndParent());
}
public ExamsWeek getExamsList() throws IOException, VulcanException {
return new ExamsWeek(getStudentAndParent());
}
public GradesList getGradesList() throws IOException, NotLoggedInErrorException {
public GradesList getGradesList() throws IOException, VulcanException {
return new GradesList(getStudentAndParent());
}
public SubjectsList getSubjectsList() throws IOException, NotLoggedInErrorException {
public SubjectsList getSubjectsList() throws IOException, VulcanException {
return new SubjectsList(getStudentAndParent());
}
public AchievementsList getAchievementsList() throws IOException, NotLoggedInErrorException {
public AchievementsList getAchievementsList() throws IOException, VulcanException {
return new AchievementsList(getStudentAndParent());
}
public NotesList getNotesList() throws IOException, NotLoggedInErrorException {
public NotesList getNotesList() throws IOException, VulcanException {
return new NotesList(getStudentAndParent());
}
public SchoolInfo getSchoolInfo() throws IOException, NotLoggedInErrorException {
public SchoolInfo getSchoolInfo() throws IOException, VulcanException {
return new SchoolInfo(getStudentAndParent());
}
public TeachersInfo getTeachersInfo() throws IOException, NotLoggedInErrorException {
public TeachersInfo getTeachersInfo() throws IOException, VulcanException {
return new TeachersInfo(getStudentAndParent());
}
public Timetable getTimetable() throws IOException, NotLoggedInErrorException {
public Timetable getTimetable() throws IOException, VulcanException {
return new Timetable(getStudentAndParent());
}
public BasicInformation getBasicInformation() throws IOException, NotLoggedInErrorException {
public BasicInformation getBasicInformation() throws IOException, VulcanException {
return new BasicInformation(getStudentAndParent());
}
public FamilyInformation getFamilyInformation() throws IOException, NotLoggedInErrorException {
public FamilyInformation getFamilyInformation() throws IOException, VulcanException {
return new FamilyInformation(getStudentAndParent());
}
public Messages getMessages() {
public Messages getMessages() throws VulcanException {
return new Messages(getClient());
}
}

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.api;
public abstract class VulcanException extends Exception {
}

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.api;
public class VulcanOfflineException extends VulcanException {
}

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.api.generic.Month;
import io.github.wulkanowy.api.generic.Subject;
@ -22,15 +23,15 @@ public class AttendanceStatistics {
this.snp = snp;
}
public Types getTypesTable() throws IOException {
public Types getTypesTable() throws IOException, VulcanException {
return getTypesTable("");
}
public Types getTypesTable(String tick) throws IOException {
public Types getTypesTable(String tick) throws IOException, VulcanException {
return getTypesTable(tick, -1);
}
public List<Subject> getSubjectList() throws IOException {
public List<Subject> getSubjectList() throws IOException, VulcanException {
Element mainContainer = snp.getSnPPageDocument(attendancePageUrl)
.select(".mainContainer #idPrzedmiot").first();
@ -46,7 +47,7 @@ public class AttendanceStatistics {
return subjectList;
}
public Types getTypesTable(String tick, Integer subjectId) throws IOException {
public Types getTypesTable(String tick, Integer subjectId) throws IOException, VulcanException {
Element mainContainer = snp.getSnPPageDocument((attendancePageUrl
+ "?data={tick}&idPrzedmiot={subject}")
.replace("{tick}", tick)

View File

@ -4,37 +4,52 @@ 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.Day;
import io.github.wulkanowy.api.generic.Lesson;
import io.github.wulkanowy.api.generic.Week;
public class AttendanceTable {
private SnP snp;
private final static String ATTENDANCE_PAGE_URL = "Frekwencja.mvc?data=";
private String attendancePageUrl = "Frekwencja.mvc?data=";
private SnP snp;
public AttendanceTable(SnP snp) {
this.snp = snp;
}
public Week<Day> getWeekTable() throws IOException {
public Week<Day> getWeekTable() throws IOException, ParseException, VulcanException {
return getWeekTable("");
}
public Week<Day> getWeekTable(String tick) throws IOException {
Element table = snp.getSnPPageDocument(attendancePageUrl + tick)
public Week<Day> getWeekTable(String tick) throws IOException, ParseException, VulcanException {
Element table = snp.getSnPPageDocument(ATTENDANCE_PAGE_URL + tick)
.select(".mainContainer .presentData").first();
Elements headerCells = table.select("thead th");
List<Day> days = new ArrayList<>();
for (int i = 1; i < headerCells.size(); i++) {
days.add(new Day().setDate(headerCells.get(i).html().split("<br>")[1]));
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);
}
Elements hoursInDays = table.select("tbody tr");
@ -46,26 +61,29 @@ public class AttendanceTable {
// fill hours in day
int size = hours.size();
for (int i = 1; i < size; i++) {
days.get(i - 1).setLesson(getNewLesson(hours.get(i)));
Lesson lesson = new Lesson();
lesson.setDate(days.get(i - 1).getDate());
lesson.setNumber(hours.get(0).text());
addLessonDetails(lesson, hours.get(i));
days.get(i - 1).setLesson(lesson);
}
}
String[] dayDescription = headerCells.get(1).html().split("<br>");
return new Week<Day>()
.setStartDayDate(dayDescription[1])
.setStartDayDate(days.get(0).getDate())
.setDays(days);
}
private Lesson getNewLesson(Element cell) {
Lesson lesson = new Lesson();
private void addLessonDetails(Lesson lesson, Element cell) {
lesson.setSubject(cell.select("span").text());
if (LessonTypes.CLASS_NOT_EXIST.equals(cell.attr("class"))) {
lesson.setNotExist(true);
lesson.setEmpty(true);
return lesson;
return;
}
switch (cell.select("div").attr("class")) {
@ -95,7 +113,5 @@ public class AttendanceTable {
lesson.setEmpty(true);
break;
}
return lesson;
}
}

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.api.generic.Week;
public class ExamsWeek {
@ -21,11 +22,11 @@ public class ExamsWeek {
this.snp = snp;
}
public Week<ExamDay> getCurrent() throws IOException {
public Week<ExamDay> getCurrent() throws IOException, VulcanException {
return getWeek("", true);
}
public Week<ExamDay> getWeek(String tick, final boolean onlyNotEmpty) throws IOException {
public Week<ExamDay> getWeek(String tick, final boolean onlyNotEmpty) throws IOException, VulcanException {
Document examsPage = snp.getSnPPageDocument(EXAMS_PAGE_URL + tick);
Elements examsDays = examsPage.select(".mainContainer > div:not(.navigation)");

View File

@ -16,6 +16,7 @@ import java.util.regex.Pattern;
import io.github.wulkanowy.api.Semester;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class GradesList {
@ -33,11 +34,11 @@ public class GradesList {
return GRADES_PAGE_URL;
}
public List<Grade> getAll() throws IOException, ParseException {
public List<Grade> getAll() throws IOException, ParseException, VulcanException {
return getAll("");
}
public List<Grade> getAll(String semester) throws IOException, ParseException {
public List<Grade> getAll(String semester) throws IOException, ParseException, VulcanException {
Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester);
Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr");
Semester currentSemester = snp.getCurrentSemester(snp.getSemesters(gradesPage));

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class SubjectsList {
@ -20,7 +21,7 @@ public class SubjectsList {
this.snp = snp;
}
public List<Subject> getAll() throws IOException {
public List<Subject> getAll() throws IOException, VulcanException {
Document subjectPage = snp.getSnPPageDocument(SUBJECTS_PAGE_URL);
Elements rows = subjectPage.select(".ocenyZwykle-table > tbody > tr");

View File

@ -1,4 +1,6 @@
package io.github.wulkanowy.api.login;
public class AccountPermissionException extends Exception {
import io.github.wulkanowy.api.VulcanException;
public class AccountPermissionException extends VulcanException {
}

View File

@ -1,4 +1,6 @@
package io.github.wulkanowy.api.login;
public class BadCredentialsException extends Exception {
import io.github.wulkanowy.api.VulcanException;
public class BadCredentialsException extends VulcanException {
}

View File

@ -8,6 +8,7 @@ import org.jsoup.select.Elements;
import java.io.IOException;
import io.github.wulkanowy.api.Client;
import io.github.wulkanowy.api.VulcanException;
public class Login {
@ -27,16 +28,13 @@ public class Login {
this.client = client;
}
public String login(String email, String password, String symbol)
throws BadCredentialsException, LoginErrorException,
AccountPermissionException, IOException, VulcanOfflineException {
public String login(String email, String password, String symbol) throws VulcanException, IOException {
String certificate = sendCredentials(email, password, symbol);
return sendCertificate(certificate, symbol);
}
String sendCredentials(String email, String password, String symbol)
throws IOException, BadCredentialsException {
String sendCredentials(String email, String password, String symbol) throws IOException, VulcanException {
this.symbol = symbol;
Document html = client.postPageByUrl(LOGIN_PAGE_URL, new String[][]{
@ -51,25 +49,20 @@ public class Login {
return html.select("input[name=wresult]").attr("value");
}
String sendCertificate(String certificate, String defaultSymbol)
throws IOException, LoginErrorException, AccountPermissionException, VulcanOfflineException {
String sendCertificate(String certificate, String defaultSymbol) throws IOException, VulcanException {
this.symbol = findSymbol(defaultSymbol, certificate);
client.setSymbol(this.symbol);
Document html = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{
String title = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{
{"wa", "wsignin1.0"},
{"wresult", certificate}
});
}).select("title").text();
if (html.getElementsByTag("title").text().equals("Logowanie")) {
if ("Logowanie".equals(title)) {
throw new AccountPermissionException();
}
if (html.getElementsByTag("title").text().equals("Przerwa techniczna")) {
throw new VulcanOfflineException();
}
if (!html.select("title").text().equals("Uonet+")) {
if (!"Uonet+".equals(title)) {
throw new LoginErrorException();
}
@ -85,7 +78,8 @@ public class Login {
}
String findSymbolInCertificate(String certificate) {
Elements els = Jsoup.parse(certificate.replaceAll(":", ""), "", Parser.xmlParser())
Elements els = Jsoup
.parse(certificate.replaceAll(":", ""), "", Parser.xmlParser())
.select("[AttributeName=\"UserInstance\"] samlAttributeValue");
if (els.isEmpty()) {
@ -94,4 +88,4 @@ public class Login {
return els.get(1).text();
}
}
}

View File

@ -1,4 +1,6 @@
package io.github.wulkanowy.api.login;
import io.github.wulkanowy.api.NotLoggedInErrorException;
public class LoginErrorException extends NotLoggedInErrorException {
}

View File

@ -1,4 +0,0 @@
package io.github.wulkanowy.api.login;
public class NotLoggedInErrorException extends Exception {
}

View File

@ -1,4 +0,0 @@
package io.github.wulkanowy.api.login;
public class VulcanOfflineException extends Exception {
}

View File

@ -1,4 +1,6 @@
package io.github.wulkanowy.api.messages;
class BadRequestException extends Exception {
import io.github.wulkanowy.api.VulcanException;
class BadRequestException extends VulcanException {
}

View File

@ -7,7 +7,8 @@ import java.io.IOException;
import java.util.List;
import io.github.wulkanowy.api.Client;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
public class Messages {
@ -37,19 +38,19 @@ public class Messages {
this.client = client;
}
public List<Message> getReceived() throws IOException, NotLoggedInErrorException, BadRequestException {
public List<Message> getReceived() throws IOException, VulcanException {
return getMessages(RECEIVED_URL);
}
public List<Message> getSent() throws IOException, NotLoggedInErrorException, BadRequestException {
public List<Message> getSent() throws IOException, VulcanException {
return getMessages(SENT_URL);
}
public List<Message> getDeleted() throws IOException, NotLoggedInErrorException, BadRequestException {
public List<Message> getDeleted() throws IOException, VulcanException {
return getMessages(DELETED_URL);
}
private List<Message> getMessages(String url) throws IOException, NotLoggedInErrorException, BadRequestException {
private List<Message> getMessages(String url) throws IOException, VulcanException {
String res = client.getJsonStringByUrl(url);
List<Message> messages;
@ -67,7 +68,7 @@ public class Messages {
return messages;
}
public Message getMessage(int id, int folder) throws IOException, BadRequestException, NotLoggedInErrorException {
public Message getMessage(int id, int folder) throws IOException, VulcanException {
String res = client.postJsonStringByUrl(MESSAGE_URL, new String[][]{
{"idWiadomosc", String.valueOf(id)},
{"Folder", String.valueOf(folder)}

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class AchievementsList {
@ -21,7 +22,7 @@ public class AchievementsList {
this.snp = snp;
}
public List<String> getAllAchievements() throws IOException {
public List<String> getAllAchievements() throws IOException, VulcanException {
Element pageFragment = snp.getSnPPageDocument(NOTES_PAGE_URL)
.select(".mainContainer > div").get(1);
Elements items = pageFragment.select("article");

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class NotesList {
@ -21,7 +22,7 @@ public class NotesList {
this.snp = snp;
}
public List<Note> getAllNotes() throws IOException {
public List<Note> getAllNotes() throws IOException, VulcanException {
Element pageFragment = snp.getSnPPageDocument(NOTES_PAGE_URL)
.select(".mainContainer > div").get(0);
Elements items = pageFragment.select("article");

View File

@ -5,6 +5,7 @@ import org.jsoup.nodes.Element;
import java.io.IOException;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class SchoolInfo {
@ -16,7 +17,7 @@ public class SchoolInfo {
this.snp = snp;
}
public SchoolData getSchoolData() throws IOException {
public SchoolData getSchoolData() throws IOException, VulcanException {
Element e = snp.getSnPPageDocument(SCHOOL_PAGE_URL)
.select(".mainContainer > article").get(0);

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class TeachersInfo {
@ -20,7 +21,7 @@ public class TeachersInfo {
this.snp = snp;
}
public TeachersData getTeachersData() throws IOException {
public TeachersData getTeachersData() throws IOException, VulcanException {
Document doc = snp.getSnPPageDocument(SCHOOL_PAGE_URL);
Elements rows = doc.select(".mainContainer > table tbody tr");
String description = doc.select(".mainContainer > p").first().text();

View File

@ -13,6 +13,7 @@ import java.util.List;
import java.util.Locale;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.api.generic.Day;
import io.github.wulkanowy.api.generic.Lesson;
import io.github.wulkanowy.api.generic.Week;
@ -27,11 +28,11 @@ public class Timetable {
this.snp = snp;
}
public Week<Day> getWeekTable() throws IOException, ParseException {
public Week<Day> getWeekTable() throws IOException, ParseException, VulcanException {
return getWeekTable("");
}
public Week<Day> getWeekTable(final String tick) throws IOException, ParseException {
public Week<Day> getWeekTable(final String tick) throws IOException, ParseException, VulcanException {
Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + tick)
.select(".mainContainer .presentData").first();

View File

@ -6,6 +6,7 @@ import org.jsoup.nodes.Element;
import java.io.IOException;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class BasicInformation {
@ -21,7 +22,7 @@ public class BasicInformation {
this.snp = snp;
}
public Document getStudentDataPageDocument() throws IOException {
public Document getStudentDataPageDocument() throws IOException, VulcanException {
if (null == studentDataPageDocument) {
studentDataPageDocument = snp.getSnPPageDocument(STUDENT_DATA_PAGE_URL);
}
@ -29,7 +30,7 @@ public class BasicInformation {
return studentDataPageDocument;
}
public PersonalData getPersonalData() throws IOException {
public PersonalData getPersonalData() throws IOException, VulcanException {
Element e = getStudentDataPageDocument().select(CONTENT_QUERY).get(0);
String name = snp.getRowDataChildValue(e, 1);
@ -48,7 +49,7 @@ public class BasicInformation {
.setParentsNames(snp.getRowDataChildValue(e, 7));
}
public AddressData getAddressData() throws IOException {
public AddressData getAddressData() throws IOException, VulcanException {
Element e = getStudentDataPageDocument().select(CONTENT_QUERY).get(1);
return new AddressData()
@ -58,7 +59,7 @@ public class BasicInformation {
}
public ContactDetails getContactDetails() throws IOException {
public ContactDetails getContactDetails() throws IOException, VulcanException {
Element e = getStudentDataPageDocument().select(CONTENT_QUERY).get(2);
return new ContactDetails()

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.SnP;
import io.github.wulkanowy.api.VulcanException;
public class FamilyInformation {
@ -19,7 +20,7 @@ public class FamilyInformation {
this.snp = snp;
}
public List<FamilyMember> getFamilyMembers() throws IOException {
public List<FamilyMember> getFamilyMembers() throws IOException, VulcanException {
Elements membersElements = snp.getSnPPageDocument(STUDENT_DATA_PAGE_URL)
.select(".mainContainer > article:nth-of-type(n+4)");

View File

@ -0,0 +1,80 @@
package io.github.wulkanowy.api;
import org.hamcrest.CoreMatchers;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Assert;
import org.junit.Test;
import io.github.wulkanowy.api.login.Login;
public class ClientTest {
private String getFixtureAsString(String fixtureFileName) {
return FixtureHelper.getAsString(getClass().getResourceAsStream(fixtureFileName));
}
@Test
public void setFullEndpointInfoTest() throws Exception {
Client client = new Client("http://fakelog.net\\\\admin", "pass", "Default");
Assert.assertEquals("fakelog.net", client.getHost());
Assert.assertEquals("Default", client.getSymbol());
}
@Test
public void checkForNoErrorsTest() throws Exception {
Client client = new Client("", "", "");
Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-success.html"));
Assert.assertEquals(doc, client.checkForErrors(doc));
}
@Test(expected = VulcanOfflineException.class)
public void checkForErrorsOffline() throws Exception {
Client client = new Client("", "", "");
Document doc = Jsoup.parse(getFixtureAsString("login/PrzerwaTechniczna.html"));
client.checkForErrors(doc);
}
@Test(expected = NotLoggedInErrorException.class)
public void checkForErrors() throws Exception {
Client client = new Client("", "", "");
Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-notLoggedIn.html"));
client.checkForErrors(doc);
}
@Test
public void getClientTest() throws Exception {
Client client = new Client("", "", "");
Assert.assertThat(client.getLogin(), CoreMatchers.instanceOf(Login.class));
}
@Test
public void getClientTwiceTest() throws Exception {
Client client = new Client("", "", "");
Assert.assertEquals(client.getLogin(), client.getLogin());
}
@Test
public void getFilledUrlTest() throws Exception {
Client client = new Client("http://fakelog.cf\\\\admin", "", "symbol123");
Assert.assertEquals("http://uonetplus.fakelog.cf/symbol123/LoginEndpoint.aspx",
client.getFilledUrl("{schema}://uonetplus.{host}/{symbol}/LoginEndpoint.aspx"));
}
@Test
public void getSymbolTest() throws Exception {
Client client = new Client("", "", "symbol4321");
Assert.assertEquals("symbol4321", client.getSymbol());
}
}

View File

@ -10,8 +10,6 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public class StudentAndParentTest {
private Client client;
@ -45,7 +43,7 @@ public class StudentAndParentTest {
Mockito.when(client.getHost()).thenReturn("vulcan.net.pl");
Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(startPageDocument);
StudentAndParent snp = new StudentAndParent(client);
StudentAndParent snp = new StudentAndParent(client, null);
Assert.assertEquals("https://uonetplus-opiekun.vulcan.net.pl/symbol/534213/Start/Index/",
snp.getSnpHomePageUrl());
@ -58,7 +56,7 @@ public class StudentAndParentTest {
);
Mockito.when(client.getPageByUrl(Mockito.anyString())).thenReturn(wrongPageDocument);
StudentAndParent snp = new StudentAndParent(client);
StudentAndParent snp = new StudentAndParent(client, null);
snp.getSnpHomePageUrl();
}

View File

@ -1,86 +1,31 @@
package io.github.wulkanowy.api;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.HashMap;
import java.util.Map;
import io.github.wulkanowy.api.login.Login;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public class VulcanTest {
private Vulcan vulcan;
@Test(expected = NotLoggedInErrorException.class)
public void getClientWithoutLoginTest() throws Exception {
Vulcan vulcan = new Vulcan();
@Before
public void setUp() throws Exception {
vulcan = new Vulcan();
vulcan.setClient(Mockito.mock(Client.class));
vulcan.setLogin(Mockito.mock(Login.class));
vulcan.getClient();
}
@Test
public void setFullEndpointInfoTest() throws Exception {
vulcan.login("http://fakelog.net\\\\admin", "pass", "Default", "123");
public void getClientTest() throws Exception {
Vulcan vulcan = new Vulcan();
vulcan.setCredentials("email", "password", "symbol", null);
Assert.assertEquals("http", vulcan.getProtocolSchema());
Assert.assertEquals("fakelog.net", vulcan.getLogHost());
Assert.assertEquals("admin", vulcan.getEmail());
Assert.assertThat(vulcan.getClient(), CoreMatchers.instanceOf(Client.class));
}
@Test
public void getClientTwiceTest() throws Exception {
Vulcan vulcan = new Vulcan();
Assert.assertTrue(vulcan.getClient().equals(vulcan.getClient()));
}
vulcan.setCredentials("email", "password", "symbol", null);
@Test
public void getLoginTwiceTest() throws Exception {
Vulcan vulcan = new Vulcan();
Assert.assertTrue(vulcan.getLogin().equals(vulcan.getLogin()));
}
@Test(expected = NotLoggedInErrorException.class)
public void getStudentAndParentNotLoggedInTest() throws Exception {
vulcan.getStudentAndParent();
}
@Test
public void getStudentAndParentTwiceTest() throws Exception {
Client client = Mockito.mock(Client.class);
Map<String, String> cookies = new HashMap<>();
cookies.put("test", "test");
Mockito.when(client.getCookies()).thenReturn(cookies);
SnP snp = Mockito.mock(StudentAndParent.class);
Mockito.doNothing().when(snp).storeContextCookies();
Vulcan vulcan = Mockito.mock(Vulcan.class);
Mockito.when(vulcan.getClient()).thenReturn(client);
Mockito.when(vulcan.getStudentAndParent()).thenCallRealMethod();
Mockito.when(vulcan.createSnp(Mockito.any(Client.class), Mockito.any())).thenReturn(snp);
vulcan.getStudentAndParent();
vulcan.getStudentAndParent();
}
@Test
public void createSnPTest() throws Exception {
vulcan.login("wulkanowy@wulkanowy.io", "wulkanowy123", "wulkan");
SnP snp1 = vulcan.createSnp(Mockito.mock(Client.class), null);
Assert.assertEquals(null, snp1.getId());
SnP snp2 = vulcan.createSnp(Mockito.mock(Client.class), "wulkan");
Assert.assertEquals("wulkan", snp2.getId());
}
@Test(expected = NotLoggedInErrorException.class)
public void getAttendanceExceptionText() throws Exception {
vulcan.getAttendanceTable();
Assert.assertEquals(vulcan.getClient(), vulcan.getClient());
}
}

View File

@ -20,8 +20,8 @@ public class AttendanceTableTest extends StudentAndParentTestCase {
@Test
public void getWeekStartByDate() throws Exception {
Assert.assertEquals("31.08.2015", excellent.getWeekTable().getStartDayDate());
Assert.assertEquals("05.09.2016", full.getWeekTable().getStartDayDate());
Assert.assertEquals("2015-08-31", excellent.getWeekTable().getStartDayDate());
Assert.assertEquals("2016-09-05", full.getWeekTable().getStartDayDate());
}
@Test
@ -38,13 +38,13 @@ public class AttendanceTableTest extends StudentAndParentTestCase {
@Test
public void getDayDate() throws Exception {
Assert.assertEquals("31.08.2015", excellent.getWeekTable().getDay(0).getDate());
Assert.assertEquals("02.09.2015", excellent.getWeekTable().getDay(2).getDate());
Assert.assertEquals("04.09.2015", excellent.getWeekTable().getDay(4).getDate());
Assert.assertEquals("2015-08-31", excellent.getWeekTable().getDay(0).getDate());
Assert.assertEquals("2015-09-02", excellent.getWeekTable().getDay(2).getDate());
Assert.assertEquals("2015-09-04", excellent.getWeekTable().getDay(4).getDate());
Assert.assertEquals("05.09.2016", full.getWeekTable().getDay(0).getDate());
Assert.assertEquals("07.09.2016", full.getWeekTable().getDay(2).getDate());
Assert.assertEquals("09.09.2016", full.getWeekTable().getDay(4).getDate());
Assert.assertEquals("2016-09-05", full.getWeekTable().getDay(0).getDate());
Assert.assertEquals("2016-09-07", full.getWeekTable().getDay(2).getDate());
Assert.assertEquals("2016-09-09", full.getWeekTable().getDay(4).getDate());
}
@Test

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.api.login;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Assert;
@ -79,13 +78,6 @@ public class LoginTest {
login.sendCertificate(getFixtureAsString("cert.xml"), "demo123");
}
@Test(expected = VulcanOfflineException.class)
public void sendCertificateVulcanOfflineTest() throws Exception {
Login login = new Login(getClient("PrzerwaTechniczna.html"));
login.sendCertificate(getFixtureAsString("cert.xml"), "demo123");
}
@Test
public void findSymbolInCertificateTest() throws Exception {
Login login = new Login(getClient("Logowanie-certyfikat.html"));

View File

@ -8,7 +8,7 @@ import java.util.List;
import io.github.wulkanowy.api.Client;
import io.github.wulkanowy.api.FixtureHelper;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.NotLoggedInErrorException;
public class MessagesTest {

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Dziennik UONET+</title>
</head>
<body>
<div id="MainPage_InfoPage">
<div class="startScreen">
<div class="topBar">
<div class="loginBox">
<div>
<a href="/LoginEndpoint.aspx" class="loginButton"></a>
<a href="/LoginEndpoint.aspx" class="loginButton">Zaloguj się</a>
</div>
</div>
</div>
<div class="bottomBar"><span>Uonet+ wersja 17.09.0007.26300</span></div>
</div>
</div>
</body>
</html>

View File

@ -28,8 +28,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 27
versionCode 2
versionName "0.1.1"
versionCode 3
versionName "0.2.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [
@ -43,6 +43,8 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
testCoverageEnabled = true
ext.enableCrashlytics = false
}
@ -60,7 +62,7 @@ android {
}
greendao {
schemaVersion 20
schemaVersion 22
generateTests = true
}
@ -78,6 +80,7 @@ dependencies {
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1'
implementation 'org.apache.commons:commons-collections4:4.1'
implementation 'org.greenrobot:greendao:3.2.2'
implementation 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.0.2'
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'joda-time:joda-time:2.9.9'
implementation 'com.google.dagger:dagger-android:2.14.1'

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.data.db.dao.entities;
import org.greenrobot.greendao.test.AbstractDaoTestLongPk;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLessonDao;
public class AttendanceLessonTest extends AbstractDaoTestLongPk<AttendanceLessonDao, AttendanceLesson> {
public AttendanceLessonTest() {
super(AttendanceLessonDao.class);
}
@Override
protected AttendanceLesson createEntity(Long key) {
AttendanceLesson entity = new AttendanceLesson();
entity.setId(key);
entity.setIsPresence(false);
entity.setIsAbsenceUnexcused(false);
entity.setIsAbsenceExcused(false);
entity.setIsUnexcusedLateness(false);
entity.setIsAbsenceForSchoolReasons(false);
entity.setIsExcusedLateness(false);
entity.setIsExemption(false);
return entity;
}
}

View File

@ -2,15 +2,18 @@ package io.github.wulkanowy.data.db.dao.entities;
import org.greenrobot.greendao.test.AbstractDaoTestLongPk;
public class LessonTest extends AbstractDaoTestLongPk<LessonDao, Lesson> {
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.TimetableLessonDao;
public LessonTest() {
super(LessonDao.class);
public class TimetableLessonTest extends AbstractDaoTestLongPk<TimetableLessonDao, TimetableLesson> {
public TimetableLessonTest() {
super(TimetableLessonDao.class);
}
@Override
protected Lesson createEntity(Long key) {
Lesson entity = new Lesson();
protected TimetableLesson createEntity(Long key) {
TimetableLesson entity = new TimetableLesson();
entity.setId(key);
entity.setIsEmpty(false);
entity.setIsDivisionIntoGroups(false);

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Wulkanowy DEV</string>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Wulkanowy DEV</string>
</resources>

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.data;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
@ -8,11 +7,9 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.login.AccountPermissionException;
import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.db.dao.entities.GradeDao;
@ -21,7 +18,8 @@ import io.github.wulkanowy.data.db.dao.entities.WeekDao;
import io.github.wulkanowy.data.db.resources.ResourcesContract;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.data.sync.SyncContract;
import io.github.wulkanowy.data.sync.login.LoginSyncContract;
import io.github.wulkanowy.data.sync.account.AccountSyncContract;
import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract;
import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract;
import io.github.wulkanowy.di.annotations.SyncGrades;
import io.github.wulkanowy.di.annotations.SyncSubjects;
@ -36,7 +34,9 @@ public class Repository implements RepositoryContract {
private final DaoSession daoSession;
private final LoginSyncContract loginSync;
private final AccountSyncContract accountSync;
private final AttendanceSyncContract attendanceSync;
private final TimetableSyncContract timetableSync;
@ -48,14 +48,16 @@ public class Repository implements RepositoryContract {
Repository(SharedPrefContract sharedPref,
ResourcesContract resources,
DaoSession daoSession,
LoginSyncContract loginSync,
AccountSyncContract accountSync,
AttendanceSyncContract attendanceSync,
TimetableSyncContract timetableSync,
@SyncGrades SyncContract gradeSync,
@SyncSubjects SyncContract subjectSync) {
this.sharedPref = sharedPref;
this.resources = resources;
this.daoSession = daoSession;
this.loginSync = loginSync;
this.accountSync = accountSync;
this.attendanceSync = attendanceSync;
this.timetableSync = timetableSync;
this.gradeSync = gradeSync;
this.subjectSync = subjectSync;
@ -82,42 +84,56 @@ public class Repository implements RepositoryContract {
}
@Override
public void loginUser(String email, String password, String symbol)
throws NotLoggedInErrorException, AccountPermissionException, IOException,
CryptoException, VulcanOfflineException, BadCredentialsException {
loginSync.loginUser(email, password, symbol);
public String getAttendanceLessonDescription(AttendanceLesson lesson) {
return resources.getAttendanceLessonDescription(lesson);
}
@Override
public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException,
IOException, CryptoException, VulcanOfflineException, BadCredentialsException {
loginSync.loginCurrentUser();
public void registerUser(String email, String password, String symbol) throws VulcanException,
IOException, CryptoException {
accountSync.registerUser(email, password, symbol);
}
@Override
public void syncGrades() throws NotLoggedInErrorException, IOException, ParseException {
public void initLastUser() throws VulcanException, IOException, CryptoException {
accountSync.initLastUser();
}
@Override
public void syncGrades() throws VulcanException, IOException, ParseException {
gradeSync.sync();
}
@Override
public void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException {
public void syncSubjects() throws VulcanException, IOException, ParseException {
subjectSync.sync();
}
@Override
public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException {
public void syncAttendance() throws ParseException, IOException, VulcanException {
attendanceSync.syncAttendance();
}
@Override
public void syncAttendance(String date) throws ParseException, IOException, VulcanException {
attendanceSync.syncAttendance(date);
}
@Override
public void syncTimetable() throws VulcanException, IOException, ParseException {
timetableSync.syncTimetable();
}
@Override
public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException {
public void syncTimetable(String date) throws VulcanException, IOException, ParseException {
timetableSync.syncTimetable(date);
}
@Override
public void syncAll() throws NotLoggedInErrorException, IOException, ParseException {
public void syncAll() throws VulcanException, IOException, ParseException {
syncSubjects();
syncGrades();
syncAttendance();
syncTimetable();
}

View File

@ -6,25 +6,26 @@ import java.util.List;
import javax.inject.Singleton;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.data.db.resources.ResourcesContract;
import io.github.wulkanowy.data.sync.login.LoginSyncContract;
import io.github.wulkanowy.data.sync.account.AccountSyncContract;
import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract;
import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract;
@Singleton
public interface RepositoryContract extends ResourcesContract, LoginSyncContract,
TimetableSyncContract {
public interface RepositoryContract extends ResourcesContract, AccountSyncContract,
AttendanceSyncContract, TimetableSyncContract {
long getCurrentUserId();
void syncGrades() throws NotLoggedInErrorException, IOException, ParseException;
void syncGrades() throws VulcanException, IOException, ParseException;
void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException;
void syncSubjects() throws VulcanException, IOException, ParseException;
void syncAll() throws NotLoggedInErrorException, IOException, ParseException;
void syncAll() throws VulcanException, IOException, ParseException;
Account getCurrentUser();

View File

@ -1,19 +1,61 @@
package io.github.wulkanowy.data.db.dao;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.github.yuweiguocn.library.greendao.MigrationHelper;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.StandardDatabase;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.BuildConfig;
import io.github.wulkanowy.data.db.dao.entities.AccountDao;
import io.github.wulkanowy.data.db.dao.entities.DaoMaster;
import io.github.wulkanowy.data.db.dao.entities.GradeDao;
import io.github.wulkanowy.data.db.dao.entities.SubjectDao;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.di.annotations.ApplicationContext;
import io.github.wulkanowy.di.annotations.DatabaseInfo;
import io.github.wulkanowy.utils.LogUtils;
@Singleton
public class DbHelper extends DaoMaster.DevOpenHelper {
public class DbHelper extends DaoMaster.OpenHelper {
private SharedPrefContract sharedPref;
@Inject
DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName) {
DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName,
SharedPrefContract sharedPref) {
super(context, dbName);
this.sharedPref = sharedPref;
}
@Override
@SuppressWarnings("unchecked")
public void onUpgrade(Database db, int oldVersion, int newVersion) {
MigrationHelper.DEBUG = BuildConfig.DEBUG;
MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
@Override
public void onCreateAllTables(Database db, boolean ifNotExists) {
DaoMaster.createAllTables(db, ifNotExists);
}
@Override
public void onDropAllTables(Database db, boolean ifExists) {
DaoMaster.dropAllTables(db, ifExists);
}
}, AccountDao.class, SubjectDao.class, GradeDao.class);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Database database = new StandardDatabase(db);
DaoMaster.dropAllTables(database, true);
onCreate(database);
sharedPref.setCurrentUserId(0);
LogUtils.info("Cleaning user data oldVersion=" + oldVersion + " newVersion=" + newVersion);
}
}

View File

@ -0,0 +1,254 @@
package io.github.wulkanowy.data.db.dao.entities;
import org.greenrobot.greendao.DaoException;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Index;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Transient;
import java.io.Serializable;
@Entity(
nameInDb = "AttendanceLessons",
active = true,
indexes = {@Index(value = "dayId,date,number", unique = true)}
)
public class AttendanceLesson implements Serializable {
@Id(autoincrement = true)
private Long id;
@Property(nameInDb = "DAY_ID")
private Long dayId;
@Property(nameInDb = "DATE")
private String date = "";
@Property(nameInDb = "NUMBER_OF_LESSON")
private int number = 0;
@Property(nameInDb = "SUBJECT_NAME")
private String subject = "";
@Property(nameInDb = "IS_PRESENCE")
private boolean isPresence = false;
@Property(nameInDb = "IS_ABSENCE_UNEXCUSED")
private boolean isAbsenceUnexcused = false;
@Property(nameInDb = "IS_ABSENCE_EXCUSED")
private boolean isAbsenceExcused = false;
@Property(nameInDb = "IS_UNEXCUSED_LATENESS")
private boolean isUnexcusedLateness = false;
@Property(nameInDb = "IS_ABSENCE_FOR_SCHOOL_REASONS")
private boolean isAbsenceForSchoolReasons = false;
@Property(nameInDb = "IS_EXCUSED_LATENESS")
private boolean isExcusedLateness = false;
@Property(nameInDb = "IS_EXEMPTION")
private boolean isExemption = false;
@Transient
private String description = "";
private static final long serialVersionUID = 42L;
/**
* Used to resolve relations
*/
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/**
* Used for active entity operations.
*/
@Generated(hash = 1936953859)
private transient AttendanceLessonDao myDao;
@Generated(hash = 1428129046)
public AttendanceLesson(Long id, Long dayId, String date, int number,
String subject, boolean isPresence, boolean isAbsenceUnexcused,
boolean isAbsenceExcused, boolean isUnexcusedLateness,
boolean isAbsenceForSchoolReasons, boolean isExcusedLateness,
boolean isExemption) {
this.id = id;
this.dayId = dayId;
this.date = date;
this.number = number;
this.subject = subject;
this.isPresence = isPresence;
this.isAbsenceUnexcused = isAbsenceUnexcused;
this.isAbsenceExcused = isAbsenceExcused;
this.isUnexcusedLateness = isUnexcusedLateness;
this.isAbsenceForSchoolReasons = isAbsenceForSchoolReasons;
this.isExcusedLateness = isExcusedLateness;
this.isExemption = isExemption;
}
@Generated(hash = 921806575)
public AttendanceLesson() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Long getDayId() {
return this.dayId;
}
public void setDayId(Long dayId) {
this.dayId = dayId;
}
public String getDate() {
return this.date;
}
public AttendanceLesson setDate(String date) {
this.date = date;
return this;
}
public int getNumber() {
return this.number;
}
public AttendanceLesson setNumber(int number) {
this.number = number;
return this;
}
public String getSubject() {
return this.subject;
}
public AttendanceLesson setSubject(String subject) {
this.subject = subject;
return this;
}
public boolean getIsPresence() {
return this.isPresence;
}
public AttendanceLesson setIsPresence(boolean isPresence) {
this.isPresence = isPresence;
return this;
}
public boolean getIsAbsenceUnexcused() {
return this.isAbsenceUnexcused;
}
public AttendanceLesson setIsAbsenceUnexcused(boolean isAbsenceUnexcused) {
this.isAbsenceUnexcused = isAbsenceUnexcused;
return this;
}
public boolean getIsAbsenceExcused() {
return this.isAbsenceExcused;
}
public AttendanceLesson setIsAbsenceExcused(boolean isAbsenceExcused) {
this.isAbsenceExcused = isAbsenceExcused;
return this;
}
public boolean getIsUnexcusedLateness() {
return this.isUnexcusedLateness;
}
public AttendanceLesson setIsUnexcusedLateness(boolean isUnexcusedLateness) {
this.isUnexcusedLateness = isUnexcusedLateness;
return this;
}
public boolean getIsAbsenceForSchoolReasons() {
return this.isAbsenceForSchoolReasons;
}
public AttendanceLesson setIsAbsenceForSchoolReasons(boolean isAbsenceForSchoolReasons) {
this.isAbsenceForSchoolReasons = isAbsenceForSchoolReasons;
return this;
}
public boolean getIsExcusedLateness() {
return this.isExcusedLateness;
}
public AttendanceLesson setIsExcusedLateness(boolean isExcusedLateness) {
this.isExcusedLateness = isExcusedLateness;
return this;
}
public boolean getIsExemption() {
return this.isExemption;
}
public AttendanceLesson setIsExemption(boolean isExemption) {
this.isExemption = isExemption;
return this;
}
public String getDescription() {
return description;
}
public AttendanceLesson setDescription(String description) {
this.description = description;
return this;
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 128553479)
public void delete() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.delete(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 1942392019)
public void refresh() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.refresh(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 713229351)
public void update() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.update(this);
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 1157101112)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getAttendanceLessonDao() : null;
}
}

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;
@ -13,7 +14,7 @@ import java.util.List;
@Entity(
nameInDb = "Days",
active = true,
indexes ={@Index(value = "userId,weekId,date", unique = true)}
indexes = {@Index(value = "userId,weekId,date", unique = true)}
)
public class Day {
@ -39,7 +40,11 @@ public class Day {
private String freeDayName = "";
@ToMany(referencedJoinProperty = "dayId")
private List<Lesson> lessons;
private List<TimetableLesson> timetableLessons;
@ToMany(referencedJoinProperty = "dayId")
@OrderBy("number ASC")
private List<AttendanceLesson> attendanceLessons;
/**
* Used to resolve relations
@ -113,12 +118,12 @@ public class Day {
return this;
}
public boolean isFreeDay() {
return isFreeDay;
public boolean getIsFreeDay() {
return this.isFreeDay;
}
public Day setFreeDay(boolean freeDay) {
isFreeDay = freeDay;
public Day setIsFreeDay(boolean isFreeDay) {
this.isFreeDay = isFreeDay;
return this;
}
@ -131,42 +136,62 @@ public class Day {
return this;
}
public boolean getIsFreeDay() {
return this.isFreeDay;
/**
* 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 = 218588195)
public List<TimetableLesson> getTimetableLessons() {
if (timetableLessons == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
TimetableLessonDao targetDao = daoSession.getTimetableLessonDao();
List<TimetableLesson> timetableLessonsNew = targetDao
._queryDay_TimetableLessons(id);
synchronized (this) {
if (timetableLessons == null) {
timetableLessons = timetableLessonsNew;
}
}
}
return timetableLessons;
}
public void setIsFreeDay(boolean isFreeDay) {
this.isFreeDay = isFreeDay;
/** Resets a to-many relationship, making the next get call to query for a fresh result. */
@Generated(hash = 1687683740)
public synchronized void resetTimetableLessons() {
timetableLessons = null;
}
/**
* 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 = 1552857303)
public List<Lesson> getLessons() {
if (lessons == null) {
@Generated(hash = 1166820581)
public List<AttendanceLesson> getAttendanceLessons() {
if (attendanceLessons == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
LessonDao targetDao = daoSession.getLessonDao();
List<Lesson> lessonsNew = targetDao._queryDay_Lessons(id);
AttendanceLessonDao targetDao = daoSession.getAttendanceLessonDao();
List<AttendanceLesson> attendanceLessonsNew = targetDao
._queryDay_AttendanceLessons(id);
synchronized (this) {
if (lessons == null) {
lessons = lessonsNew;
if (attendanceLessons == null) {
attendanceLessons = attendanceLessonsNew;
}
}
}
return lessons;
return attendanceLessons;
}
/**
* Resets a to-many relationship, making the next get call to query for a fresh result.
*/
@Generated(hash = 1769801440)
public synchronized void resetLessons() {
lessons = null;
/** Resets a to-many relationship, making the next get call to query for a fresh result. */
@Generated(hash = 1343075564)
public synchronized void resetAttendanceLessons() {
attendanceLessons = null;
}
/**

View File

@ -10,11 +10,11 @@ import org.greenrobot.greendao.annotation.Property;
import java.io.Serializable;
@Entity(
nameInDb = "Lessons",
nameInDb = "TimetableLessons",
active = true,
indexes = {@Index(value = "dayId,date,startTime,endTime", unique = true)}
)
public class Lesson implements Serializable {
public class TimetableLesson implements Serializable {
@Id(autoincrement = true)
private Long id;
@ -78,14 +78,15 @@ public class Lesson implements Serializable {
/**
* Used for active entity operations.
*/
@Generated(hash = 610143130)
private transient LessonDao myDao;
@Generated(hash = 1119360138)
private transient TimetableLessonDao myDao;
@Generated(hash = 140778287)
public Lesson(Long id, Long dayId, String number, String subject, String teacher, String room,
String description, String groupName, String startTime, String endTime, String date,
boolean isEmpty, boolean isDivisionIntoGroups, boolean isPlanning, boolean isRealized,
boolean isMovedOrCanceled, boolean isNewMovedInOrChanged) {
@Generated(hash = 627457324)
public TimetableLesson(Long id, Long dayId, String number, String subject,
String teacher, String room, String description, String groupName,
String startTime, String endTime, String date, boolean isEmpty,
boolean isDivisionIntoGroups, boolean isPlanning, boolean isRealized,
boolean isMovedOrCanceled, boolean isNewMovedInOrChanged) {
this.id = id;
this.dayId = dayId;
this.number = number;
@ -105,8 +106,8 @@ public class Lesson implements Serializable {
this.isNewMovedInOrChanged = isNewMovedInOrChanged;
}
@Generated(hash = 1669664117)
public Lesson() {
@Generated(hash = 1878030142)
public TimetableLesson() {
}
public Long getId() {
@ -126,186 +127,138 @@ public class Lesson implements Serializable {
}
public String getNumber() {
return number;
return this.number;
}
public Lesson setNumber(String number) {
public TimetableLesson setNumber(String number) {
this.number = number;
return this;
}
public String getSubject() {
return subject;
return this.subject;
}
public Lesson setSubject(String subject) {
public TimetableLesson setSubject(String subject) {
this.subject = subject;
return this;
}
public String getTeacher() {
return teacher;
return this.teacher;
}
public Lesson setTeacher(String teacher) {
public TimetableLesson setTeacher(String teacher) {
this.teacher = teacher;
return this;
}
public String getRoom() {
return room;
return this.room;
}
public Lesson setRoom(String room) {
public TimetableLesson setRoom(String room) {
this.room = room;
return this;
}
public String getDescription() {
return description;
return this.description;
}
public Lesson setDescription(String description) {
public TimetableLesson setDescription(String description) {
this.description = description;
return this;
}
public String getGroupName() {
return groupName;
return this.groupName;
}
public Lesson setGroupName(String groupName) {
public TimetableLesson setGroupName(String groupName) {
this.groupName = groupName;
return this;
}
public String getStartTime() {
return startTime;
return this.startTime;
}
public Lesson setStartTime(String startTime) {
public TimetableLesson setStartTime(String startTime) {
this.startTime = startTime;
return this;
}
public String getEndTime() {
return endTime;
return this.endTime;
}
public Lesson setEndTime(String endTime) {
public TimetableLesson setEndTime(String endTime) {
this.endTime = endTime;
return this;
}
public String getDate() {
return date;
return this.date;
}
public Lesson setDate(String date) {
public TimetableLesson setDate(String date) {
this.date = date;
return this;
}
public boolean isEmpty() {
return isEmpty;
}
public Lesson setEmpty(boolean empty) {
isEmpty = empty;
return this;
}
public boolean isDivisionIntoGroups() {
return isDivisionIntoGroups;
}
public Lesson setDivisionIntoGroups(boolean divisionIntoGroups) {
isDivisionIntoGroups = divisionIntoGroups;
return this;
}
public boolean isPlanning() {
return isPlanning;
}
public Lesson setPlanning(boolean planning) {
isPlanning = planning;
return this;
}
public boolean isRealized() {
return isRealized;
}
public Lesson setRealized(boolean realized) {
isRealized = realized;
return this;
}
public boolean isMovedOrCanceled() {
return isMovedOrCanceled;
}
public Lesson setMovedOrCanceled(boolean movedOrCanceled) {
isMovedOrCanceled = movedOrCanceled;
return this;
}
public boolean isNewMovedInOrChanged() {
return isNewMovedInOrChanged;
}
public Lesson setNewMovedInOrChanged(boolean newMovedInOrChanged) {
isNewMovedInOrChanged = newMovedInOrChanged;
return this;
}
public boolean getIsEmpty() {
return this.isEmpty;
}
public void setIsEmpty(boolean isEmpty) {
public TimetableLesson setEmpty(boolean isEmpty) {
this.isEmpty = isEmpty;
return this;
}
public boolean getIsDivisionIntoGroups() {
return this.isDivisionIntoGroups;
}
public void setIsDivisionIntoGroups(boolean isDivisionIntoGroups) {
public TimetableLesson setDivisionIntoGroups(boolean isDivisionIntoGroups) {
this.isDivisionIntoGroups = isDivisionIntoGroups;
return this;
}
public boolean getIsPlanning() {
return this.isPlanning;
}
public void setIsPlanning(boolean isPlanning) {
public TimetableLesson setPlanning(boolean isPlanning) {
this.isPlanning = isPlanning;
return this;
}
public boolean getIsRealized() {
return this.isRealized;
}
public void setIsRealized(boolean isRealized) {
public TimetableLesson setRealized(boolean isRealized) {
this.isRealized = isRealized;
return this;
}
public boolean getIsMovedOrCanceled() {
return this.isMovedOrCanceled;
}
public void setIsMovedOrCanceled(boolean isMovedOrCanceled) {
public TimetableLesson setMovedOrCanceled(boolean isMovedOrCanceled) {
this.isMovedOrCanceled = isMovedOrCanceled;
return this;
}
public boolean getIsNewMovedInOrChanged() {
return this.isNewMovedInOrChanged;
}
public void setIsNewMovedInOrChanged(boolean isNewMovedInOrChanged) {
public TimetableLesson setNewMovedInOrChanged(boolean isNewMovedInOrChanged) {
this.isNewMovedInOrChanged = isNewMovedInOrChanged;
return this;
}
/**
@ -344,10 +297,34 @@ public class Lesson implements Serializable {
myDao.update(this);
}
public void setIsEmpty(boolean isEmpty) {
this.isEmpty = isEmpty;
}
public void setIsDivisionIntoGroups(boolean isDivisionIntoGroups) {
this.isDivisionIntoGroups = isDivisionIntoGroups;
}
public void setIsPlanning(boolean isPlanning) {
this.isPlanning = isPlanning;
}
public void setIsRealized(boolean isRealized) {
this.isRealized = isRealized;
}
public void setIsMovedOrCanceled(boolean isMovedOrCanceled) {
this.isMovedOrCanceled = isMovedOrCanceled;
}
public void setIsNewMovedInOrChanged(boolean isNewMovedInOrChanged) {
this.isNewMovedInOrChanged = isNewMovedInOrChanged;
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 2078826279)
@Generated(hash = 1885258429)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getLessonDao() : null;
myDao = daoSession != null ? daoSession.getTimetableLessonDao() : null;
}
}

View File

@ -26,6 +26,12 @@ public class Week {
@Property(nameInDb = "START_DATE")
private String startDayDate = "";
@Property(nameInDb = "IS_ATTENDANCE_SYNCED")
private boolean isAttendanceSynced = false;
@Property(nameInDb = "IS_TIMETABLE_SYNCED")
private boolean isTimetableSynced = false;
@ToMany(referencedJoinProperty = "weekId")
private List<Day> dayList;
@ -37,11 +43,14 @@ public class Week {
@Generated(hash = 1019310398)
private transient WeekDao myDao;
@Generated(hash = 36829814)
public Week(Long id, Long userId, String startDayDate) {
@Generated(hash = 1745118398)
public Week(Long id, Long userId, String startDayDate, boolean isAttendanceSynced,
boolean isTimetableSynced) {
this.id = id;
this.userId = userId;
this.startDayDate = startDayDate;
this.isAttendanceSynced = isAttendanceSynced;
this.isTimetableSynced = isTimetableSynced;
}
@Generated(hash = 2135529658)
@ -75,6 +84,22 @@ public class Week {
return this;
}
public boolean getIsAttendanceSynced() {
return this.isAttendanceSynced;
}
public void setIsAttendanceSynced(boolean isAttendanceSynced) {
this.isAttendanceSynced = isAttendanceSynced;
}
public boolean getIsTimetableSynced() {
return this.isTimetableSynced;
}
public void setIsTimetableSynced(boolean isTimetableSynced) {
this.isTimetableSynced = isTimetableSynced;
}
/**
* To-many relationship, resolved on first access (and after reset).
* Changes to to-many relations are not persisted, make changes to the target entity.

View File

@ -11,8 +11,9 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.R;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.api.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanOfflineException;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.di.annotations.ApplicationContext;
import io.github.wulkanowy.utils.AppConstant;
import io.github.wulkanowy.utils.LogUtils;
@ -56,4 +57,35 @@ public class AppResources implements ResourcesContract {
return exception.getMessage();
}
}
@Override
public String getAttendanceLessonDescription(AttendanceLesson lesson) {
int id = R.string.attendance_present;
if (lesson.getIsAbsenceForSchoolReasons()) {
id = R.string.attendance_absence_for_school_reasons;
}
if (lesson.getIsAbsenceExcused()) {
id = R.string.attendance_absence_excused;
}
if (lesson.getIsAbsenceUnexcused()) {
id = R.string.attendance_absence_unexcused;
}
if (lesson.getIsExemption()) {
id = R.string.attendance_exemption;
}
if (lesson.getIsExcusedLateness()) {
id = R.string.attendance_excused_lateness;
}
if (lesson.getIsUnexcusedLateness()) {
id = R.string.attendance_unexcused_lateness;
}
return resources.getString(id);
}
}

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.data.db.resources;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
public interface ResourcesContract {
String[] getSymbolsKeysArray();
@ -7,4 +9,6 @@ public interface ResourcesContract {
String[] getSymbolsValuesArray();
String getErrorLoginMessage(Exception e);
String getAttendanceLessonDescription(AttendanceLesson lesson);
}

View File

@ -3,9 +3,9 @@ package io.github.wulkanowy.data.sync;
import java.io.IOException;
import java.text.ParseException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
public interface SyncContract {
void sync() throws NotLoggedInErrorException, IOException, ParseException;
void sync() throws VulcanException, IOException, ParseException;
}

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.data.sync.login;
package io.github.wulkanowy.data.sync.account;
import android.content.Context;
@ -8,10 +8,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.login.AccountPermissionException;
import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
@ -21,7 +18,7 @@ import io.github.wulkanowy.utils.security.CryptoException;
import io.github.wulkanowy.utils.security.Scrambler;
@Singleton
public class LoginSync implements LoginSyncContract {
public class AccountSync implements AccountSyncContract {
private final DaoSession daoSession;
@ -32,8 +29,8 @@ public class LoginSync implements LoginSyncContract {
private final Context context;
@Inject
LoginSync(DaoSession daoSession, SharedPrefContract sharedPref,
Vulcan vulcan, @ApplicationContext Context context) {
AccountSync(DaoSession daoSession, SharedPrefContract sharedPref,
Vulcan vulcan, @ApplicationContext Context context) {
this.daoSession = daoSession;
this.sharedPref = sharedPref;
this.vulcan = vulcan;
@ -41,13 +38,12 @@ public class LoginSync implements LoginSyncContract {
}
@Override
public void loginUser(String email, String password, String symbol)
throws NotLoggedInErrorException, AccountPermissionException, IOException,
CryptoException, VulcanOfflineException, BadCredentialsException {
public void registerUser(String email, String password, String symbol)
throws VulcanException, IOException, CryptoException {
LogUtils.debug("Login new user email=" + email);
LogUtils.debug("Register new user email=" + email);
vulcan.login(email, password, symbol);
vulcan.setCredentials(email, password, symbol, null);
Account account = new Account()
.setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName())
@ -56,24 +52,25 @@ public class LoginSync implements LoginSyncContract {
.setSymbol(vulcan.getSymbol())
.setSnpId(vulcan.getStudentAndParent().getId());
sharedPref.setCurrentUserId(daoSession.getAccountDao().insert(account));
daoSession.getAccountDao().insert(account);
sharedPref.setCurrentUserId(account.getId());
}
@Override
public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException,
IOException, CryptoException, VulcanOfflineException, BadCredentialsException {
public void initLastUser() throws VulcanException, IOException, CryptoException {
long userId = sharedPref.getCurrentUserId();
if (userId == 0) {
throw new IOException("Can't find logged user");
throw new IOException("Can't find saved user");
}
LogUtils.debug("Login current user id=" + userId);
LogUtils.debug("Initialization current user id=" + userId);
Account account = daoSession.getAccountDao().load(userId);
vulcan.login(account.getEmail(),
vulcan.setCredentials(account.getEmail(),
Scrambler.decrypt(account.getEmail(), account.getPassword()),
account.getSymbol(),
account.getSnpId());

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.sync.account;
import java.io.IOException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.utils.security.CryptoException;
public interface AccountSyncContract {
void registerUser(String email, String password, String symbol)
throws VulcanException, IOException,
CryptoException;
void initLastUser() throws VulcanException, IOException,
CryptoException;
}

View File

@ -0,0 +1,156 @@
package io.github.wulkanowy.data.sync.attendance;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.api.generic.Lesson;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLessonDao;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.DayDao;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.data.db.dao.entities.WeekDao;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.utils.DataObjectConverter;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.TimeUtils;
@Singleton
public class AttendanceSync implements AttendanceSyncContract {
private final DaoSession daoSession;
private final SharedPrefContract sharedPref;
private final Vulcan vulcan;
private long userId;
@Inject
AttendanceSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) {
this.daoSession = daoSession;
this.sharedPref = sharedPref;
this.vulcan = vulcan;
}
@Override
public void syncAttendance() throws IOException, ParseException, VulcanException {
syncAttendance(null);
}
@Override
public void syncAttendance(String date) throws IOException, ParseException, VulcanException {
this.userId = sharedPref.getCurrentUserId();
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> weekApi = getWeekFromApi(getNormalizedDate(date));
Week weekDb = getWeekFromDb(weekApi.getStartDayDate());
long weekId = updateWeekInDb(weekDb, weekApi);
List<AttendanceLesson> lessonList = updateDays(weekApi.getDays(), weekId);
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)) : "";
}
private io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> getWeekFromApi(String date)
throws IOException, ParseException, VulcanException {
return vulcan.getAttendanceTable().getWeekTable(date);
}
private Week getWeekFromDb(String date) {
return daoSession.getWeekDao()
.queryBuilder()
.where(WeekDao.Properties.UserId.eq(userId), WeekDao.Properties.StartDayDate.eq(date))
.unique();
}
private Long updateWeekInDb(Week dbWeekEntity, io.github.wulkanowy.api.generic.Week fromApi) {
if (dbWeekEntity != null) {
dbWeekEntity.setIsAttendanceSynced(true);
dbWeekEntity.update();
return dbWeekEntity.getId();
}
Week apiWeekEntity = DataObjectConverter.weekToWeekEntity(fromApi).setUserId(userId);
apiWeekEntity.setIsAttendanceSynced(true);
return daoSession.getWeekDao().insert(apiWeekEntity);
}
private List<AttendanceLesson> updateDays(List<io.github.wulkanowy.api.generic.Day> dayListFromApi, long weekId) {
List<AttendanceLesson> updatedLessonList = new ArrayList<>();
for (io.github.wulkanowy.api.generic.Day dayFromApi : dayListFromApi) {
Day dbDayEntity = getDayFromDb(dayFromApi.getDate());
Day apiDayEntity = DataObjectConverter.dayToDayEntity(dayFromApi);
long dayId = updateDay(dbDayEntity, apiDayEntity, weekId);
updateLessons(dayFromApi.getLessons(), updatedLessonList, dayId);
}
return updatedLessonList;
}
private Day getDayFromDb(String date) {
return daoSession.getDayDao()
.queryBuilder()
.where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.Date.eq(date))
.unique();
}
private long updateDay(Day dbDayEntity, Day apiDayEntity, long weekId) {
if (null != dbDayEntity) {
return dbDayEntity.getId();
}
apiDayEntity.setUserId(userId);
apiDayEntity.setWeekId(weekId);
return daoSession.getDayDao().insert(apiDayEntity);
}
private void updateLessons(List<Lesson> lessons, List<AttendanceLesson> updatedLessons, long dayId) {
List<AttendanceLesson> lessonsFromApiEntities = DataObjectConverter
.lessonsToAttendanceLessonsEntities(lessons);
for (AttendanceLesson apiLessonEntity : lessonsFromApiEntities) {
AttendanceLesson lessonFromDb = getLessonFromDb(apiLessonEntity, dayId);
apiLessonEntity.setDayId(dayId);
if (lessonFromDb != null) {
apiLessonEntity.setId(lessonFromDb.getId());
}
if (!"".equals(apiLessonEntity.getSubject())) {
updatedLessons.add(apiLessonEntity);
}
}
}
private AttendanceLesson getLessonFromDb(AttendanceLesson apiEntity, long dayId) {
return daoSession.getAttendanceLessonDao().queryBuilder()
.where(AttendanceLessonDao.Properties.DayId.eq(dayId),
AttendanceLessonDao.Properties.Date.eq(apiEntity.getDate()),
AttendanceLessonDao.Properties.Number.eq(apiEntity.getNumber()))
.unique();
}
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.sync.attendance;
import java.io.IOException;
import java.text.ParseException;
import io.github.wulkanowy.api.VulcanException;
public interface AttendanceSyncContract {
void syncAttendance(String date) throws IOException, ParseException, VulcanException;
void syncAttendance() throws IOException, ParseException, VulcanException;
}

View File

@ -9,7 +9,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Grade;
@ -29,6 +29,8 @@ public class GradeSync implements SyncContract {
private final SharedPrefContract sharedPref;
private Long userId;
@Inject
GradeSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) {
this.daoSession = daoSession;
@ -37,36 +39,53 @@ public class GradeSync implements SyncContract {
}
@Override
public void sync() throws IOException, NotLoggedInErrorException, ParseException {
public void sync() throws IOException, VulcanException, ParseException {
long userId = sharedPref.getCurrentUserId();
userId = sharedPref.getCurrentUserId();
Account account = daoSession.getAccountDao().load(userId);
account.resetGradeList();
account.resetSubjectList();
resetAccountRelations(account);
List<Grade> gradesFromNet = DataObjectConverter
.gradesToGradeEntities(vulcan.getGradesList().getAll());
List<Grade> gradesFromDb = account.getGradeList();
List<Grade> updatedGrades = EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb);
daoSession.getGradeDao().deleteInTx(gradesFromDb);
List<Grade> lastList = new ArrayList<>();
for (Grade grade : updatedGrades) {
grade.setUserId(userId);
grade.setSubjectId(daoSession.getSubjectDao().queryBuilder()
.where(SubjectDao.Properties.Name.eq(grade.getSubject()),
SubjectDao.Properties.UserId.eq(userId))
.build()
.uniqueOrThrow().getId());
lastList.add(grade);
}
List<Grade> lastList = getUpdatedList(getComparedList(account));
daoSession.getGradeDao().deleteInTx(account.getGradeList());
daoSession.getGradeDao().insertInTx(lastList);
LogUtils.debug("Synchronization grades (amount = " + lastList.size() + ")");
}
private void resetAccountRelations(Account account) {
account.resetSubjectList();
account.resetGradeList();
}
private List<Grade> getUpdatedList(List<Grade> comparedList) {
List<Grade> updatedList = new ArrayList<>();
for (Grade grade : comparedList) {
grade.setUserId(userId);
grade.setSubjectId(getSubjectId(grade.getSubject()));
updatedList.add(grade);
}
return updatedList;
}
private List<Grade> getComparedList(Account account) throws IOException, VulcanException,
ParseException {
List<Grade> gradesFromNet = DataObjectConverter
.gradesToGradeEntities(vulcan.getGradesList().getAll());
List<Grade> gradesFromDb = account.getGradeList();
return EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb);
}
private Long getSubjectId(String subjectName) {
return daoSession.getSubjectDao().queryBuilder()
.where(SubjectDao.Properties.Name.eq(subjectName),
SubjectDao.Properties.UserId.eq(userId))
.build()
.uniqueOrThrow()
.getId();
}
}

View File

@ -1,19 +0,0 @@
package io.github.wulkanowy.data.sync.login;
import java.io.IOException;
import io.github.wulkanowy.api.login.AccountPermissionException;
import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.utils.security.CryptoException;
public interface LoginSyncContract {
void loginUser(String email, String password, String symbol)
throws NotLoggedInErrorException, AccountPermissionException, IOException,
CryptoException, VulcanOfflineException, BadCredentialsException;
void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, IOException,
CryptoException, VulcanOfflineException, BadCredentialsException;
}

View File

@ -9,10 +9,10 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Subject;
import io.github.wulkanowy.data.db.dao.entities.SubjectDao;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.data.sync.SyncContract;
import io.github.wulkanowy.utils.DataObjectConverter;
@ -21,41 +21,51 @@ import io.github.wulkanowy.utils.LogUtils;
@Singleton
public class SubjectSync implements SyncContract {
private final SubjectDao subjectDao;
private final DaoSession daoSession;
private final Vulcan vulcan;
private final SharedPrefContract sharedPref;
private Long userId;
@Inject
SubjectSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) {
this.subjectDao = daoSession.getSubjectDao();
this.daoSession = daoSession;
this.sharedPref = sharedPref;
this.vulcan = vulcan;
}
@Override
public void sync() throws NotLoggedInErrorException, IOException, ParseException {
public void sync() throws VulcanException, IOException, ParseException {
long userId = sharedPref.getCurrentUserId();
userId = sharedPref.getCurrentUserId();
List<Subject> subjectsFromNet = DataObjectConverter
.subjectsToSubjectEntities(vulcan.getSubjectsList().getAll());
List<Subject> lastList = getUpdatedList(getSubjectsFromNet());
subjectDao.deleteInTx(subjectDao.queryBuilder()
.where(SubjectDao.Properties.UserId.eq(userId))
.build()
.list());
List<Subject> lastList = new ArrayList<>();
for (Subject subject : subjectsFromNet) {
subject.setUserId(userId);
lastList.add(subject);
}
subjectDao.insertInTx(lastList);
daoSession.getSubjectDao().deleteInTx(getSubjectsFromDb());
daoSession.getSubjectDao().insertInTx(lastList);
LogUtils.debug("Synchronization subjects (amount = " + lastList.size() + ")");
}
private List<Subject> getSubjectsFromNet() throws VulcanException, IOException {
return DataObjectConverter.subjectsToSubjectEntities(vulcan.getSubjectsList().getAll());
}
private List<Subject> getSubjectsFromDb() {
Account account = daoSession.getAccountDao().load(userId);
account.resetSubjectList();
return account.getSubjectList();
}
private List<Subject> getUpdatedList(List<Subject> subjectsFromNet) {
List<Subject> updatedList = new ArrayList<>();
for (Subject subject : subjectsFromNet) {
subject.setUserId(userId);
updatedList.add(subject);
}
return updatedList;
}
}

View File

@ -9,12 +9,13 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.api.generic.Lesson;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.DayDao;
import io.github.wulkanowy.data.db.dao.entities.Lesson;
import io.github.wulkanowy.data.db.dao.entities.LessonDao;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.TimetableLessonDao;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.data.db.dao.entities.WeekDao;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
@ -27,9 +28,11 @@ public class TimetableSync implements TimetableSyncContract {
private final DaoSession daoSession;
private final SharedPrefContract sharedPref;
private final Vulcan vulcan;
private final SharedPrefContract sharedPref;
private long userId;
@Inject
TimetableSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) {
@ -39,82 +42,121 @@ public class TimetableSync implements TimetableSyncContract {
}
@Override
public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException {
long userId = sharedPref.getCurrentUserId();
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> weekFromNet =
date == null ? vulcan.getTimetable().getWeekTable()
: vulcan.getTimetable().getWeekTable(String.valueOf(TimeUtils.getNetTicks(date)));
Week weekFromDb = daoSession.getWeekDao().queryBuilder()
.where(WeekDao.Properties.UserId.eq(userId),
WeekDao.Properties.StartDayDate.eq(weekFromNet.getStartDayDate()))
.unique();
Long weekId;
if (weekFromDb == null) {
Week weekFromNetEntity = DataObjectConverter.weekToWeekEntity(weekFromNet).setUserId(userId);
weekId = daoSession.getWeekDao().insert(weekFromNetEntity);
} else {
weekId = weekFromDb.getId();
}
List<io.github.wulkanowy.api.generic.Day> dayListFromNet = weekFromNet.getDays();
List<Lesson> updatedLessonList = new ArrayList<>();
for (io.github.wulkanowy.api.generic.Day dayFromNet : dayListFromNet) {
Day dayFromNetEntity = DataObjectConverter.dayToDayEntity(dayFromNet);
Day dayFromDb = daoSession.getDayDao().queryBuilder()
.where(DayDao.Properties.UserId.eq(userId),
DayDao.Properties.WeekId.eq(weekId),
DayDao.Properties.Date.eq(dayFromNetEntity.getDate()))
.unique();
dayFromNetEntity.setUserId(userId);
dayFromNetEntity.setWeekId(weekId);
Long dayId;
if (dayFromDb != null) {
dayFromNetEntity.setId(dayFromDb.getId());
daoSession.getDayDao().save(dayFromNetEntity);
dayId = dayFromNetEntity.getId();
} else {
dayId = daoSession.getDayDao().insert(dayFromNetEntity);
}
List<Lesson> lessonListFromNetEntities = DataObjectConverter
.lessonsToLessonsEntities(dayFromNet.getLessons());
for (Lesson lessonFromNetEntity : lessonListFromNetEntities) {
Lesson lessonFromDb = daoSession.getLessonDao().queryBuilder()
.where(LessonDao.Properties.DayId.eq(dayId),
LessonDao.Properties.Date.eq(lessonFromNetEntity.getDate()),
LessonDao.Properties.StartTime.eq(lessonFromNetEntity.getStartTime()),
LessonDao.Properties.EndTime.eq(lessonFromNetEntity.getEndTime()))
.unique();
if (lessonFromDb != null) {
lessonFromNetEntity.setId(lessonFromDb.getId());
}
lessonFromNetEntity.setDayId(dayFromNetEntity.getId());
if (!"".equals(lessonFromNetEntity.getSubject())) {
updatedLessonList.add(lessonFromNetEntity);
}
}
}
daoSession.getLessonDao().saveInTx(updatedLessonList);
LogUtils.debug("Synchronization lessons (amount = " + updatedLessonList.size() + ")");
public void syncTimetable() throws IOException, ParseException, VulcanException {
syncTimetable(null);
}
@Override
public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException {
syncTimetable(null);
public void syncTimetable(String date) throws IOException, ParseException, VulcanException {
this.userId = sharedPref.getCurrentUserId();
io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> weekApi = getWeekFromApi(getNormalizedDate(date));
Week weekDb = getWeekFromDb(weekApi.getStartDayDate());
long weekId = updateWeekInDb(weekDb, weekApi);
List<TimetableLesson> lessonList = updateDays(weekApi.getDays(), weekId);
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)) : "";
}
private io.github.wulkanowy.api.generic.Week<io.github.wulkanowy.api.generic.Day> getWeekFromApi(String date)
throws IOException, VulcanException, ParseException {
return vulcan.getTimetable().getWeekTable(date);
}
private Week getWeekFromDb(String date) {
return daoSession.getWeekDao()
.queryBuilder()
.where(WeekDao.Properties.UserId.eq(userId), WeekDao.Properties.StartDayDate.eq(date))
.unique();
}
private Long updateWeekInDb(Week dbEntity, io.github.wulkanowy.api.generic.Week fromApi) {
if (dbEntity != null) {
dbEntity.setIsTimetableSynced(true);
dbEntity.update();
return dbEntity.getId();
}
Week apiEntity = DataObjectConverter.weekToWeekEntity(fromApi).setUserId(userId);
apiEntity.setIsTimetableSynced(true);
return daoSession.getWeekDao().insert(apiEntity);
}
private List<TimetableLesson> updateDays(List<io.github.wulkanowy.api.generic.Day> dayListFromApi, long weekId) {
List<TimetableLesson> updatedLessonList = new ArrayList<>();
for (io.github.wulkanowy.api.generic.Day dayFromApi : dayListFromApi) {
Day dbDayEntity = getDayFromDb(dayFromApi.getDate());
Day apiDayEntity = DataObjectConverter.dayToDayEntity(dayFromApi);
long dayId = updateDay(dbDayEntity, apiDayEntity, weekId);
updateLessons(dayFromApi.getLessons(), updatedLessonList, dayId);
}
return updatedLessonList;
}
private Day getDayFromDb(String date) {
return daoSession.getDayDao()
.queryBuilder()
.where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.Date.eq(date))
.unique();
}
private long updateDay(Day dayFromDb, Day apiDayEntity, long weekId) {
apiDayEntity.setUserId(userId);
apiDayEntity.setWeekId(weekId);
if (null != dayFromDb) {
apiDayEntity.setId(dayFromDb.getId());
daoSession.getDayDao().save(apiDayEntity);
dayFromDb.refresh();
return dayFromDb.getId();
}
return daoSession.getDayDao().insert(apiDayEntity);
}
private void updateLessons(List<Lesson> lessons, List<TimetableLesson> updatedLessons, long dayId) {
List<TimetableLesson> lessonsFromApiEntities = DataObjectConverter
.lessonsToTimetableLessonsEntities(lessons);
for (TimetableLesson apiLessonEntity : lessonsFromApiEntities) {
TimetableLesson lessonFromDb = getLessonFromDb(apiLessonEntity, dayId);
apiLessonEntity.setDayId(dayId);
if (lessonFromDb != null) {
apiLessonEntity.setId(lessonFromDb.getId());
}
if (!"".equals(apiLessonEntity.getSubject())) {
updatedLessons.add(apiLessonEntity);
}
}
}
private TimetableLesson getLessonFromDb(TimetableLesson apiEntity, long dayId) {
return daoSession.getTimetableLessonDao().queryBuilder()
.where(TimetableLessonDao.Properties.DayId.eq(dayId),
TimetableLessonDao.Properties.Date.eq(apiEntity.getDate()),
TimetableLessonDao.Properties.StartTime.eq(apiEntity.getStartTime()),
TimetableLessonDao.Properties.EndTime.eq(apiEntity.getEndTime()))
.unique();
}
}

View File

@ -3,11 +3,11 @@ package io.github.wulkanowy.data.sync.timetable;
import java.io.IOException;
import java.text.ParseException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
public interface TimetableSyncContract {
void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException;
void syncTimetable(String date) throws VulcanException, IOException, ParseException;
void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException;
void syncTimetable() throws VulcanException, IOException, ParseException;
}

View File

@ -4,6 +4,7 @@ import dagger.Component;
import io.github.wulkanowy.di.annotations.PerFragment;
import io.github.wulkanowy.di.modules.FragmentModule;
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment;
import io.github.wulkanowy.ui.main.attendance.AttendanceTabFragment;
import io.github.wulkanowy.ui.main.dashboard.DashboardFragment;
import io.github.wulkanowy.ui.main.grades.GradesFragment;
import io.github.wulkanowy.ui.main.timetable.TimetableFragment;
@ -17,6 +18,8 @@ public interface FragmentComponent {
void inject(AttendanceFragment attendanceFragment);
void inject(AttendanceTabFragment attendanceTabFragment);
void inject(DashboardFragment dashboardFragment);
void inject(TimetableFragment timetableFragment);

View File

@ -21,9 +21,11 @@ import io.github.wulkanowy.data.db.resources.ResourcesContract;
import io.github.wulkanowy.data.db.shared.SharedPref;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.data.sync.SyncContract;
import io.github.wulkanowy.data.sync.account.AccountSync;
import io.github.wulkanowy.data.sync.account.AccountSyncContract;
import io.github.wulkanowy.data.sync.attendance.AttendanceSync;
import io.github.wulkanowy.data.sync.attendance.AttendanceSyncContract;
import io.github.wulkanowy.data.sync.grades.GradeSync;
import io.github.wulkanowy.data.sync.login.LoginSync;
import io.github.wulkanowy.data.sync.login.LoginSyncContract;
import io.github.wulkanowy.data.sync.subjects.SubjectSync;
import io.github.wulkanowy.data.sync.timetable.TimetableSync;
import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract;
@ -98,8 +100,8 @@ public class ApplicationModule {
@Singleton
@Provides
LoginSyncContract provideLoginSync(LoginSync loginSync) {
return loginSync;
AccountSyncContract provideLoginSync(AccountSync accountSync) {
return accountSync;
}
@SyncGrades
@ -122,6 +124,12 @@ public class ApplicationModule {
return timetableSync;
}
@Singleton
@Provides
AttendanceSyncContract provideAttendanceSync(AttendanceSync attendanceSync) {
return attendanceSync;
}
@Provides
FirebaseJobDispatcher provideDispatcher() {
return new FirebaseJobDispatcher(new GooglePlayDriver(application));

View File

@ -7,7 +7,11 @@ import dagger.Provides;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import io.github.wulkanowy.di.annotations.PerFragment;
import io.github.wulkanowy.ui.main.attendance.AttendanceContract;
import io.github.wulkanowy.ui.main.attendance.AttendanceHeaderItem;
import io.github.wulkanowy.ui.main.attendance.AttendancePagerAdapter;
import io.github.wulkanowy.ui.main.attendance.AttendancePresenter;
import io.github.wulkanowy.ui.main.attendance.AttendanceTabContract;
import io.github.wulkanowy.ui.main.attendance.AttendanceTabPresenter;
import io.github.wulkanowy.ui.main.dashboard.DashboardContract;
import io.github.wulkanowy.ui.main.dashboard.DashboardPresenter;
import io.github.wulkanowy.ui.main.grades.GradeHeaderItem;
@ -47,6 +51,22 @@ public class FragmentModule {
return dashboardPresenter;
}
@PerFragment
@Provides
AttendanceTabContract.Presenter provideAttendanceTabPresenter(AttendanceTabPresenter timetableTabPresenter) {
return timetableTabPresenter;
}
@Provides
AttendancePagerAdapter provideAttendancePagerAdapter() {
return new AttendancePagerAdapter(fragment.getChildFragmentManager());
}
@Provides
FlexibleAdapter<AttendanceHeaderItem> provideAttendanceTabAdapter() {
return new FlexibleAdapter<>(null);
}
@PerFragment
@Provides
TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter) {
@ -65,12 +85,12 @@ public class FragmentModule {
}
@Provides
FlexibleAdapter<GradeHeaderItem> provideGradesAdapter() {
FlexibleAdapter<TimetableHeaderItem> provideTimetableTabAdapter() {
return new FlexibleAdapter<>(null);
}
@Provides
FlexibleAdapter<TimetableHeaderItem> provideTimetableTabAdapter() {
FlexibleAdapter<GradeHeaderItem> provideGradesAdapter() {
return new FlexibleAdapter<>(null);
}
}

View File

@ -62,7 +62,7 @@ public class SyncJob extends SimpleJobService {
@Override
public int onRunJob(JobParameters job) {
try {
repository.loginCurrentUser();
repository.initLastUser();
repository.syncAll();
gradeList = repository.getNewGrades();

View File

@ -25,6 +25,10 @@ public class BasePresenter<V extends BaseContract.View> implements BaseContract.
view = null;
}
protected boolean isViewAttached() {
return view != null;
}
public final RepositoryContract getRepository() {
return repository;
}

View File

@ -42,6 +42,8 @@ public interface LoginContract {
void onStartAsync();
void onDoInBackground(int stepNumber) throws Exception;
void onLoginProgress(int step);
void onEndAsync(boolean success, Exception exception);

View File

@ -17,35 +17,32 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
private LoginTask loginAsync;
private String email;
private String password;
private String symbol;
@Inject
LoginPresenter(RepositoryContract repository) {
super(repository);
}
@Override
public void onDestroy() {
super.onDestroy();
if (loginAsync != null) {
loginAsync.cancel(true);
loginAsync = null;
}
}
@Override
public void attemptLogin(String email, String password, String symbol) {
getView().resetViewErrors();
this.email = email;
this.password = password;
this.symbol = getNormalizedSymbol(symbol);
if (!isAllFieldCorrect(password, email)) {
getView().showSoftInput();
return;
}
if (getView().isNetworkConnected()) {
// Dopóki używamy AsyncTask presenter będzie musiał "wiedzieć" o AsyncTaskach
loginAsync = new LoginTask(this,
email,
password,
getNormalizedSymbol(symbol));
loginAsync = new LoginTask(this);
loginAsync.execute();
} else {
@ -57,7 +54,21 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
@Override
public void onStartAsync() {
getView().showLoginProgress(true);
if (isViewAttached()) {
getView().showLoginProgress(true);
}
}
@Override
public void onDoInBackground(int stepNumber) throws Exception {
switch (stepNumber) {
case 1:
getRepository().registerUser(email, password, symbol);
break;
case 2:
getRepository().syncAll();
break;
}
}
@Override
@ -90,7 +101,9 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
@Override
public void onCanceledAsync() {
getView().showLoginProgress(false);
if (isViewAttached()) {
getView().showLoginProgress(false);
}
}
private boolean isEmailValid(String email) {
@ -140,4 +153,13 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
}
return correct;
}
@Override
public void onDestroy() {
if (loginAsync != null) {
loginAsync.cancel(true);
loginAsync = null;
}
super.onDestroy();
}
}

View File

@ -4,21 +4,12 @@ import android.os.AsyncTask;
public class LoginTask extends AsyncTask<Void, Integer, Boolean> {
private String email;
private String password;
private String symbol;
private LoginContract.Presenter presenter;
private Exception exception;
LoginTask(LoginContract.Presenter presenter, String email, String password, String symbol) {
LoginTask(LoginContract.Presenter presenter) {
this.presenter = presenter;
this.email = email;
this.password = password;
this.symbol = symbol;
}
@Override
@ -30,10 +21,10 @@ public class LoginTask extends AsyncTask<Void, Integer, Boolean> {
protected Boolean doInBackground(Void... params) {
try {
publishProgress(1);
presenter.getRepository().loginUser(email, password, symbol);
presenter.onDoInBackground(1);
publishProgress(2);
presenter.getRepository().syncAll();
presenter.onDoInBackground(2);
} catch (Exception e) {
exception = e;
return false;

View File

@ -18,6 +18,7 @@ import butterknife.ButterKnife;
import io.github.wulkanowy.R;
import io.github.wulkanowy.services.SyncJob;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment;
import io.github.wulkanowy.ui.main.dashboard.DashboardFragment;
import io.github.wulkanowy.ui.main.grades.GradesFragment;
import io.github.wulkanowy.ui.main.timetable.TimetableFragment;
@ -134,7 +135,7 @@ public class MainActivity extends BaseActivity implements MainContract.View,
private void initiationViewPager() {
pagerAdapter.addFragment(new GradesFragment());
pagerAdapter.addFragment(new DashboardFragment());
pagerAdapter.addFragment(new AttendanceFragment());
pagerAdapter.addFragment(new DashboardFragment());
pagerAdapter.addFragment(new TimetableFragment());
pagerAdapter.addFragment(new DashboardFragment());

View File

@ -1,35 +1,35 @@
package io.github.wulkanowy.ui.main.timetable;
package io.github.wulkanowy.ui.main;
import android.support.v4.app.Fragment;
import java.util.ArrayList;
import java.util.List;
class TabsData {
public class TabsData {
private List<Fragment> fragments = new ArrayList<>();
private List<String> titles = new ArrayList<>();
Fragment getFragment(int index) {
public Fragment getFragment(int index) {
return fragments.get(index);
}
void addFragment(Fragment fragment) {
public void addFragment(Fragment fragment) {
if (fragment != null) {
fragments.add(fragment);
}
}
int getFragmentsCount() {
public int getFragmentsCount() {
return fragments.size();
}
String getTitle(int index) {
public String getTitle(int index) {
return titles.get(index);
}
void addTitle(String title) {
public void addTitle(String title) {
if (title != null) {
titles.add(title);
}

View File

@ -1,8 +1,9 @@
package io.github.wulkanowy.ui.main.attendance;
import io.github.wulkanowy.di.annotations.PerFragment;
import io.github.wulkanowy.di.annotations.PerActivity;
import io.github.wulkanowy.ui.base.BaseContract;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
public interface AttendanceContract {
@ -10,14 +11,26 @@ public interface AttendanceContract {
void setActivityTitle();
void scrollViewPagerToPosition(int position);
void setTabDataToAdapter(TabsData tabsData);
void setAdapterWithTabLayout();
void setChildFragmentSelected(int position, boolean selected);
boolean isMenuVisible();
}
@PerFragment
@PerActivity
interface Presenter extends BaseContract.Presenter<View> {
void onStart(View view, OnFragmentIsReadyListener listener);
void onFragmentVisible(boolean isVisible);
void onTabSelected(int position);
void onTabUnselected(int position);
void onStart(View view, OnFragmentIsReadyListener listener);
}
}

View File

@ -0,0 +1,91 @@
package io.github.wulkanowy.ui.main.attendance;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.github.wulkanowy.R;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
public class AttendanceDialogFragment extends DialogFragment {
private static final String ARGUMENT_KEY = "Item";
private AttendanceLesson lesson;
@BindView(R.id.attendance_dialog_subject_value)
TextView subject;
@BindView(R.id.attendance_dialog_date_value)
TextView date;
@BindView(R.id.attendance_dialog_number_value)
TextView number;
@BindView(R.id.attendance_dialog_description_value)
TextView description;
public AttendanceDialogFragment() {
//empty constructor for fragment
}
public static AttendanceDialogFragment newInstance(AttendanceLesson lesson) {
AttendanceDialogFragment dialogFragment = new AttendanceDialogFragment();
Bundle bundle = new Bundle();
bundle.putSerializable(ARGUMENT_KEY, lesson);
dialogFragment.setArguments(bundle);
return dialogFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
lesson = (AttendanceLesson) getArguments().getSerializable(ARGUMENT_KEY);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.attendance_dialog, container, false);
ButterKnife.bind(this, view);
if (!lesson.getSubject().isEmpty()) {
subject.setText(lesson.getSubject());
}
if (!lesson.getDate().isEmpty()) {
date.setText(lesson.getDate());
}
if (0 != lesson.getNumber()) {
number.setText(String.valueOf(lesson.getNumber()));
}
description.setText(lesson.getDescription());
if (lesson.getIsAbsenceUnexcused()) {
description.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
}
return view;
}
@OnClick(R.id.attendance_dialog_close)
void onClickCloseButton() {
dismiss();
}
}

View File

@ -2,30 +2,42 @@ 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;
import android.view.View;
import android.view.ViewGroup;
import javax.inject.Inject;
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.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
public class AttendanceFragment extends BaseFragment implements AttendanceContract.View {
public class AttendanceFragment extends BaseFragment implements AttendanceContract.View, TabLayout.OnTabSelectedListener {
@BindView(R.id.attendance_fragment_viewpager)
ViewPager viewPager;
@BindView(R.id.attendance_fragment_tab_layout)
TabLayout tabLayout;
@Inject
AttendancePagerAdapter pagerAdapter;
@Inject
AttendanceContract.Presenter presenter;
public AttendanceFragment() {
// empty constructor for fragment
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_attendance, container, false);
FragmentComponent component = getFragmentComponent();
@ -46,14 +58,60 @@ public class AttendanceFragment extends BaseFragment implements AttendanceContra
}
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
presenter.onTabSelected(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
presenter.onTabUnselected(tab.getPosition());
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
//do nothing
}
@Override
public void setTabDataToAdapter(TabsData tabsData) {
pagerAdapter.setTabsData(tabsData);
}
@Override
public void setAdapterWithTabLayout() {
viewPager.setAdapter(pagerAdapter);
tabLayout.setupWithViewPager(viewPager);
tabLayout.addOnTabSelectedListener(this);
}
@Override
public void setChildFragmentSelected(int position, boolean selected) {
((AttendanceTabFragment) pagerAdapter.getItem(position)).setSelected(selected);
}
@Override
public void scrollViewPagerToPosition(int position) {
viewPager.setCurrentItem(position, false);
}
@Override
public void setActivityTitle() {
setTitle(getString(R.string.dashboard_text));
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 onDestroyView() {
super.onDestroyView();
presenter.onDestroy();
super.onDestroyView();
}
}

View File

@ -0,0 +1,135 @@
package io.github.wulkanowy.ui.main.attendance;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.List;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem;
import eu.davidea.viewholders.ExpandableViewHolder;
import io.github.wulkanowy.R;
import io.github.wulkanowy.data.db.dao.entities.Day;
public class AttendanceHeaderItem
extends AbstractExpandableHeaderItem<AttendanceHeaderItem.HeaderViewHolder, AttendanceSubItem> {
private Day day;
AttendanceHeaderItem(Day day) {
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttendanceHeaderItem that = (AttendanceHeaderItem) o;
return new EqualsBuilder()
.append(day, that.day)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(day)
.toHashCode();
}
@Override
public int getLayoutRes() {
return R.layout.attendance_header;
}
@Override
public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) {
return new HeaderViewHolder(view, adapter);
}
@Override
public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) {
holder.onBind(day, getSubItems());
}
static class HeaderViewHolder extends ExpandableViewHolder {
@BindView(R.id.attendance_header_day)
TextView dayName;
@BindView(R.id.attendance_header_date)
TextView date;
@BindView(R.id.attendance_header_description)
TextView description;
@BindView(R.id.attendance_header_alert_image)
ImageView alert;
@BindView(R.id.attendance_header_free_name)
TextView freeName;
@BindColor(R.color.secondary_text)
int secondaryColor;
@BindColor(R.color.free_day)
int backgroundFreeDay;
HeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
view.setOnClickListener(this);
ButterKnife.bind(this, view);
}
void onBind(Day item, List<AttendanceSubItem> subItems) {
dayName.setText(StringUtils.capitalize(item.getDayName()));
date.setText(item.getDate());
int numberOfHours = countNotPresentHours(subItems);
description.setText((getContentView().getResources().getQuantityString(R.plurals.numberOfAbsences,
numberOfHours, numberOfHours)));
description.setVisibility(numberOfHours > 0 ? View.VISIBLE : View.INVISIBLE);
alert.setVisibility(isSubItemsHasChanges(subItems) ? View.VISIBLE : View.INVISIBLE);
freeName.setVisibility(subItems.isEmpty() ? View.VISIBLE : View.INVISIBLE);
if (item.getAttendanceLessons().isEmpty()) {
((FrameLayout) getContentView()).setForeground(null);
getContentView().setBackgroundColor(backgroundFreeDay);
dayName.setTextColor(secondaryColor);
}
}
private int countNotPresentHours(List<AttendanceSubItem> subItems) {
int i = 0;
for (AttendanceSubItem subItem : subItems) {
if (subItem.getLesson().getIsAbsenceUnexcused()) {
i++;
}
}
return i;
}
private boolean isSubItemsHasChanges(List<AttendanceSubItem> subItems) {
for (AttendanceSubItem subItem : subItems) {
if (subItem.getLesson().getIsAbsenceUnexcused() || subItem.getLesson().getIsUnexcusedLateness()) {
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.ui.main.attendance;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import io.github.wulkanowy.ui.main.TabsData;
public class AttendancePagerAdapter extends FragmentStatePagerAdapter {
private TabsData tabsData;
public AttendancePagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
void setTabsData(TabsData tabsData) {
this.tabsData = tabsData;
}
@Override
public Fragment getItem(int position) {
return tabsData.getFragment(position);
}
@Override
public int getCount() {
return tabsData.getFragmentsCount();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return tabsData.getTitle(position);
}
}

View File

@ -1,16 +1,33 @@
package io.github.wulkanowy.ui.main.attendance;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
import io.github.wulkanowy.utils.TimeUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
implements AttendanceContract.Presenter {
implements AttendanceContract.Presenter, AsyncListeners.OnFirstLoadingListener {
private AbstractTask loadingTask;
private List<String> dates = new ArrayList<>();
private TabsData tabsData = new TabsData();
private OnFragmentIsReadyListener listener;
private int positionToScroll;
private boolean isFirstSight = false;
@Inject
AttendancePresenter(RepositoryContract repository) {
super(repository);
@ -25,7 +42,18 @@ public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
getView().setActivityTitle();
}
this.listener.onFragmentIsReady();
if (dates.isEmpty()) {
dates = TimeUtils.getMondaysFromCurrentSchoolYear();
}
positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true));
if (!isFirstSight) {
isFirstSight = true;
loadingTask = new AbstractTask();
loadingTask.setOnFirstLoadingListener(this);
loadingTask.execute();
}
}
@Override
@ -34,4 +62,50 @@ public class AttendancePresenter extends BasePresenter<AttendanceContract.View>
getView().setActivityTitle();
}
}
@Override
public void onTabSelected(int position) {
getView().setChildFragmentSelected(position, true);
}
@Override
public void onTabUnselected(int position) {
getView().setChildFragmentSelected(position, false);
}
@Override
public void onDoInBackgroundLoading() throws Exception {
for (String date : dates) {
tabsData.addTitle(date);
tabsData.addFragment(AttendanceTabFragment.newInstance(date));
}
}
@Override
public void onCanceledLoadingAsync() {
//do nothing
}
@Override
public void onEndLoadingAsync(boolean result, Exception exception) {
if (result) {
getView().setTabDataToAdapter(tabsData);
getView().setAdapterWithTabLayout();
getView().scrollViewPagerToPosition(positionToScroll);
listener.onFragmentIsReady();
}
}
@Override
public void onDestroy() {
isFirstSight = false;
if (loadingTask != null) {
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
}
}

View File

@ -0,0 +1,119 @@
package io.github.wulkanowy.ui.main.attendance;
import android.content.Context;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractSectionableItem;
import eu.davidea.viewholders.FlexibleViewHolder;
import io.github.wulkanowy.R;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
class AttendanceSubItem
extends AbstractSectionableItem<AttendanceSubItem.SubItemViewHolder, AttendanceHeaderItem> {
private AttendanceLesson lesson;
AttendanceSubItem(AttendanceHeaderItem header, AttendanceLesson lesson) {
super(header);
this.lesson = lesson;
}
AttendanceLesson getLesson() {
return lesson;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttendanceSubItem that = (AttendanceSubItem) o;
return new EqualsBuilder()
.append(lesson, that.lesson)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(lesson)
.toHashCode();
}
@Override
public int getLayoutRes() {
return R.layout.attendance_subitem;
}
@Override
public AttendanceSubItem.SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) {
return new AttendanceSubItem.SubItemViewHolder(view, adapter);
}
@Override
public void bindViewHolder(FlexibleAdapter adapter, AttendanceSubItem.SubItemViewHolder holder, int position, List payloads) {
holder.onBind(lesson);
}
static class SubItemViewHolder extends FlexibleViewHolder {
@BindView(R.id.attendance_subItem_lesson)
TextView lessonName;
@BindView(R.id.attendance_subItem_number)
TextView lessonNumber;
@BindView(R.id.attendance_subItem_description)
TextView lessonDescription;
@BindView(R.id.attendance_subItem_alert_image)
ImageView alert;
private Context context;
private AttendanceLesson item;
SubItemViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
ButterKnife.bind(this, view);
context = view.getContext();
view.setOnClickListener(this);
}
void onBind(AttendanceLesson lesson) {
item = lesson;
lessonName.setText(lesson.getSubject());
lessonNumber.setText((String.valueOf(lesson.getNumber())));
lessonDescription.setText(lesson.getDescription());
alert.setVisibility(lesson.getIsAbsenceUnexcused() || lesson.getIsUnexcusedLateness()
? View.VISIBLE : View.INVISIBLE);
}
@Override
public void onClick(View view) {
super.onClick(view);
showDialog();
}
private void showDialog() {
AttendanceDialogFragment dialogFragment = AttendanceDialogFragment.newInstance(item);
dialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
dialogFragment.show(((FragmentActivity) context).getSupportFragmentManager(), item.toString());
}
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.ui.main.attendance;
import java.util.List;
import io.github.wulkanowy.ui.base.BaseContract;
public interface AttendanceTabContract {
interface View extends BaseContract.View {
void updateAdapterList(List<AttendanceHeaderItem> headerItems);
void onRefreshSuccess();
void hideRefreshingBar();
void showNoItem(boolean show);
void showProgressBar(boolean show);
}
interface Presenter extends BaseContract.Presenter<AttendanceTabContract.View> {
void onFragmentSelected(boolean isSelected);
void setArgumentDate(String date);
void onStart(AttendanceTabContract.View view, boolean isPrimary);
void onRefresh();
}
}

View File

@ -0,0 +1,170 @@
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.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import javax.inject.Inject;
import butterknife.BindView;
import 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,
SwipeRefreshLayout.OnRefreshListener {
private static final String ARGUMENT_KEY = "date";
private static final String SAVED_KEY = "isSelected";
private boolean isPrimary = false;
private boolean isSelected = false;
@BindView(R.id.attendance_tab_fragment_recycler)
RecyclerView recyclerView;
@BindView(R.id.attendance_tab_fragment_swipe_refresh)
SwipeRefreshLayout refreshLayout;
@BindView(R.id.attendance_tab_fragment_progress_bar)
View progressBar;
@BindView(R.id.attendance_tab_fragment_no_item_container)
View noItemView;
@Inject
AttendanceTabContract.Presenter presenter;
@Inject
FlexibleAdapter<AttendanceHeaderItem> adapter;
public static AttendanceTabFragment newInstance(String date) {
AttendanceTabFragment fragmentTab = new AttendanceTabFragment();
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT_KEY, date);
fragmentTab.setArguments(bundle);
return fragmentTab;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
isSelected = savedInstanceState.getBoolean(SAVED_KEY, isSelected);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_attendance_tab, container, false);
FragmentComponent component = getFragmentComponent();
if (component != null) {
component.inject(this);
setButterKnife(ButterKnife.bind(this, view));
if (getArguments() != null) {
presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY));
}
presenter.onStart(this, isPrimary);
}
return view;
}
@Override
protected void setUpOnViewCreated(View fragmentView) {
adapter.setAutoCollapseOnExpand(true);
adapter.setAutoScrollOnExpand(true);
adapter.expandItemsAtStartUp();
recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext()));
recyclerView.setAdapter(adapter);
refreshLayout.setColorSchemeResources(android.R.color.black);
refreshLayout.setOnRefreshListener(this);
}
@Override
public void updateAdapterList(List<AttendanceHeaderItem> headerItems) {
adapter.updateDataSet(headerItems);
}
@Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
if (presenter != null && getView() != null) {
presenter.onFragmentSelected(isSelected);
} else if (isSelected) {
isPrimary = true;
}
}
@Override
public void onRefresh() {
presenter.onRefresh();
}
@Override
public void onRefreshSuccess() {
onError(R.string.timetable_refresh_success);
}
@Override
public void hideRefreshingBar() {
refreshLayout.setRefreshing(false);
}
@Override
public void showProgressBar(boolean show) {
progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void showNoItem(boolean show) {
noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
public void setSelected(boolean selected) {
isSelected = selected;
}
@Override
public void onError(String message) {
if (getActivity() != null) {
Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager),
message, Snackbar.LENGTH_LONG).show();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(SAVED_KEY, isSelected);
super.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() {
isPrimary = false;
presenter.onDestroy();
super.onDestroyView();
}
}

View File

@ -0,0 +1,174 @@
package io.github.wulkanowy.ui.main.attendance;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
public class AttendanceTabPresenter extends BasePresenter<AttendanceTabContract.View>
implements AttendanceTabContract.Presenter, AsyncListeners.OnRefreshListener,
AsyncListeners.OnFirstLoadingListener {
private AbstractTask refreshTask;
private AbstractTask loadingTask;
private List<AttendanceHeaderItem> headerItems = new ArrayList<>();
private String date;
private boolean isFirstSight = false;
@Inject
AttendanceTabPresenter(RepositoryContract repository) {
super(repository);
}
@Override
public void onStart(AttendanceTabContract.View view, boolean isPrimary) {
super.onStart(view);
getView().showProgressBar(true);
getView().showNoItem(false);
onFragmentSelected(isPrimary);
}
@Override
public void onFragmentSelected(boolean isSelected) {
if (!isFirstSight && isSelected) {
isFirstSight = true;
loadingTask = new AbstractTask();
loadingTask.setOnFirstLoadingListener(this);
loadingTask.execute();
}
}
@Override
public void onRefresh() {
if (getView().isNetworkConnected()) {
refreshTask = new AbstractTask();
refreshTask.setOnRefreshListener(this);
refreshTask.execute();
} else {
getView().onNoNetworkError();
getView().hideRefreshingBar();
}
}
@Override
public void onDoInBackgroundRefresh() throws Exception {
syncData();
}
@Override
public void onCanceledRefreshAsync() {
if (isViewAttached()) {
getView().hideRefreshingBar();
}
}
@Override
public void onEndRefreshAsync(boolean result, Exception exception) {
if (result) {
loadingTask = new AbstractTask();
loadingTask.setOnFirstLoadingListener(this);
loadingTask.execute();
getView().onRefreshSuccess();
} else {
getView().onError(getRepository().getErrorLoginMessage(exception));
}
getView().hideRefreshingBar();
}
@Override
public void onDoInBackgroundLoading() throws Exception {
Week week = getRepository().getWeek(date);
if (week == null || !week.getIsAttendanceSynced()) {
syncData();
week = getRepository().getWeek(date);
}
List<Day> dayList = week.getDayList();
headerItems = new ArrayList<>();
boolean isEmptyWeek = true;
for (Day day : dayList) {
AttendanceHeaderItem headerItem = new AttendanceHeaderItem(day);
if (isEmptyWeek) {
isEmptyWeek = day.getAttendanceLessons().isEmpty();
}
List<AttendanceLesson> lessonList = day.getAttendanceLessons();
List<AttendanceSubItem> subItems = new ArrayList<>();
for (AttendanceLesson lesson : lessonList) {
lesson.setDescription(getRepository().getAttendanceLessonDescription(lesson));
subItems.add(new AttendanceSubItem(headerItem, lesson));
}
headerItem.setSubItems(subItems);
headerItem.setExpanded(false);
headerItems.add(headerItem);
}
if (isEmptyWeek) {
headerItems = new ArrayList<>();
}
}
@Override
public void onCanceledLoadingAsync() {
// do nothing
}
@Override
public void onEndLoadingAsync(boolean result, Exception exception) {
if (headerItems.isEmpty()) {
getView().showNoItem(true);
getView().updateAdapterList(null);
} else {
getView().updateAdapterList(headerItems);
getView().showNoItem(false);
}
getView().showProgressBar(false);
}
@Override
public void setArgumentDate(String date) {
this.date = date;
}
private void syncData() throws Exception {
getRepository().syncAttendance(date);
}
@Override
public void onDestroy() {
isFirstSight = false;
if (refreshTask != null) {
refreshTask.cancel(true);
refreshTask = null;
}
if (loadingTask != null) {
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
}
}

View File

@ -91,15 +91,16 @@ public class GradeHeaderItem
numberText.setText(resources.getQuantityString(R.plurals.numberOfGradesPlurals,
subItems.size(), subItems.size()));
averageText.setText(getGradesAverageString(item));
alertImage.setVisibility(isSubItemsRead(subItems) ? View.INVISIBLE : View.VISIBLE);
alertImage.setTag(item.getName());
alertImage.setVisibility(isSubItemsReadAndSaveAlertView(subItems)
? View.INVISIBLE : View.VISIBLE);
}
private boolean isSubItemsRead(List<GradesSubItem> subItems) {
private boolean isSubItemsReadAndSaveAlertView(List<GradesSubItem> subItems) {
boolean isRead = true;
for (GradesSubItem item : subItems) {
isRead = item.getGrade().getRead();
item.setSubjectAlertImage(alertImage);
}
return isRead;
}

View File

@ -71,14 +71,15 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
@Override
public void onDoInBackgroundRefresh() throws Exception {
getRepository().loginCurrentUser();
getRepository().syncSubjects();
getRepository().syncGrades();
}
@Override
public void onCanceledRefreshAsync() {
getView().hideRefreshingBar();
if (isViewAttached()) {
getView().hideRefreshingBar();
}
}
@Override
@ -138,13 +139,14 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
} else {
getView().updateAdapterList(headerItems);
getView().showNoItem(false);
listener.onFragmentIsReady();
}
listener.onFragmentIsReady();
}
@Override
public void onDestroy() {
super.onDestroy();
isFirstSight = false;
if (refreshTask != null) {
refreshTask.cancel(true);
refreshTask = null;
@ -153,5 +155,6 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.main.grades;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
@ -27,6 +26,8 @@ public class GradesSubItem
private static int numberOfNotReadGrade;
private View subjectAlertImage;
GradesSubItem(GradeHeaderItem header, Grade grade) {
super(header);
this.grade = grade;
@ -36,6 +37,10 @@ public class GradesSubItem
return grade;
}
public void setSubjectAlertImage(View subjectAlertImage) {
this.subjectAlertImage = subjectAlertImage;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -68,7 +73,7 @@ public class GradesSubItem
@Override
public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) {
holder.onBind(grade);
holder.onBind(grade, subjectAlertImage);
}
static class SubItemViewHolder extends FlexibleViewHolder {
@ -85,6 +90,8 @@ public class GradesSubItem
@BindView(R.id.grade_subitem_alert_image)
View alert;
private View subjectAlertImage;
private Context context;
private Grade item;
@ -96,8 +103,9 @@ public class GradesSubItem
view.setOnClickListener(this);
}
void onBind(Grade item) {
void onBind(Grade item, View subjectAlertImage) {
this.item = item;
this.subjectAlertImage = subjectAlertImage;
value.setText(item.getValue());
value.setBackgroundResource(item.getValueColor());
@ -119,8 +127,7 @@ public class GradesSubItem
numberOfNotReadGrade--;
if (numberOfNotReadGrade == 0) {
((Activity) context).findViewById(R.id.grade_fragment_container)
.findViewWithTag(item.getSubject()).setVisibility(View.INVISIBLE);
subjectAlertImage.setVisibility(View.INVISIBLE);
}
item.setIsNew(false);
item.setRead(true);

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.main.timetable;
import io.github.wulkanowy.di.annotations.PerActivity;
import io.github.wulkanowy.ui.base.BaseContract;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
public interface TimetableContract {

View File

@ -15,13 +15,13 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.github.wulkanowy.R;
import io.github.wulkanowy.data.db.dao.entities.Lesson;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
public class TimetableDialogFragment extends DialogFragment {
private static final String ARGUMENT_KEY = "Item";
private Lesson lesson;
private TimetableLesson lesson;
@BindView(R.id.timetable_dialog_lesson_value)
TextView lessonName;
@ -54,7 +54,7 @@ public class TimetableDialogFragment extends DialogFragment {
//empty constructor for fragment
}
public static TimetableDialogFragment newInstance(Lesson lesson) {
public static TimetableDialogFragment newInstance(TimetableLesson lesson) {
TimetableDialogFragment dialogFragment = new TimetableDialogFragment();
Bundle bundle = new Bundle();
@ -69,7 +69,7 @@ public class TimetableDialogFragment extends DialogFragment {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
lesson = (Lesson) getArguments().getSerializable(ARGUMENT_KEY);
lesson = (TimetableLesson) getArguments().getSerializable(ARGUMENT_KEY);
}
}

View File

@ -18,6 +18,7 @@ import io.github.wulkanowy.R;
import io.github.wulkanowy.di.component.FragmentComponent;
import io.github.wulkanowy.ui.base.BaseFragment;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
public class TimetableFragment extends BaseFragment implements TimetableContract.View, TabLayout.OnTabSelectedListener {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.main.timetable;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@ -10,6 +11,7 @@ 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;
@ -73,6 +75,15 @@ public class TimetableHeaderItem
@BindView(R.id.timetable_header_alert_image)
ImageView alert;
@BindView(R.id.timetable_header_free_name)
TextView freeName;
@BindColor(R.color.secondary_text)
int secondaryColor;
@BindColor(R.color.free_day)
int backgroundFreeDay;
HeaderViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
view.setOnClickListener(this);
@ -83,6 +94,14 @@ public class TimetableHeaderItem
dayName.setText(StringUtils.capitalize(item.getDayName()));
date.setText(item.getDate());
alert.setVisibility(isSubItemNewMovedInOrChanged(subItems) ? View.VISIBLE : View.INVISIBLE);
freeName.setVisibility(item.getIsFreeDay() ? View.VISIBLE : View.INVISIBLE);
freeName.setText(item.getFreeDayName());
if (item.getIsFreeDay()) {
((FrameLayout) getContentView()).setForeground(null);
getContentView().setBackgroundColor(backgroundFreeDay);
dayName.setTextColor(secondaryColor);
}
}
private boolean isSubItemNewMovedInOrChanged(List<TimetableSubItem> subItems) {

View File

@ -5,6 +5,8 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import io.github.wulkanowy.ui.main.TabsData;
public class TimetablePagerAdapter extends FragmentStatePagerAdapter {
private TabsData tabsData;

View File

@ -8,6 +8,7 @@ import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.ui.main.TabsData;
import io.github.wulkanowy.utils.TimeUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
@ -98,10 +99,12 @@ public class TimetablePresenter extends BasePresenter<TimetableContract.View>
@Override
public void onDestroy() {
super.onDestroy();
isFirstSight = false;
if (loadingTask != null) {
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
}
}

View File

@ -8,6 +8,9 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.List;
import butterknife.BindView;
@ -16,26 +19,41 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractSectionableItem;
import eu.davidea.viewholders.FlexibleViewHolder;
import io.github.wulkanowy.R;
import io.github.wulkanowy.data.db.dao.entities.Lesson;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
public class TimetableSubItem
extends AbstractSectionableItem<TimetableSubItem.SubItemViewHolder, TimetableHeaderItem> {
private Lesson lesson;
private TimetableLesson lesson;
public TimetableSubItem(TimetableHeaderItem header, Lesson lesson) {
public TimetableSubItem(TimetableHeaderItem header, TimetableLesson lesson) {
super(header);
this.lesson = lesson;
}
public Lesson getLesson() {
public TimetableLesson getLesson() {
return lesson;
}
@Override
public boolean equals(Object o) {
return this == o;
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimetableSubItem that = (TimetableSubItem) o;
return new EqualsBuilder()
.append(lesson, that.lesson)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(lesson)
.toHashCode();
}
@Override
@ -72,7 +90,7 @@ public class TimetableSubItem
private Context context;
private Lesson item;
private TimetableLesson item;
SubItemViewHolder(View view, FlexibleAdapter adapter) {
super(view, adapter);
@ -81,7 +99,7 @@ public class TimetableSubItem
view.setOnClickListener(this);
}
void onBind(Lesson lesson) {
void onBind(TimetableLesson lesson) {
item = lesson;
lessonName.setText(lesson.getSubject());

View File

@ -14,7 +14,11 @@ public interface TimetableTabContract {
void hideRefreshingBar();
void showNoItem(boolean show);
void showProgressBar(boolean show);
void setFreeWeekName(String text);
}
interface Presenter extends BaseContract.Presenter<View> {

View File

@ -9,6 +9,7 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
@ -25,7 +26,9 @@ import io.github.wulkanowy.ui.base.BaseFragment;
public class TimetableTabFragment extends BaseFragment implements TimetableTabContract.View,
SwipeRefreshLayout.OnRefreshListener {
private static final String ARGUMENT_KEY = "Date";
private static final String ARGUMENT_KEY = "date";
private static final String SAVED_KEY = "isSelected";
private boolean isPrimary = false;
@ -40,6 +43,12 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo
@BindView(R.id.timetable_tab_fragment_progress_bar)
View progressBar;
@BindView(R.id.timetable_tab_fragment_no_item_container)
View noItemView;
@BindView(R.id.timetable_tab_fragment_no_item_name)
TextView noItemName;
@Inject
TimetableTabContract.Presenter presenter;
@ -56,6 +65,14 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo
return fragmentTab;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
isSelected = savedInstanceState.getBoolean(SAVED_KEY, isSelected);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -104,6 +121,11 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo
}
}
@Override
public void setFreeWeekName(String text) {
noItemName.setText(text);
}
@Override
public void onRefresh() {
presenter.onRefresh();
@ -124,6 +146,11 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo
progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void showNoItem(boolean show) {
noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
public void setSelected(boolean selected) {
isSelected = selected;
}
@ -136,6 +163,12 @@ public class TimetableTabFragment extends BaseFragment implements TimetableTabCo
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(SAVED_KEY, isSelected);
super.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() {
isPrimary = false;

View File

@ -8,7 +8,7 @@ import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.Lesson;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.async.AbstractTask;
@ -26,6 +26,8 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
private String date;
private String freeWeekName;
private boolean isFirstSight = false;
@Inject
@ -37,6 +39,7 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
public void onStart(TimetableTabContract.View view, boolean isPrimary) {
super.onStart(view);
getView().showProgressBar(true);
getView().showNoItem(false);
onFragmentSelected(isPrimary);
}
@ -70,7 +73,9 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
@Override
public void onCanceledRefreshAsync() {
// do nothing
if (isViewAttached()) {
getView().hideRefreshingBar();
}
}
@Override
@ -91,7 +96,7 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
public void onDoInBackgroundLoading() throws Exception {
Week week = getRepository().getWeek(date);
if (week == null) {
if (week == null || !week.getIsTimetableSynced()) {
syncData();
week = getRepository().getWeek(date);
}
@ -100,14 +105,20 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
headerItems = new ArrayList<>();
boolean isFreeWeek = true;
for (Day day : dayList) {
TimetableHeaderItem headerItem = new TimetableHeaderItem(day);
List<Lesson> lessonList = day.getLessons();
if (isFreeWeek) {
isFreeWeek = day.getIsFreeDay();
}
List<TimetableLesson> lessonList = day.getTimetableLessons();
List<TimetableSubItem> subItems = new ArrayList<>();
for (Lesson lesson : lessonList) {
for (TimetableLesson lesson : lessonList) {
subItems.add(new TimetableSubItem(headerItem, lesson));
}
@ -115,6 +126,11 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
headerItem.setExpanded(false);
headerItems.add(headerItem);
}
if (isFreeWeek) {
freeWeekName = dayList.get(4).getFreeDayName();
headerItems = new ArrayList<>();
}
}
@Override
@ -124,7 +140,14 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
@Override
public void onEndLoadingAsync(boolean result, Exception exception) {
getView().updateAdapterList(headerItems);
if (headerItems.isEmpty()) {
getView().showNoItem(true);
getView().setFreeWeekName(freeWeekName);
getView().updateAdapterList(null);
} else {
getView().updateAdapterList(headerItems);
getView().showNoItem(false);
}
getView().showProgressBar(false);
}
@ -134,13 +157,11 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
}
private void syncData() throws Exception {
getRepository().loginCurrentUser();
getRepository().syncTimetable(date);
}
@Override
public void onDestroy() {
super.onDestroy();
isFirstSight = false;
if (refreshTask != null) {
@ -151,5 +172,6 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
loadingTask.cancel(true);
loadingTask = null;
}
super.onDestroy();
}
}

View File

@ -6,6 +6,7 @@ import javax.inject.Inject;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.LogUtils;
public class SplashPresenter extends BasePresenter<SplashContract.View>
implements SplashContract.Presenter {
@ -23,7 +24,12 @@ public class SplashPresenter extends BasePresenter<SplashContract.View>
if (getRepository().getCurrentUserId() == 0) {
getView().openLoginActivity();
} else {
getView().openMainActivity();
try {
getRepository().initLastUser();
getView().openMainActivity();
} catch (Exception e) {
LogUtils.error("An error occurred when the application was started", e);
}
}
}
}

View File

@ -4,10 +4,11 @@ package io.github.wulkanowy.utils;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.db.dao.entities.Lesson;
import io.github.wulkanowy.data.db.dao.entities.Subject;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.Week;
public final class DataObjectConverter {
@ -60,7 +61,7 @@ public final class DataObjectConverter {
return new Day()
.setDate(day.getDate())
.setDayName(day.getDayName())
.setFreeDay(day.isFreeDay())
.setIsFreeDay(day.isFreeDay())
.setFreeDayName(day.getFreeDayName());
}
@ -75,8 +76,8 @@ public final class DataObjectConverter {
return dayEntityList;
}
public static Lesson lessonToLessonEntity(io.github.wulkanowy.api.generic.Lesson lesson) {
return new Lesson()
public static TimetableLesson lessonToTimetableLessonEntity(io.github.wulkanowy.api.generic.Lesson lesson) {
return new TimetableLesson()
.setNumber(lesson.getNumber())
.setSubject(lesson.getSubject())
.setTeacher(lesson.getTeacher())
@ -94,12 +95,36 @@ public final class DataObjectConverter {
.setNewMovedInOrChanged(lesson.isNewMovedInOrChanged());
}
public static List<Lesson> lessonsToLessonsEntities(List<io.github.wulkanowy.api.generic.Lesson> lessonList) {
public static AttendanceLesson lessonToAttendanceLessonEntity(io.github.wulkanowy.api.generic.Lesson lesson) {
return new AttendanceLesson()
.setNumber(Integer.valueOf(lesson.getNumber()))
.setSubject(lesson.getSubject())
.setDate(lesson.getDate())
.setIsPresence(lesson.isPresence())
.setIsAbsenceUnexcused(lesson.isAbsenceUnexcused())
.setIsAbsenceExcused(lesson.isAbsenceExcused())
.setIsUnexcusedLateness(lesson.isUnexcusedLateness())
.setIsAbsenceForSchoolReasons(lesson.isAbsenceForSchoolReasons())
.setIsExcusedLateness(lesson.isExcusedLateness())
.setIsExemption(lesson.isExemption());
}
List<Lesson> lessonEntityList = new ArrayList<>();
public static List<TimetableLesson> lessonsToTimetableLessonsEntities(List<io.github.wulkanowy.api.generic.Lesson> lessonList) {
List<TimetableLesson> lessonEntityList = new ArrayList<>();
for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) {
lessonEntityList.add(lessonToLessonEntity(lesson));
lessonEntityList.add(lessonToTimetableLessonEntity(lesson));
}
return lessonEntityList;
}
public static List<AttendanceLesson> lessonsToAttendanceLessonsEntities(List<io.github.wulkanowy.api.generic.Lesson> lessonList) {
List<AttendanceLesson> lessonEntityList = new ArrayList<>();
for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) {
lessonEntityList.add(lessonToAttendanceLessonEntity(lesson));
}
return lessonEntityList;
}

View File

@ -19,4 +19,8 @@ public final class LogUtils {
public static void error(String message) {
Log.e(AppConstant.APP_NAME, message);
}
public static void info(String message) {
Log.i(AppConstant.APP_NAME, message);
}
}

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/attendance_dialog_relative_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:paddingEnd="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingStart="20dp"
android:paddingTop="10dp"
tools:ignore="UselessParent">
<TextView
android:id="@+id/attendance_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_gravity="start"
android:gravity="center_vertical"
android:maxLines="5"
android:minHeight="60dp"
android:minLines="2"
android:paddingTop="10dp"
android:text="@string/generic_dialog_details"
android:textIsSelectable="true"
android:textSize="20sp" />
<TextView
android:id="@+id/attendance_dialog_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_details"
android:layout_marginTop="10dp"
android:text="@string/attendance_dialog_subject"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
android:id="@+id/attendance_dialog_subject_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_subject"
android:layout_marginTop="3dp"
android:text="@string/generic_app_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/attendance_dialog_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_subject_value"
android:layout_marginTop="10dp"
android:text="@string/attendance_dialog_description"
android:textSize="17sp" />
<TextView
android:id="@+id/attendance_dialog_description_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_description"
android:layout_marginTop="3dp"
android:text="@string/generic_app_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/attendance_dialog_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_description_value"
android:layout_marginTop="10dp"
android:text="@string/attendance_dialog_date"
android:textSize="17sp" />
<TextView
android:id="@+id/attendance_dialog_date_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_date"
android:layout_marginTop="3dp"
android:text="@string/generic_app_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/attendance_dialog_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_date_value"
android:layout_marginTop="10dp"
android:text="@string/attendance_dialog_number"
android:textSize="17sp" />
<TextView
android:id="@+id/attendance_dialog_number_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_number"
android:layout_marginTop="3dp"
android:text="@string/generic_app_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<Button
android:id="@+id/attendance_dialog_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/attendance_dialog_number_value"
android:layout_marginTop="25dp"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:text="@string/generic_dialog_close"
android:textAllCaps="true"
android:textSize="15sp" />
</RelativeLayout>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,86 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_border"
android:foreground="?attr/selectableItemBackgroundBorderless">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<TextView
android:id="@+id/attendance_header_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginEnd="15dp"
android:layout_marginRight="15dp"
android:layout_toLeftOf="@id/attendance_header_alert_image"
android:layout_toStartOf="@+id/attendance_header_alert_image"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="19sp" />
<TextView
android:id="@+id/attendance_header_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_header_day"
android:layout_marginTop="5dp"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/secondary_text"
android:textSize="14sp" />
<TextView
android:id="@+id/attendance_header_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/attendance_header_date"
android:layout_toRightOf="@+id/attendance_header_date"
android:layout_below="@+id/attendance_header_day"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/secondary_text"
android:textSize="14sp" />
<TextView
android:id="@+id/attendance_header_free_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="180dp"
android:layout_marginStart="180dp"
android:gravity="end"
android:maxLines="2"
android:text="@string/attendance_no_entries"
android:textColor="@color/secondary_text"
android:textSize="16sp" />
<ImageView
android:id="@+id/attendance_header_alert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
app:srcCompat="@drawable/ic_exclamation_24dp"
tools:ignore="contentDescription" />
</RelativeLayout>
</FrameLayout>

View File

@ -0,0 +1,78 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/attendance_subItem_cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
android:layout_marginEnd="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginStart="5dp"
android:foreground="?attr/selectableItemBackgroundBorderless"
card_view:cardElevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
android:layout_marginEnd="7dp"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"
android:layout_marginStart="7dp"
android:layout_marginTop="7dp">
<TextView
android:id="@+id/attendance_subItem_number"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="center"
android:maxLength="2"
android:text="0"
android:textSize="32sp"
tool:ignore="all"/>
<TextView
android:id="@+id/attendance_subItem_lesson"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginEnd="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="40dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@+id/attendance_subItem_number"
android:layout_toRightOf="@+id/attendance_subItem_number"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="17sp"
tool:ignore="all"/>
<TextView
android:id="@+id/attendance_subItem_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/attendance_subItem_number"
android:layout_alignLeft="@id/attendance_subItem_lesson"
android:layout_alignStart="@id/attendance_subItem_lesson"
android:maxLines="1"
android:text="@string/grades_text"
android:textColor="@color/secondary_text"
android:textSize="12sp" />
<ImageView
android:id="@+id/attendance_subItem_alert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
app:srcCompat="@drawable/ic_exclamation_24dp"
tool:ignore="contentDescription"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -1,36 +1,25 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="io.github.wulkanowy.ui.main.attendance.AttendanceFragment">
android:layout_alignParentBottom="true"
android:id="@+id/attendance_fragment_container">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
android:layout_height="match_parent">
<ImageView
android:id="@+id/wrench_under_construction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView3"
android:layout_centerHorizontal="true"
android:layout_marginTop="45dp"
android:contentDescription="@string/activity_dashboard_text"
android:minHeight="100dp"
android:minWidth="100dp"
app:srcCompat="@drawable/ic_wrench_24dp" />
<TextView
<android.support.design.widget.TabLayout
android:id="@+id/attendance_fragment_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="47dp"
android:text="@string/activity_under_construction"
android:textSize="17sp"
android:gravity="center"
android:textAlignment="center"
android:id="@+id/textView3" />
</RelativeLayout>
app:tabMinWidth="125dp"
app:tabMode="scrollable"/>
<android.support.v4.view.ViewPager
android:id="@+id/attendance_fragment_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/attendance_fragment_tab_layout" />
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>

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