1
0

Compare commits

...

15 Commits
0.4.2 ... 0.4.4

48 changed files with 347 additions and 210 deletions

View File

@ -6,7 +6,7 @@
[![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/)
[![Scrutinizer](https://img.shields.io/scrutinizer/g/wulkanowy/wulkanowy.svg?style=flat-square)](https://scrutinizer-ci.com/g/wulkanowy/wulkanowy/?branch=master)
[![Bintray](https://img.shields.io/bintray/v/wulkanowy/wulkanowy/api.svg?style=flat-square)](https://bintray.com/wulkanowy/wulkanowy/api)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/JMG2rhJ)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[Pobierz wersję beta](https://play.google.com/store/apps/details?id=io.github.wulkanowy&utm_source=vcs)

View File

@ -63,7 +63,7 @@ public class Client {
private boolean isLoggedIn() {
return getCookies().size() > 0 && lastSuccessRequest != null &&
29 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime());
5 > TimeUnit.MILLISECONDS.toMinutes(new Date().getTime() - lastSuccessRequest.getTime());
}
@ -118,7 +118,7 @@ public class Client {
this.cookies.addItems(response.cookies());
Document doc = checkForErrors(response.parse());
Document doc = checkForErrors(response.parse(), response.statusCode());
if (loginBefore) {
lastSuccessRequest = new Date();
@ -142,9 +142,9 @@ public class Client {
this.cookies.addItems(response.cookies());
response.bufferUp(); // fixes cert parsing issues
response.bufferUp(); // fixes cert parsing issues #109
return checkForErrors(response.parse());
return checkForErrors(response.parse(), response.statusCode());
}
public String getJsonStringByUrl(String url) throws IOException, VulcanException {
@ -182,7 +182,7 @@ public class Client {
return response.body();
}
Document checkForErrors(Document doc) throws VulcanException {
Document checkForErrors(Document doc, int code) throws VulcanException {
lastSuccessRequest = null;
String title = doc.select("title").text();
@ -196,7 +196,7 @@ public class Client {
}
if ("Błąd strony".equals(title)) {
throw new VulcanException("Nieznany błąd");
throw new NotLoggedInErrorException(title + " " + doc.selectFirst("p, body") + ", status: " + code);
}
return doc;

View File

@ -65,12 +65,12 @@ public class StudentAndParent implements SnP {
return getBaseUrl();
}
// get url to uonetplus-opiekun.vulcan.net.pl
// get url to uonetplus-opiekun.fakelog.cf
Document startPage = client.getPageByUrl(START_PAGE_URL);
Element studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a").first();
if (null == studentTileLink) {
throw new NotLoggedInErrorException("You are probably not logged in. Force login");
throw new VulcanException("Na pewno używasz konta z dostępem do Witryny ucznia i rodzica?");
}
String snpPageUrl = studentTileLink.attr("href");
@ -84,7 +84,7 @@ public class StudentAndParent implements SnP {
String[] path = snpPageUrl.split(client.getHost())[1].split("/");
if (5 != path.length) {
throw new NotLoggedInErrorException("You are probably not logged in");
throw new NotLoggedInErrorException("You are probably not logged in " + snpPageUrl);
}
return path[2];

View File

@ -63,7 +63,7 @@ public class AttendanceTable {
for (int i = 1; i < size; i++) {
Lesson lesson = new Lesson();
lesson.setDate(days.get(i - 1).getDate());
lesson.setNumber(hours.get(0).text());
lesson.setNumber(Integer.valueOf(hours.get(0).text()));
addLessonDetails(lesson, hours.get(i));

View File

@ -2,7 +2,7 @@ package io.github.wulkanowy.api.generic;
public class Lesson {
private String number = "";
private int number = 0;
private String subject = "";
@ -48,12 +48,13 @@ public class Lesson {
private boolean isExemption = false;
public String getNumber() {
public int getNumber() {
return number;
}
public void setNumber(String number) {
public Lesson setNumber(int number) {
this.number = number;
return this;
}
public String getSubject() {

View File

@ -9,6 +9,7 @@ import org.jsoup.select.Elements;
import java.io.IOException;
import io.github.wulkanowy.api.Client;
import io.github.wulkanowy.api.NotLoggedInErrorException;
import io.github.wulkanowy.api.VulcanException;
public class Login {
@ -27,6 +28,10 @@ public class Login {
public String login(String email, String password, String symbol) throws VulcanException, IOException {
Document certDoc = sendCredentials(email, password);
if ("Błąd".equals(certDoc.title())) {
throw new NotLoggedInErrorException(certDoc.selectFirst("body").text());
}
return sendCertificate(certDoc, symbol);
}
@ -36,29 +41,29 @@ public class Login {
{"Password", password}
};
String nextUrl = LOGIN_PAGE_URL;
Document loginPage = client.getPageByUrl(nextUrl, false);
Document nextDoc = sendCredentialsData(credentials, LOGIN_PAGE_URL);
Element formFirst = loginPage.select("#form1").first();
if (null != formFirst) { // on adfs login
Document formSecond = client.postPageByUrl(
formFirst.attr("abs:action"),
getFormStateParams(formFirst, "", "")
);
credentials = getFormStateParams(formSecond, email, password);
nextUrl = formSecond.select("#form1").first().attr("abs:action");
} else if (!"Logowanie".equals(loginPage.select("#h1Default").text())) {
throw new VulcanException("Expected login page, got page with title: " + loginPage.title());
}
Document html = client.postPageByUrl(nextUrl, credentials);
Element errorMessage = html.select(".ErrorMessage, #ErrorTextLabel").first();
Element errorMessage = nextDoc.selectFirst(".ErrorMessage, #ErrorTextLabel");
if (null != errorMessage) {
throw new BadCredentialsException(errorMessage.text());
}
return html;
return nextDoc;
}
private Document sendCredentialsData(String[][] credentials, String nextUrl) throws IOException, VulcanException {
Element formFirst = client.getPageByUrl(nextUrl, false).selectFirst("#form1");
if (null != formFirst) { // only on adfs login
Document formSecond = client.postPageByUrl(
formFirst.attr("abs:action"),
getFormStateParams(formFirst, "", "")
);
credentials = getFormStateParams(formSecond, credentials[0][1], credentials[1][1]);
nextUrl = formSecond.selectFirst("#form1").attr("abs:action");
}
return client.postPageByUrl(nextUrl, credentials);
}
private String[][] getFormStateParams(Element form, String email, String password) {
@ -77,13 +82,7 @@ public class Login {
}
String sendCertificate(Document doc, String defaultSymbol) throws IOException, VulcanException {
String certificate = doc.select("input[name=wresult]").val();
if ("".equals(certificate)) {
throw new VulcanException("Expected certificate, got empty string. Page title: " + doc.title());
}
client.setSymbol(findSymbol(defaultSymbol, certificate));
client.setSymbol(findSymbol(defaultSymbol, doc.select("input[name=wresult]").val()));
Document targetDoc = sendCertData(doc);
String title = targetDoc.title();
@ -106,10 +105,6 @@ public class Login {
private Document sendCertData(Document doc) throws IOException, VulcanException {
String url = doc.select("form[name=hiddenform]").attr("action");
if (!doc.title().equals("Working...")) {
throw new VulcanException("Expected certificate page, got page with title: " + doc.title());
}
return client.postPageByUrl(url.replaceFirst("Default", "{symbol}"), new String[][]{
{"wa", "wsignin1.0"},
{"wresult", doc.select("input[name=wresult]").val()},

View File

@ -81,7 +81,7 @@ public class Timetable {
lesson.setStartTime(startEndEnd[0]);
lesson.setEndTime(startEndEnd[1]);
lesson.setDate(days.get(i - 2).getDate());
lesson.setNumber(hours.get(0).text());
lesson.setNumber(Integer.valueOf(hours.get(0).text()));
addLessonDetails(lesson, hours.get(i).select("div"));
@ -98,8 +98,10 @@ public class Timetable {
addLessonInfoFromElement(lesson, e.first());
break;
case 2:
Element span = e.last().select("span").first();
if (span.hasClass(LessonTypes.CLASS_MOVED_OR_CANCELED)) {
Element span = e.last().selectFirst("span");
if (null == span) {
addLessonInfoFromElement(lesson, e.first());
} else if (span.hasClass(LessonTypes.CLASS_MOVED_OR_CANCELED)) {
lesson.setNewMovedInOrChanged(true);
lesson.setDescription("poprzednio: " + getLessonAndGroupInfoFromSpan(span)[0]);
addLessonInfoFromElement(lesson, e.first());
@ -120,7 +122,9 @@ public class Timetable {
Elements warn = e.select(".uwaga-panel");
if (!warn.isEmpty()) {
e.select(".x-treelabel-rlz").last().text("(" + warn.text() + ")");
e.select("span").last()
.addClass("x-treelabel-rlz")
.text(warn.text());
e.remove(1);
}
}

View File

@ -25,7 +25,7 @@ public class ClientTest {
Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-success.html"));
Assert.assertEquals(doc, client.checkForErrors(doc));
Assert.assertEquals(doc, client.checkForErrors(doc, 200));
}
@Test(expected = VulcanOfflineException.class)
@ -34,7 +34,7 @@ public class ClientTest {
Document doc = Jsoup.parse(getFixtureAsString("login/PrzerwaTechniczna.html"));
client.checkForErrors(doc);
client.checkForErrors(doc, 200);
}
@Test(expected = NotLoggedInErrorException.class)
@ -43,7 +43,7 @@ public class ClientTest {
Document doc = Jsoup.parse(getFixtureAsString("login/Logowanie-notLoggedIn.html"));
client.checkForErrors(doc);
client.checkForErrors(doc, 200);
}
@Test

View File

@ -53,7 +53,7 @@ public class StudentAndParentTest {
snp.getSnpHomePageUrl());
}
@Test(expected = NotLoggedInErrorException.class)
@Test(expected = VulcanException.class)
public void getSnpPageUrlWithWrongPage() throws Exception {
Document wrongPageDocument = Jsoup.parse(
FixtureHelper.getAsString(getClass().getResourceAsStream("OcenyWszystkie-semester.html"))

View File

@ -83,11 +83,11 @@ public class TimetableTest extends StudentAndParentTestCase {
@Test
public void getLessonNumberTest() throws Exception {
Assert.assertEquals("2", std.getWeekTable().getDay(0).getLesson(1).getNumber());
Assert.assertEquals("5", std.getWeekTable().getDay(2).getLesson(4).getNumber());
Assert.assertEquals("0", full.getWeekTable().getDay(0).getLesson(0).getNumber());
Assert.assertEquals("13", full.getWeekTable().getDay(4).getLesson(13).getNumber());
Assert.assertEquals("3", holidays.getWeekTable().getDay(3).getLesson(3).getNumber());
Assert.assertEquals(2, std.getWeekTable().getDay(0).getLesson(1).getNumber());
Assert.assertEquals(5, std.getWeekTable().getDay(2).getLesson(4).getNumber());
Assert.assertEquals(0, full.getWeekTable().getDay(0).getLesson(0).getNumber());
Assert.assertEquals(13, full.getWeekTable().getDay(4).getLesson(13).getNumber());
Assert.assertEquals(3, holidays.getWeekTable().getDay(3).getLesson(3).getNumber());
}
@Test
@ -105,6 +105,7 @@ public class TimetableTest extends StudentAndParentTestCase {
Assert.assertEquals("Zajęcia techniczne", std.getWeekTable().getDay(2).getLesson(4).getSubject());
Assert.assertEquals("Wychowanie fizyczne", std.getWeekTable().getDay(1).getLesson(1).getSubject());
Assert.assertEquals("Język angielski", full.getWeekTable().getDay(0).getLesson(1).getSubject());
Assert.assertEquals("Wychowanie fizyczne", full.getWeekTable().getDay(0).getLesson(9).getSubject());
Assert.assertEquals("Wychowanie do życia w rodzinie", full.getWeekTable().getDay(2).getLesson(0).getSubject());
Assert.assertEquals("Wychowanie fizyczne", full.getWeekTable().getDay(3).getLesson(1).getSubject());
Assert.assertEquals("Uroczyste zakończenie roku szkolnego", full.getWeekTable().getDay(4).getLesson(0).getSubject());
@ -155,6 +156,7 @@ public class TimetableTest extends StudentAndParentTestCase {
Assert.assertEquals("poprzednio: Wychowanie fizyczne", full.getWeekTable().getDay(4).getLesson(2).getDescription());
Assert.assertEquals("egzamin", full.getWeekTable().getDay(3).getLesson(0).getDescription());
Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getDescription());
Assert.assertEquals("opis w uwadze bez klasy w spanie", full.getWeekTable().getDay(4).getLesson(4).getDescription());
Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getDescription());
}

View File

@ -319,7 +319,16 @@
<span></span>
</div>
</td>
<td></td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-inv">Język polski</span>
<span class="x-treelabel-ppl x-treelabel-inv"> </span>
<span class="x-treelabel-ppl x-treelabel-inv">16</span>
<span class="random-class">(oddział nieobecny)</span>
</div>
<button type="button" class="uwaga-btn">Uwaga</button>
<div class="uwaga-panel">opis w uwadze bez klasy w spanie</div>
</td>
</tr>
<tr>
<td>5</td>
@ -461,7 +470,18 @@
<tr>
<td>9</td>
<td>14:50 15:35</td>
<td></td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-zas">Wychowanie fizyczne [zaw2]</span>
<span class="x-treelabel-ppl x-treelabel-zas"></span>
<span class="x-treelabel-ppl x-treelabel-zas"></span>
<span class="x-treelabel-ppl x-treelabel-zas">G3</span>
<span class="x-treelabel-rlz">(przeniesiona z lekcji 7, 01.12.2017)</span>
</div>
<div>
<!--<span>WTF</span>-->
</div>
</td>
<td>
<div>
<span class="x-treelabel-ppl x-treelabel-inv">Język niemiecki [J1]</span>

View File

@ -9,7 +9,7 @@ buildscript {
classpath "org.greenrobot:greendao-gradle-plugin:$greenDaoGradle"
classpath "io.fabric.tools:gradle:$fabricGradle"
classpath "com.google.gms:oss-licenses:0.9.2"
classpath 'com.github.triplet.gradle:play-publisher:1.2.0'
classpath "com.github.triplet.gradle:play-publisher:$playPublisher"
}
}
@ -41,8 +41,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 26
versionCode 10
versionName "0.4.2"
versionCode 12
versionName "0.4.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
@ -91,7 +91,7 @@ play {
}
greendao {
schemaVersion 27
schemaVersion 28
generateTests = true
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy;
import android.app.Application;
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.core.CrashlyticsCore;
import com.jakewharton.threetenabp.AndroidThreeTen;
@ -61,9 +62,12 @@ public class WulkanowyApp extends Application {
private void initializeFabric() {
Fabric.with(new Fabric.Builder(this)
.kits(new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build())
.kits(
new Crashlytics.Builder()
.core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build(),
new Answers()
)
.debuggable(BuildConfig.DEBUG)
.build());
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.db.dao.entities.Subject;
import io.github.wulkanowy.data.db.dao.entities.Symbol;
import io.github.wulkanowy.data.db.dao.entities.Week;
public interface DbContract {
@ -20,6 +21,8 @@ public interface DbContract {
long getCurrentSymbolId();
Symbol getCurrentSymbol();
long getCurrentDiaryId();
long getSemesterId(int name);

View File

@ -19,6 +19,7 @@ import io.github.wulkanowy.data.db.dao.entities.DaoMaster;
import io.github.wulkanowy.data.db.dao.migrations.Migration23;
import io.github.wulkanowy.data.db.dao.migrations.Migration26;
import io.github.wulkanowy.data.db.dao.migrations.Migration27;
import io.github.wulkanowy.data.db.dao.migrations.Migration28;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
import io.github.wulkanowy.di.annotations.ApplicationContext;
import io.github.wulkanowy.di.annotations.DatabaseInfo;
@ -78,6 +79,7 @@ public class DbHelper extends DaoMaster.OpenHelper {
migrations.add(new Migration23());
migrations.add(new Migration26());
migrations.add(new Migration27());
migrations.add(new Migration28());
// Sorting just to be safe, in case other people add migrations in the wrong order.
Comparator<Migration> migrationComparator = new Comparator<Migration>() {

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.data.db.dao.entities.Semester;
import io.github.wulkanowy.data.db.dao.entities.SemesterDao;
import io.github.wulkanowy.data.db.dao.entities.StudentDao;
import io.github.wulkanowy.data.db.dao.entities.Subject;
import io.github.wulkanowy.data.db.dao.entities.Symbol;
import io.github.wulkanowy.data.db.dao.entities.SymbolDao;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.data.db.dao.entities.WeekDao;
@ -57,10 +58,15 @@ public class DbRepository implements DbContract {
}
@Override
public long getCurrentSymbolId() {
public Symbol getCurrentSymbol() {
return daoSession.getSymbolDao().queryBuilder().where(
SymbolDao.Properties.UserId.eq(sharedPref.getCurrentUserId())
).unique().getId();
).unique();
}
@Override
public long getCurrentSymbolId() {
return getCurrentSymbol().getId();
}
@Override

View File

@ -5,6 +5,7 @@ import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Index;
import org.greenrobot.greendao.annotation.OrderBy;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.ToMany;
@ -35,9 +36,11 @@ public class Day {
@Property(nameInDb = "free_day_name")
private String freeDayName = "";
@OrderBy("number ASC")
@ToMany(referencedJoinProperty = "dayId")
private List<TimetableLesson> timetableLessons;
@OrderBy("number ASC")
@ToMany(referencedJoinProperty = "dayId")
private List<AttendanceLesson> attendanceLessons;
@ -50,9 +53,7 @@ public class Day {
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/**
* Used for active entity operations.
*/
/** Used for active entity operations. */
@Generated(hash = 312167767)
private transient DayDao myDao;
@ -185,6 +186,36 @@ public class Day {
attendanceLessons = null;
}
/**
* To-many relationship, resolved on first access (and after reset).
* Changes to to-many relations are not persisted, make changes to the target entity.
*/
@Generated(hash = 1231531946)
public List<Exam> getExams() {
if (exams == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
ExamDao targetDao = daoSession.getExamDao();
List<Exam> examsNew = targetDao._queryDay_Exams(id);
synchronized (this) {
if (exams == null) {
exams = examsNew;
}
}
}
return exams;
}
/**
* Resets a to-many relationship, making the next get call to query for a fresh result.
*/
@Generated(hash = 841969952)
public synchronized void resetExams() {
exams = null;
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.
@ -221,40 +252,12 @@ public class Day {
myDao.update(this);
}
/**
* To-many relationship, resolved on first access (and after reset).
* Changes to to-many relations are not persisted, make changes to the target entity.
*/
@Generated(hash = 1231531946)
public List<Exam> getExams() {
if (exams == null) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
ExamDao targetDao = daoSession.getExamDao();
List<Exam> examsNew = targetDao._queryDay_Exams(id);
synchronized (this) {
if (exams == null) {
exams = examsNew;
}
}
}
return exams;
}
/**
* Resets a to-many relationship, making the next get call to query for a fresh result.
*/
@Generated(hash = 841969952)
public synchronized void resetExams() {
exams = null;
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 1409317752)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getDayDao() : null;
}
}

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.data.db.dao.entities;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.greenrobot.greendao.DaoException;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
@ -12,7 +14,7 @@ import java.io.Serializable;
@Entity(
nameInDb = "TimetableLessons",
active = true,
indexes = {@Index(value = "dayId,date,startTime,endTime", unique = true)}
indexes = {@Index(value = "dayId,date,number,startTime,endTime", unique = true)}
)
public class TimetableLesson implements Serializable {
@ -25,7 +27,7 @@ public class TimetableLesson implements Serializable {
private Long dayId;
@Property(nameInDb = "number")
private String number;
private int number = 0;
@Property(nameInDb = "subject")
private String subject = "";
@ -75,18 +77,15 @@ public class TimetableLesson implements Serializable {
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/**
* Used for active entity operations.
*/
/** Used for active entity operations. */
@Generated(hash = 1119360138)
private transient TimetableLessonDao myDao;
@Generated(hash = 1955911128)
public TimetableLesson(Long id, Long dayId, String number, String subject,
String teacher, String room, String description, String group,
String startTime, String endTime, String date, boolean empty,
boolean divisionIntoGroups, boolean planning, boolean realized,
boolean movedOrCanceled, boolean newMovedInOrChanged) {
@Generated(hash = 1665905034)
public TimetableLesson(Long id, Long dayId, int number, String subject, String teacher,
String room, String description, String group, String startTime, String endTime,
String date, boolean empty, boolean divisionIntoGroups, boolean planning,
boolean realized, boolean movedOrCanceled, boolean newMovedInOrChanged) {
this.id = id;
this.dayId = dayId;
this.number = number;
@ -109,7 +108,7 @@ public class TimetableLesson implements Serializable {
@Generated(hash = 1878030142)
public TimetableLesson() {
}
public Long getId() {
return this.id;
}
@ -127,11 +126,11 @@ public class TimetableLesson implements Serializable {
return this;
}
public String getNumber() {
public int getNumber() {
return this.number;
}
public TimetableLesson setNumber(String number) {
public TimetableLesson setNumber(int number) {
this.number = number;
return this;
}
@ -262,6 +261,32 @@ public class TimetableLesson implements Serializable {
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimetableLesson lesson = (TimetableLesson) o;
return new EqualsBuilder()
.append(number, lesson.number)
.append(startTime, lesson.startTime)
.append(endTime, lesson.endTime)
.append(date, lesson.date)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(number)
.append(startTime)
.append(endTime)
.append(date)
.toHashCode();
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.db.dao.migrations;
import org.greenrobot.greendao.database.Database;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.data.db.dao.DbHelper;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
public class Migration28 implements DbHelper.Migration {
@Override
public Integer getVersion() {
return 28;
}
@Override
public void runMigration(final Database db, final SharedPrefContract sharedPref, final Vulcan vulcan) throws Exception {
throw new Exception("No migrations");
}
}

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.data.sync;
import android.content.Context;
import org.greenrobot.greendao.database.Database;
import java.io.IOException;
import java.util.List;
@ -11,6 +13,7 @@ import javax.inject.Singleton;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.api.VulcanException;
import io.github.wulkanowy.data.db.dao.entities.Account;
import io.github.wulkanowy.data.db.dao.entities.DaoMaster;
import io.github.wulkanowy.data.db.dao.entities.DaoSession;
import io.github.wulkanowy.data.db.dao.entities.Diary;
import io.github.wulkanowy.data.db.dao.entities.DiaryDao;
@ -49,6 +52,8 @@ public class AccountSync {
public void registerUser(String email, String password, String symbol)
throws VulcanException, IOException, CryptoException {
clearUserData();
vulcan.setCredentials(email, password, symbol, null, null, null);
daoSession.getDatabase().beginTransaction();
@ -118,12 +123,12 @@ public class AccountSync {
daoSession.getSemesterDao().insertInTx(semesterList);
}
public void initLastUser() throws IOException, CryptoException {
public void initLastUser() throws CryptoException {
long userId = sharedPref.getCurrentUserId();
if (userId == 0) {
throw new IOException("Can't find saved user");
throw new NotRegisteredUserException("Can't find user id in SharedPreferences");
}
LogUtils.debug("Initialization current user id=" + userId);
@ -152,4 +157,11 @@ public class AccountSync {
diary.getValue()
);
}
private void clearUserData() {
Database database = daoSession.getDatabase();
DaoMaster.dropAllTables(database, true);
DaoMaster.createAllTables(database, true);
sharedPref.setCurrentUserId(0);
}
}

View File

@ -135,6 +135,7 @@ public class ExamsSync {
.where(ExamDao.Properties.DayId.eq(dayId),
ExamDao.Properties.EntryDate.eq(examApi.getEntryDate()),
ExamDao.Properties.SubjectAndGroup.eq(examApi.getSubjectAndGroup()),
ExamDao.Properties.Type.eq(examApi.getType()),
ExamDao.Properties.Teacher.eq(examApi.getTeacher()))
.unique();
}

View File

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

View File

@ -1,5 +1,7 @@
package io.github.wulkanowy.data.sync;
import org.apache.commons.collections4.CollectionUtils;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
@ -125,6 +127,16 @@ public class TimetableSync {
List<TimetableLesson> lessonsFromApiEntities = DataObjectConverter
.lessonsToTimetableLessonsEntities(lessons);
List<TimetableLesson> lessonsFromDbEntities = getLessonsFromDb(dayId);
if (!lessonsFromDbEntities.isEmpty()) {
List<TimetableLesson> lessonToRemove = new ArrayList<>(CollectionUtils.removeAll(lessonsFromDbEntities, lessonsFromApiEntities));
for (TimetableLesson timetableLesson : lessonToRemove) {
daoSession.getTimetableLessonDao().delete(timetableLesson);
}
}
for (TimetableLesson apiLessonEntity : lessonsFromApiEntities) {
TimetableLesson lessonFromDb = getLessonFromDb(apiLessonEntity, dayId);
@ -148,4 +160,8 @@ public class TimetableSync {
TimetableLessonDao.Properties.EndTime.eq(apiEntity.getEndTime()))
.unique();
}
private List<TimetableLesson> getLessonsFromDb(long dayId) {
return daoSession.getDayDao().load(dayId).getTimetableLessons();
}
}

View File

@ -24,6 +24,7 @@ import io.github.wulkanowy.R;
import io.github.wulkanowy.WulkanowyApp;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.sync.NotRegisteredUserException;
import io.github.wulkanowy.services.notifies.GradeNotify;
import io.github.wulkanowy.ui.main.MainActivity;
import io.github.wulkanowy.utils.LogUtils;
@ -74,9 +75,12 @@ public class SyncJob extends SimpleJobService {
showNotification();
}
return JobService.RESULT_SUCCESS;
} catch (NotRegisteredUserException e) {
logError(e);
stop(getApplicationContext());
return JobService.RESULT_FAIL_NORETRY;
} catch (Exception e) {
Crashlytics.logException(e);
LogUtils.error("During background synchronization an error occurred", e);
logError(e);
return JobService.RESULT_FAIL_RETRY;
}
}
@ -116,4 +120,9 @@ public class SyncJob extends SimpleJobService {
gradeList.size(), gradeList.size());
}
}
private void logError(Exception e) {
Crashlytics.logException(e);
LogUtils.error("During background synchronization an error occurred", e);
}
}

View File

@ -11,6 +11,7 @@ import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.data.RepositoryContract;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.AppConstant;
import io.github.wulkanowy.utils.FabricUtils;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
@ -84,6 +85,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
@Override
public void onEndAsync(boolean success, Exception exception) {
if (success) {
FabricUtils.logRegister(true, getRepository().getDbRepo().getCurrentSymbol().getSymbol());
getView().openMainActivity();
return;
} else if (exception instanceof BadCredentialsException) {
@ -93,6 +95,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
getView().setErrorSymbolRequired();
getView().showSoftInput();
} else {
FabricUtils.logRegister(false, symbol);
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
}

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.entities.AttendanceLesson;
import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.FabricUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
@ -88,6 +89,8 @@ public class AttendanceTabPresenter extends BasePresenter<AttendanceTabContract.
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
}
getView().hideRefreshingBar();
FabricUtils.logRefresh("Attendance", result, date);
}
@Override

View File

@ -135,7 +135,7 @@ public class ExamsTabFragment extends BaseFragment implements ExamsTabContract.V
@Override
public void onDestroyView() {
super.onDestroyView();
presenter.onDestroy();
super.onDestroyView();
}
}

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.Exam;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.FabricUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
@ -93,6 +94,8 @@ public class ExamsTabPresenter extends BasePresenter<ExamsTabContract.View>
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
}
getView().hideRefreshingBar();
FabricUtils.logRefresh("Exams", result, date);
}
@Override

View File

@ -40,5 +40,7 @@ public interface GradesContract {
void onStart(View view, OnFragmentIsReadyListener listener);
void onSemesterChange(int which);
void onSemesterSwitchActive();
}
}

View File

@ -80,6 +80,7 @@ public class GradesFragment extends BaseFragment implements GradesContract.View
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_filter) {
presenter.onSemesterSwitchActive();
CharSequence[] items = new CharSequence[]{
getResources().getString(R.string.semester_text, 1),
getResources().getString(R.string.semester_text, 2),

View File

@ -1,5 +1,10 @@
package io.github.wulkanowy.ui.main.grades;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import org.threeten.bp.LocalDate;
import java.util.ArrayList;
import java.util.List;
@ -10,6 +15,7 @@ import io.github.wulkanowy.data.db.dao.entities.Grade;
import io.github.wulkanowy.data.db.dao.entities.Subject;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener;
import io.github.wulkanowy.utils.FabricUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
@ -53,12 +59,19 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
}
}
@Override
public void onSemesterSwitchActive() {
cancelAsyncTasks();
}
@Override
public void onSemesterChange(int which) {
semesterName = which + 1;
getView().setCurrentSemester(which);
reloadGrades();
Answers.getInstance().logCustom(new CustomEvent("Semester change")
.putCustomAttribute("Name", semesterName));
}
private void reloadGrades() {
@ -100,8 +113,8 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
}
@Override
public void onEndRefreshAsync(boolean success, Exception exception) {
if (success) {
public void onEndRefreshAsync(boolean result, Exception exception) {
if (result) {
reloadGrades();
int numberOfNewGrades = getRepository().getDbRepo().getNewGrades(semesterName).size();
@ -115,6 +128,8 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
}
getView().hideRefreshingBar();
FabricUtils.logRefresh("Grades", result, LocalDate.now().toString());
}
@Override
@ -156,10 +171,7 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
listener.onFragmentIsReady();
}
@Override
public void onDestroy() {
isFirstSight = false;
private void cancelAsyncTasks() {
if (refreshTask != null) {
refreshTask.cancel(true);
refreshTask = null;
@ -168,6 +180,12 @@ public class GradesPresenter extends BasePresenter<GradesContract.View>
loadingTask.cancel(true);
loadingTask = null;
}
}
@Override
public void onDestroy() {
isFirstSight = false;
cancelAsyncTasks();
super.onDestroy();
}
}

View File

@ -106,7 +106,7 @@ public class TimetableSubItem
lessonName.setText(lesson.getSubject());
lessonTime.setText(getLessonTimeString());
numberOfLesson.setText(lesson.getNumber());
numberOfLesson.setText(String.valueOf(lesson.getNumber()));
room.setText(getRoomString());
alert.setVisibility(lesson.getMovedOrCanceled() || lesson.getNewMovedInOrChanged()
? View.VISIBLE : View.INVISIBLE);

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.main.timetable;
import java.util.ArrayList;
import java.util.List;
@ -11,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.entities.Day;
import io.github.wulkanowy.data.db.dao.entities.TimetableLesson;
import io.github.wulkanowy.data.db.dao.entities.Week;
import io.github.wulkanowy.ui.base.BasePresenter;
import io.github.wulkanowy.utils.FabricUtils;
import io.github.wulkanowy.utils.async.AbstractTask;
import io.github.wulkanowy.utils.async.AsyncListeners;
@ -91,6 +91,8 @@ public class TimetableTabPresenter extends BasePresenter<TimetableTabContract.Vi
getView().onError(getRepository().getResRepo().getErrorLoginMessage(exception));
}
getView().hideRefreshingBar();
FabricUtils.logRefresh("Timetable", result, date);
}
@Override

View File

@ -5,7 +5,6 @@ import android.os.Bundle;
import javax.inject.Inject;
import butterknife.ButterKnife;
import io.github.wulkanowy.services.jobs.SyncJob;
import io.github.wulkanowy.services.notifies.NotificationService;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.login.LoginActivity;
@ -48,9 +47,4 @@ public class SplashActivity extends BaseActivity implements SplashContract.View
public void cancelNotifications() {
new NotificationService(getApplicationContext()).cancelAll();
}
@Override
public void stopSyncService() {
SyncJob.stop(getApplicationContext());
}
}

View File

@ -13,8 +13,6 @@ public interface SplashContract {
void openMainActivity();
void cancelNotifications();
void stopSyncService();
}
@PerActivity

View File

@ -24,7 +24,6 @@ public class SplashPresenter extends BasePresenter<SplashContract.View>
getView().openMainActivity();
} else {
getView().openLoginActivity();
getView().stopSyncService();
}
}
}

View File

@ -156,7 +156,7 @@ public final class DataObjectConverter {
for (io.github.wulkanowy.api.generic.Lesson lesson : lessonList) {
lessonEntityList.add(new AttendanceLesson()
.setNumber(Integer.valueOf(lesson.getNumber()))
.setNumber(lesson.getNumber())
.setSubject(lesson.getSubject())
.setDate(lesson.getDate())
.setPresence(lesson.isPresence())

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.utils;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import com.crashlytics.android.answers.SignUpEvent;
public final class FabricUtils {
private FabricUtils() {
throw new IllegalStateException("Utility class");
}
public static void logRegister(boolean result, String symbol) {
Answers.getInstance().logSignUp(new SignUpEvent()
.putMethod("Login activity")
.putSuccess(result)
.putCustomAttribute("symbol", symbol));
}
public static void logRefresh(String name, boolean result, String date) {
Answers.getInstance().logCustom(
new CustomEvent(name + " refresh")
.putCustomAttribute("Success", result ? 1 : 0)
.putCustomAttribute("Date", date)
);
}
}

View File

@ -1,38 +0,0 @@
package io.github.wulkanowy.utils;
import android.os.Build;
import java.io.File;
public final class RootChecker {
private RootChecker() {
throw new IllegalStateException("Utility class");
}
public static boolean isRooted() {
return checkOne() || checkTwo() || checkThree();
}
private static boolean checkOne() {
return Build.TAGS != null && Build.TAGS.contains("test-keys");
}
private static boolean checkTwo() {
return new File("/system/app/Superuser.apk").exists();
}
private static boolean checkThree() {
String[] commands = {"/system/xbin/which su", "/system/bin/which su", "which su"};
for (String command : commands) {
try {
Runtime.getRuntime().exec(command);
return true;
} catch (Exception e) {
// ignore
}
}
return false;
}
}

View File

@ -27,7 +27,6 @@ import javax.crypto.CipherOutputStream;
import javax.security.auth.x500.X500Principal;
import io.github.wulkanowy.utils.LogUtils;
import io.github.wulkanowy.utils.RootChecker;
public final class Scrambler {
@ -46,23 +45,16 @@ public final class Scrambler {
loadKeyStore();
generateNewKey(email, context);
return encryptString(email, plainText);
} else {
if (RootChecker.isRooted()) {
return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT));
} else {
throw new UnsupportedOperationException("Stored data in this devices " +
"isn't safe because android is rooted");
}
}
return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT));
}
public static String decrypt(String email, String encryptedText) throws CryptoException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
loadKeyStore();
return decryptString(email, encryptedText);
} else {
return new String(Base64.decode(encryptedText, Base64.DEFAULT));
}
return new String(Base64.decode(encryptedText, Base64.DEFAULT));
}
private static void loadKeyStore() throws CryptoException {

View File

@ -7,4 +7,4 @@ Wyróżnione cechy i funkcje:
- Aktywne wsparcie i rozwój
GitHub: https://github.com/wulkanowy/wulkanowy
Discord: https://discord.gg/JMG2rhJ
Discord: https://discord.gg/vccAQBr

View File

@ -1,4 +1,5 @@
Wersja 0.4.2:
- naprawiono logowanie do niestandardowych dzienników
- naprawiono synchronizację sprawdzianów
- naprawiono problemy z logowaniem (powinny występować rzadziej)
Wersja 0.4.4:
- naprawiono błędy w synchronizacji planu lekcji
- naprawiono błędy podczas pierwszego logowania
- naprawiono błąd podczas zmiany semestru
- kolejny raz poprawiono synchronizację w tle

View File

@ -25,7 +25,7 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_below="@id/login_activity_progress_text"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:minHeight="30dp"
@ -35,9 +35,9 @@
android:id="@+id/login_activity_progress_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/login_activity_progress_bar"
android:layout_centerHorizontal="true"
android:layout_marginBottom="42dp" />
android:layout_marginBottom="15dp"
android:text="@string/app_name" />
</RelativeLayout>
<ScrollView
@ -61,13 +61,13 @@
android:id="@+id/login_activity_email_text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:layout_marginBottom="12dp">
<EditText
android:id="@+id/login_activity_email_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1" />
@ -76,6 +76,7 @@
<android.support.design.widget.TextInputLayout
android:id="@+id/login_activity_pass_text_input"
android:layout_width="match_parent"
android:hint="@string/prompt_password"
android:layout_height="wrap_content">
<EditText
@ -83,7 +84,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in"
android:imeOptions="actionDone"
android:inputType="textPassword"
@ -95,16 +95,15 @@
android:id="@+id/login_activity_symbol_text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_symbol"
android:visibility="gone">
<AutoCompleteTextView
android:id="@+id/login_activity_symbol_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_symbol"
android:imeActionLabel="@string/action_sign_in"
android:imeOptions="actionDone"
android:importantForAutofill="noExcludeDescendants"
android:inputType="textAutoComplete"
android:maxLines="1" />

View File

@ -44,7 +44,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:text="@string/attendance_no_entries"
android:textAlignment="center"
android:gravity="center"
android:textSize="20sp" />
</RelativeLayout>

View File

@ -43,8 +43,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:gravity="center"
android:text="@string/exams_no_entries"
android:textAlignment="center"
android:textSize="20sp" />
</RelativeLayout>

View File

@ -31,7 +31,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:text="@string/fragment_no_grades"
android:textAlignment="center"
android:gravity="center"
android:textSize="20sp" />
</RelativeLayout>

View File

@ -1,6 +1,6 @@
<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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timetable_tab_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -43,19 +43,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:gravity="center"
android:text="@string/info_free_week"
android:textAlignment="center"
android:textSize="20sp" />
<TextView
android:id="@+id/timetable_tab_fragment_no_item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="20sp"
android:layout_below="@id/timetable_tab_fragment_no_item_image"
android:layout_marginTop="15dp"
android:text="@string/app_name"
android:textAlignment="center"
android:layout_marginTop="15dp" />
android:textSize="20sp" />
</RelativeLayout>
<android.support.v4.widget.SwipeRefreshLayout

View File

@ -47,7 +47,7 @@ ext {
dagger2 = "2.16"
ahbottom = "2.2.0"
jsoup = "1.11.3"
gson = "2.8.4"
gson = "2.8.5"
ossLicenses = "15.0.1"
debugDb = "1.0.3"
@ -57,9 +57,11 @@ ext {
mockito = "2.18.3"
testRunner = "1.0.2"
fabricGradle = "1.25.3"
crashlyticsSdk = "2.9.2"
crashlyticsAnswers = "1.4.1"
fabricGradle = "1.25.4"
crashlyticsSdk = "2.9.3"
crashlyticsAnswers = "1.4.2"
playPublisher = "1.2.2"
}
allprojects {