Compare commits

...

14 Commits

Author SHA1 Message Date
30b6ac2a06 [4.0-rc.2] Update build.gradle, singing and changelog. 2020-03-26 20:46:03 +01:00
a7fa7cb5e4 [API/Librus] Fix a typo. 2020-03-26 20:45:46 +01:00
f3e87f9016 [API/Librus] Fix missing login data error. 2020-03-26 20:42:58 +01:00
a983af6c28 [4.0-rc.1] Update build.gradle, singing and changelog. 2020-03-26 20:40:00 +01:00
114c841f0c [API/Liburs] Fix Librus API push config endpoint. 2020-03-26 20:34:59 +01:00
e271048577 [UI] Clarify some errors. Fix deselecting the mini drawer. 2020-03-26 18:48:54 +01:00
b8c5925e82 [API/Librus] Fix attendance NPE. 2020-03-26 17:55:39 +01:00
9bda6c8869 [API/Librus] Disable push config with no premium. Disable API homework. Enable Synergia homework. 2020-03-26 16:31:11 +01:00
d1608d308c [API/Librus] Disable login with credentials in Messages. 2020-03-26 15:57:13 +01:00
b8e1e1d33a [Event/Manual] Fix dropdowns not showing any data. 2020-03-25 17:36:22 +01:00
8099a037e7 [Proguard] Update proguard rules to fix BetterLink and MiniDrawer. 2020-03-24 21:03:19 +01:00
af23c932a6 [API] Change the Cookie jar to fix most cookie problems. 2020-03-24 20:43:41 +01:00
4edabbb186 [Messages/Compose] Enable HTML messages for Idziennik. 2020-03-24 16:31:46 +01:00
37a5bea79b [Libraries] Update gradle, NavLib and Firebase. 2020-03-24 16:29:45 +01:00
36 changed files with 385 additions and 242 deletions

View File

@ -128,6 +128,7 @@ dependencies {
implementation "com.mikepenz:iconics-core:${versions.iconics}"
implementation "com.mikepenz:iconics-views:${versions.iconics}"
implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar"
implementation "com.mikepenz:materialize:1.2.1"
implementation "com.github.kuba2k2:NavLib:${versions.navlib}"

View File

@ -31,6 +31,12 @@
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); }
-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; }
-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); }
-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; }
-keep class .R
-keep class **.R$* {
<fields>;

View File

@ -1,12 +1,13 @@
<h3>Wersja 4.0-beta.14, 2020-03-24</h3>
<h3>Wersja 4.0-rc.2, 2020-03-26</h3>
<ul>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych.</li>
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli &#x1F44F;</li>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych</li>
<li>Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze</li>
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
<li>Nowe <b>Oceny</b> - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej</li>
<li>Opcja wyłączenia wybranych powiadomień z aplikacji</li>
<li>Znaczki nieprzeczytanych informacji na obrazkach profili.</li>
<br>
<br>
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
@ -21,18 +22,8 @@
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
<li><strike>Występują natomiast nowe błędy, dlatego proszę o ich zgłaszanie :)</strike></li>
</ul>
<br>
<br>
<br>
<b>Uwaga.</b> Ponieważ to wersja <i>beta</i>, niektóre funkcje mogą nie działać prawidłowo.<br>
Staramy się usuwać takie przypadki, jednak na chwilę obecną mogą występować błędy w:
<ul>
<li>Wysyłanie wiadomości może nie działać w pełni prawidłowo - proszę o zgłaszanie wszystkich błędów na naszym serwerze Discord</li>
</ul>
<br>
<br>
<br>
Dzięki za korzystanie ze Szkolnego!<br>
<i>&copy; Kuba Szczodrzyński, Kacper Ziubryniewicz 2020</i>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0xc4, 0x97, 0xfb, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0x30, 0xfe, 0xe2, 0x8d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -28,9 +28,6 @@ import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import im.wangchao.mhttp.MHttp
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor
import kotlinx.coroutines.*
import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient
@ -41,6 +38,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.network.NetworkUtils
import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
@ -125,7 +123,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
.followSslRedirects(false)
.build()
}
val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(this)) }
val cookieJar by lazy { DumbCookieJar(this) }
/* _____ _ _
/ ____(_) | |

View File

@ -31,8 +31,8 @@ import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IProfile
import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@ -90,7 +90,6 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import pl.szczodrzynski.navlib.drawer.NavDrawer
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
import pl.szczodrzynski.navlib.drawer.items.withAppTitle
import java.io.File
import java.io.IOException
import java.util.*
@ -361,7 +360,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
false
}
drawerProfileLongClickListener = { _, profile, _, view ->
if (profile is ProfileDrawerItem) {
if (view != null && profile is ProfileDrawerItem) {
showProfileContextMenu(profile, view)
true
}
@ -915,7 +914,8 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
bottomSheet.removeAllContextual()
bottomSheet.toggleGroupEnabled = false
drawer.close()
drawer.setSelection(target.id, fireOnClick = false)
if (drawer.getSelection() != target.id)
drawer.setSelection(target.id, fireOnClick = false)
navView.toolbar.setTitle(target.title ?: target.name)
navView.bottomBar.fabEnable = false
navView.bottomBar.fabExtended = false
@ -1063,7 +1063,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val item = DrawerPrimaryItem()
.withIdentifier(target.id.toLong())
.withName(target.name)
.withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
.withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
.also { if (target.description != null) it.withDescription(target.description!!) }
.also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }
@ -1127,7 +1127,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
drawer.addProfileSettings(*drawerProfiles.toTypedArray())
}
private fun showProfileContextMenu(profile: IProfile<*>, view: View) {
private fun showProfileContextMenu(profile: IProfile, view: View) {
val profileId = profile.identifier.toInt()
val popupMenu = PopupMenu(this, view)
popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings)
@ -1140,7 +1140,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
}
loadTarget(DRAWER_ITEM_SETTINGS, null)
} else if (item.itemId == 2) {
ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?")
ProfileRemoveDialog(this, profileId, profile.name?.getText(this) ?: "?")
}
true
}

View File

@ -70,13 +70,13 @@ val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
)

View File

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -43,8 +42,8 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
if (semester == null && url.contains("start")) {
profile?.also { profile ->
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
val semesterCookie = cookies.firstOrNull { it.name() == "semester" }?.value()?.toIntOrNull()
val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
val semesterCookie = cookies["semester"]?.toIntOrNull()
semesterCookie?.let { data.currentSemester = it }
@ -75,13 +74,7 @@ open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Lon
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("sessionid")
.value(data.webSessionId!!)
.domain("dziennikel.appspot.com")
.secure().httpOnly().build()
))
data.app.cookieJar.set("dziennikel.appspot.com", "sessionid", data.webSessionId)
Request.builder()
.url(url)

View File

@ -24,7 +24,7 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("dziennikel.appspot.com")
data.app.cookieJar.clear("dziennikel.appspot.com")
if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
loginWithCredentials()
}
@ -59,8 +59,8 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
}
}
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
val sessionId = cookies.firstOrNull { it.name() == "sessionid" }?.value()
val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com")
val sessionId = cookies["sessionid"]
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)

View File

@ -5,7 +5,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
import androidx.core.util.set
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
@ -24,18 +23,8 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(
loginMethods.clear()
if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("ASP.NET_SessionId_iDziennik")
.value(webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId)
app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth)
}
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_IDZIENNIK_API

View File

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
@ -24,22 +23,12 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
init { run {
if (data.isWebLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("ASP.NET_SessionId_iDziennik")
.value(data.webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(data.webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId)
data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth)
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("iuczniowie.progman.pl")
data.app.cookieJar.clear("iuczniowie.progman.pl")
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
loginWithCredentials()
}
@ -62,11 +51,11 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
// login succeeded: there is a start page
if (text.contains("czyWyswietlicDostepMobilny")) {
val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl")
val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl")
run {
data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
data.apiBearer = cookies["Bearer"]?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API
@ -31,23 +30,11 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
loginMethods += LOGIN_METHOD_LIBRUS_API
if (isSynergiaLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId)
}
if (isMessagesLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId)
}
}

View File

@ -64,7 +64,7 @@ val LibrusFeatures = listOf(
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
!data.app.config.sync.tokenLibrusList.contains(data.profileId)
(data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId)
},
@ -116,11 +116,11 @@ val LibrusFeatures = listOf(
* Homework - using API.
* Sync only if account has premium access.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
/*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
(data as DataLibrus).isPremium
},
},*/
/**
* Behaviour - using API.
*/
@ -227,9 +227,9 @@ val LibrusFeatures = listOf(
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)).withShouldSync { data ->
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data ->
!(data as DataLibrus).isPremium
},
}*/,
/**
* Messages inbox - using messages website.

View File

@ -12,7 +12,6 @@ import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import org.json.JSONObject
import org.json.XML
import org.jsoup.Jsoup
@ -89,14 +88,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()
@ -180,14 +172,7 @@ open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) {
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()

View File

@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiAttendances(override val data: DataLibrus,
override val lastSync: Long?,
@ -45,7 +46,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
val typeObject = data.attendanceTypes[type] ?: null
val topic = typeObject?.name ?: ""
val startTime = data.lessonRanges.get(lessonNo).startTime
val startTime = data.lessonRanges.get(lessonNo)?.startTime
val lesson = if (lessonId != -1L)
data.librusLessons.singleOrNull { it.lessonId == lessonId }
@ -59,7 +60,7 @@ class LibrusApiAttendances(override val data: DataLibrus,
semester,
topic,
lessonDate,
startTime,
startTime ?: Time(0, 0, 0),
typeObject?.type ?: Attendance.TYPE_CUSTOM
)

View File

@ -4,15 +4,12 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -41,9 +38,10 @@ class LibrusApiLuckyNumber(override val data: DataLibrus,
luckyNumber
)
//if (luckyNumberDate > Date.getToday()) {
if (luckyNumberDate >= Date.getToday())
nextSync = luckyNumberDate.combineWith(Time(15, 0, 0))
//}
else
nextSync = System.currentTimeMillis() + 6*HOUR*1000
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(

View File

@ -21,6 +21,14 @@ class LibrusApiPushConfig(override val data: DataLibrus,
}
init { data.app.config.sync.tokenLibrus?.also { tokenLibrus ->
if(tokenLibrus.isEmpty()) {
data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS)
data.app.config.sync.tokenLibrusList =
data.app.config.sync.tokenLibrusList + profileId
onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG)
return@also
}
apiGet(TAG, "ChangeRegister", payload = JsonObject(
"provider" to "FCM",
"device" to tokenLibrus,

View File

@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -64,21 +63,15 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
}
if (data.isMessagesLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId)
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("wiadomosci.librus.pl")
data.app.cookieJar.clear("wiadomosci.librus.pl")
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
loginWithSynergia()
}
else if (data.apiLogin != null && data.apiPassword != null) {
else if (data.apiLogin != null && data.apiPassword != null && false) {
loginWithCredentials()
}
else {
@ -148,7 +141,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
}
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
var sessionId = data.app.cookieJar.get("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
sessionId = sessionId?.replace("MAINT", "") // dunno what's this
if (sessionId == null) {

View File

@ -37,19 +37,19 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
}
else if (data.portalRefreshToken != null) {
if (data.fakeLogin) {
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
data.app.cookieJar.clear("librus.szkolny.eu")
}
else {
data.app.cookieJar.clearForDomain("portal.librus.pl")
data.app.cookieJar.clear("portal.librus.pl")
}
accessToken(null, data.portalRefreshToken)
}
else {
if (data.fakeLogin) {
data.app.cookieJar.clearForDomain("librus.szkolny.eu")
data.app.cookieJar.clear("librus.szkolny.eu")
}
else {
data.app.cookieJar.clearForDomain("portal.librus.pl")
data.app.cookieJar.clear("portal.librus.pl")
}
authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL)
}

View File

@ -8,7 +8,6 @@ import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
@ -30,17 +29,11 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
}
if (data.isSynergiaLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", data.synergiaSessionId)
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("synergia.librus.pl")
data.app.cookieJar.clear("synergia.librus.pl")
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) {
loginWithApi()
}
@ -92,7 +85,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
}
if (location?.endsWith("centrum_powiadomien") == true) {
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
val sessionId = data.app.cookieJar.get("synergia.librus.pl", "DZIENNIKSID")
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID)
.withResponse(response)
@ -117,7 +110,7 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
}
}
data.app.cookieJar.clearForDomain("synergia.librus.pl")
data.app.cookieJar.clear("synergia.librus.pl")
Request.builder()
.url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien")
.userAgent(LIBRUS_USER_AGENT)

View File

@ -8,7 +8,6 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
@ -105,18 +104,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name(data.webSessionKey!!)
.value(data.webSessionValue!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name("SERVERID")
.value(data.webServerId!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
Request.builder()
.url(url)
@ -187,18 +176,8 @@ open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: L
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name(data.webSessionKey!!)
.value(data.webSessionValue!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name("SERVERID")
.value(data.webServerId!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build()
))
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue)
data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId)
Request.builder()
.url(url)

View File

@ -26,7 +26,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
}
else {
if (data.loginServerName.isNotNullNorEmpty() && data.loginUsername.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
data.app.cookieJar.clearForDomain(data.loginServerName + ".mobidziennik.pl")
data.app.cookieJar.clear("${data.loginServerName}.mobidziennik.pl")
loginWithCredentials()
}
else {
@ -58,10 +58,10 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
}
}
val cookies = data.app.cookieJar.getForDomain("${data.loginServerName}.mobidziennik.pl")
val cookie = cookies.singleOrNull { it.name().length > 32 }
val sessionKey = cookie?.name()
val sessionId = cookie?.value()
val cookies = data.app.cookieJar.getAll("${data.loginServerName}.mobidziennik.pl")
val cookie = cookies.entries.firstOrNull { it.key.length > 32 }
val sessionKey = cookie?.key
val sessionId = cookie?.value
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID)
.withResponse(response)
@ -71,7 +71,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
data.webSessionKey = sessionKey
data.webSessionValue = sessionId
data.webServerId = data.app.cookieJar.getCookie("${data.loginServerName}.mobidziennik.pl", "SERVERID")
data.webServerId = data.app.cookieJar.get("${data.loginServerName}.mobidziennik.pl", "SERVERID")
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess()
}

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.template
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_TEMPLATE_API
@ -28,13 +27,7 @@ class DataTemplate(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
loginMethods.clear()
if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_TEMPLATE_WEB
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("AuthCookie")
.value(webCookie!!)
.domain("eregister.example.com")
.secure().httpOnly().build()
))
app.cookieJar.set("eregister.example.com", "AuthCookie", webCookie)
}
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_TEMPLATE_API

View File

@ -4,12 +4,11 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.template.login
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_DATA_MISSING
import pl.szczodrzynski.edziennik.data.api.ERROR_PROFILE_MISSING
import pl.szczodrzynski.edziennik.data.api.edziennik.template.DataTemplate
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.currentTimeUnix
class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
companion object {
@ -23,17 +22,11 @@ class TemplateLoginWeb(val data: DataTemplate, val onSuccess: () -> Unit) {
}
if (data.isWebLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("AuthCookie")
.value(data.webCookie!!)
.domain("eregister.example.com")
.secure().httpOnly().build()
))
data.app.cookieJar.set("eregister.example.com", "AuthCookie", data.webCookie)
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("eregister.example.com")
data.app.cookieJar.clear("eregister.example.com")
if (/*data.webLogin != null && data.webPassword != null && */true) {
loginWithCredentials()
}

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDurcz1Rjg===.$param2".sha256()
return "$param1.MTIzNDU2Nzg5MDmD46bIpY===.$param2".sha256()
}
}

View File

@ -9,8 +9,8 @@ import android.app.PendingIntent
import android.app.PendingIntent.CanceledException
import android.content.Intent
import android.util.Log
import com.google.firebase.iid.zzaq
import com.google.firebase.iid.zzv
import com.google.firebase.iid.zzad
import com.google.firebase.iid.zzaz
import com.google.firebase.messaging.zzc
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
@ -36,7 +36,7 @@ open class FirebaseService : zzc() {
// apparently this gets the correct intent from some
// kind of queue inside Firebase's InstanceID Receiver
final override fun zza(intent: Intent?) = zzaq.zza()?.zzb()
final override fun zza(intent: Intent?) = zzaz.zza()?.zzb()
final override fun zzb(intent: Intent?): Boolean {
val action = intent?.action
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
@ -80,7 +80,7 @@ open class FirebaseService : zzc() {
val ackBundle = Bundle(
"google.message_id" to messageId
)
zzv.zza(this).zza(2, ackBundle)
zzad.zza(this).zza(2, ackBundle)
}
// check for duplicate message
// and add it to queue

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
*/
package pl.szczodrzynski.edziennik.network.cookie
import okhttp3.Cookie
class DumbCookie(var cookie: Cookie) {
constructor(domain: String, name: String, value: String, expiresAt: Long? = null) : this(
Cookie.Builder()
.name(name)
.value(value)
.also { if (expiresAt != null) it.expiresAt(expiresAt) }
.domain(domain)
.build()
)
init {
cookie = Cookie.Builder()
.name(cookie.name())
.value(cookie.value())
.expiresAt(cookie.expiresAt())
.domain(cookie.domain())
.build()
}
fun domainMatches(host: String): Boolean {
val domain = cookie.domain()
return host == domain || host.endsWith(".$domain")
}
override fun equals(other: Any?): Boolean {
if (other !is DumbCookie) return false
if (this.cookie === other.cookie) return true
return cookie.name() == other.cookie.name()
&& cookie.domain() == other.cookie.domain()
}
override fun hashCode(): Int {
var hash = 17
hash = 31 * hash + cookie.name().hashCode()
hash = 31 * hash + cookie.domain().hashCode()
return hash
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-3-24.
*/
package pl.szczodrzynski.edziennik.network.cookie
import android.content.Context
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
/**
* A simple cookie jar that does not care about the [Cookie.secure], [Cookie.hostOnly],
* [Cookie.httpOnly] and [Cookie.path] attributes.
*/
class DumbCookieJar(
/**
* A context to create the shared prefs file.
*/
context: Context,
/**
* Whether to persist session cookies as well, when [Cookie.persistent] is false.
*/
private val persistAll: Boolean = false
) : CookieJar {
private val prefs = context.getSharedPreferences("cookies", Context.MODE_PRIVATE)
private val sessionCookies = mutableSetOf<DumbCookie>()
private val savedCookies = mutableSetOf<DumbCookie>()
private fun save(dc: DumbCookie) {
sessionCookies.remove(dc)
sessionCookies.add(dc)
if (dc.cookie.persistent() || persistAll) {
savedCookies.remove(dc)
savedCookies.add(dc)
}
}
private fun delete(vararg toRemove: DumbCookie) {
sessionCookies.removeAll(toRemove)
savedCookies.removeAll(toRemove)
}
override fun saveFromResponse(url: HttpUrl?, cookies: List<Cookie>) {
for (cookie in cookies) {
val dc = DumbCookie(cookie)
save(dc)
}
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return sessionCookies.filter {
it.cookie.matches(url)
}.map { it.cookie }
}
fun get(domain: String, name: String): String? {
return sessionCookies.firstOrNull {
it.domainMatches(domain) && it.cookie.name() == name
}?.cookie?.value()
}
fun set(domain: String, name: String, value: String?, isSession: Boolean) = set(
domain, name, value,
if (isSession) null
else System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000L
)
/**
* Add a cookie to the cache.
* By default a session cookie is added. If [expiresAt] is set, the cookie is
* additionally persisted.
*/
fun set(domain: String, name: String?, value: String?, expiresAt: Long? = null) {
name ?: return
if (value == null) {
remove(domain, name)
return
}
val dc = DumbCookie(domain, name, value, expiresAt)
save(dc)
}
fun getAll(domain: String): Map<String, String> {
return sessionCookies.filter {
it.domainMatches(domain)
}.map { it.cookie.name() to it.cookie.value() }.toMap()
}
fun remove(domain: String, name: String) {
val toRemove = sessionCookies.filter {
it.domainMatches(domain) && it.cookie.name() == name
}
delete(*toRemove.toTypedArray())
}
fun clear(domain: String) {
val toRemove = sessionCookies.filter {
it.domainMatches(domain)
}
delete(*toRemove.toTypedArray())
}
}

View File

@ -241,7 +241,7 @@ class EventManualDialog(
with (b.dateDropdown) {
db = app.db
profileId = profileId
profileId = this@EventManualDialog.profileId
showWeekDays = false
showDays = true
showOtherDate = true
@ -269,7 +269,7 @@ class EventManualDialog(
with (b.timeDropdown) {
db = app.db
profileId = profileId
profileId = this@EventManualDialog.profileId
showAllDay = true
showCustomTime = true
lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday()
@ -289,7 +289,7 @@ class EventManualDialog(
with (b.teamDropdown) {
db = app.db
profileId = profileId
profileId = this@EventManualDialog.profileId
showNoTeam = true
loadItems()
selectTeamClass()
@ -299,7 +299,7 @@ class EventManualDialog(
with (b.subjectDropdown) {
db = app.db
profileId = profileId
profileId = this@EventManualDialog.profileId
showNoSubject = true
showCustomSubject = false
loadItems()
@ -309,7 +309,7 @@ class EventManualDialog(
with (b.teacherDropdown) {
db = app.db
profileId = profileId
profileId = this@EventManualDialog.profileId
showNoTeacher = true
loadItems()
selectDefault(editingEvent?.teacherId)

View File

@ -438,7 +438,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
return
var textHtml = if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN && app.profile.loginStoreType != LoginStore.LOGIN_TYPE_IDZIENNIK) {
var textHtml = if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN) {
HtmlCompat.toHtml(SpannableString(text), HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
.replace("\n", "")
.replace(" dir=\"ltr\"", "")

View File

@ -6,12 +6,11 @@ package pl.szczodrzynski.edziennik.utils.html
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.BulletSpan
import androidx.core.graphics.ColorUtils
import androidx.core.text.HtmlCompat
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.resolveAttr
import pl.szczodrzynski.navlib.blendColors
@ -59,16 +58,12 @@ object BetterHtml {
}*/
@Suppress("DEPRECATION")
val htmlSpannable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(
text,
Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
null,
LiTagHandler()
)
} else {
Html.fromHtml(text, null, LiTagHandler())
}
val htmlSpannable = HtmlCompat.fromHtml(
text,
HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
null,
LiTagHandler()
)
val spannableBuilder = SpannableStringBuilder(htmlSpannable)
val bulletSpans = spannableBuilder.getSpans(0, spannableBuilder.length, BulletSpan::class.java)

View File

@ -1,6 +1,6 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<merge 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:id="@+id/material_drawer_account_header"
android:layout_width="match_parent"
android:layout_height="@dimen/material_drawer_account_header_height"
@ -35,10 +35,27 @@
android:elevation="2dp"
android:focusable="true"
android:scaleType="centerCrop"
app:biv_selectorOnPress="#80ffffff"
app:materialDrawerSelectorOnPress="#80ffffff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_current_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_current"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_current"
tools:text="99" />
<com.mikepenz.materialdrawer.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_first"
style="@style/BezelImageView"
@ -50,12 +67,29 @@
android:clickable="true"
android:elevation="2dp"
android:focusable="true"
android:scaleType="fitCenter"
android:visibility="visible"
android:scaleType="centerCrop"
app:biv_selectorOnPress="#80ffffff"
app:materialDrawerSelectorOnPress="#80ffffff"
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_second"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_first_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_first"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_first"
tools:text="99" />
<com.mikepenz.materialdrawer.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_second"
style="@style/BezelImageView"
@ -67,12 +101,29 @@
android:clickable="true"
android:elevation="2dp"
android:focusable="true"
android:scaleType="fitCenter"
android:visibility="visible"
android:scaleType="centerCrop"
app:biv_selectorOnPress="#80ffffff"
app:materialDrawerSelectorOnPress="#80ffffff"
app:layout_constraintEnd_toStartOf="@id/material_drawer_account_header_small_third"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_second_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_second"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_second"
tools:text="99" />
<com.mikepenz.materialdrawer.view.BezelImageView
android:id="@+id/material_drawer_account_header_small_third"
style="@style/BezelImageView"
@ -84,12 +135,29 @@
android:clickable="true"
android:elevation="2dp"
android:focusable="true"
android:scaleType="fitCenter"
android:visibility="visible"
android:scaleType="centerCrop"
app:biv_selectorOnPress="#80ffffff"
app:materialDrawerSelectorOnPress="#80ffffff"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/material_drawer_statusbar_guideline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_account_header_small_third_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_badge_small_text"
app:layout_constraintBottom_toBottomOf="@id/material_drawer_account_header_small_third"
app:layout_constraintStart_toStartOf="@id/material_drawer_account_header_small_third"
tools:text="99" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/material_drawer_text_guideline"
android:layout_width="wrap_content"
@ -138,4 +206,4 @@
android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View File

@ -4,6 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/material_drawer_item_profile"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="@dimen/material_drawer_vertical_padding"
android:paddingLeft="@dimen/material_drawer_vertical_padding"
@ -44,7 +46,7 @@
android:textDirection="anyRtl"
android:textSize="@dimen/material_drawer_item_profile_text"
app:layout_constraintBottom_toTopOf="@id/material_drawer_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/material_drawer_badge_container"
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
@ -69,9 +71,37 @@
android:textDirection="anyRtl"
android:textSize="@dimen/material_drawer_item_profile_description"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/material_drawer_badge_container"
app:layout_constraintStart_toEndOf="@id/material_drawer_profileIcon"
app:layout_constraintTop_toBottomOf="@id/material_drawer_name"
tools:text="Some drawer text" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/material_drawer_badge_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:gravity="center"
android:paddingStart="@dimen/material_drawer_padding"
android:paddingLeft="@dimen/material_drawer_padding"
android:paddingEnd="0dp"
android:paddingRight="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/material_drawer_badge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:gravity="center"
android:lines="1"
android:minWidth="20dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:singleLine="true"
android:textSize="@dimen/material_drawer_item_primary_text"
tools:text="99" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -197,14 +197,14 @@
<string name="error_62_reason">Brak internetu</string>
<string name="error_63_reason">Brak internetu: połączenie SSL nie powiodło się</string>
<string name="error_100_reason">Brak odpowiedzi serwera. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
<string name="error_101_reason">Dane logowania niekompletne</string>
<string name="error_101_reason">Dane logowania niekompletne. Skontaktuj się z twórcą aplikacji.</string>
<string name="error_102_reason">Nieprawidłowe dane logowania</string>
<string name="error_105_reason">Profil nie został ustawiony</string>
<string name="error_106_reason">Profil jest archiwalny - synchronizacja profilu z poprzedniego roku szkolnego nie jest możliwa</string>
<string name="error_110_reason">Nieprawidłowy sposób logowania</string>
<string name="error_111_reason">Nie można wywołać metody logowania</string>
<string name="error_110_reason">Nieprawidłowy sposób logowania. Skontaktuj się z twórcą aplikacji.</string>
<string name="error_111_reason">Nie można wywołać metody logowania. Skontaktuj się z twórcą aplikacji.</string>
<string name="error_112_reason">Nie zaimplementowano</string>
<string name="error_113_reason">Wystąpił błąd podczas pobierania pliku</string>
<string name="error_113_reason">Wystąpił błąd podczas pobierania pliku. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
<string name="error_115_reason">Brak uczniów przypisanych do konta</string>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.61'
release = [
versionName: "4.0-beta.14",
versionCode: 4000014
versionName: "4.0-rc.2",
versionCode: 4000029
]
setup = [
@ -17,6 +17,8 @@ buildscript {
]
versions = [
gradleAndroid : "4.0.0-beta03",
kotlin : ext.kotlin_version,
ktx : "1.2.0",
@ -32,24 +34,24 @@ buildscript {
navigationFragment: "1.0.0",
legacy : "1.0.0",
room : "2.2.4",
room : "2.2.5",
lifecycle : "2.2.0",
work : "2.3.2",
work : "2.3.4",
firebase : '17.2.2',
firebasemessaging: "20.1.0",
firebasemessaging: "20.1.3",
play_services : "17.0.0",
materialdialogs : "0.9.6.0",
materialdrawer : "cad66092a6",
materialdrawer : "817e45765c367034b03046aaea6e95eeabcb40e9",
iconics : "4.0.1",
font_cmd : "3.5.95.1-kotlin",
navlib : "5c8b13c0d9db0d9e822fdae82c8afca6c01ab41e",
navlib : "28cdab341470dffa5f331379fe9702482681d7de",
gifdrawable : "1.2.15",
retrofit : '2.6.2'
retrofit : "2.6.4"
]
}
@ -61,11 +63,11 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-beta01'
classpath "com.android.tools.build:gradle:${versions.gradleAndroid}"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath 'me.tatarka:gradle-retrolambda:3.7.0'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.28.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip