1
0

Compare commits

...

17 Commits
0.4.0 ... 0.4.3

Author SHA1 Message Date
9c97962e89 Version 0.4.3 2018-05-19 23:37:32 +02:00
a4445a8a97 Fix api login error (#115) 2018-05-19 23:02:54 +02:00
3f54d13b6b Add condition to exam query (#116) 2018-05-19 21:11:04 +02:00
5685e73e46 Fix text alignment <= lv l20 (#117) 2018-05-19 19:58:33 +02:00
e9b357e92d Remove the root check (#114) 2018-05-19 12:48:12 +02:00
62bc00cd68 Delete non-existing lessons on sync (#112) 2018-05-19 12:30:17 +02:00
54e6aee82e Stop SyncJob if user is not registered in app (#113) 2018-05-16 20:43:29 +02:00
3000c077c4 Version 0.4.2 2018-05-14 22:20:17 +02:00
7d5072b529 Fix exams sync (#108) 2018-05-14 22:13:08 +02:00
052d5e3911 Stopping job when user not registered in app (#111) 2018-05-14 21:39:09 +02:00
0014b74c6b Fix certificate parsing issues (#109) 2018-05-14 21:12:45 +02:00
08dd316aee Fix adfs login (#110) 2018-05-14 20:56:17 +02:00
95caa21f2a Version 0.4.1 2018-05-13 18:16:39 +02:00
cd6e14b13b [API] Fix first login (#107) 2018-05-13 17:33:31 +02:00
2cf262130e [API] Get rid of NULL in timetable lesson descriptions (#106) 2018-05-13 16:02:51 +02:00
23183c9d7a Call of SyncJob only after user register in app (#105) 2018-05-13 15:25:56 +02:00
be8ee4c835 Fix parsing grade description with symbol containing special chars (#104) 2018-05-11 21:55:38 +02:00
49 changed files with 402 additions and 218 deletions

View File

@ -199,24 +199,40 @@ jobs:
workflows:
version: 2
build_check_tests:
build-test-deploy:
jobs:
- build
- build:
filters:
tags:
only: /.*/
- lint:
filters:
tags:
only: /.*/
requires:
- build
- app-test:
filters:
tags:
only: /.*/
requires:
- build
- api-test:
filters:
tags:
only: /.*/
requires:
- build
- instrumented:
requires:
- build
filters:
tags:
only: /.*/
requires:
- build
- sonarcube:
filters:
tags:
only: /.*/
requires:
- build
- lint
@ -228,4 +244,6 @@ workflows:
- instrumented
filters:
tags:
only: /.*/
only: /\d+\.\d+\.\d+/
branches:
ignore: /.*/

View File

@ -4,9 +4,11 @@
[![Bitrise](https://img.shields.io/bitrise/daeff1893f3c8128/master.svg?token=Hjm1ACamk86JDeVVJHOeqQ&style=flat-square)](https://www.bitrise.io/app/daeff1893f3c8128)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![BCH compliance](https://bettercodehub.com/edge/badge/wulkanowy/wulkanowy?branch=master)](https://bettercodehub.com/)
[![Scrutinizer](https://img.shields.io/scrutinizer/g/wulkanowy/wulkanowy.svg)](https://scrutinizer-ci.com/g/wulkanowy/wulkanowy/?branch=master)
[![Known Vulnerabilities](https://snyk.io/test/github/wulkanowy/wulkanowy/badge.svg?targetFile=app%2Fbuild.gradle&style=flat-square)](https://snyk.io/test/github/wulkanowy/wulkanowy?targetFile=app%2Fbuild.gradle)
[![Scrutinizer](https://img.shields.io/scrutinizer/g/wulkanowy/wulkanowy.svg?style=flat-square)](https://scrutinizer-ci.com/g/wulkanowy/wulkanowy/?branch=master)
[![Bintray](https://img.shields.io/bintray/v/wulkanowy/wulkanowy/api.svg?style=flat-square)](https://bintray.com/wulkanowy/wulkanowy/api)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[Pobierz wersję beta](https://play.google.com/store/apps/details?id=io.github.wulkanowy&amp;utm_source=vcs)
[Pobierz wersję rozwojową](https://bitrise-redirector.herokuapp.com/v0.1/apps/daeff1893f3c8128/builds/master/artifacts/app-debug-bitrise-signed.apk)

View File

@ -23,7 +23,7 @@ public class Client {
private String symbol;
private Date lastSuccessRequest = null;
private Date lastSuccessRequest;
private Cookies cookies = new Cookies();
@ -58,13 +58,12 @@ public class Client {
return;
}
this.cookies = new Cookies();
this.symbol = new Login(this).login(email, password, symbol);
}
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());
}
@ -103,7 +102,7 @@ public class Client {
return getPageByUrl(url, loginBefore, null);
}
public Document getPageByUrl(String url, boolean loginBefore, Map<String, String> cookies) throws IOException, VulcanException {
public synchronized Document getPageByUrl(String url, boolean loginBefore, Map<String, String> cookies) throws IOException, VulcanException {
if (loginBefore) {
login();
}
@ -128,7 +127,7 @@ public class Client {
return doc;
}
public Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException {
public synchronized Document postPageByUrl(String url, String[][] params) throws IOException, VulcanException {
Connection connection = Jsoup.connect(getFilledUrl(url));
for (String[] data : params) {
@ -143,6 +142,8 @@ public class Client {
this.cookies.addItems(response.cookies());
response.bufferUp(); // fixes cert parsing issues #109
return checkForErrors(response.parse());
}
@ -194,8 +195,8 @@ public class Client {
throw new NotLoggedInErrorException(singIn);
}
if ("Błąd strony".equals(title)) {
throw new VulcanException("Nieznany błąd");
if (title.startsWith("Błąd")) {
throw new NotLoggedInErrorException(title + " " + doc.selectFirst("p, body"));
}
return doc;

View File

@ -106,7 +106,11 @@ public class StudentAndParent implements SnP {
Document doc = client.getPageByUrl(getBaseUrl() + url, true, cookies);
if ("Witryna ucznia i rodzica Strona główna".equals(doc.select("title").first().text())) {
if (!doc.title().startsWith("Witryna ucznia i rodzica")) {
throw new VulcanException("Expected SnP page, got page with title: " + doc.title());
}
if (doc.title().endsWith("Strona główna")) {
throw new VulcanException("Sesja została nieprawidłowo zainicjowana");
}

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

@ -56,7 +56,7 @@ public class GradesList {
String descriptions = row.select("td:nth-child(3)").text();
String symbol = descriptions.split(", ")[0];
String description = descriptions.replaceFirst(symbol, "").replaceFirst(", ", "");
String description = descriptions.replaceFirst(Pattern.quote(symbol), "").replaceFirst(", ", "");
String color = getColor(row.select("td:nth-child(2) span.ocenaCzastkowa").attr("style"));
String date = formatDate(row.select("td:nth-child(5)").text());

View File

@ -13,7 +13,7 @@ import io.github.wulkanowy.api.VulcanException;
public class Login {
private static final String LOGIN_PAGE_URL = "{schema}://cufs.{host}/{symbol}/Account/LogOn" +
static final String LOGIN_PAGE_URL = "{schema}://cufs.{host}/{symbol}/Account/LogOn" +
"?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" +
"{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" +
"{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx";
@ -36,27 +36,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");
}
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) {
@ -75,16 +77,13 @@ public class Login {
}
String sendCertificate(Document doc, String defaultSymbol) throws IOException, VulcanException {
String certificate = doc.select("input[name=wresult]").val();
String symbol = findSymbol(defaultSymbol, certificate);
client.setSymbol(symbol);
client.setSymbol(findSymbol(defaultSymbol, doc.select("input[name=wresult]").val()));
Document targetDoc = sendCertData(doc);
String title = targetDoc.select("title").text();
String title = targetDoc.title();
if ("Working...".equals(title)) { // on adfs login
title = sendCertData(targetDoc).select("title").text();
title = sendCertData(targetDoc).title();
}
if ("Logowanie".equals(title)) {
@ -95,7 +94,7 @@ public class Login {
throw new LoginErrorException("Expected page title `UONET+`, got " + title);
}
return symbol;
return client.getSymbol();
}
private Document sendCertData(Document doc) throws IOException, VulcanException {
@ -108,7 +107,7 @@ public class Login {
});
}
private String findSymbol(String symbol, String certificate) {
private String findSymbol(String symbol, String certificate) throws AccountPermissionException {
if ("Default".equals(symbol)) {
return findSymbolInCertificate(certificate);
}
@ -116,15 +115,19 @@ public class Login {
return symbol;
}
String findSymbolInCertificate(String certificate) {
String findSymbolInCertificate(String certificate) throws AccountPermissionException {
Elements instances = Jsoup
.parse(certificate.replaceAll(":", ""), "", Parser.xmlParser())
.select("[AttributeName=\"UserInstance\"] samlAttributeValue");
if (instances.isEmpty()) {
if (instances.isEmpty()) { // on adfs login
return "";
}
if (instances.size() < 2) { // 1st index is always `Default`
throw new AccountPermissionException("First login detected, specify symbol");
}
return instances.get(1).text();
}
}

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"));
@ -94,14 +94,18 @@ public class Timetable {
moveWarningToLessonNode(e);
switch (e.size()) {
case 1:
addLessonInfoFromElement(lesson, e.first());
break;
case 2:
Element span = e.last().select("span").first();
if (span.hasClass(LessonTypes.CLASS_MOVED_OR_CANCELED)) {
lesson.setNewMovedInOrChanged(true);
lesson.setDescription("poprzednio: " + getLessonAndGroupInfoFromSpan(span)[0]);
addLessonInfoFromElement(lesson, e.first());
} else {
addLessonInfoFromElement(lesson, e.last());
}
case 1:
addLessonInfoFromElement(lesson, e.first());
break;
case 3:
addLessonInfoFromElement(lesson, e.get(1));
@ -167,7 +171,8 @@ public class Timetable {
lesson.setRoom(spans.get(5).text());
lesson.setMovedOrCanceled(false);
lesson.setNewMovedInOrChanged(true);
lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")")
lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween(
spans.last().text(), "(", ")"), spans.last().text())
+ " (poprzednio: " + spans.get(0).text() + ")");
} else if (9 == spans.size()) {
String[] subjectAndGroupInfo = getLessonAndGroupInfoFromSpan(spans.get(4));
@ -178,13 +183,15 @@ public class Timetable {
lesson.setMovedOrCanceled(false);
lesson.setNewMovedInOrChanged(true);
lesson.setDivisionIntoGroups(true);
lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")")
lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween(
spans.last().text(), "(", ")"), spans.last().text())
+ " (poprzednio: " + getLessonAndGroupInfoFromSpan(spans.get(0))[0] + ")");
} else if (4 <= spans.size()) {
lesson.setSubject(spans.get(0).text());
lesson.setTeacher(spans.get(1).text());
lesson.setRoom(spans.get(2).text());
lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")"));
lesson.setDescription(StringUtils.defaultString(StringUtils.substringBetween(
spans.last().text(), "(", ")"), spans.last().text()));
}
}
@ -212,7 +219,8 @@ public class Timetable {
return new String[]{
span.text().replace(" " + groupName, ""),
StringUtils.substringBetween(groupName, "[", "]")
StringUtils.defaultString(StringUtils.substringBetween(
groupName, "[", "]"), groupName)
};
}
}

View File

@ -19,7 +19,7 @@ public class GradesListTest extends StudentAndParentTestCase {
@Test
public void getAllTest() throws Exception {
Assert.assertEquals(6, filled.getAll().size()); // 2 items are skipped
Assert.assertEquals(7, filled.getAll().size()); // 2 items are skipped
}
@Test
@ -60,6 +60,7 @@ public class GradesListTest extends StudentAndParentTestCase {
Assert.assertEquals("BW3", list.get(3).getSymbol());
Assert.assertEquals("STR", list.get(4).getSymbol());
Assert.assertEquals("K", list.get(5).getSymbol());
Assert.assertEquals("+Odp", list.get(6).getSymbol());
}
@Test
@ -70,6 +71,7 @@ public class GradesListTest extends StudentAndParentTestCase {
Assert.assertEquals("Writing", list.get(3).getDescription());
Assert.assertEquals("", list.get(4).getDescription());
Assert.assertEquals("Kordian", list.get(5).getDescription());
Assert.assertEquals("Kordian", list.get(6).getDescription());
}
@Test

View File

@ -34,6 +34,10 @@ public class LoginTest {
Client client = getClient("Logowanie-success.html");
Mockito.when(client.getPageByUrl(Mockito.anyString(), Mockito.anyBoolean()))
.thenReturn(getFixtureAsDocument("Logowanie-error.html"));
Mockito.when(client.postPageByUrl(Mockito.eq(Login.LOGIN_PAGE_URL), Mockito.any(String[][].class)))
.thenReturn(getFixtureAsDocument("Logowanie-certyfikat.html"));
Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString());
Mockito.when(client.getSymbol()).thenCallRealMethod();
Login login = new Login(client);
Assert.assertEquals("d123", login.login("a@a", "pswd", "d123"));
@ -57,22 +61,31 @@ public class LoginTest {
Login login = new Login(client);
Assert.assertEquals(
getFixtureAsString("cert-stock.xml").replaceAll("\\s+",""),
login.sendCredentials("a@a", "passwd").select("input[name=wresult]").attr("value").replaceAll("\\s+","")
getFixtureAsString("cert-stock.xml").replaceAll("\\s+", ""),
login.sendCredentials("a@a", "passwd")
.select("input[name=wresult]")
.attr("value")
.replaceAll("\\s+", "")
);
}
@Test
public void sendCertificateNotDefaultSymbolSuccessTest() throws Exception {
Login login = new Login(getClient("Logowanie-success.html"));
Client client = getClient("Logowanie-success.html");
Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString());
Mockito.when(client.getSymbol()).thenCallRealMethod();
Login login = new Login(client);
Assert.assertEquals("wulkanowyschool321",
login.sendCertificate(new Document(""), "wulkanowyschool321"));
Assert.assertEquals("wulkanowyschool321", login.sendCertificate(
getFixtureAsDocument("Logowanie-certyfikat.html"), "wulkanowyschool321"));
}
@Test
public void sendCertificateDefaultSymbolSuccessTest() throws Exception {
Login login = new Login(getClient("Logowanie-success.html"));
Client client = getClient("Logowanie-success.html");
Mockito.doCallRealMethod().when(client).setSymbol(Mockito.anyString());
Mockito.when(client.getSymbol()).thenCallRealMethod();
Login login = new Login(client);
Assert.assertEquals("demo12345",
login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "Default"));
@ -80,16 +93,18 @@ public class LoginTest {
@Test(expected = AccountPermissionException.class)
public void sendCertificateAccountPermissionTest() throws Exception {
Login login = new Login(getClient("Logowanie-brak-dostepu.html"));
Client client = getClient("Logowanie-brak-dostepu.html");
login.sendCertificate(getFixtureAsDocument("cert-stock.xml"), "demo123");
Login login = new Login(client);
login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "demo123");
}
@Test(expected = LoginErrorException.class)
public void sendCertificateLoginErrorTest() throws Exception {
Login login = new Login(getClient("Logowanie-certyfikat.html")); // change to other document
login.sendCertificate(getFixtureAsDocument("cert-stock.xml"), "demo123");
login.sendCertificate(getFixtureAsDocument("Logowanie-certyfikat.html"), "demo123");
}
@Test
@ -101,10 +116,10 @@ public class LoginTest {
Assert.assertEquals("demo12345", login.findSymbolInCertificate(certificate));
}
@Test
public void findSymbolInInvalidCertificateTest() throws Exception {
@Test(expected = AccountPermissionException.class)
public void findSymbolInCertificateWithoutSecondInstanceTest() throws Exception {
Login login = new Login(getClient("Logowanie-certyfikat.html"));
Assert.assertEquals("", login.findSymbolInCertificate("<xml></xml>")); // change to real cert with empty symbols
login.findSymbolInCertificate(getFixtureAsString("cert-no-symbols.xml"));
}
}

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
@ -151,6 +151,7 @@ public class TimetableTest extends StudentAndParentTestCase {
Assert.assertEquals("zastępstwo (poprzednio: Wychowanie fizyczne)", full.getWeekTable().getDay(3).getLesson(1).getDescription());
Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(0).getDescription());
Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getDescription());
Assert.assertEquals("bez nawiasów (poprzednio: Religia)", full.getWeekTable().getDay(4).getLesson(3).getDescription());
Assert.assertEquals("poprzednio: Wychowanie fizyczne", full.getWeekTable().getDay(4).getLesson(2).getDescription());
Assert.assertEquals("egzamin", full.getWeekTable().getDay(3).getLesson(0).getDescription());
Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(1).getDescription());

View File

@ -100,6 +100,14 @@
<td>06.02.2017</td>
<td>Amelia Stępień</td>
</tr>
<tr>
<td>Język polski</td>
<td class="break-word"><span class="ocenaCzastkowa" style="color:#6ECD07;">5</span></td>
<td class="break-word">+Odp, Kordian</td>
<td>5,00</td>
<td>11.05.2017</td>
<td>Amelia Stępień</td>
</tr>
</tbody>
</table>
</main>

View File

@ -6,6 +6,9 @@
<body>
<div id="MainDiv">
<form>
<div class="LogOnBoard">
<h1 id="h1Default">Logowanie</h1>
</div>
<div class="ErrorMessage center">
Zła nazwa użytkownika lub hasło
</div>

View File

@ -0,0 +1,13 @@
<trust:RequestSecurityTokenResponseCollection xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<trust:RequestSecurityTokenResponse Context="https://uonetplus.fakelog.cf/Default/LoginEndpoint.aspx">
<trust:RequestedSecurityToken>
<saml:Assertion AssertionID="_12345678-1234-1234-1234-1234567890ab" IssueInstant="2017-10-18T22:00:29.006Z" Issuer="CUFSTokenService" MajorVersion="1" MinorVersion="1" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
<saml:AttributeStatement>
<saml:Attribute AttributeName="UserInstance" AttributeNamespace="http://schemas.fakelog.cf/ws/identity/claims">
<saml:AttributeValue>Default</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</trust:RequestedSecurityToken>
</trust:RequestSecurityTokenResponse>
</trust:RequestSecurityTokenResponseCollection>

View File

@ -3,6 +3,40 @@
<head>
<meta charset="utf-8">
<title>Witryna ucznia i rodzica Plan lekcji</title>
<style>
table, th, td {
border: 1px solid;
border-collapse: collapse;
}
table td > div:not(:last-child) {
border-bottom: 5px solid silver;
padding-bottom: 5px;
margin-bottom: 5px;
}
table td div span:nth-child(1) {
display: block;
}
table span {
font-size: smaller;
}
.x-treelabel-ppl {
background: #99f;
font-style: italic;
}
.x-treelabel-rlz {
background: #f3a;
}
.x-treelabel-inv {
background: #f00;
}
.x-treelabel-zas {
background: #0f0;
}
table span:not([class]),
table span[class=""] {
background: #999;
}
</style>
</head>
<body>
<main class="mainContainer">
@ -238,7 +272,17 @@
<span></span>
</div>
</td>
<td></td>
<td>
<div>
<span class="x-treelabel-inv">Religia</span>
<span class="x-treelabel-inv">Cyranka Krystian</span>
<span class="x-treelabel-inv">3</span>
<span class="x-treelabel-ppl x-treelabel-zas">Wychowanie do życia w rodzinie</span>
<span class="x-treelabel-ppl x-treelabel-zas">Nowak Jadwiga</span>
<span class="x-treelabel-ppl x-treelabel-zas">3</span>
<span class="x-treelabel-rlz">bez nawiasów</span>
</div>
</td>
</tr>
<tr>
<td>4</td>

View File

@ -41,8 +41,8 @@ android {
testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15
targetSdkVersion 26
versionCode 8
versionName "0.4.0"
versionCode 11
versionName "0.4.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
playAccountConfig = playAccountConfigs.defaultAccountConfig
@ -91,7 +91,7 @@ play {
}
greendao {
schemaVersion 26
schemaVersion 28
generateTests = true
}

View File

@ -18,6 +18,8 @@ import io.github.wulkanowy.api.Vulcan;
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;
@ -76,6 +78,8 @@ public class DbHelper extends DaoMaster.OpenHelper {
List<Migration> migrations = new ArrayList<>();
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

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

@ -4,13 +4,15 @@ import org.greenrobot.greendao.DaoException;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Index;
import org.greenrobot.greendao.annotation.Property;
import java.io.Serializable;
@Entity(
nameInDb = "Exams",
active = true
active = true,
indexes = {@Index(value = "dayId,entryDate,subjectAndGroup,type,teacher", unique = true)}
)
public class Exam implements Serializable {

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,24 @@
package io.github.wulkanowy.data.db.dao.migrations;
import org.greenrobot.greendao.database.Database;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.data.db.dao.DbHelper;
import io.github.wulkanowy.data.db.dao.entities.ExamDao;
import io.github.wulkanowy.data.db.shared.SharedPrefContract;
public class Migration27 implements DbHelper.Migration {
@Override
public Integer getVersion() {
return 27;
}
@Override
public void runMigration(Database db, SharedPrefContract sharedPref, Vulcan vulcan) throws Exception {
ExamDao.dropTable(db, true);
ExamDao.createTable(db, true);
db.execSQL("UPDATE Weeks SET exams_synced = 0");
}
}

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

@ -52,7 +52,7 @@ public class ResourcesRepository implements ResourcesContract {
} else if (exception instanceof SocketTimeoutException) {
return resources.getString(R.string.generic_timeout_error);
} else if (exception instanceof NotLoggedInErrorException || exception instanceof IOException) {
return resources.getString(R.string.login_denied_text);
return resources.getString(R.string.login_failed_text);
} else {
return exception.getMessage();
}

View File

@ -118,12 +118,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);

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

@ -17,6 +17,7 @@ import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.github.wulkanowy.R;
import io.github.wulkanowy.services.jobs.SyncJob;
import io.github.wulkanowy.ui.base.BaseActivity;
import io.github.wulkanowy.ui.base.BasePagerAdapter;
import io.github.wulkanowy.ui.main.attendance.AttendanceFragment;
@ -140,6 +141,11 @@ public class MainActivity extends BaseActivity implements MainContract.View,
viewPager.setCurrentItem(tabPosition, false);
}
@Override
public void startSyncService(int interval, boolean useOnlyWifi) {
SyncJob.start(getApplicationContext(), interval, useOnlyWifi);
}
@Override
protected void onDestroy() {
super.onDestroy();

View File

@ -18,6 +18,8 @@ public interface MainContract {
void initiationViewPager(int tabPosition);
void initiationBottomNav(int tabPosition);
void startSyncService(int interval, boolean useOnlyWifi);
}
@PerActivity

View File

@ -32,6 +32,11 @@ public class MainPresenter extends BasePresenter<MainContract.View>
getView().initiationBottomNav(tabPosition);
getView().initiationViewPager(tabPosition);
if (getRepository().getSharedRepo().isServicesEnable()) {
getView().startSyncService(getRepository().getSharedRepo().getServicesInterval(),
getRepository().getSharedRepo().isMobileDisable());
}
}
@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

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

@ -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;
@ -44,11 +43,6 @@ public class SplashActivity extends BaseActivity implements SplashContract.View
finish();
}
@Override
public void startSyncService(int interval, boolean useOnlyWifi) {
SyncJob.start(getApplicationContext(), interval, useOnlyWifi);
}
@Override
public void cancelNotifications() {
new NotificationService(getApplicationContext()).cancelAll();

View File

@ -12,8 +12,6 @@ public interface SplashContract {
void openMainActivity();
void startSyncService(int interval, boolean useOnlyWifi);
void cancelNotifications();
}

View File

@ -20,11 +20,6 @@ public class SplashPresenter extends BasePresenter<SplashContract.View>
super.onStart(activity);
getView().cancelNotifications();
if (getRepository().getSharedRepo().isServicesEnable()) {
getView().startSyncService(getRepository().getSharedRepo().getServicesInterval(),
getRepository().getSharedRepo().isMobileDisable());
}
if (getRepository().getSharedRepo().isUserLoggedIn()) {
getView().openMainActivity();
} else {

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

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

@ -1,3 +1,5 @@
Aplikacja jest we wczesnej fazie rozwoju, ciągle pracujemy nad kolejnymi funkcjami.
Wyróżnione cechy i funkcje:
- Całkowicie darmowa i otwarta (brak jakichkolwiek reklam i mikropłatności)
- Powiadomienia
@ -5,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,11 +1,4 @@
Wersja 0.4.0:
- dodano widget planu lekcji
- dodano widok sprawdzianów
- dodano informacje o końcowych ocenach z przedmiotu
- dodano przełącznik semestru w ocenach
- dodano opcję ukrycia obecności
- dodano wyróżnik aktualnego tygodnia
- dodano podstawową obsługę niestandardowych dzienników Vulcan (np. Opolska eSzkoła)
- naprawiono animacje w ocenach
- naprawiono wyświetlanie oceny w szczególnych przypadkach
- optymalizacja aplikacji
Wersja 0.4.3:
- naprawiono błąd z pokazywaniem usuniętych lekcji w planie
- naprawiono drobne błędy z wyrównaniem tekstu na starszych urządzeniach
- poprawiono synchronizację w tle spowodowaną błędem w komunikacji z serwerem

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

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

@ -19,7 +19,7 @@
<string name="login_accepted_text">Pomyślnie zalogowano</string>
<string name="login_bad_credentials_text">Niepoprawny e-mail lub hasło</string>
<string name="login_bad_account_permission_text">Brak uprawnień do otwarcia dziennika. Sprawdź wprowadzoną nazwę powiatu</string>
<string name="login_denied_text">Logowanie nie powiodło się. Spróbuj zrestartować aplikację</string>
<string name="login_failed_text">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
<string name="action_create_account">Nie masz jeszcze konta? Załóż je</string>
<string name="action_forgot_password">Zapomniałeś hasła?</string>

View File

@ -19,7 +19,7 @@
<string name="login_accepted_text">Login is successful</string>
<string name="login_bad_credentials_text">Bad e-mail or password</string>
<string name="login_bad_account_permission_text">No permission to open log. Check entered symbol</string>
<string name="login_denied_text">Login is failed. Try restart the app</string>
<string name="login_failed_text">Login is failed. Try again or restart the app</string>
<string name="action_create_account">No account yet? Create one</string>
<string name="action_forgot_password">Forgot password?</string>