Compare commits

...

30 Commits

Author SHA1 Message Date
d20102c3bd [3.9.17-dev] Update build.gradle and signing 2019-12-25 00:02:51 +01:00
f165ee32e5 [API/Edudziennik] Fix getting teacher in timetable and fix getting team name. 2019-12-24 15:52:15 +01:00
60ad2e81f3 [API/Edudziennik] Add getting attendance. 2019-12-24 15:39:22 +01:00
5ca8b642da [API/Edudziennik] Add getting event type in events. 2019-12-24 14:44:01 +01:00
f40cd7f26c [API/Edudziennik] Add userCode 2019-12-24 14:04:46 +01:00
e04b519e9b [API/Edudziennik] Move getting grades and info to new endpoints. 2019-12-24 13:41:06 +01:00
658e59bed6 [API/Edudziennik] Add getting teams and exams. 2019-12-24 13:26:06 +01:00
cb5eb19abc [API/Timetable] Move timetableNotPublic to student data. 2019-12-24 12:01:09 +01:00
d336531ca8 [API/Edudziennik] Use Regexes instead of Jsoup in first login. 2019-12-23 23:20:45 +01:00
30ee71f4e3 [API/Edudziennik] Add getting grades and add basic error handling. 2019-12-23 22:56:23 +01:00
844d5b33bc [API/Edudziennik] Fix getting students on first login. 2019-12-23 20:31:44 +01:00
e3741f1c75 [API/Edudziennik] Add getting timetable. 2019-12-23 18:34:39 +01:00
21a6e4d8c6 [API/Edudziennik] Add start and lucky number endpoint. 2019-12-23 16:33:20 +01:00
ec14ba76c9 [Edudziennik] Add first login. 2019-12-23 15:15:38 +01:00
90e7b1e9c7 [Edudziennik] Implement base of the new e-register. 2019-12-23 00:46:06 +01:00
a09d943344 [API/IDziennik] Fix final grade names. 2019-12-22 23:42:03 +01:00
52ac40c826 [API/Grades] Add data remove model for grades. 2019-12-22 23:21:03 +01:00
8f8eb64364 [APIv2/Librus] Show starting points only when greater than 0. 2019-12-22 22:22:46 +01:00
fe40ab0ab4 [Home/LuckyNumberCard] Fix crashing because of invalid argument type. 2019-12-22 22:13:03 +01:00
5991ef820f [Home/TimetableCard] Add counting in seconds. 2019-12-22 21:31:12 +01:00
d67c2a90b1 [Hotfix] Fix null cast exception in timetable day fragment. 2019-12-22 20:44:41 +01:00
e85d6fbc3b [UI/Counter] Add new counter activity. 2019-12-22 20:05:01 +01:00
62a9604bd2 [UI/Home] Debug card is bacc. 2019-12-22 15:36:57 +01:00
0aae2174c1 [UI/Timetable] Update date selection algorithm and no lessons info. Show empty timetable info in widget. 2019-12-21 22:56:54 +01:00
b66bd6fec9 [Dialog/BellSync] Add showing info when there are no lessons. 2019-12-21 21:19:29 +01:00
b399a3f5ad [UI/Timetable] Handle no timetable or no lessons in Home card. 2019-12-21 17:39:17 +01:00
1f5927eec0 [Manifest] Set the default theme to dark. 2019-12-20 23:49:47 +01:00
2d838e7003 [Dialog/BellSync] Add coroutine timer and make some small changes. 2019-12-20 23:48:47 +01:00
f242c30476 [API/Events] Update Event Manual dialog sharing. 2019-12-20 22:00:55 +01:00
2cf204ff79 [Dialog/BellSync] Implement bell sync dialog. 2019-12-20 00:40:14 +01:00
99 changed files with 3647 additions and 777 deletions

View File

@ -12,7 +12,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/SplashTheme"
android:theme="@style/AppTheme.Dark"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<activity
@ -99,12 +99,6 @@
__/ |
|_
-->
<activity
android:name=".widgets.timetable.LessonDetailsActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" />
<activity android:name=".widgets.timetable.LessonDialogActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
@ -122,6 +116,9 @@
android:name=".ui.modules.webpush.WebPushConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme.Dark" />
<activity
android:name=".ui.modules.home.CounterActivityOld"
android:theme="@style/AppTheme.Black" />
<activity
android:name=".ui.modules.home.CounterActivity"
android:theme="@style/AppTheme.Black" />

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0x97, 0x0e, 0x93, 0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0xc4, 0x14, 0x3c, 0x14, 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

@ -112,15 +112,13 @@ fun String.swapFirstLastName(): String {
}
}
fun String.getFirstLastName(): Pair<String, String>? {
fun String.splitName(): Pair<String, String>? {
return this.split(" ").let {
if (it.size >= 2) Pair(it[0], it[1])
else null
}
}
fun String.getLastFirstName() = this.getFirstLastName()
fun changeStringCase(s: String): String {
val delimiters = " '-/"
val sb = StringBuilder()
@ -179,6 +177,20 @@ fun colorFromName(context: Context, name: String?): Int {
return context.getColorFromRes(color)
}
fun colorFromCssName(name: String): Int {
return when (name) {
"red" -> 0xffff0000
"green" -> 0xff008000
"blue" -> 0xff0000ff
"violet" -> 0xffee82ee
"brown" -> 0xffa52a2a
"orange" -> 0xffffa500
"black" -> 0xff000000
"white" -> 0xffffffff
else -> -1
}.toInt()
}
fun MutableList<Profile>.filterOutArchived(): MutableList<Profile> {
this.removeAll { it.archived }
return this
@ -576,52 +588,65 @@ operator fun StringBuilder.plusAssign(str: String?) {
this.append(str)
}
fun Context.timeTill(time: Int, delimiter: String = " "): String {
fun Context.timeTill(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to hours; prefixAdded = true
parts += R.plurals.time_till_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to minutes; prefixAdded = true
parts += R.plurals.time_till_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_till_text to seconds; prefixAdded = true
parts += R.plurals.time_till_seconds to seconds
if (!countInSeconds) {
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to hours
prefixAdded = true
parts += R.plurals.time_till_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to minutes
prefixAdded = true
parts += R.plurals.time_till_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_till_text to seconds
prefixAdded = true
parts += R.plurals.time_till_seconds to seconds
}
} else {
parts += R.plurals.time_till_text to time
parts += R.plurals.time_till_seconds to time
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
fun Context.timeLeft(time: Int, delimiter: String = " "): String {
fun Context.timeLeft(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String {
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to hours
prefixAdded = true
parts += R.plurals.time_left_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to minutes
prefixAdded = true
parts += R.plurals.time_left_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_left_text to seconds
prefixAdded = true
parts += R.plurals.time_left_seconds to seconds
if (!countInSeconds) {
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to hours
prefixAdded = true
parts += R.plurals.time_left_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to minutes
prefixAdded = true
parts += R.plurals.time_left_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_left_text to seconds
prefixAdded = true
parts += R.plurals.time_left_seconds to seconds
}
} else {
parts += R.plurals.time_left_text to time
parts += R.plurals.time_left_seconds to time
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
@ -641,3 +666,19 @@ fun Drawable.setTintColor(color: Int): Drawable {
)
return this
}
inline fun <T> List<T>.ifNotEmpty(block: (List<T>) -> Unit) {
if (!isEmpty())
block(this)
}
val String.firstLettersName: String
get() {
var nameShort = ""
this.split(" ").forEach {
if (it.isBlank())
return@forEach
nameShort += it[0].toLowerCase()
}
return nameShort
}

View File

@ -58,7 +58,7 @@ import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
@ -127,7 +127,7 @@ class MainActivity : AppCompatActivity() {
val list: MutableList<NavTarget> = mutableListOf()
// home item
list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragmentV2::class)
list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class)
.withTitle(R.string.app_name)
.withIcon(CommunityMaterial.Icon2.cmd_home_outline)
.isInDrawer(true)

View File

@ -43,6 +43,8 @@ class WidgetTimetable : AppWidgetProvider() {
super.onReceive(context, intent)
}
private val ignoreCancelled = true
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
val thisWidget = ComponentName(context, WidgetTimetable::class.java)
@ -122,7 +124,7 @@ class WidgetTimetable : AppWidgetProvider() {
val method = declaredMethods[m]
if (method.name == "setDrawableParameters") {
method.isAccessible = true
method.invoke(views, R.id.widgetTimetableListView, true, -1, colorFilter.toInt(), mode, -1)
method.invoke(views, R.id.widgetTimetableBackground, true, -1, colorFilter.toInt(), mode, -1)
method.invoke(views, R.id.widgetTimetableHeader, true, -1, colorFilter.toInt(), mode, -1)
break
}
@ -185,12 +187,30 @@ class WidgetTimetable : AppWidgetProvider() {
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
var lessons = lessonList.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.displayEndTime > now
&& !(it.isCancelled && ignoreCancelled)
}
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
it.type != Lesson.TYPE_NO_LESSONS
&& (it.displayDate != today
|| (it.displayDate == today
&& it.displayEndTime != null
&& it.displayEndTime!! >= now))
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
lessons = lessonList.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& !(it.isCancelled && ignoreCancelled)
}
if (lessons.isEmpty() && timetableDate.weekDay <= 5)
break
checkedDays++
}
@ -199,6 +219,32 @@ class WidgetTimetable : AppWidgetProvider() {
if (lessons.isNotEmpty())
displayingDate = timetableDate
profileId = profile.id
if (lessons.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
views.setViewVisibility(R.id.widgetTimetableNoTimetable, View.VISIBLE)
}
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
views.setViewVisibility(R.id.widgetTimetableListView, View.GONE)
views.setViewVisibility(R.id.widgetTimetableNoLessons, View.VISIBLE)
}
}
else {
if (lessons.isEmpty()) {
val separator = ItemWidgetTimetableModel()
separator.profileId = profile.id
separator.bigStyle = widgetConfig.bigStyle
separator.darkTheme = widgetConfig.darkTheme
separator.isNoTimetableItem = true;
models.add(separator)
}
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
val separator = ItemWidgetTimetableModel()
separator.profileId = profile.id
separator.bigStyle = widgetConfig.bigStyle
separator.darkTheme = widgetConfig.darkTheme
separator.isNoLessonsItem = true;
models.add(separator)
}
}
// get all events for the current date
@ -298,13 +344,6 @@ class WidgetTimetable : AppWidgetProvider() {
val pendingOpenIntent = PendingIntent.getActivity(app, appWidgetId, openIntent, 0)
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent)
if (lessonList.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
views.setRemoteAdapter(R.id.widgetTimetableListView, Intent())
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons))
return
}
timetables!!.put(appWidgetId, models)
// apply the list service to the list view
@ -315,7 +354,7 @@ class WidgetTimetable : AppWidgetProvider() {
// create an intent used to display the lesson details dialog
val intentTemplate = Intent(app, LessonDialogActivity::class.java)
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK /*or Intent.FLAG_ACTIVITY_CLEAR_TASK*/)
val pendingIntentTimetable = PendingIntent.getActivity(app, appWidgetId, intentTemplate, 0)
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable)
@ -336,11 +375,8 @@ class WidgetTimetable : AppWidgetProvider() {
}
companion object {
val ACTION_SYNC_DATA = "ACTION_SYNC_DATA"
private val TAG = "WidgetTimetable"
private val modeInt = 0
const val ACTION_SYNC_DATA = "ACTION_SYNC_DATA"
private const val TAG = "WidgetTimetable"
var timetables: SparseArray<List<ItemWidgetTimetableModel>>? = null

View File

@ -101,3 +101,5 @@ const val VULCAN_API_ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/Wia
const val VULCAN_API_ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane"
const val VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS = "mobile-api/Uczen.v3.Uczen/ZmienStatusWiadomosci"
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -158,6 +158,12 @@ const val ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA = 441
const val ERROR_IDZIENNIK_API_ACCESS_DENIED = 450
const val ERROR_IDZIENNIK_API_OTHER = 451
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501
const val ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER = 510
const val ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID = 511
const val ERROR_EDUDZIENNIK_WEB_TIMETABLE_NOT_PUBLIC = 520
const val ERROR_EDUDZIENNIK_WEB_TEAM_MISSING = 530
const val ERROR_TEMPLATE_WEB_OTHER = 801
const val EXCEPTION_API_TASK = 900
@ -175,5 +181,6 @@ const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920
const val LOGIN_NO_ARGUMENTS = 1201

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi
@ -130,6 +131,14 @@ val idziennikLoginMethods = listOf(
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB }
)
const val LOGIN_TYPE_EDUDZIENNIK = 5
const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100
val edudziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_EDUDZIENNIK, LOGIN_METHOD_EDUDZIENNIK_WEB, EdudziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
val templateLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java)
.withIsPossible { _, _ -> true }

View File

@ -4,76 +4,84 @@
package pl.szczodrzynski.edziennik.data.api
import kotlin.text.RegexOption.DOT_MATCHES_ALL
object Regexes {
val STYLE_CSS_COLOR by lazy {
"""color: \w+?;?"?""".toRegex()
}
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
"""<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_COLOR by lazy {
"""background-color:([#A-Fa-f0-9]+);""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""background-color:([#A-Fa-f0-9]+);""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_CATEGORY by lazy {
""">&nbsp;(.+?):</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
""">&nbsp;(.+?):</span>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy {
"""Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy {
"""Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy {
"""Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_DETAILS by lazy {
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?(?:<small>\((.+?)\)</small>.*?)?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>.*?(?:Komentarz:.*?<strong>(.+?)</strong>)?</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?(?:<small>\((.+?)\)</small>.*?)?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>.*?(?:Komentarz:.*?<strong>(.+?)</strong>)?</span>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_EVENT_TYPE by lazy {
"""\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_LUCKY_NUMBER by lazy {
"""class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*</a>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*</a>""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_CLASS_CALENDAR by lazy {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE)
}
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_ERROR by lazy {
"""id="spanErrorMessage">(.*?)</""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""id="spanErrorMessage">(.*?)</""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME by lazy {
"""Imię i nazwisko:.+?">(.+?)</div>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""Imię i nazwisko:.+?">(.+?)</div>""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy {
"""id="ctl00_CzyRodzic" value="([01])" />""".toRegex()
}
val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy {
"""name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy {
"""<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(DOT_MATCHES_ALL)
}
val VULCAN_SHITFT_ANNOTATION by lazy {
val VULCAN_SHIFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
}
@ -82,4 +90,49 @@ object Regexes {
val LIBRUS_ATTACHMENT_KEY by lazy {
"""singleUseKey=([0-9A-f_]+)""".toRegex()
}
val EDUDZIENNIK_STUDENTS_START by lazy {
"""<li><a href="/Students/([\w-_]+?)/start/">(.*?)</a>""".toRegex()
}
val EDUDZIENNIK_ACCOUNT_NAME_START by lazy {
"""<span id='user_dn'>(.*?)</span>""".toRegex()
}
val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy {
"""<td id="([\d-]+?):(\d+?)".*?>(.+?)</td>""".toRegex()
}
val EDUDZIENNIK_ATTENDANCE_TYPES by lazy {
"""<div class="info">.*?<p>(.*?)</p>""".toRegex(DOT_MATCHES_ALL)
}
val EDUDZIENNIK_ATTENDANCE_TYPE by lazy {
"""\((.+?)\) (.+)""".toRegex()
}
val EDUDZIENNIK_SUBJECT_ID by lazy {
"""/Courses/([\w-_]+?)/""".toRegex()
}
val EDUDZIENNIK_GRADE_ID by lazy {
"""/Grades/([\w-_]+?)/""".toRegex()
}
val EDUDZIENNIK_EXAM_ID by lazy {
"""/Evaluations/([\w-_]+?)/""".toRegex()
}
val EDUDZIENNIK_EVENT_TYPE_ID by lazy {
"""/GradeLabels/([\w-_]+?)/""".toRegex()
}
val EDUDZIENNIK_SCHOOL_DETAIL_ID by lazy {
"""<a id="School_detail".*?/School/([\w-_]+?)/""".toRegex(DOT_MATCHES_ALL)
}
val EDUDZIENNIK_SCHOOL_DETAIL_NAME by lazy {
"""</li>.*?<p>(.*?)</p>.*?<li>""".toRegex(DOT_MATCHES_ALL)
}
val EDUDZIENNIK_CLASS_DETAIL_ID by lazy {
"""<a id="Klass_detail".*?/Klass/([\w-_]+?)/""".toRegex(DOT_MATCHES_ALL)
}
val EDUDZIENNIK_CLASS_DETAIL_NAME by lazy {
"""<a id="Klass_detail".*?>(.*?)</a>""".toRegex(DOT_MATCHES_ALL)
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
/**
* Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art
*
* Use https://codepen.io/kubasz/pen/RwwwbGN to easily generate the student data getters/setters
*/
class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isWebLoginValid() = webSessionIdExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
loginMethods.clear()
if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_EDUDZIENNIK_WEB
}
}
private var mLoginEmail: String? = null
var loginEmail: String?
get() { mLoginEmail = mLoginEmail ?: loginStore.getLoginData("email", null); return mLoginEmail }
set(value) { loginStore.putLoginData("email", value); mLoginEmail = value }
private var mLoginPassword: String? = null
var loginPassword: String?
get() { mLoginPassword = mLoginPassword ?: loginStore.getLoginData("password", null); return mLoginPassword }
set(value) { loginStore.putLoginData("password", value); mLoginPassword = value }
private var mStudentId: String? = null
var studentId: String?
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
private var mSchoolId: String? = null
var schoolId: String?
get() { mSchoolId = mSchoolId ?: profile?.getStudentData("schoolId", null); return mSchoolId }
set(value) { profile?.putStudentData("schoolId", value) ?: return; mSchoolId = value }
private var mClassId: String? = null
var classId: String?
get() { mClassId = mClassId ?: profile?.getStudentData("classId", null); return mClassId }
set(value) { profile?.putStudentData("classId", value) ?: return; mClassId = value }
/* __ __ _
\ \ / / | |
\ \ /\ / /__| |__
\ \/ \/ / _ \ '_ \
\ /\ / __/ |_) |
\/ \/ \___|_._*/
private var mWebSessionId: String? = null
var webSessionId: String?
get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId }
set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value }
private var mWebSessionIdExpiryTime: Long? = null
var webSessionIdExpiryTime: Long
get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("webSessionIdExpiryTime", 0L); return mWebSessionIdExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("webSessionIdExpiryTime", value); mWebSessionIdExpiryTime = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
private var mSchoolName: String? = null
var schoolName: String?
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
private var mTimetableNotPublic: Boolean? = null
var timetableNotPublic: Boolean
get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false }
set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value }
val studentEndpoint: String
get() = "Students/$studentId/"
val schoolEndpoint: String
get() = "Schools/$schoolId/"
val schoolClassEndpoint: String
get() = "Schools/$classId/"
val studentAndClassEndpoint: String
get() = "Students/$studentId/Klass/$classId/"
val courseEndpoint: String
get() = "Course/$studentId/"
val timetableEndpoint: String
get() = "Plan/$studentId/"
fun getSubject(longId: String, name: String): Subject {
val id = longId.crc32()
return subjectList.singleOrNull { it.id == id } ?: run {
val subject = Subject(profileId, id, name, name)
subjectList.put(id, subject)
subject
}
}
fun getTeacher(firstName: String, lastName: String): Teacher {
val name = "$firstName $lastName".fixName()
val id = name.crc32()
return teacherList.singleOrNull { it.id == id } ?: run {
val teacher = Teacher(profileId, id, firstName, lastName)
teacherList.put(id, teacher)
teacher
}
}
fun getEventType(longId: String, name: String): EventType {
val id = longId.crc16().toLong()
return eventTypes.singleOrNull { it.id == id } ?: run {
val eventType = EventType(profileId, id, name, colorFromName(app, name))
eventTypes.put(id, eventType)
eventType
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TIMETABLE_NOT_PUBLIC
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID
import pl.szczodrzynski.edziennik.data.api.edudziennikLoginMethods
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikData
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin.EdudziennikFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLogin
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Edudziennik"
}
val internalErrorList = mutableListOf<Int>()
val data: DataEdudziennik
init {
data = DataEdudziennik(app, profile, loginStore).apply {
callback = wrapCallback(this@Edudziennik.callback)
satisfyLoginMethods()
}
}
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(edudziennikLoginMethods, EdudziennikFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
EdudziennikLogin(data) {
EdudziennikData(data) {
completed()
}
}
}
private fun login() {
d(TAG, "Trying to login with ${data.targetLoginMethodIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
EdudziennikLogin(data) {
data()
}
}
private fun data() {
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
EdudziennikData(data) {
completed()
}
}
override fun getMessage(message: MessageFull) {
}
override fun markAllAnnouncementsAsRead() {
}
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
}
override fun firstLogin() {
EdudziennikFirstLogin(data) {
completed()
}
}
override fun cancel() {
d(TAG, "Cancelled")
data.cancel()
}
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
return object : EdziennikCallback {
override fun onCompleted() {
callback.onCompleted()
}
override fun onProgress(step: Float) {
callback.onProgress(step)
}
override fun onStartProgress(stringRes: Int) {
callback.onStartProgress(stringRes)
}
override fun onError(apiError: ApiError) {
if (apiError.errorCode in internalErrorList) {
// finish immediately if the same error occurs twice during the same sync
callback.onError(apiError)
return
}
internalErrorList.add(apiError.errorCode)
when (apiError.errorCode) {
ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID -> {
login()
}
ERROR_EDUDZIENNIK_WEB_TIMETABLE_NOT_PUBLIC -> {
data.timetableNotPublic = true
data()
}
else -> callback.onError(apiError)
}
}
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-23
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.Feature
const val ENDPOINT_EDUDZIENNIK_WEB_START = 1000
const val ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE = 1001
const val ENDPOINT_EDUDZIENNIK_WEB_EXAMS = 1002
const val ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE = 1003
const val ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER = 1010
val EdudziennikFeatures = listOf(
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_EDUDZIENNIK_WEB_START to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)),
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_TIMETABLE, listOf(
ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)),
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_GRADES, listOf(
ENDPOINT_EDUDZIENNIK_WEB_START to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)),
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_AGENDA, listOf(
ENDPOINT_EDUDZIENNIK_WEB_EXAMS to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)),
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ATTENDANCE, listOf(
ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)),
Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER to LOGIN_METHOD_EDUDZIENNIK_WEB
), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB))
)

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.*
import pl.szczodrzynski.edziennik.utils.Utils
class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "TemplateData"
}
init {
nextEndpoint(onSuccess)
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
onSuccess()
return
}
if (data.cancelled) {
onSuccess()
return
}
useEndpoint(data.targetEndpointIds.removeAt(0)) {
data.progress(data.progressStep)
nextEndpoint(onSuccess)
}
}
private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId")
when (endpointId) {
ENDPOINT_EDUDZIENNIK_WEB_START -> {
data.startProgress(R.string.edziennik_progress_endpoint_data)
EdudziennikWebStart(data, onSuccess)
}
ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
EdudziennikWebTimetable(data, onSuccess)
}
ENDPOINT_EDUDZIENNIK_WEB_EXAMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
EdudziennikWebExams(data, onSuccess)
}
ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
EdudziennikWebAttendance(data, onSuccess)
}
ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER -> {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
EdudziennikWebLuckyNumber(data, onSuccess)
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
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.EDUDZIENNIK_USER_AGENT
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
import pl.szczodrzynski.edziennik.data.api.ERROR_RESPONSE_EMPTY
import pl.szczodrzynski.edziennik.data.api.EXCEPTION_EDUDZIENNIK_WEB_REQUEST
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
open class EdudziennikWeb(open val data: DataEdudziennik) {
companion object {
private const val TAG = "EdudziennikWeb"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun webGet(tag: String, endpoint: String, xhr: Boolean = false, onSuccess: (text: String) -> Unit) {
val url = "https://dziennikel.appspot.com/" + when (endpoint.endsWith('/') || endpoint.contains('?') || endpoint.isEmpty()) {
true -> endpoint
else -> "$endpoint/"
}
d(tag, "Request: Edudziennik/Web - $url")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null || response == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_WEB_REQUEST)
.withThrowable(e)
.withResponse(response)
.withApiResponse(text))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("sessionid")
.value(data.webSessionId!!)
.domain("dziennikel.appspot.com")
.secure().httpOnly().build(),
Cookie.Builder()
.name("semester")
.value((profile?.currentSemester ?: 1).toString())
.domain("dziennikel.appspot.com")
.secure().httpOnly().build()
))
Request.builder()
.url(url)
.userAgent(EDUDZIENNIK_USER_AGENT)
.apply {
if (xhr) header("X-Requested-With", "XMLHttpRequest")
}
.get()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-24
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_ENTRIES
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPE
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPES
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class EdudziennikWebAttendance(override val data: DataEdudziennik,
val onSuccess: () -> Unit) : EdudziennikWeb(data) {
companion object {
private const val TAG = "EdudziennikWebAttendance"
}
init { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "Presence") { text ->
val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map {
val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim())
val symbol = type?.get(1)?.trim()
val name = type?.get(2)?.trim()
return@map Triple(
symbol,
name,
when (name?.toLowerCase(Locale.ROOT)) {
"obecność" -> Attendance.TYPE_PRESENT
"nieobecność" -> Attendance.TYPE_ABSENT
"spóźnienie" -> Attendance.TYPE_BELATED
"nieobecność usprawiedliwiona" -> Attendance.TYPE_ABSENT_EXCUSED
"dzień wolny" -> Attendance.TYPE_RELEASED
"brak zajęć" -> Attendance.TYPE_RELEASED
"oddelegowany" -> Attendance.TYPE_RELEASED
else -> Attendance.TYPE_CUSTOM
}
)
} ?: emptyList()
EDUDZIENNIK_ATTENDANCE_ENTRIES.findAll(text).forEach { attendanceElement ->
val date = Date.fromY_m_d(attendanceElement[1])
val lessonNumber = attendanceElement[2].toInt()
val attendanceSymbol = attendanceElement[3]
val lessons = data.app.db.timetableDao().getForDateNow(profileId, date)
val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber }
val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32()
val (_, name, type) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol }
?: return@forEach
val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime
?: return@forEach
val attendanceObject = Attendance(
profileId,
id,
lesson?.displayTeacherId ?: -1,
lesson?.displaySubjectId ?: -1,
profile.currentSemester,
name,
date,
lesson?.displayStartTime ?: startTime,
type
)
data.attendanceList.add(attendanceObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
onSuccess()
}
} ?: onSuccess() }
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-24
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EVENT_TYPE_ID
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EXAM_ID
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.models.Date
class EdudziennikWebExams(override val data: DataEdudziennik,
val onSuccess: () -> Unit) : EdudziennikWeb(data) {
companion object {
const val TAG = "EdudziennikWebExams"
}
init { profile?.also { profile ->
webGet(TAG, data.studentAndClassEndpoint + "Evaluations", xhr = true) { text ->
val doc = Jsoup.parseBodyFragment("<table>" + text.trim() + "</table>")
doc.select("tr").forEach { examElement ->
val id = EDUDZIENNIK_EXAM_ID.find(examElement.child(0).child(0).attr("href"))
?.get(1)?.crc32() ?: return@forEach
val topic = examElement.child(0).text().trim()
val subjectElement = examElement.child(1).child(0)
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
?: return@forEach
val subjectName = subjectElement.text().trim()
val subject = data.getSubject(subjectId, subjectName)
val dateString = examElement.child(2).text().trim()
if (dateString.isBlank()) return@forEach
val date = Date.fromY_m_d(dateString)
val lessons = data.app.db.timetableDao().getForDateNow(profileId, date)
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.startTime
val eventTypeElement = examElement.child(3).child(0)
val eventTypeId = EDUDZIENNIK_EVENT_TYPE_ID.find(eventTypeElement.attr("href"))?.get(1)
?: return@forEach
val eventTypeName = eventTypeElement.text()
val eventType = data.getEventType(eventTypeId, eventTypeName)
val eventObject = Event(
profileId,
id,
date,
startTime,
topic,
-1,
eventType.id.toInt(),
false,
-1,
subject.id,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_EVENT,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_DEFAULT))
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_EXAMS, SYNC_ALWAYS)
onSuccess()
}
}}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-23
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
class EdudziennikWebLuckyNumber(override val data: DataEdudziennik,
val onSuccess: () -> Unit) : EdudziennikWeb(data) {
companion object {
private const val TAG = "EdudziennikWebLuckyNumber"
}
init { data.profile?.also { profile ->
webGet(TAG, data.schoolEndpoint + "Lucky", xhr = true) { text ->
text.toIntOrNull()?.let { luckyNumber ->
val luckyNumberObject = LuckyNumber(
profileId,
Date.getToday(),
luckyNumber
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER, SYNC_ALWAYS)
onSuccess()
}
} ?: onSuccess() }
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-23
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_START
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
class EdudziennikWebStart(override val data: DataEdudziennik,
val onSuccess: () -> Unit) : EdudziennikWeb(data) {
companion object {
private const val TAG = "EdudziennikWebStart"
}
init {
webGet(TAG, data.studentEndpoint + "start") { text ->
val doc = Jsoup.parse(text)
EdudziennikWebStartInfo(data, text)
EdudziennikWebStartGrades(data, doc)
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_START, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-24
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import android.graphics.Color
import org.jsoup.nodes.Document
import pl.szczodrzynski.edziennik.colorFromCssName
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class EdudziennikWebStartGrades(val data: DataEdudziennik, val doc: Document) {
companion object {
const val TAG = "EdudziennikWebStartGrades"
}
init {data.profile?.also { profile ->
val subjects = doc.select("#student_grades tbody").firstOrNull()?.children()
if (subjects.isNullOrEmpty()) return@also
subjects.forEach { subjectElement ->
if (subjectElement.id().isBlank()) return@forEach
val subjectId = subjectElement.id().trim()
val subjectName = subjectElement.child(0).text().trim()
val subject = data.getSubject(subjectId, subjectName)
val grades = subjectElement.select(".grade")
val gradesInfo = subjectElement.select(".grade-tip")
val gradeValues = subjects.select(".avg-$subjectId .grade-tip > p").first()
.text().split('+').map {
val split = it.split('*')
val weight = split[0].trim().toFloat()
val value = split[1].trim().toFloat()
Pair(value, weight)
}
grades.forEachIndexed { index, gradeElement ->
val id = Regexes.EDUDZIENNIK_GRADE_ID.find(gradeElement.attr("href"))?.get(1)?.crc32()
?: return@forEachIndexed
val (value, weight) = gradeValues[index]
val name = gradeElement.text().trim().let {
if (it.contains(',') || it.contains('.')) {
val replaced = it.replace(',', '.')
val float = replaced.toFloatOrNull()
if (float != null && float % 1 == 0f) float.toInt().toString()
else it
} else it
}
val info = gradesInfo[index]
val category = info.child(4).text().trim()
val (teacherLastName, teacherFirstName) = info.child(1).text().split(' ')
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
val addedDate = info.child(2).text().split(' ').let {
val day = it[0].toInt()
val month = Utils.monthFromName(it[1])
val year = it[2].toInt()
Date(year, month, day).inMillis
}
val color = Regexes.STYLE_CSS_COLOR.find(gradeElement.attr("style"))?.get(1)?.let {
if (it.startsWith('#')) Color.parseColor(it)
else colorFromCssName(it)
} ?: -1
val gradeObject = Grade(
data.profileId,
id,
category,
color,
"",
name,
value,
weight,
profile.currentSemester,
teacher.id,
subject.id
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
data.profileId,
Metadata.TYPE_GRADE,
id,
profile.empty,
profile.empty,
addedDate
))
}
val proposed = subjectElement.select(".proposal").firstOrNull()?.text()?.trim()
if (proposed != null && proposed.isNotBlank()) {
val proposedGradeObject = Grade(
data.profileId,
(-1 * subject.id) - 1,
"",
-1,
"",
proposed,
proposed.toFloatOrNull() ?: 0f,
0f,
profile.currentSemester,
-1,
subject.id
).apply {
type = when (semester) {
1 -> TYPE_SEMESTER1_PROPOSED
else -> TYPE_SEMESTER2_PROPOSED
}
}
data.gradeList.add(proposedGradeObject)
data.metadataList.add(Metadata(
data.profileId,
Metadata.TYPE_GRADE,
proposedGradeObject.id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
val final = subjectElement.select(".final").firstOrNull()?.text()?.trim()
if (final != null && final.isNotBlank()) {
val finalGradeObject = Grade(
data.profileId,
(-1 * subject.id) - 2,
"",
-1,
"",
final,
final.toFloatOrNull() ?: 0f,
0f,
profile.currentSemester,
-1,
subject.id
).apply {
type = when (semester) {
1 -> TYPE_SEMESTER1_FINAL
else -> TYPE_SEMESTER2_FINAL
}
}
data.gradeList.add(finalGradeObject)
data.metadataList.add(Metadata(
data.profileId,
Metadata.TYPE_GRADE,
finalGradeObject.id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
}
data.toRemove.addAll(listOf(
TYPE_NORMAL,
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
TYPE_SEMESTER1_FINAL,
TYPE_SEMESTER2_FINAL
).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
}}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-24
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TEAM_MISSING
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.firstLettersName
import pl.szczodrzynski.edziennik.get
class EdudziennikWebStartInfo(val data: DataEdudziennik, val text: String) {
companion object {
const val TAG = "EdudziennikWebStartInfo"
}
init { run {
val schoolId = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_ID.find(text)?.get(1)?.trim()
val schoolLongName = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_NAME.find(text)?.get(1)?.trim()
data.schoolId = schoolId
val classId = Regexes.EDUDZIENNIK_CLASS_DETAIL_ID.find(text)?.get(1)?.trim()
val className = Regexes.EDUDZIENNIK_CLASS_DETAIL_NAME.find(text)?.get(1)?.trim()
data.classId = classId
if (classId == null || className == null || schoolId == null || schoolLongName == null) {
data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TEAM_MISSING)
.withApiResponse(text))
return@run
}
val schoolName = schoolId.crc32().toString() + schoolLongName.firstLettersName + "_edu"
data.schoolName = schoolName
val teamId = classId.crc32()
val teamCode = "$schoolName:$className"
val teamObject = Team(
data.profileId,
teamId,
className,
Team.TYPE_CLASS,
teamCode,
-1
)
data.teamClass = teamObject
data.teamList.put(teamObject.id, teamObject)
}}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-23
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TIMETABLE_NOT_PUBLIC
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.splitName
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
class EdudziennikWebTimetable(override val data: DataEdudziennik,
val onSuccess: () -> Unit) : EdudziennikWeb(data) {
companion object {
private const val TAG = "EdudziennikWebTimetable"
}
init { data.profile?.also { profile ->
val currentWeekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) {
currentWeekStart.stepForward(0, 0, 7)
}
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
webGet(TAG, data.timetableEndpoint + "print?date=$getDate") { text ->
val doc = Jsoup.parse(text)
val dataDays = mutableListOf<Int>()
val dataStart = weekStart.clone()
while (dataStart <= weekEnd) {
dataDays += dataStart.value
dataStart.stepForward(0, 0, 1)
}
val table = doc.select("#Schedule tbody").first()
if (table.text().trim() == "Brak planu lekcji.") {
data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TIMETABLE_NOT_PUBLIC)
.withApiResponse(text))
onSuccess()
return@webGet
}
table.children().forEach { row ->
val rowElements = row.children()
val lessonNumber = rowElements[0].text().toInt()
val times = rowElements[1].text().split('-')
val startTime = Time.fromH_m(times[0].trim())
val endTime = Time.fromH_m(times[1].trim())
data.lessonRanges.singleOrNull {
it.lessonNumber == lessonNumber && it.startTime == startTime && it.endTime == endTime
} ?: run {
data.lessonRanges.put(lessonNumber, LessonRange(profileId, lessonNumber, startTime, endTime))
}
rowElements.subList(2, rowElements.size).forEachIndexed { index, lesson ->
val course = lesson.select(".course").firstOrNull() ?: return@forEachIndexed
val info = course.select("span > span")
if (info.isEmpty()) return@forEachIndexed
val type = when (course.hasClass("substitute")) {
true -> Lesson.TYPE_CHANGE
else -> Lesson.TYPE_NORMAL
}
/* Getting subject */
val subjectElement = info[0].child(0)
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
?: return@forEachIndexed
val subjectName = subjectElement.text().trim()
val subject = data.getSubject(subjectId, subjectName)
/* Getting teacher */
val teacherId = if (info.size >= 2) {
val teacherElement = info[1].child(0)
val teacherName = teacherElement.text().trim()
teacherName.splitName()?.let { (teacherLastName, teacherFirstName) ->
data.getTeacher(teacherFirstName, teacherLastName)
}?.id ?: -1
} else -1
val lessonObject = Lesson(profileId, -1).also {
it.type = type
it.date = weekStart.clone().stepForward(0, 0, index)
it.lessonNumber = lessonNumber
it.startTime = startTime
it.endTime = endTime
it.subjectId = subject.id
it.teacherId = teacherId
it.id = it.buildId()
}
data.lessonNewList.add(lessonObject)
dataDays.remove(lessonObject.date!!.value)
if (type != Lesson.TYPE_NORMAL) {
val seen = profile.empty || lessonObject.date!! < Date.getToday()
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
seen,
seen,
System.currentTimeMillis()
))
}
}
}
for (day in dataDays) {
val lessonDate = Date.fromValue(day)
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply {
type = Lesson.TYPE_NO_LESSONS
date = lessonDate
}
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
if (data.timetableNotPublic) data.timetableNotPublic = false
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
onSuccess()
}
} ?: onSuccess() }
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ACCOUNT_NAME_START
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_STUDENTS_START
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getShortName
import pl.szczodrzynski.edziennik.utils.Utils
class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "EdudziennikFirstLogin"
}
private val web = EdudziennikWeb(data)
private val profileList = mutableListOf<Profile>()
init {
EdudziennikLoginWeb(data) {
web.webGet(TAG, "") { text ->
val accountName = EDUDZIENNIK_ACCOUNT_NAME_START.find(text)?.get(1)?.fixName()
EDUDZIENNIK_STUDENTS_START.findAll(text).forEach {
val studentId = it[1]
val studentName = it[2].fixName()
if (studentId.isBlank() || studentName.isBlank()) return@forEach
val profile = Profile()
profile.studentNameLong = studentName
profile.studentNameShort = studentName.getShortName()
profile.accountNameLong = if (studentName == accountName) null else accountName
profile.studentSchoolYear = Utils.getCurrentSchoolYear()
profile.name = studentName
profile.subname = data.loginEmail
profile.empty = true
profile.putStudentData("studentId", studentId)
profileList.add(profile)
}
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.utils.Utils
class EdudziennikLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "EdudziennikLogin"
}
private var cancelled = false
init {
nextLoginMethod(onSuccess)
}
private fun nextLoginMethod(onSuccess: () -> Unit) {
if (data.targetLoginMethodIds.isEmpty()) {
onSuccess()
return
}
if (cancelled) {
onSuccess()
return
}
useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId ->
data.progress(data.progressStep)
if (usedMethodId != -1)
data.loginMethods.add(usedMethodId)
nextLoginMethod(onSuccess)
}
}
private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) {
// this should never be true
if (data.loginMethods.contains(loginMethodId)) {
onSuccess(-1)
return
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_EDUDZIENNIK_WEB -> {
data.startProgress(R.string.edziennik_progress_login_edudziennik_web)
EdudziennikLoginWeb(data) { onSuccess(loginMethodId) }
}
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.Utils.d
class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "EdudziennikLoginWeb"
}
init { run {
if (data.isWebLoginValid()) {
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("dziennikel.appspot.com")
if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
loginWithCredentials()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun loginWithCredentials() {
d(TAG, "Request: Edudziennik/Login/Web - https://dziennikel.appspot.com/login/?next=/")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null || response == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val url = response.raw().request().url().toString()
if (!url.contains("Student")) {
when {
text.contains("Wprowadzono nieprawidłową nazwę użytkownika lub hasło.") -> ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN
else -> ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
val sessionId = cookies.firstOrNull { it.name() == "sessionid" }?.value()
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.webSessionId = sessionId
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */
onSuccess()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("https://dziennikel.appspot.com/login/?next=/")
.userAgent(EDUDZIENNIK_USER_AGENT)
.contentType("application/x-www-form-urlencoded")
.addParameter("email", data.loginEmail)
.addParameter("password", data.loginPassword)
.addParameter("auth_method", "password")
.addParameter("next", "/")
.post()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -23,7 +24,7 @@ class IdziennikWebGrades(override val data: DataIdziennik,
private const val TAG = "IdziennikWebGrades"
}
init {
init { data.profile?.also { profile ->
webApiGet(TAG, IDZIENNIK_WEB_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
@ -123,12 +124,12 @@ class IdziennikWebGrades(override val data: DataIdziennik,
}
1 -> {
gradeObject.type = Grade.TYPE_SEMESTER1_FINAL
gradeObject.name = name
gradeObject.name = value.toInt().toString()
gradeObject.weight = 0f
}
2 -> {
gradeObject.type = Grade.TYPE_YEAR_FINAL
gradeObject.name = name
gradeObject.name = value.toInt().toString()
gradeObject.weight = 0f
}
}
@ -141,15 +142,22 @@ class IdziennikWebGrades(override val data: DataIdziennik,
profileId,
Metadata.TYPE_GRADE,
id,
data.profile?.empty ?: false,
data.profile?.empty ?: false,
data.profile.empty,
data.profile.empty,
addedDate
))
}
}
data.toRemove.addAll(listOf(
Grade.TYPE_NORMAL,
Grade.TYPE_SEMESTER1_FINAL,
Grade.TYPE_YEAR_FINAL
).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
} ?: onSuccess() }
}

View File

@ -4,13 +4,14 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_MISSING_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_PROPOSED
@ -106,8 +107,11 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
}
}
data.toRemove.addAll(listOf(TYPE_SEMESTER1_PROPOSED, TYPE_YEAR_PROPOSED).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS)
onSuccess()
}
}}
} ?: onSuccess() }
}

View File

@ -6,12 +6,12 @@ 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
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
@ -149,7 +149,8 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
set(value) { loginStore.putLoginData("accountCode", value) ?: return; mApiCode = value }
set(value) {
loginStore.putLoginData("accountCode", value); mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
@ -157,7 +158,8 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin }
set(value) { loginStore.putLoginData("accountPin", value) ?: return; mApiPin = value }
set(value) {
loginStore.putLoginData("accountPin", value); mApiPin = value }
/**
* A Synergia API access token.
@ -256,6 +258,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
var startPointsSemester1: Int
get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 }
set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value }
private var mStartPointsSemester2: Int? = null
var startPointsSemester2: Int
get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 }
@ -265,8 +268,14 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
var enablePointGrades: Boolean
get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true }
set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value }
private var mEnableDescriptiveGrades: Boolean? = null
var enableDescriptiveGrades: Boolean
get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true }
set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value }
private var mTimetableNotPublic: Boolean? = null
var timetableNotPublic: Boolean
get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false }
set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value }
}

View File

@ -204,7 +204,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
// TODO PORTAL CAPTCHA
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
loginStore.putLoginData("timetableNotPublic", true)
data.timetableNotPublic = true
data()
}
ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE,

View File

@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
@ -23,56 +24,60 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
private val nameFormat by lazy { DecimalFormat("#.##") }
init { data.profile?.let { profile ->
init { data.profile?.also { profile ->
apiGet(TAG, "BehaviourGrades/Points") { json ->
val semester1StartGradeObject = Grade(
profileId,
-101,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 1),
nameFormat.format(data.startPointsSemester1),
data.startPointsSemester1.toFloat(),
-1f,
1,
-1,
1
).apply { type = Grade.TYPE_BEHAVIOUR }
if (data.startPointsSemester1 > 0) {
val semester1StartGradeObject = Grade(
profileId,
-101,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 1),
nameFormat.format(data.startPointsSemester1),
data.startPointsSemester1.toFloat(),
-1f,
1,
-1,
1
).apply { type = Grade.TYPE_BEHAVIOUR }
data.gradeList.add(semester1StartGradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
semester1StartGradeObject.id,
true,
true,
profile.getSemesterStart(1).inMillis
))
data.gradeList.add(semester1StartGradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
semester1StartGradeObject.id,
true,
true,
profile.getSemesterStart(1).inMillis
))
}
val semester2StartGradeObject = Grade(
profileId,
-102,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 2),
nameFormat.format(data.startPointsSemester2),
data.startPointsSemester2.toFloat(),
-1f,
2,
-1,
1
).apply { type = Grade.TYPE_BEHAVIOUR }
if (data.startPointsSemester2 > 0) {
val semester2StartGradeObject = Grade(
profileId,
-102,
data.app.getString(R.string.grade_start_points),
0xffbdbdbd.toInt(),
data.app.getString(R.string.grade_start_points_format, 2),
nameFormat.format(data.startPointsSemester2),
data.startPointsSemester2.toFloat(),
-1f,
2,
-1,
1
).apply { type = Grade.TYPE_BEHAVIOUR }
data.gradeList.add(semester2StartGradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
semester2StartGradeObject.id,
true,
true,
profile.getSemesterStart(2).inMillis
))
data.gradeList.add(semester2StartGradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
semester2StartGradeObject.id,
true,
true,
profile.getSemesterStart(2).inMillis
))
}
json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade ->
val id = grade.getLong("Id") ?: return@forEach
@ -143,8 +148,9 @@ class LibrusApiBehaviourGrades(override val data: DataLibrus,
))
}
data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, Grade.TYPE_BEHAVIOUR))
data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, SYNC_ALWAYS)
onSuccess()
}
}}
} ?: onSuccess() }
}

View File

@ -4,8 +4,10 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
@ -17,7 +19,7 @@ class LibrusApiGrades(override val data: DataLibrus,
const val TAG = "LibrusApiGrades"
}
init {
init { data.profile?.also { profile ->
apiGet(TAG, "Grades") { json ->
val grades = json.getJsonArray("Grades").asJsonObjectList()
@ -68,15 +70,15 @@ class LibrusApiGrades(override val data: DataLibrus,
when {
grade.getBoolean("IsConstituent") ?: false ->
gradeObject.type = Grade.TYPE_NORMAL
gradeObject.type = TYPE_NORMAL
grade.getBoolean("IsSemester") ?: false -> // semester final
gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER2_FINAL
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL
grade.getBoolean("IsSemesterProposition") ?: false -> // semester proposed
gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_PROPOSED else Grade.TYPE_SEMESTER2_PROPOSED
gradeObject.type = if (gradeObject.semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED
grade.getBoolean("IsFinal") ?: false -> // year final
gradeObject.type = Grade.TYPE_YEAR_FINAL
gradeObject.type = TYPE_YEAR_FINAL
grade.getBoolean("IsFinalProposition") ?: false -> // year final
gradeObject.type = Grade.TYPE_YEAR_PROPOSED
gradeObject.type = TYPE_YEAR_PROPOSED
}
grade.getJsonObject("Improvement")?.also {
@ -94,14 +96,25 @@ class LibrusApiGrades(override val data: DataLibrus,
profileId,
Metadata.TYPE_GRADE,
id,
profile?.empty ?: false,
profile?.empty ?: false,
profile.empty,
profile.empty,
addedDate
))
}
data.toRemove.addAll(listOf(
TYPE_NORMAL,
TYPE_SEMESTER1_FINAL,
TYPE_SEMESTER2_FINAL,
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
TYPE_YEAR_FINAL,
TYPE_YEAR_PROPOSED
).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
} ?: onSuccess() }
}

View File

@ -26,12 +26,7 @@ class LibrusApiSchools(override val data: DataLibrus,
// create the school's short name using first letters of each long name's word
// append the town name and save to student data
var schoolNameShort = ""
schoolNameLong?.split(" ")?.forEach {
if (it.isBlank())
return@forEach
schoolNameShort += it[0].toLowerCase()
}
val schoolNameShort = schoolNameLong?.firstLettersName
val schoolTown = school?.getString("Town")?.toLowerCase(Locale.getDefault())
data.schoolName = schoolId.toString() + schoolNameShort + "_" + schoolTown

View File

@ -69,6 +69,8 @@ class LibrusApiTimetables(override val data: DataLibrus,
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
if (data.timetableNotPublic) data.timetableNotPublic = false
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS)
onSuccess()

View File

@ -5,12 +5,13 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
init { run {
init { data.profile?.also { profile -> run {
data.db.gradeDao().getDetails(
data.profileId,
data.gradeAddedDates,
@ -73,6 +74,17 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
subjectId)
gradeObject.type = type
data.toRemove.addAll(listOf(
TYPE_NORMAL,
TYPE_SEMESTER1_FINAL,
TYPE_SEMESTER2_FINAL,
TYPE_SEMESTER1_PROPOSED,
TYPE_SEMESTER2_PROPOSED,
TYPE_YEAR_FINAL,
TYPE_YEAR_PROPOSED
).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
@ -85,5 +97,5 @@ class MobidziennikApiGrades(val data: DataMobidziennik, rows: List<String>) {
))
addedDate++
}
}}
}}}
}

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -25,8 +26,10 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
private const val TAG = "MobidziennikWebGrades"
}
init {
webGet(TAG, "/dziennik/oceny?semestr=${profile?.currentSemester ?: 1}") { text ->
init { data.profile?.also { profile ->
val currentSemester = profile.currentSemester
webGet(TAG, "/dziennik/oceny?semestr=$currentSemester") { text ->
MobidziennikLuckyNumberExtractor(data, text)
val doc = Jsoup.parse(text)
@ -90,7 +93,7 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
)
val time = Time.fromH_m_s(it[4])
gradeAddedDateMillis = gradeAddedDate.combineWith(time)
gradeSemester = profile?.dateToSemester(gradeAddedDate) ?: 1
gradeSemester = profile.dateToSemester(gradeAddedDate)
}
if (Regexes.MOBIDZIENNIK_GRADES_COUNT_TO_AVG.containsMatchIn(html)) {
@ -129,8 +132,8 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
profile.empty,
profile.empty,
gradeAddedDateMillis
))
}
@ -143,8 +146,9 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
}
}
data.toRemove.add(DataRemoveModel.Grades.semesterWithType(currentSemester, Grade.TYPE_NORMAL))
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}}
}

View File

@ -7,7 +7,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.Mobidzie
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.Utils
class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) {
companion object {
@ -39,12 +39,11 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un
if (student1.size == 2)
return@forEach
val today = Date.getToday()
val profile = Profile()
profile.studentNameLong = "${student1[2]} ${student1[4]}".fixName()
profile.studentNameShort = "${student1[2]} ${student1[4][0]}.".fixName()
profile.accountNameLong = if (accountNameLong == profile.studentNameLong) null else accountNameLong
profile.studentSchoolYear = "${today.year}/${today.year+1}"
profile.studentSchoolYear = Utils.getCurrentSchoolYear()
profile.name = profile.studentNameLong
profile.subname = data.loginUsername
profile.empty = true

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -109,6 +110,7 @@ class VulcanApiGrades(override val data: DataVulcan, val onSuccess: () -> Unit)
))
}
data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, Grade.TYPE_NORMAL))
data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS)
onSuccess()
}

View File

@ -49,7 +49,7 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () ->
val senderName = message.getString("Nadawca") ?: ""
senderName.getLastFirstName()?.let { (senderLastName, senderFirstName) ->
senderName.splitName()?.let { (senderLastName, senderFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(senderName.toByteArray()).toLong(),

View File

@ -54,7 +54,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
?: {
val receiverName = receiver.getString("Nazwa") ?: ""
receiverName.getLastFirstName()?.let { (receiverLastName, receiverFirstName) ->
receiverName.splitName()?.let { (receiverLastName, receiverFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(receiverName.toByteArray()).toLong(),

View File

@ -6,7 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.Regexes.VULCAN_SHIFT_ANNOTATION
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_TIMETABLE
@ -144,7 +144,7 @@ class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Uni
}
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val shift = VULCAN_SHIFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }

View File

@ -27,17 +27,23 @@ open class DataRemoveModel {
}
}
class Grades(private val all: Boolean, private val semester: Int?) : DataRemoveModel() {
class Grades(private val all: Boolean, private val semester: Int?, private val type: Int?) : DataRemoveModel() {
companion object {
fun all() = Grades(true, null)
fun semester(semester: Int) = Grades(false, semester)
fun all() = Grades(true, null, null)
fun allWithType(type: Int) = Grades(true, null, type)
fun semester(semester: Int) = Grades(false, semester, null)
fun semesterWithType(semester: Int, type: Int) = Grades(false, semester, type)
}
fun commit(profileId: Int, dao: GradeDao) {
if (all) {
dao.clear(profileId)
if (type != null) dao.clearWithType(profileId, type)
else dao.clear(profileId)
}
semester?.let {
if (type != null) dao.clearForSemesterWithType(profileId, it, type)
else dao.clearForSemester(profileId, it)
}
semester?.let { dao.clearForSemester(profileId, it) }
}
}

View File

@ -36,7 +36,7 @@ class Szkolny(val app: App, val callback: EdziennikCallback) {
completed()
}
fun shareEvent(event: EventFull) {
/*fun shareEvent(event: EventFull) {
api.shareEvent(event)
completed()
}
@ -44,7 +44,7 @@ class Szkolny(val app: App, val callback: EdziennikCallback) {
fun unshareEvent(event: EventFull) {
api.unshareEvent(event)
completed()
}
}*/
private fun completed() {
callback.onCompleted()

View File

@ -15,7 +15,9 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureIntercep
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
import pl.szczodrzynski.edziennik.utils.models.Date
@ -102,29 +104,33 @@ class SzkolnyApi(val app: App) {
return events
}
fun shareEvent(event: EventFull) {
fun shareEvent(event: EventFull): ApiResponse<Nothing>? {
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
api.shareEvent(EventShareRequest(
return api.shareEvent(EventShareRequest(
deviceId = app.deviceId,
sharedByName = event.sharedByName,
shareTeamCode = team.code,
event = event
)).execute()
)).execute().body()
}
fun unshareEvent(event: EventFull) {
fun unshareEvent(event: Event): ApiResponse<Nothing>? {
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
api.shareEvent(EventShareRequest(
return api.shareEvent(EventShareRequest(
deviceId = app.deviceId,
sharedByName = event.sharedByName,
unshareTeamCode = team.code,
eventId = event.id
)).execute()
)).execute().body()
}
fun pairBrowser(browserId: String?, pairToken: String?): List<WebPushResponse.Browser> {
/*fun eventEditRequest(requesterName: String, event: Event): ApiResponse<Nothing>? {
}*/
fun pairBrowser(browserId: String?, pairToken: String?, onError: ((List<ApiResponse.Error>) -> Unit)? = null): List<WebPushResponse.Browser> {
val response = api.webPush(WebPushRequest(
action = "pairBrowser",
deviceId = app.deviceId,
@ -132,10 +138,15 @@ class SzkolnyApi(val app: App) {
pairToken = pairToken
)).execute().body()
response?.errors?.let {
onError?.invoke(it)
return emptyList()
}
return response?.data?.browsers ?: emptyList()
}
fun listBrowsers(): List<WebPushResponse.Browser> {
fun listBrowsers(onError: ((List<ApiResponse.Error>) -> Unit)? = null): List<WebPushResponse.Browser> {
val response = api.webPush(WebPushRequest(
action = "listBrowsers",
deviceId = app.deviceId

View File

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

View File

@ -14,6 +14,7 @@ data class EventShareRequest (
val sharedByName: String,
val shareTeamCode: String? = null,
val unshareTeamCode: String? = null,
val requesterName: String? = null,
val eventId: Long? = null,
val event: EventFull? = null

View File

@ -4,6 +4,7 @@ import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.Edudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.Idziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.Librus
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik
@ -59,6 +60,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback)
LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_EDUDZIENNIK -> Edudziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback)
else -> null
}

View File

@ -16,8 +16,8 @@ class SzkolnyTask(val request: Any) : IApiTask(-1) {
private const val TAG = "SzkolnyTask"
fun sync(profiles: List<ProfileFull>) = SzkolnyTask(SyncRequest(profiles))
fun shareEvent(event: EventFull) = SzkolnyTask(ShareEventRequest(event))
fun unshareEvent(event: EventFull) = SzkolnyTask(UnshareEventRequest(event))
/*fun shareEvent(event: EventFull) = SzkolnyTask(ShareEventRequest(event))
fun unshareEvent(event: EventFull) = SzkolnyTask(UnshareEventRequest(event))*/
}
private lateinit var szkolny: Szkolny
@ -35,12 +35,12 @@ class SzkolnyTask(val request: Any) : IApiTask(-1) {
when (request) {
is SyncRequest -> szkolny.sync(request.profiles)
is ShareEventRequest -> szkolny.shareEvent(request.event)
is UnshareEventRequest -> szkolny.unshareEvent(request.event)
/*is ShareEventRequest -> szkolny.shareEvent(request.event)
is UnshareEventRequest -> szkolny.unshareEvent(request.event)*/
}
}
data class SyncRequest(val profiles: List<ProfileFull>)
data class ShareEventRequest(val event: EventFull)
data class UnshareEventRequest(val event: EventFull)
/*data class ShareEventRequest(val event: EventFull)
data class UnshareEventRequest(val event: EventFull)*/
}

View File

@ -27,9 +27,15 @@ public abstract class GradeDao {
@Query("DELETE FROM grades WHERE profileId = :profileId")
public abstract void clear(int profileId);
@Query("DELETE FROM grades WHERE profileId = :profileId AND gradeType = :type")
public abstract void clearWithType(int profileId, int type);
@Query("DELETE FROM grades WHERE profileId = :profileId AND gradeSemester = :semester")
public abstract void clearForSemester(int profileId, int semester);
@Query("DELETE FROM grades WHERE profileId = :profileId AND gradeSemester = :semester AND gradeType = :type")
public abstract void clearForSemesterWithType(int profileId, int semester, int type);
@RawQuery(observedEntities = {Grade.class})
abstract LiveData<List<GradeFull>> getAll(SupportSQLiteQuery query);
public LiveData<List<GradeFull>> getAll(int profileId, String filter, String orderBy) {

View File

@ -12,6 +12,8 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.crc32
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.*
import java.util.*
@ -35,6 +37,7 @@ class ProfileFull : Profile {
LOGIN_TYPE_LIBRUS -> getStudentData("schoolName", "LIBRUS_UN") + ":" + getStudentData("accountLogin", "LIBRUS_LOGIN_UN")
LOGIN_TYPE_IUCZNIOWIE -> getLoginData("schoolName", "IUCZNIOWIE_UN") + ":" + getLoginData("username", "IUCZNIOWIE_UN") + ":" + getStudentData("registerId", -1)
LOGIN_TYPE_VULCAN -> getStudentData("schoolName", "VULCAN_UN") + ":" + getStudentData("studentId", -1)
LOGIN_TYPE_EDUDZIENNIK -> getStudentData("schoolName", "EDU_UN") + ":" + getLoginData("email", "EDU_UN") + ":" + getStudentData("studentId", null)?.crc32()
LOGIN_TYPE_DEMO -> getLoginData("serverName", "DEMO_UN") + ":" + getLoginData("username", "DEMO_UN") + ":" + getStudentData("studentId", -1)
else -> "TYPE_UNKNOWN"
}
@ -57,19 +60,7 @@ class ProfileFull : Profile {
fragmentIds.add(DRAWER_ITEM_ATTENDANCE)
return fragmentIds
}
LOGIN_TYPE_LIBRUS -> {
fragmentIds = ArrayList()
fragmentIds.add(DRAWER_ITEM_TIMETABLE)
fragmentIds.add(DRAWER_ITEM_AGENDA)
fragmentIds.add(DRAWER_ITEM_GRADES)
fragmentIds.add(DRAWER_ITEM_MESSAGES)
fragmentIds.add(DRAWER_ITEM_HOMEWORK)
fragmentIds.add(DRAWER_ITEM_BEHAVIOUR)
fragmentIds.add(DRAWER_ITEM_ATTENDANCE)
fragmentIds.add(DRAWER_ITEM_ANNOUNCEMENTS)
return fragmentIds
}
LOGIN_TYPE_IUCZNIOWIE -> {
LOGIN_TYPE_LIBRUS, LOGIN_TYPE_IUCZNIOWIE, LOGIN_TYPE_EDUDZIENNIK -> {
fragmentIds = ArrayList()
fragmentIds.add(DRAWER_ITEM_TIMETABLE)
fragmentIds.add(DRAWER_ITEM_AGENDA)

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-20
*/
package pl.szczodrzynski.edziennik.ui.dialogs.bell
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogBellSyncBinding
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class BellSyncDialog(
val activity: AppCompatActivity,
private val bellTime: Time,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
const val TAG = "BellSyncDialog"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var dialog: AlertDialog
private lateinit var b: DialogBellSyncBinding
private val app by lazy { activity.application as App }
private var counterJob: Job? = null
private val actualBellDiff: Pair<Time, Int>
get() {
val now = Time.getNow()
val bellDiff = Time.diff(now, bellTime)
val multiplier = if (bellTime > now) -1 else 1
return Pair(bellDiff, multiplier)
}
init { apply {
if (activity.isFinishing)
return@apply
job = Job()
b = DialogBellSyncBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setView(b.root)
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
counterJob?.cancel()
onDismissListener?.invoke(TAG)
}
.show()
initView()
}}
private fun initView() {
b.bellSyncButton.setOnClickListener {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
app.config.timetable.bellSyncDiff = bellDiff
app.config.timetable.bellSyncMultiplier = multiplier
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(app.getString(R.string.bell_sync_results, bellDiffText))
.setPositiveButton(R.string.ok) { resultsDialog, _ ->
resultsDialog.dismiss()
dialog.dismiss()
if (activity is MainActivity) activity.reloadTarget()
}
.show()
}
if (Time.diff(Time.getNow(), bellTime) > Time(2, 0, 0)) { // Easter egg ^^
b.bellSyncButton.setImageDrawable(app.resources.getDrawable(R.drawable.ic_bell_wtf)) // wtf
}
launch {
counterJob = startCoroutineTimer(repeatMillis = 1000) {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.bell_sync_howto, bellTime.stringHM, bellDiffText)
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-20
*/
package pl.szczodrzynski.edziennik.ui.dialogs.bell
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.databinding.DialogBellSyncTimeChooseBinding
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class BellSyncTimeChooseDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
const val TAG = "BellSyncTimeChooseDialog"
private const val MAX_DIFF_MINUTES = 10
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var dialog: AlertDialog
private lateinit var b: DialogBellSyncTimeChooseBinding
private val app by lazy { activity.application as App }
private val today = Date.getToday()
private val selectedTime: Time?
get() = b.timeDropdown.selected?.tag as Time?
init { apply {
if (activity.isFinishing)
return@apply
job = Job()
b = DialogBellSyncTimeChooseBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ ->
dialog.dismiss()
selectedTime?.let {
BellSyncDialog(activity, it)
}
}
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.create()
.apply {
setButton(AlertDialog.BUTTON_NEUTRAL, app.getString(R.string.reset)) { _, _ ->
showResetDialog()
}
}
initView()
}}
private fun initView() {
b.bellSyncHowto.text = app.getString(R.string.bell_sync_choose_howto)
app.config.timetable.bellSyncDiff?.let { bellDiff ->
val multiplier = app.config.timetable.bellSyncMultiplier
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.concat_2_strings,
app.getString(R.string.bell_sync_choose_howto),
app.getString(R.string.bell_sync_current_dialog, bellDiffText)
)
}
loadTimeList()
}
private fun checkForLessons(timeList: List<Time>): Boolean {
return if (timeList.isNotEmpty()) {
val now = Time.getNow()
val first = timeList.first()
val last = timeList.last()
now.stepForward(0, MAX_DIFF_MINUTES, 0) >= first &&
now.stepForward(0, -1 * MAX_DIFF_MINUTES, 0) <= last
} else false
}
private fun loadTimeList() { launch {
val timeItems = withContext(Dispatchers.Default) {
val lessons = app.db.timetableDao().getForDateNow(App.profileId, today)
val items = mutableListOf<TextInputDropDown.Item>()
lessons.forEach {
if (it.type != Lesson.TYPE_NO_LESSONS &&
it.type != Lesson.TYPE_CANCELLED &&
it.type != Lesson.TYPE_SHIFTED_SOURCE) {
items += TextInputDropDown.Item(
it.displayStartTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_lesson_item, it.displaySubjectName, it.displayStartTime?.stringHM),
tag = it.displayStartTime
)
items += TextInputDropDown.Item(
it.displayEndTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_break_item, it.displayEndTime?.stringHM),
tag = it.displayEndTime
)
}
}
items
}
if (!checkForLessons(timeItems.map { it.tag as Time })) {
/* Synchronization not possible */
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_cannot_now)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
} else {
b.timeDropdown.clear()
b.timeDropdown.append(timeItems)
timeItems.forEachIndexed { index, item ->
val time = item.tag as Time
if (time < Time.getNow()) {
b.timeDropdown.select(if (timeItems.size > index + 1) timeItems[index + 1] else item)
}
}
b.timeDropdown.isEnabled = true
// TODO Fix popup cutting off
dialog.show()
}
}}
private fun showResetDialog() {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_reset_confirm)
.setPositiveButton(R.string.yes) { confirmDialog, _ ->
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
confirmDialog.dismiss()
initView()
if (activity is MainActivity) activity.reloadTarget()
}
.setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() }
.show()
}
}

View File

@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
@ -39,6 +40,10 @@ class EventDetailsDialog(
private lateinit var adapter: EventListAdapter
private val api by lazy {
SzkolnyApi(app)
}
init { run {
if (activity.isFinishing)
return@run
@ -126,19 +131,27 @@ class EventDetailsDialog(
}
private fun removeEvent() {
if (eventShared && eventOwn) {
Toast.makeText(activity, "Unshare + remove own event", Toast.LENGTH_SHORT).show()
launch {
if (eventShared && eventOwn) {
Toast.makeText(activity, "Unshare + remove own event", Toast.LENGTH_SHORT).show()
SzkolnyTask.unshareEvent(event).enqueue(activity)
finishRemoving()
}
else if (eventShared && !eventOwn) {
Toast.makeText(activity, "Remove + blacklist somebody's event", Toast.LENGTH_SHORT).show()
// TODO
}
else {
Toast.makeText(activity, "Remove event", Toast.LENGTH_SHORT).show()
finishRemoving()
val response = withContext(Dispatchers.Default) {
api.unshareEvent(event!!)
}
response?.errors?.ifNotEmpty {
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
return@launch
}
finishRemoving()
} else if (eventShared && !eventOwn) {
Toast.makeText(activity, "Remove + blacklist somebody's event", Toast.LENGTH_SHORT).show()
// TODO
} else {
Toast.makeText(activity, "Remove event", Toast.LENGTH_SHORT).show()
finishRemoving()
}
}
}

View File

@ -20,6 +20,7 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
@ -60,13 +61,17 @@ class EventManualDialog(
private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private var removeEventDialog: AlertDialog? = null
private var defaultLoaded = false
private lateinit var event: Event
private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null
private val editingOwn = editingEvent?.sharedBy == "self"
private var removeEventDialog: AlertDialog? = null
private var defaultLoaded = false
private val api by lazy {
SzkolnyApi(app)
}
init { run {
if (activity.isFinishing)
@ -617,7 +622,21 @@ class EventManualDialog(
else if (!share && editingShared) {
Toast.makeText(activity, "Unshare own event", Toast.LENGTH_SHORT).show()
SzkolnyTask.unshareEvent(eventObject.withMetadata(metadataObject)).enqueue(activity)
eventObject.apply {
sharedBy = null
sharedByName = profile?.studentNameLong
}
val response = withContext(Dispatchers.Default) {
api.unshareEvent(eventObject)
}
response?.errors?.ifNotEmpty {
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
return@launch
}
eventObject.sharedByName = null
finishAdding(eventObject, metadataObject)
}
else if (share) {
@ -630,7 +649,14 @@ class EventManualDialog(
metadataObject.addedDate = System.currentTimeMillis()
SzkolnyTask.shareEvent(eventObject.withMetadata(metadataObject)).enqueue(activity)
val response = withContext(Dispatchers.Default) {
api.shareEvent(eventObject.withMetadata(metadataObject))
}
response?.errors?.ifNotEmpty {
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
return@launch
}
eventObject.sharedBy = "self"
finishAdding(eventObject, metadataObject)
@ -642,19 +668,27 @@ class EventManualDialog(
}
private fun removeEvent() {
if (editingShared && editingOwn) {
Toast.makeText(activity, "Unshare + remove own event", Toast.LENGTH_SHORT).show()
launch {
if (editingShared && editingOwn) {
Toast.makeText(activity, "Unshare + remove own event", Toast.LENGTH_SHORT).show()
editingEvent?.let { SzkolnyTask.unshareEvent(it).enqueue(activity) }
finishRemoving()
}
else if (editingShared && !editingOwn) {
Toast.makeText(activity, "Remove + blacklist somebody's event", Toast.LENGTH_SHORT).show()
// TODO
}
else {
Toast.makeText(activity, "Remove event", Toast.LENGTH_SHORT).show()
finishRemoving()
val response = withContext(Dispatchers.Default) {
api.unshareEvent(editingEvent!!)
}
response?.errors?.ifNotEmpty {
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
return@launch
}
finishRemoving()
} else if (editingShared && !editingOwn) {
Toast.makeText(activity, "Remove + blacklist somebody's event", Toast.LENGTH_SHORT).show()
// TODO
} else {
Toast.makeText(activity, "Remove event", Toast.LENGTH_SHORT).show()
finishRemoving()
}
}
}

View File

@ -7,7 +7,7 @@ package pl.szczodrzynski.edziennik.ui.modules.home
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2.Companion.swapCards
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.Companion.swapCards
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, private val refreshLayout: SwipeRefreshLayoutNoIndicator?) : ItemTouchHelper.Callback() {
@ -46,4 +46,4 @@ class CardItemTouchHelperCallback(private val cardAdapter: HomeCardAdapter, priv
dragCardView = null
}
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-21
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class CounterActivity : AppCompatActivity(), CoroutineScope {
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var counterJob: Job? = null
private val app by lazy { application as App }
private lateinit var b: ActivityCounterBinding
private val lessonList = mutableListOf<LessonFull>()
private var bellSyncDiffMillis = 0L
private val syncedNow: Time
get() = Time.fromMillis(Time.getNow().inMillis - bellSyncDiffMillis)
private val countInSeconts: Boolean
get() = app.config.timetable.countInSeconds
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
b = ActivityCounterBinding.inflate(layoutInflater)
setContentView(b.root)
initView()
}
private fun initView() { launch {
withContext(Dispatchers.Default) {
lessonList.apply {
clear()
addAll(app.db.timetableDao().getForDateNow(App.profileId, Date.getToday())
.filter {
it.type != Lesson.TYPE_NO_LESSONS && it.type != Lesson.TYPE_CANCELLED &&
it.type != Lesson.TYPE_SHIFTED_SOURCE
})
}
}
app.config.timetable.bellSyncDiff?.let {
bellSyncDiffMillis = (it.hour * 60 * 60 * 1000 + it.minute * 60 * 1000 + it.second * 1000).toLong()
bellSyncDiffMillis *= app.config.timetable.bellSyncMultiplier.toLong()
}
counterJob = startCoroutineTimer(repeatMillis = 1000) {
update()
}
}}
private fun update() {
if (lessonList.isEmpty()) {
b.lessonName.text = app.getString(R.string.no_lessons_today)
b.timeLeft.text = ""
} else {
val now = syncedNow
val next = lessonList.firstOrNull {
it.displayStartTime != null && it.displayStartTime!! > now
}
val actual = lessonList.firstOrNull {
it.displayStartTime != null && it.displayEndTime != null &&
it.displayStartTime!! <= now && now <= it.displayEndTime!!
}
when {
actual != null -> {
b.lessonName.text = actual.displaySubjectName
val left = actual.displayEndTime!! - now
b.timeLeft.text = timeLeft(left.toInt(), "\n", countInSeconts)
}
next != null -> {
b.lessonName.text = next.displaySubjectName
val till = next.displayStartTime!! - now
b.timeLeft.text = timeTill(till.toInt(), "\n", countInSeconts)
}
else -> {
b.lessonName.text = app.getString(R.string.lessons_finished)
b.timeLeft.text = ""
}
}
}
}
override fun onDestroy() {
super.onDestroy()
counterJob?.cancel()
}
}

View File

@ -20,11 +20,11 @@ import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval;
import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentOld.updateInterval;
public class CounterActivity extends AppCompatActivity {
public class CounterActivityOld extends AppCompatActivity {
private static final String TAG = "CounterActivity";
private static final String TAG = "CounterActivityOld";
private App app;
private ActivityCounterBinding b;
@ -153,7 +153,7 @@ public class CounterActivity extends AppCompatActivity {
private short counterType = TIME_LEFT;
private long updateCounter(Time syncedNow) {
Time diff = Time.diff(counterTarget, syncedNow);
b.timeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.config.getTimetable().getCountInSeconds(), "\n") : HomeFragment.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds(), "\n"));
b.timeLeft.setText(counterType == TIME_TILL ? HomeFragmentOld.timeTill(app, diff, app.config.getTimetable().getCountInSeconds(), "\n") : HomeFragmentOld.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds(), "\n"));
return updateInterval(app, diff);
}

View File

@ -26,8 +26,9 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentHomeV2Binding
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeDebugCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
@ -36,7 +37,7 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext
class HomeFragmentV2 : Fragment(), CoroutineScope {
class HomeFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "HomeFragment"
@ -56,7 +57,7 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: FragmentHomeV2Binding
private lateinit var b: FragmentHomeBinding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
@ -67,7 +68,7 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
context ?: return null
app = activity.application as App
context!!.theme.applyStyle(Themes.appTheme, true)
b = FragmentHomeV2Binding.inflate(inflater)
b = FragmentHomeBinding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
job = Job()
return b.root
@ -121,6 +122,8 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
else -> null
}
}
if (App.devMode)
items += HomeDebugCard(100, app, activity, this, app.profile)
val adapter = HomeCardAdapter(items)
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))

View File

@ -53,7 +53,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject;
import pl.szczodrzynski.edziennik.databinding.CardLuckyNumberBinding;
import pl.szczodrzynski.edziennik.databinding.CardUpdateBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentHomeOldBinding;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.ui.modules.login.LoginLibrusCaptchaActivity;
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeActivity;
@ -67,7 +67,6 @@ import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
import static pl.szczodrzynski.edziennik.App.UPDATES_ON_PLAY_STORE;
import static pl.szczodrzynski.edziennik.MainActivity.DRAWER_ITEM_GRADES;
import static pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_FINAL;
import static pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_PROPOSED;
import static pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER2_FINAL;
@ -76,11 +75,11 @@ import static pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_YEAR_
import static pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_YEAR_PROPOSED;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
public class HomeFragment extends Fragment {
private static final String TAG = "HomeFragment";
public class HomeFragmentOld extends Fragment {
private static final String TAG = "HomeFragmentOld";
private App app = null;
private MainActivity activity = null;
private FragmentHomeBinding b = null;
private FragmentHomeOldBinding b = null;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -92,7 +91,7 @@ public class HomeFragment extends Fragment {
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false);
b = DataBindingUtil.inflate(inflater, R.layout.fragment_home_old, container, false);
b.refreshLayout.setParent(activity.getSwipeRefreshLayout());
return b.getRoot();
}
@ -261,7 +260,7 @@ public class HomeFragment extends Fragment {
b.cardLuckyNumber.setOnClickListener(v1 -> setNumberDialog());
}
timetableCard = new HomeTimetableCard(app, activity, this, layoutInflater, insertPoint);
timetableCard = new HomeTimetableCardOld(app, activity, this, layoutInflater, insertPoint);
timetableCard.run();
configCardGrades(activity, layoutInflater, activity, insertPoint);
@ -571,7 +570,7 @@ public class HomeFragment extends Fragment {
Button cardGradesButton = root.findViewById(R.id.cardGradesButton);
buttonAddDrawable(c, cardGradesButton, CommunityMaterial.Icon.cmd_arrow_right);
cardGradesButton.setOnClickListener((v1 -> new Handler().postDelayed(() -> a.runOnUiThread(() -> {
activity.loadTarget(DRAWER_ITEM_GRADES, null);
activity.loadTarget(MainActivity.DRAWER_ITEM_GRADES, null);
}), 100)));
//new Handler().postDelayed(() -> a.runOnUiThread(() -> updateCardGrades(c, a, root)), newRefreshInterval);
@ -591,5 +590,5 @@ public class HomeFragment extends Fragment {
insertPoint.addView(root, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
private HomeTimetableCard timetableCard;
private HomeTimetableCardOld timetableCard;
}

View File

@ -1,177 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-22
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import android.content.Intent
import android.os.AsyncTask
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.databinding.DataBindingUtil
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.databinding.CardTimetableBinding
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.*
class HomeTimetableCard(
private val app: App,
private val activity: MainActivity,
private val homeFragment: HomeFragment,
private val layoutInflater: LayoutInflater,
private val insertPoint: ViewGroup
) {
companion object {
private const val TAG = "HomeTimetableCard"
const val TIME_TILL = 0
const val TIME_LEFT = 1
}
private lateinit var timetableTimer: Timer
private lateinit var b: CardTimetableBinding
private var bellSyncTime: Time? = null
private var counterType = TIME_TILL
private val counterTarget = Time(0, 0, 0)
private val lessons = mutableListOf<Lesson>()
private val events = mutableListOf<Event>()
fun run() {
timetableTimer = Timer()
b = DataBindingUtil.inflate(layoutInflater, R.layout.card_timetable, null, false)
update()
insertPoint.addView(b.root, ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
b.cardTimetableFullscreenCounter.setOnClickListener {
activity.startActivity(Intent(activity, CounterActivity::class.java))
}
b.cardTimetableBellSync.setOnClickListener {
if (bellSyncTime == null) {
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(R.string.bell_sync_cannot_now)
.positiveText(R.string.ok)
.show()
} else {
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_howto, bellSyncTime!!.stringHM).toString() +
when {
app.config.timetable.bellSyncDiff != null -> app.getString(R.string.bell_sync_current_dialog,
(if (app.config.timetable.bellSyncMultiplier == -1) "-" else "+") + app.config.timetable.bellSyncDiff?.stringHMS)
else -> ""
})
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.neutralText(R.string.reset)
.onPositive { _, _: DialogAction? ->
val bellDiff = Time.diff(Time.getNow(), bellSyncTime)
app.config.timetable.bellSyncDiff = bellDiff
app.config.timetable.bellSyncMultiplier = if (bellSyncTime!!.value > Time.getNow().value) -1 else 1
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_results, if (bellSyncTime!!.value > Time.getNow().value) "-" else "+", bellDiff.stringHMS))
.positiveText(R.string.ok)
.show()
}
.onNeutral { _, _ ->
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
.content(R.string.bell_sync_reset_confirm)
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive { _, _ ->
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
app.saveConfig("bellSyncDiff", "bellSyncMultiplier")
}
.show()
}
.show()
}
}
HomeFragment.buttonAddDrawable(activity, b.cardTimetableButton, CommunityMaterial.Icon.cmd_arrow_right)
}
fun destroy() {
try {
timetableTimer.apply {
cancel()
purge()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun update() {
if (!homeFragment.isAdded) return
val now = Time.getNow()
val syncedNow: Time = when (app.config.timetable.bellSyncDiff != null) {
true -> when {
app.config.timetable.bellSyncMultiplier < 0 -> Time.sum(now, app.config.timetable.bellSyncDiff)
app.config.timetable.bellSyncMultiplier > 0 -> Time.diff(now, app.config.timetable.bellSyncDiff)
else -> now
}
else -> now
}
if (lessons.size == 0 || syncedNow.value > counterTarget.value) {
findLessons(syncedNow)
} else {
scheduleUpdate(updateCounter(syncedNow))
}
}
private fun updateCounter(syncedNow: Time): Long {
val diff = Time.diff(counterTarget, syncedNow)
b.cardTimetableTimeLeft.text = when (counterType) {
TIME_TILL -> HomeFragment.timeTill(app, diff, app.config.timetable.countInSeconds)
else -> HomeFragment.timeLeft(app, diff, app.config.timetable.countInSeconds)
}
bellSyncTime = counterTarget.clone()
b.cardTimetableFullscreenCounter.visibility = View.VISIBLE
return updateInterval(app, diff)
}
private fun scheduleUpdate(newRefreshInterval: Long) {
timetableTimer.schedule(object : TimerTask() {
override fun run() {
activity.runOnUiThread { update() }
}
}, newRefreshInterval)
}
private fun findLessons(syncedNow: Time) {
AsyncTask.execute {
val today = Date.getToday()
val searchEnd = Date.getToday().stepForward(0, 0, -today.weekDay)
lessons.apply {
clear()
addAll(app.db.timetableDao().getBetweenDatesNow(today, searchEnd))
}
}
}
}

View File

@ -32,21 +32,21 @@ import pl.szczodrzynski.edziennik.utils.models.Week;
import static pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CANCELLED;
import static pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CHANGE;
import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment.updateInterval;
import static pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentOld.updateInterval;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
public class HomeTimetableCardOld {
private static final String TAG = "HomeTimetableCardOld";
private App app;
private MainActivity a;
private HomeFragment f;
private HomeFragmentOld f;
private LayoutInflater layoutInflater;
private ViewGroup insertPoint;
private CardTimetableBinding b;
private Timer timetableTimer;
private Time bellSyncTime = null;
public HomeTimetableCardOld(App app, MainActivity a, HomeFragment f, LayoutInflater layoutInflater, ViewGroup insertPoint) {
public HomeTimetableCardOld(App app, MainActivity a, HomeFragmentOld f, LayoutInflater layoutInflater, ViewGroup insertPoint) {
this.app = app;
this.a = a;
this.f = f;
@ -61,7 +61,7 @@ public class HomeTimetableCardOld {
insertPoint.addView(b.getRoot(), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
b.cardTimetableFullscreenCounter.setOnClickListener((v -> {
Intent intent = new Intent(a, CounterActivity.class);
Intent intent = new Intent(a, CounterActivityOld.class);
a.startActivity(intent);
}));
@ -109,7 +109,7 @@ public class HomeTimetableCardOld {
}
});
HomeFragment.buttonAddDrawable(a, b.cardTimetableButton, CommunityMaterial.Icon.cmd_arrow_right);
HomeFragmentOld.buttonAddDrawable(a, b.cardTimetableButton, CommunityMaterial.Icon.cmd_arrow_right);
}
private List<LessonFull> lessons = new ArrayList<>();
@ -228,7 +228,7 @@ public class HomeTimetableCardOld {
private short counterType = TIME_TILL;
private long updateCounter(Time syncedNow) {
Time diff = Time.diff(counterTarget, syncedNow);
b.cardTimetableTimeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.config.getTimetable().getCountInSeconds()) : HomeFragment.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds()));
b.cardTimetableTimeLeft.setText(counterType == TIME_TILL ? HomeFragmentOld.timeTill(app, diff, app.config.getTimetable().getCountInSeconds()) : HomeFragmentOld.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds()));
bellSyncTime = counterTarget;
b.cardTimetableFullscreenCounter.setVisibility(View.VISIBLE);
return updateInterval(app, diff);

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.work.WorkManager
import com.chuckerteam.chucker.api.Chucker
import com.hypertrack.hyperlog.HyperLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginLibrusCaptchaActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeActivity
import kotlin.coroutines.CoroutineContext
class HomeDebugCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeDebugCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
val b = CardHomeDebugBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
b.composeButton.onClick {
app.startActivity(Intent(activity, MessagesComposeActivity::class.java));
}
b.pruneWorkButton.onClick {
WorkManager.getInstance(app).pruneWork()
}
b.runChucker.onClick {
app.startActivity(Chucker.getLaunchIntent(activity, 1));
}
b.librusCaptchaButton.onClick {
app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java))
}
b.getLogs.onClick {
val logs = HyperLog.getDeviceLogsInFile(activity, true)
val intent = Intent(Intent.ACTION_SEND)
if (logs.exists()) {
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + logs.absolutePath))
intent.putExtra(Intent.EXTRA_SUBJECT, "Share debug logs")
intent.putExtra(Intent.EXTRA_TEXT, "Share debug logs")
app.startActivity(Intent.createChooser(intent, "Share debug logs"))
}
}
holder.root.onClick {
// do stuff
}
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -36,7 +36,7 @@ import pl.szczodrzynski.edziennik.databinding.CardHomeGradesBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel
@ -46,7 +46,7 @@ class HomeGradesCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {

View File

@ -21,7 +21,7 @@ import pl.szczodrzynski.edziennik.databinding.CardHomeLuckyNumberBinding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
@ -29,7 +29,7 @@ class HomeLuckyNumberCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
@ -51,6 +51,9 @@ class HomeLuckyNumberCard(
val today = Date.getToday()
val todayValue = today.value
val tomorrow = Date.getToday().stepForward(0, 0, 1)
val tomorrowValue = tomorrow.value
val subTextRes = if (profile.studentNumber == -1)
R.string.home_lucky_number_details_click_to_set
else
@ -59,28 +62,26 @@ class HomeLuckyNumberCard(
app.db.luckyNumberDao().getNearestFuture(App.profileId, todayValue).observe(fragment, Observer { luckyNumber ->
val isYours = luckyNumber?.number == profile.studentNumber
val titleRes = when {
luckyNumber == null -> R.string.home_lucky_number_no_info
luckyNumber.number == -1 -> R.string.home_lucky_number_no_number
val res: Pair<Int, Array<out Any>> = when {
luckyNumber == null -> R.string.home_lucky_number_no_info to emptyArray()
luckyNumber.number == -1 -> R.string.home_lucky_number_no_number to emptyArray()
else -> when (isYours) {
true -> when (luckyNumber.date.value) {
todayValue -> R.string.home_lucky_number_yours_today
todayValue + 1 -> R.string.home_lucky_number_yours_tomorrow
else -> R.string.home_lucky_number_yours_later
todayValue -> R.string.home_lucky_number_yours_today to emptyArray()
tomorrowValue -> R.string.home_lucky_number_yours_tomorrow to emptyArray()
else -> R.string.home_lucky_number_yours_later to arrayOf(luckyNumber.date.formattedString)
}
false -> when (luckyNumber.date.value) {
todayValue -> R.string.home_lucky_number_today
todayValue + 1 -> R.string.home_lucky_number_tomorrow
else -> R.string.home_lucky_number_later
todayValue -> R.string.home_lucky_number_today to arrayOf(luckyNumber.number)
tomorrowValue -> R.string.home_lucky_number_tomorrow to arrayOf(luckyNumber.number)
else -> R.string.home_lucky_number_later to arrayOf(luckyNumber.date.formattedString, luckyNumber.number)
}
}
}
b.title.setText(
titleRes,
luckyNumber?.number ?: 0,
luckyNumber?.date?.formattedString ?: ""
)
val (titleRes, resArguments) = res
b.title.setText(titleRes, *resArguments)
val drawableRes = when {
luckyNumber == null || luckyNumber.number == -1 -> R.drawable.emoji_sad
@ -104,4 +105,4 @@ class HomeLuckyNumberCard(
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-22.
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeTemplateBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.onClick
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import kotlin.coroutines.CoroutineContext
class HomeTemplateCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeTemplateCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
val b = CardHomeTemplateBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
// do stuff
holder.root.onClick {
// do stuff
}
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -14,17 +15,25 @@ import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.CardHomeTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.bell.BellSyncTimeChooseDialog
import pl.szczodrzynski.edziennik.ui.modules.home.CounterActivity
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
@ -35,7 +44,7 @@ class HomeTimetableCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val fragment: HomeFragment,
val profile: Profile
) : HomeCard, CoroutineScope {
companion object {
@ -58,7 +67,7 @@ class HomeTimetableCard(
private var bellSyncDiffMillis = 0L
private val syncedNow: Time
get() = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
get() = Time.fromMillis(Time.getNow().inMillis - bellSyncDiffMillis)
private var counterJob: Job? = null
private var counterStart: Time? = null
@ -67,6 +76,9 @@ class HomeTimetableCard(
private val ignoreCancelled = true
private val countInSeconds: Boolean
get() = app.config.timetable.countInSeconds
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
holder.root.removeAllViews()
b = CardHomeTimetableBinding.inflate(LayoutInflater.from(holder.root.context))
@ -79,11 +91,40 @@ class HomeTimetableCard(
.colorAttr(activity, R.attr.colorIcon)
.sizeDp(20))
b.bellSync.setImageDrawable(IconicsDrawable(activity, SzkolnyFont.Icon.szf_alarm_bell_outline)
.colorAttr(activity, R.attr.colorIcon)
.sizeDp(20))
b.showCounter.setImageDrawable(IconicsDrawable(activity, CommunityMaterial.Icon.cmd_fullscreen)
.colorAttr(activity, R.attr.colorIcon)
.sizeDp(20))
b.bellSync.setOnClickListener {
BellSyncTimeChooseDialog(
activity
)
}
b.showCounter.setOnClickListener {
activity.startActivity(Intent(activity, CounterActivity::class.java))
}
b.root.onClick {
activity.loadTarget(MainActivity.DRAWER_ITEM_TIMETABLE, Bundle().apply {
putString("timetableDate", timetableDate.stringY_m_d)
})
}
if (app.profile.getStudentData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE
b.notPublicLayout.visibility = View.VISIBLE
return
}
// get current bell-sync params
app.config.timetable.bellSyncDiff?.let {
bellSyncDiffMillis = (it.hour * 60 * 60 * 1000 + it.minute * 60 * 1000 + it.second * 1000).toLong()
bellSyncDiffMillis *= app.config.timetable.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get all lessons within the search bounds
@ -92,11 +133,7 @@ class HomeTimetableCard(
update()
})
b.root.setOnClickListener {
activity.loadTarget(MainActivity.DRAWER_ITEM_TIMETABLE, Bundle().apply {
putString("timetableDate", timetableDate.stringY_m_d)
})
}
EventBus.getDefault().register(this)
}
private fun update() { launch {
@ -111,21 +148,26 @@ class HomeTimetableCard(
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.displayEndTime > now
&& it.type != Lesson.TYPE_NO_LESSONS
&& !(it.isCancelled && ignoreCancelled)
}
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
it.type != Lesson.TYPE_NO_LESSONS
&& (it.displayDate != today
|| (it.displayDate == today
&& it.displayEndTime != null
&& it.displayEndTime!! >= now))
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = allLessons.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.type != Lesson.TYPE_NO_LESSONS
&& !(it.isCancelled && ignoreCancelled)
}
if (lessons.isEmpty() && timetableDate.weekDay <= 5)
break
checkedDays++
}
timetableDate
@ -133,6 +175,43 @@ class HomeTimetableCard(
timetableDate = deferred.await()
if (lessons.isEmpty()) {
// timetable is not downloaded yet
b.timetableLayout.visibility = View.GONE
b.noTimetableLayout.visibility = View.VISIBLE
b.noLessonsLayout.visibility = View.GONE
val weekStart = timetableDate.weekStart
b.noTimetableText.setText(
R.string.home_timetable_no_timetable_text,
weekStart.stringY_m_d
)
b.noTimetableSync.onClick {
it.isEnabled = false
EdziennikTask.syncProfile(
profileId = App.profileId,
viewIds = listOf(
MainActivity.DRAWER_ITEM_TIMETABLE to 0
),
arguments = JsonObject(
"weekStart" to weekStart
)
).enqueue(activity)
}
return@launch
}
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
// in next 7 days only NO_LESSONS is found
b.timetableLayout.visibility = View.GONE
b.noTimetableLayout.visibility = View.GONE
b.noLessonsLayout.visibility = View.VISIBLE
timetableDate = timetableDate.weekStart
return@launch
}
b.timetableLayout.visibility = View.VISIBLE
b.noTimetableLayout.visibility = View.GONE
b.noLessonsLayout.visibility = View.GONE
val isToday = today == timetableDate
b.progress.visibility = View.GONE
@ -264,7 +343,7 @@ class HomeTimetableCard(
b.progress.visibility = View.GONE
b.counter.visibility = View.VISIBLE
val diff = counterStart - now
b.counter.text = activity.timeTill(diff.toInt(), "\n")
b.counter.text = activity.timeTill(diff.toInt(), "\n", countInSeconds)
}
else {
// the lesson is right now
@ -273,11 +352,16 @@ class HomeTimetableCard(
val lessonLength = counterEnd - counterStart
val timePassed = now - counterStart
val timeLeft = counterEnd - now
b.counter.text = activity.timeLeft(timeLeft.toInt(), "\n")
b.counter.text = activity.timeLeft(timeLeft.toInt(), "\n", countInSeconds)
b.progress.max = lessonLength.toInt()
b.progress.progress = timePassed.toInt()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) {
b.noTimetableSync.isEnabled = true
}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -21,7 +21,7 @@ import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentOld;
import pl.szczodrzynski.edziennik.utils.models.Date;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
@ -53,7 +53,7 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
else if (dayDiff == 2) {
return context.getString(R.string.the_day_after);
}
return HomeFragment.plural(context, R.plurals.time_till_days, Math.abs(dayDiff));
return HomeFragmentOld.plural(context, R.plurals.time_till_days, Math.abs(dayDiff));
}
else if (dayDiff < 0) {
if (dayDiff == -1) {
@ -62,7 +62,7 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
else if (dayDiff == -2) {
return context.getString(R.string.the_day_before);
}
return context.getString(R.string.ago_format, HomeFragment.plural(context, R.plurals.time_till_days, Math.abs(dayDiff)));
return context.getString(R.string.ago_format, HomeFragmentOld.plural(context, R.plurals.time_till_days, Math.abs(dayDiff)));
}
return context.getString(R.string.today);
}

View File

@ -54,6 +54,7 @@ public class LoginChooserFragment extends Fragment {
b.loginLibrusJstLogo.setOnClickListener((v) -> nav.navigate(R.id.loginLibrusJstFragment, null, LoginActivity.navOptions));
b.loginVulcanLogo.setOnClickListener((v) -> nav.navigate(R.id.loginVulcanFragment, null, LoginActivity.navOptions));
b.loginIuczniowieLogo.setOnClickListener((v) -> nav.navigate(R.id.loginIuczniowieFragment, null, LoginActivity.navOptions));
b.loginEdudziennikLogo.setOnClickListener((v) -> nav.navigate(R.id.loginEdudziennikFragment, null, LoginActivity.navOptions));
if (LoginActivity.firstCompleted) {
// we are navigated here from LoginSummary

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-23
*/
package pl.szczodrzynski.edziennik.ui.modules.login
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.Navigation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK
import pl.szczodrzynski.edziennik.databinding.FragmentLoginEdudziennikBinding
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import kotlin.coroutines.CoroutineContext
class LoginEdudziennikFragment : Fragment(), CoroutineScope {
private val app by lazy { activity?.application as App? }
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var b: FragmentLoginEdudziennikBinding
private lateinit var nav: NavController
private lateinit var errorSnackbar: ErrorSnackbar
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity?.also { activity ->
nav = Navigation.findNavController(activity, R.id.nav_host_fragment)
errorSnackbar = (activity as LoginActivity).errorSnackbar
}
b = FragmentLoginEdudziennikBinding.inflate(inflater, container, false)
return b.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
startCoroutineTimer(delayMillis = 100) {
val error = LoginActivity.error
if (error != null) {
when (error.errorCode) {
ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN ->
b.loginPasswordLayout.error = getString(R.string.login_error_incorrect_login_or_password)
}
errorSnackbar.addError(error)
LoginActivity.error = null
}
}
b.backButton.setOnClickListener { nav.navigateUp() }
b.loginButton.setOnClickListener { login() }
}}
private fun login() {
var errors = false
b.loginEmailLayout.error = null
b.loginPasswordLayout.error = null
val emailEditable = b.loginEmail.text
val passwordEditable = b.loginPassword.text
if (emailEditable.isNullOrBlank()) {
b.loginEmailLayout.error = getString(R.string.login_error_no_email)
errors = true
}
if (passwordEditable.isNullOrBlank()) {
b.loginPasswordLayout.error = getString(R.string.login_error_no_password)
errors = true
}
if (errors)
return
nav.navigate(R.id.loginProgressFragment, Bundle().apply {
putInt("loginType", LOGIN_TYPE_EDUDZIENNIK)
putString("email", emailEditable.toString())
putString("password", passwordEditable.toString())
}, LoginActivity.navOptions)
}
}

View File

@ -31,6 +31,7 @@ import pl.szczodrzynski.edziennik.databinding.RowLoginProfileListItemBinding;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_MODE_LIBRUS_EMAIL;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_MODE_VULCAN_API;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_MODE_VULCAN_WEB;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_TYPE_EDUDZIENNIK;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_TYPE_IDZIENNIK;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_TYPE_LIBRUS;
import static pl.szczodrzynski.edziennik.data.api.LoginMethodsKt.LOGIN_TYPE_MOBIDZIENNIK;
@ -225,6 +226,9 @@ public class LoginSummaryFragment extends Fragment {
imageRes = R.drawable.logo_dzienniczek;
}
}
else if (m.loginType == LOGIN_TYPE_EDUDZIENNIK) {
imageRes = R.drawable.logo_edudziennik;
}
if (imageRes != 0) {
b.registerIcon.setImageResource(imageRes);
}

View File

@ -52,7 +52,7 @@ import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog;
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentOld;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.Date;
@ -513,14 +513,14 @@ public class SettingsNewFragment extends MaterialAboutFragment {
if (app.config.getSync().getInterval() < 60 * 60)
return getString(
R.string.settings_sync_sync_interval_subtext_format,
HomeFragment.plural(activity, R.plurals.time_till_minutes, app.config.getSync().getInterval() / 60)
HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, app.config.getSync().getInterval() / 60)
);
return getString(
R.string.settings_sync_sync_interval_subtext_format,
HomeFragment.plural(activity, R.plurals.time_till_hours, app.config.getSync().getInterval() / 60 / 60) +
HomeFragmentOld.plural(activity, R.plurals.time_till_hours, app.config.getSync().getInterval() / 60 / 60) +
(app.config.getSync().getInterval() / 60 % 60 == 0 ?
"" :
" " + HomeFragment.plural(activity, R.plurals.time_till_minutes, app.config.getSync().getInterval() / 60 % 60)
" " + HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, app.config.getSync().getInterval() / 60 % 60)
)
);
}
@ -566,16 +566,16 @@ public class SettingsNewFragment extends MaterialAboutFragment {
syncCardIntervalItem.setOnClickAction(() -> {
List<CharSequence> intervalNames = new ArrayList<>();
if (App.devMode && false) {
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_seconds, 30));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_minutes, 2));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_seconds, 30));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, 2));
}
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_minutes, 30));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_minutes, 45));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_hours, 1));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_hours, 1)+" "+HomeFragment.plural(activity, R.plurals.time_till_minutes, 30));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_hours, 2));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_hours, 3));
intervalNames.add(HomeFragment.plural(activity, R.plurals.time_till_hours, 4));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, 30));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, 45));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_hours, 1));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_hours, 1)+" "+ HomeFragmentOld.plural(activity, R.plurals.time_till_minutes, 30));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_hours, 2));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_hours, 3));
intervalNames.add(HomeFragmentOld.plural(activity, R.plurals.time_till_hours, 4));
List<Integer> intervals = new ArrayList<>();
if (App.devMode && false) {
intervals.add(30);

View File

@ -52,7 +52,7 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentOld;
import pl.szczodrzynski.edziennik.utils.SpannableHtmlTagHandler;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
@ -184,7 +184,7 @@ public class TimetableFragment extends Fragment {
return;
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(App.profileId, today.getWeekStart(), today);
displayingDate = HomeFragment.findDateWithLessons(App.profileId, lessons);
displayingDate = HomeFragmentOld.findDateWithLessons(App.profileId, lessons);
pageSelection = 2 + Date.diffDays(displayingDate, today); // DEFAULT HERE
activity.runOnUiThread(() -> {

View File

@ -20,7 +20,6 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
@ -88,7 +87,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
if (app.profile == null || !isAdded)
return@launch
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
if (app.profile.getStudentData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE
b.timetableNotPublicLayout.visibility = View.VISIBLE
return@launch

View File

@ -18,7 +18,6 @@ import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
@ -151,15 +150,15 @@ class TimetableDayFragment : Fragment(), CoroutineScope {
}
// reload the fragment when: no lessons, user wants to sync the week, the timetable is not public, pager gets removed
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
if (app.profile.getStudentData("timetableNotPublic", false)) {
activity.reloadTarget()
// TODO fix for (not really)possible infinite loops
return
}
// clear the root view and add the ScrollView
(view as FrameLayout).removeAllViews()
(view as FrameLayout).addView(dayScroll)
(view as FrameLayout?)?.removeAllViews()
(view as FrameLayout?)?.addView(dayScroll)
// Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>()

View File

@ -39,6 +39,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
@ -418,7 +419,7 @@ public class Utils {
byte[] iv = new byte[16];
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keyObj, ivSpec);
byte [] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));
byte [] encryptedByteValue = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8));
String encryptedValue64 = Base64.encodeToString(encryptedByteValue, Base64.DEFAULT);
return encryptedValue64;
@ -433,7 +434,7 @@ public class Utils {
cipher.init(Cipher.DECRYPT_MODE, keyObj, ivSpec);
byte[] decryptedValue64 = Base64.decode(value, Base64.DEFAULT);
byte [] decryptedByteValue = cipher.doFinal(decryptedValue64);
String decryptedValue = new String(decryptedByteValue,"utf-8");
String decryptedValue = new String(decryptedByteValue, StandardCharsets.UTF_8);
return decryptedValue;
}
@ -612,7 +613,7 @@ public class Utils {
if (sourceFile.isDirectory()) {
zipSubFolder(out, sourceFile, sourceFile.getParent().length());
} else {
byte data[] = new byte[BUFFER];
byte[] data = new byte[BUFFER];
FileInputStream fi = new FileInputStream(sourcePath);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(getLastPathComponent(sourcePath));
@ -648,7 +649,7 @@ public class Utils {
if (file.isDirectory()) {
zipSubFolder(out, file, basePathLength);
} else {
byte data[] = new byte[BUFFER];
byte[] data = new byte[BUFFER];
String unmodifiedFilePath = file.getPath();
String relativePath = unmodifiedFilePath
.substring(basePathLength);
@ -797,4 +798,10 @@ public class Utils {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
return dateFormat.format(date);
}
public static String getCurrentSchoolYear() {
pl.szczodrzynski.edziennik.utils.models.Date today = pl.szczodrzynski.edziennik.utils.models.Date.getToday();
if (today.month >= 9) return today.year + "/" + (today.year + 1);
else return (today.year - 1) + "/" + today.year;
}
}

View File

@ -25,4 +25,8 @@ public class ItemWidgetTimetableModel {
public List<Integer> eventColors = new ArrayList<>();
public boolean bigStyle = false;
public boolean darkTheme = false;
public boolean isNoTimetableItem = false;
public boolean isNoLessonsItem = false;
public boolean isNotPublicItem = false;
}

View File

@ -167,7 +167,7 @@ public class Time implements Comparable<Time> {
long t2millis = t2.getInMillis();
int multiplier = (t1millis > t2millis ? 1 : -1);
Time diff = Time.fromMillis((t1millis - t2millis)*multiplier);
diff.hour -= 1;
// diff.hour -= 1;
return diff;
}
@ -175,7 +175,7 @@ public class Time implements Comparable<Time> {
long t1millis = t1.getInMillis();
long t2millis = t2.getInMillis();
Time sum = Time.fromMillis((t1millis + t2millis));
sum.hour += 1;
// sum.hour += 1;
return sum;
}

View File

@ -1,67 +0,0 @@
package pl.szczodrzynski.edziennik.widgets.timetable;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
public class LessonDetailsActivity extends AppCompatActivity {
public LessonDetailsActivity() {
super();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setBackgroundDrawable(new ColorDrawable(0));
setTheme(Themes.INSTANCE.getAppThemeNoDisplay());
App app = (App)getApplication();
Bundle extras = getIntent().getExtras();
if (app != null && extras != null) {
int profileId = extras.getInt("profileId", -1);
if (extras.getBoolean("separatorItem", false)) {
Intent i = new Intent(this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
.putExtra("profileId", profileId)
.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
app.getContext().startActivity(i);
finish();
return;
}
Date date = Date.fromYmd(extras.getString("date", "20181109"));
Time startTime = Time.fromHms(extras.getString("startTime", "20181109"));
//Time endTime = Time.fromHms(extras.getString("endTime", "20181109"));
/* new EventListDialogOld(this, profileId)
.withDismissListener((dialog -> {
finish();
Intent intent = new Intent(app.getContext(), WidgetTimetable.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int[] ids = AppWidgetManager.getInstance(app)
.getAppWidgetIds(new ComponentName(app, WidgetTimetable.class));
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
app.sendBroadcast(intent);
}))
.show(app, date, startTime); */
return;
}
Toast.makeText(app, R.string.error_reading_lesson_details, Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@ -33,6 +33,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
public class WidgetTimetableListProvider implements RemoteViewsService.RemoteViewsFactory {
private static final String TAG = "WidgetTimetableProvider";
@ -149,6 +151,8 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
views.setViewVisibility(R.id.widgetTimetableProfileName, View.VISIBLE);
views.setViewVisibility(R.id.widgetTimetableContent, View.GONE);
views.setTextViewText(R.id.widgetTimetableProfileName, lesson.separatorProfileName);
views.setTextViewTextSize(R.id.widgetTimetableProfileName, COMPLEX_UNIT_SP, lesson.bigStyle ? 30 : 20);
views.setTextColor(R.id.widgetTimetableProfileName, lesson.darkTheme ? 0xff000000 : 0xffffffff);
Intent intent = new Intent();
intent.putExtra("profileId", lesson.profileId);
@ -158,6 +162,37 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
return views;
}
if (lesson.isNoTimetableItem) {
views.setImageViewBitmap(R.id.widgetTimetableBackground, null);
views.setViewVisibility(R.id.widgetTimetableProfileName, View.VISIBLE);
views.setViewVisibility(R.id.widgetTimetableContent, View.GONE);
views.setTextViewText(R.id.widgetTimetableProfileName, context.getString(R.string.widget_timetable_short_no_timetable));
views.setTextViewTextSize(R.id.widgetTimetableProfileName, COMPLEX_UNIT_SP, lesson.bigStyle ? 26 : 18);
views.setTextColor(R.id.widgetTimetableProfileName, lesson.darkTheme ? 0xffffffff : 0xff000000);
Intent intent = new Intent();
intent.putExtra("profileId", lesson.profileId);
intent.putExtra("separatorItem", true);
intent.putExtra("isNoTimetableItem", true);
views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent);
return views;
}
if (lesson.isNoLessonsItem) {
views.setImageViewBitmap(R.id.widgetTimetableBackground, null);
views.setViewVisibility(R.id.widgetTimetableProfileName, View.VISIBLE);
views.setViewVisibility(R.id.widgetTimetableContent, View.GONE);
views.setTextViewText(R.id.widgetTimetableProfileName, context.getString(R.string.widget_timetable_short_no_lessons));
views.setTextViewTextSize(R.id.widgetTimetableProfileName, COMPLEX_UNIT_SP, lesson.bigStyle ? 26 : 18);
views.setTextColor(R.id.widgetTimetableProfileName, lesson.darkTheme ? 0xffffffff : 0xff000000);
Intent intent = new Intent();
intent.putExtra("profileId", lesson.profileId);
intent.putExtra("separatorItem", true);
intent.putExtra("isNoLessonsItem", true);
views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent);
return views;
}
views.setViewVisibility(R.id.widgetTimetableBackground, View.VISIBLE);
views.setViewVisibility(R.id.widgetTimetableProfileName, View.GONE);
views.setViewVisibility(R.id.widgetTimetableContent, View.VISIBLE);

View File

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M48.3984,50.668L34.668,50.668L34.668,48L48.3984,48C50.3984,48 52,46.3984 52,44.3984L52,26.668L54.668,26.668L54.668,44.3984C54.668,47.8672 51.8672,50.668 48.3984,50.668Z"
android:fillColor="#C5CAE9"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M34.668,56L18.668,56C16.5352,56 14.668,54.2656 14.668,52L14.668,42.668C14.668,40.5352 16.5352,38.668 18.668,38.668L34.668,38.668C36.8008,38.668 38.668,40.5352 38.668,42.668L38.668,52C38.668,54.2656 36.8008,56 34.668,56Z"
android:fillColor="#546E7A"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M46.668,28C46.668,39.0664 37.7344,48 26.668,48C15.6016,48 6.668,39.0664 6.668,28C6.668,16.9336 15.6016,8 26.668,8C37.7344,8 46.668,16.9336 46.668,28Z"
android:fillColor="#FF3D00"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M26.668,42.668C16,42.668 7.332,34.9336 6.668,26.9336C6.668,27.332 6.668,27.7344 6.668,28C6.668,39.0664 15.6016,48 26.668,48C37.7344,48 46.668,39.0664 46.668,28C46.668,27.6016 46.668,27.1992 46.668,26.9336C46,34.9336 37.332,42.668 26.668,42.668Z"
android:fillColor="#DD2C00"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M14.668,44L14.668,47.1992C18.1328,49.332 22.2656,50.668 26.668,50.668C31.0664,50.668 35.1992,49.332 38.668,47.1992L38.668,44C35.332,46.5352 31.1992,48 26.668,48C22.1328,48 18,46.5352 14.668,44Z"
android:fillColor="#37474F"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M34.668,28C34.668,32.3984 31.0664,36 26.668,36C22.2656,36 18.668,32.3984 18.668,28C18.668,23.6016 22.2656,20 26.668,20C31.0664,20 34.668,23.6016 34.668,28Z"
android:fillColor="#C5CAE9"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M29.332,28C29.332,29.4648 28.1328,30.668 26.668,30.668C25.1992,30.668 24,29.4648 24,28C24,26.5352 25.1992,25.332 26.668,25.332C28.1328,25.332 29.332,26.5352 29.332,28Z"
android:fillColor="#7986CB"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M57.332,26.668C57.332,28.9336 55.6016,30.668 53.332,30.668C51.0664,30.668 49.332,28.9336 49.332,26.668C49.332,24.3984 51.0664,22.668 53.332,22.668C55.6016,22.668 57.332,24.3984 57.332,26.668Z"
android:fillColor="#9FA8DA"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,78 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M45.5508,36.1758C44.8242,32.8359 43.625,29.5391 43.3516,26.125C43.1641,23.7617 43.0859,21.375 42.6016,19.0508C42.1758,16.9883 41.3516,14.6016 39.4258,13.4883C38.0391,12.6875 36.4375,12.6641 34.8633,12.7109C33.0117,12.7734 31.1875,12.8008 29.3359,12.75C26.1367,12.6758 22.7617,12.7617 20.6484,15.5234C19.0234,17.6484 18.4883,20.4258 18.1758,23.0234C17.8242,25.9141 17.6992,28.8125 17.1484,31.6758C16.4766,35.1875 15.5625,38.7734 15.9375,42.375C17.7617,41.6641 19.8984,41.5234 21.8008,41.4609C25.4141,41.3359 29.0234,41.1875 32.6484,41.2734C34.6641,41.3242 36.6992,41.3359 38.6992,41.5859C39.6875,41.7109 40.6875,41.8984 41.6875,41.9766C42.5859,42.0391 43.4883,41.9766 44.3867,41.9883C44.9766,42 45.5742,42.0234 46.1641,42.125C46.2383,40.1367 45.9766,38.1367 45.5508,36.1758ZM27.3984,36.5859C27.3984,36.8867 27.25,37.1992 26.9375,37.2891C24.3633,38.0508 21.6758,37.8984 19.0742,37.3125C18.2383,37.125 18.4609,35.8359 19.2734,35.9492C19.2891,34.875 19.3359,33.8008 19.7109,32.7617C19.7891,32.5742 19.8984,32.4609 20.0391,32.3984C20.1367,32.2891 20.2734,32.2109 20.4609,32.1875C21.7383,32.0234 23.0234,31.8359 24.3125,31.8125C25.125,31.8008 26.1875,31.8516 26.8125,32.4883C27.875,33.5859 27.4141,35.2383 27.3984,36.5859ZM27.5742,27.8359C27.5117,29 27.0508,29.9141 25.8867,30.2891C24.2383,30.8125 22.4609,30.6875 20.7734,30.9492C20.4375,31 20.2109,30.8359 20.125,30.6016C20.0742,30.5625 20.0117,30.5234 19.9766,30.4609C19.5234,29.9492 19.6133,29.0508 19.6758,28.4258C19.7617,27.5117 19.9609,26.625 20.1641,25.7383C20.4883,24.3008 20.6875,22.8867 20.8867,21.4375C21.1016,19.8633 21.375,18.1133 22.25,16.7383C23.1875,15.2734 24.5742,14.5859 26.3125,14.7891C26.5859,14.8125 26.875,15.125 26.8633,15.4141C26.6484,19.5742 27.7891,23.6758 27.5742,27.8359Z"
android:fillColor="#EF4823"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M25.8242,33.4766C25.2266,33.0234 24.1992,33.1992 23.5117,33.25C22.6367,33.3125 21.7734,33.4258 20.8984,33.5391C20.625,34.3867 20.6484,35.3359 20.6484,36.2109C20.6484,36.2109 20.6484,36.2109 20.6484,36.2266C22.4492,36.5117 24.2734,36.5625 26.0508,36.1133C26.0742,35.625 26.125,35.1484 26.125,34.6641C26.1367,34.2891 26.1758,33.7383 25.8242,33.4766Z"
android:fillColor="#D6E5E5"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M18.1758,11.0234C18.1758,11.0391 18.1875,11.0508 18.1992,11.0625C18.3633,11.2266 18.5234,11.25 18.6367,11.2266C18.4609,11.2109 18.3008,11.1484 18.1758,11.0234Z"
android:fillColor="#D6E5E5"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M26.2266,27.0234C26.2109,23.375 25.3867,19.7617 25.4492,16.125C22.8242,16.3125 22.3984,20.1016 22.1484,22.1992C22,23.5 21.7734,24.7383 21.4883,26.0117C21.3125,26.8125 21.125,27.625 21.0391,28.4375C21,28.7266 20.9766,29.0234 21,29.3125C21.0117,29.4258 21.0117,29.4883 21.0234,29.5391C22.4883,29.3633 24.0625,29.4766 25.4375,28.9883C26.3242,28.6758 26.2266,27.8867 26.2266,27.0234Z"
android:fillColor="#D6E5E5"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M49.6641,45.9883C49.0625,45.0859 48.25,44.1484 47.1875,43.8008C46.8984,43.9766 46.4492,43.8633 46.2383,43.5625C44.7891,43.3008 43.3242,43.4375 41.8359,43.3633C39.875,43.2617 37.9375,42.8633 35.9609,42.7617C33.9141,42.6641 31.8633,42.6133 29.8125,42.625C27.8242,42.625 25.8516,42.6992 23.875,42.7734C22.5859,42.8242 21.2891,42.8359 20,42.9492C18.6992,43.0625 17.375,43.25 16.1758,43.7891C15.0234,44.3008 14.1992,45.1133 13.5742,46.1992C12.8867,47.3867 12.4258,48.7109 12.1133,50.0508C11.8359,51.2109 11.5859,52.5117 11.7617,53.7266C12.2734,53.5625 12.875,53.5234 13.3008,53.4883C14.8984,53.3125 16.5117,53.2383 18.125,53.1758C29.3633,52.7109 40.6133,52.6641 51.8633,52.6484C51.5508,50.3516 50.9492,47.9258 49.6641,45.9883Z"
android:fillColor="#EF4823"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M53.25,52.875C52.9609,50.3984 52.3359,47.9258 51.125,45.7383C50.5234,44.6641 49.7109,43.6758 48.6641,43.0117C48.3008,42.7891 47.9141,42.6016 47.5117,42.4609C47.6758,38.9492 46.8516,35.5391 45.9883,32.1484C45.5391,30.4258 44.9883,28.6484 44.8008,26.875C44.6875,25.7891 44.625,24.6875 44.5508,23.6016C44.2383,19.2617 43.625,13.2734 38.75,11.6875C37.0625,11.1367 35.125,11.3242 33.3867,11.3984C31.6016,11.4766 29.8242,11.3867 28.0391,11.3633C26.3125,11.3359 24.5859,11.4766 22.9766,12.1016C21.6484,12.6133 20.5117,13.5234 19.6367,14.6367C17.9609,16.75 17.3125,19.5117 16.9375,22.125C16.5391,24.9375 16.4766,27.7734 15.9883,30.5742C15.2891,34.5742 14.1367,38.6133 14.6133,42.7109C14.625,42.8359 14.6641,42.9375 14.7266,43.0117C14.0234,43.4492 13.3867,44.0117 12.8867,44.75C11.9375,46.125 11.3359,47.6641 10.8984,49.2734C10.4375,50.9492 10.1641,52.7891 10.5234,54.5117C10.5742,54.7617 10.7266,54.8984 10.8867,54.9492C11.0508,55.3242 11.6367,55.4375 11.9883,55.1484C12.0234,55.1367 12.1133,55.1016 12.1367,55.0859C12.3984,55.0117 12.4141,55.0117 12.7617,54.9609C13.5234,54.8516 14.2891,54.7891 15.0508,54.7266C17.1641,54.5742 19.2891,54.5117 21.3984,54.4492C25.6992,54.3242 30.0117,54.1758 34.3125,54.1016C40.4883,54 46.6484,54.0391 52.8242,54.0391C53.4883,54.0117 53.6016,53.2617 53.25,52.875ZM17.1484,31.6875C17.6992,28.8125 17.8242,25.9258 18.1758,23.0391C18.4883,20.4375 19.0234,17.6641 20.6484,15.5391C22.7617,12.7617 26.1367,12.6875 29.3359,12.7617C31.1875,12.8008 33.0117,12.7734 34.8633,12.7266C36.4258,12.6758 38.0234,12.6992 39.4258,13.5C41.3516,14.6133 42.1758,17.0117 42.6016,19.0625C43.0742,21.3867 43.1484,23.7734 43.3516,26.1367C43.625,29.5508 44.8359,32.8516 45.5508,36.1875C45.9766,38.1367 46.2383,40.1484 46.1641,42.1484C45.5742,42.0625 44.9766,42.0234 44.3867,42.0117C43.4883,42 42.5859,42.0508 41.6875,42C40.6875,41.9375 39.6992,41.7383 38.6992,41.6133C36.6992,41.3516 34.6641,41.3359 32.6484,41.3008C29.0234,41.2109 25.4141,41.3633 21.8008,41.4883C19.9141,41.5508 17.7734,41.6875 15.9375,42.3984C15.5742,38.7891 16.4883,35.1992 17.1484,31.6875ZM18.1016,53.1641C16.4883,53.2266 14.875,53.3008 13.2734,53.4766C12.8516,53.5234 12.25,53.5508 11.7383,53.7109C11.5625,52.5 11.8125,51.1992 12.0859,50.0391C12.3984,48.6992 12.8633,47.375 13.5508,46.1875C14.1758,45.1016 15,44.2891 16.1484,43.7734C17.3516,43.2383 18.6758,43.0508 19.9766,42.9375C21.2617,42.8242 22.5625,42.8242 23.8516,42.7617C25.8242,42.6758 27.8125,42.6133 29.7891,42.6133C31.8359,42.6133 33.8867,42.6641 35.9375,42.75C37.9141,42.8359 39.8516,43.25 41.8125,43.3516C43.3008,43.4258 44.7617,43.2891 46.2109,43.5508C46.4375,43.8516 46.875,43.9492 47.1641,43.7891C48.2266,44.1367 49.0508,45.0742 49.6367,45.9766C50.9258,47.9141 51.5234,50.3359 51.8125,52.6367C40.5859,52.6484 29.3359,52.7109 18.1016,53.1641Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M26.8359,15.4141C26.8516,15.125 26.5625,14.8242 26.2891,14.7891C24.5508,14.5859 23.1641,15.2734 22.2266,16.7383C21.3516,18.1016 21.0742,19.8516 20.8633,21.4375C20.6641,22.8984 20.4609,24.3008 20.1367,25.7383C19.9375,26.625 19.75,27.5234 19.6484,28.4258C19.5859,29.0508 19.5,29.9492 19.9492,30.4609C20,30.5117 20.0508,30.5625 20.1016,30.6016C20.1992,30.8359 20.4141,31 20.75,30.9492C22.4492,30.6875 24.2109,30.8008 25.8633,30.2891C27.0234,29.9258 27.4883,29 27.5508,27.8359C27.7891,23.6758 26.6484,19.5742 26.8359,15.4141ZM25.4375,28.9883C24.0508,29.4766 22.4766,29.3633 21.0234,29.5391C21.0117,29.4883 21.0117,29.4141 21,29.3125C20.9766,29.0234 21.0117,28.7266 21.0391,28.4375C21.1367,27.625 21.3125,26.8125 21.4883,26.0117C21.7734,24.7383 21.9883,23.5 22.1484,22.1992C22.3984,20.1016 22.8242,16.3125 25.4492,16.125C25.3867,19.7734 26.1992,23.375 26.2266,27.0234C26.2266,27.8867 26.3242,28.6758 25.4375,28.9883Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M26.8125,32.5117C26.1875,31.8867 25.1367,31.8242 24.3125,31.8359C23.0234,31.8633 21.75,32.0508 20.4609,32.2109C20.2734,32.2383 20.1367,32.3125 20.0391,32.4258C19.8984,32.4766 19.7734,32.5859 19.7109,32.7891C19.3359,33.8125 19.2734,34.8867 19.2734,35.9766C18.4609,35.8633 18.2383,37.1484 19.0742,37.3359C21.6758,37.9258 24.3633,38.0742 26.9375,37.3125C27.25,37.2266 27.3867,36.9141 27.3984,36.6133C27.4141,35.2383 27.875,33.5859 26.8125,32.5117ZM26.125,34.6758C26.1133,35.1641 26.0742,35.6367 26.0508,36.125C24.2734,36.5625 22.4492,36.5234 20.6484,36.2383C20.6484,36.2383 20.6484,36.2383 20.6484,36.2266C20.6484,35.3359 20.625,34.3984 20.8984,33.5508C21.7734,33.4375 22.6367,33.3359 23.5117,33.2617C24.1992,33.2109 25.2266,33.0391 25.8242,33.4883C26.1758,33.7383 26.1367,34.2891 26.125,34.6758Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M50.9883,18.2383C50.125,16.3359 49.875,14.1875 49.0859,12.25C48.2383,10.1875 46.75,8.2266 44.2891,8.3984C43.3984,8.4609 43.5117,9.8359 44.3867,9.7734C46.3867,9.6367 47.3633,11.5859 47.9766,13.1992C48.6484,14.9883 48.8984,16.9492 49.6875,18.6992C50.0508,19.4883 51.3516,19.0391 50.9883,18.2383Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M56.875,15.8125C56.5508,14.0625 56.1641,12.2734 55.6641,10.5742C55.3359,9.4609 54.875,8.3984 54.3125,7.3984C53.9609,6.7891 53.3984,6.3359 52.8633,5.8867C52.1133,5.2617 50.25,3.7734 49.5234,5.2266C49.1758,5.9375 50.1367,6.5117 50.625,6.0234C50.8633,6.125 51.0859,6.2617 51.3008,6.4141C51.7617,6.7383 52.1875,7.1133 52.6133,7.4883C53.125,7.9492 53.3984,8.5391 53.6875,9.1758C54.1133,10.1016 54.3984,11.0859 54.6484,12.0742C54.9766,13.3984 55.2891,14.7383 55.5391,16.0742C55.75,17.1992 56.0859,18.75 55.5391,19.8359C55.125,20.625 56.3633,21.2383 56.7734,20.4492C57.4883,19.0742 57.1484,17.2617 56.875,15.8125Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M19.3359,10.5391C19.1758,9.6758 18.3867,9.7734 17.8242,10.1758C17.5117,10.4141 17.2109,10.6758 16.9141,10.9375C16.6484,11.1641 16.2891,11.375 16.0625,11.6367C15.3125,12.5117 14.6992,13.5 14.3008,14.5742C14.0859,15.1484 13.9375,15.7383 13.8633,16.3516C13.8008,16.8516 13.7109,17.5391 13.8984,18.0117C14.0234,18.3359 14.4258,18.5859 14.7734,18.4375C15.125,18.2734 15.3008,18.0391 15.375,17.6484C15.4375,17.3633 15.3359,17.1484 15.1641,17.0117C15.1758,16.8633 15.1992,16.7266 15.2109,16.6133C15.25,16.1992 15.3516,15.8008 15.4609,15.4141C15.6875,14.6133 16.1133,13.8516 16.6016,13.1758C16.9375,12.6992 17.3516,12.375 17.7891,11.9883C17.9883,11.8125 18.1992,11.6367 18.4141,11.4609C18.5,11.3867 18.5859,11.3242 18.6758,11.2617C18.6875,11.25 18.6875,11.25 18.6992,11.25C19.0742,11.2383 19.4258,11 19.3359,10.5391ZM18.1875,11.0625C18.1758,11.0508 18.1758,11.0391 18.1641,11.0234C18.2891,11.1484 18.4492,11.2109 18.625,11.2266C18.5117,11.25 18.3516,11.2266 18.1875,11.0625Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M15.9883,5.8125C9.875,8.7266 7.8984,16.4609 7.2891,22.6367C7.1992,23.5117 8.4375,23.7734 8.6367,22.8984C8.6992,22.8125 8.7734,22.7383 8.8516,22.6641C9.1758,22.3359 9.0508,21.9258 8.7617,21.7109C9.1016,19.0625 9.6875,16.4375 10.6992,13.9609C11.8359,11.1758 13.6367,8.4492 16.4375,7.1133C17.2383,6.7383 16.7891,5.4258 15.9883,5.8125Z"
android:fillColor="#010101"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-12-22.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp"
tools:ignore="HardcodedText">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Warning! Do not move this debug card, or you will probably confuse the internal card ID list, thus resulting in a weird and (maybe)non-undoable card order."/>
<com.google.android.material.button.MaterialButton
android:id="@+id/getLogs"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Debug Logs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/librusCaptchaButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Librus Captcha" />
<com.google.android.material.button.MaterialButton
android:id="@+id/runChucker"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Launch Chucker" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<com.google.android.material.button.MaterialButton
android:id="@+id/composeButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:text="Compose" />
<com.google.android.material.button.MaterialButton
android:id="@+id/composeNewButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:text="Compose 2" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/pruneWorkButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Prune finished work" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-12-22.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello world"/>
</LinearLayout>
</layout>

View File

@ -6,118 +6,264 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp">
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/ic_sync"/>
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/dayInfo"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Title"
tools:text="Jutro" />
android:text="@string/home_timetable_no_timetable" />
<TextView
android:id="@+id/lessonInfo"
android:layout_width="match_parent"
android:id="@+id/noTimetableText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="7 lekcji - 8:10 do 14:45" />
android:textSize="16sp"
android:text="@string/home_timetable_no_timetable_text"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:text="@string/home_timetable_no_timetable_sync" />
</LinearLayout>
<ImageView
android:id="@+id/settings"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:background="?selectableItemBackgroundBorderless"
android:visibility="gone"
tools:src="@sample/settings" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@color/dividerColor" />
<LinearLayout
android:id="@+id/noLessonsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:visibility="gone"
tools:layout_marginTop="150dp"
tools:visibility="visible">
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/ic_timetable"/>
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/lessonBig"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="Pierwsza: informatyka" />
android:textAppearance="@style/NavView.TextView.Title"
android:text="@string/home_timetable_no_lessons" />
<TextView
android:id="@+id/classroom"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Small"
tools:text="09a komputerowa" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:max="2700"
android:progress="780" />
android:textSize="16sp"
android:text="@string/home_timetable_no_lessons_text"/>
</LinearLayout>
<TextView
android:id="@+id/counter"
android:layout_width="wrap_content"
</LinearLayout>
<LinearLayout
android:id="@+id/notPublicLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
tools:layout_marginTop="220dp"
tools:visibility="visible">
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/ic_no_timetable"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
tools:text="zostały\n2 minuty\n35 sekund" />
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Title"
android:text="@string/home_timetable_not_public" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@string/home_timetable_not_public_text"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@color/dividerColor" />
<TextView
android:id="@+id/nextLessons"
<LinearLayout
android:id="@+id/timetableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Póżniej:\n9:05 informatyka\n10:00 urządzenia techniki komputerowej\n11:00 projektowanie lokalnych sieci komputerowych\n11:55 zajęcia z wychowawcą\n13:00 język polski\n14:05 język niemiecki" />
android:orientation="vertical"
tools:layout_marginTop="350dp">
</LinearLayout>
</layout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/dayInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Title"
tools:text="Jutro" />
<TextView
android:id="@+id/lessonInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="7 lekcji - 8:10 do 14:45" />
</LinearLayout>
<ImageView
android:id="@+id/settings"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:background="?selectableItemBackgroundBorderless"
android:visibility="gone"
tools:src="@sample/settings" />
<ImageView
android:id="@+id/bellSync"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:background="?selectableItemBackgroundBorderless"
tools:src="@sample/settings" />
<ImageView
android:id="@+id/showCounter"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:background="?selectableItemBackgroundBorderless"
tools:src="@sample/settings" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@color/dividerColor" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/lessonBig"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="Pierwsza: informatyka" />
<TextView
android:id="@+id/classroom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Small"
tools:text="09a komputerowa" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:visibility="gone"
tools:visibility="visible"
tools:max="2700"
tools:progress="780" />
</LinearLayout>
<TextView
android:id="@+id/counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
tools:text="zostały\n2 minuty\n35 sekund" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@color/dividerColor" />
<TextView
android:id="@+id/nextLessons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="Póżniej:\n9:05 informatyka\n10:00 urządzenia techniki komputerowej\n11:00 projektowanie lokalnych sieci komputerowych\n11:55 zajęcia z wychowawcą\n13:00 język polski\n14:05 język niemiecki" />
</LinearLayout>
</FrameLayout>
</layout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kacper Ziubryniewicz 2019-12-20
-->
<layout 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/bellSyncHowto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/bell_sync_howto" />
<ImageView
android:id="@+id/bellSyncButton"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center"
android:layout_margin="32dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_bell" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kacper Ziubryniewicz 2019-12-20
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="24dp">
<TextView
android:id="@+id/bellSyncHowto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
tools:text="@string/bell_sync_choose_howto" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/bell_sync_time_title">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/timeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="lekcja matematyka (8:00)"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
</layout>

View File

@ -1,101 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".HomeFragment">
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-23.
-->
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context=".ui.modules.home.HomeFragment">
android:layout_height="match_parent"
tools:listitem="@layout/card_home" />
<LinearLayout
android:id="@+id/cardInsertPoint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<TextView
android:id="@+id/nextSync"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
tools:text="TextView" />
<LinearLayout
android:id="@+id/devMode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical"
tools:visibility="visible"
tools:ignore="HardcodedText">
<com.google.android.material.button.MaterialButton
android:id="@+id/getLogs"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Save Debug Logs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/librusCaptchaButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Librus Captcha" />
<com.google.android.material.button.MaterialButton
android:id="@+id/runChucker"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Launch Chucker" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<com.google.android.material.button.MaterialButton
android:id="@+id/mobidziennikMessagesSwitch"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:text="Zmień moduł wiadomości" />
<com.google.android.material.button.MaterialButton
android:id="@+id/composeButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:text="Compose" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/pruneWorkButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Prune finished work" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout>
</layout>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".HomeFragment">
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context=".ui.modules.home.HomeFragmentOld">
<LinearLayout
android:id="@+id/cardInsertPoint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<TextView
android:id="@+id/nextSync"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
tools:text="TextView" />
<LinearLayout
android:id="@+id/devMode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical"
tools:visibility="visible"
tools:ignore="HardcodedText">
<com.google.android.material.button.MaterialButton
android:id="@+id/getLogs"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Save Debug Logs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/librusCaptchaButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Librus Captcha" />
<com.google.android.material.button.MaterialButton
android:id="@+id/runChucker"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Launch Chucker" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<com.google.android.material.button.MaterialButton
android:id="@+id/mobidziennikMessagesSwitch"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:text="Zmień moduł wiadomości" />
<com.google.android.material.button.MaterialButton
android:id="@+id/composeButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:text="Compose" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/pruneWorkButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Prune finished work" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-23.
-->
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/card_home" />
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout>

View File

@ -184,18 +184,17 @@
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:visibility="invisible">
android:layout_weight="1">
<!--<ImageView
android:id="@+id/loginIuczniowieLogo"
<ImageView
android:id="@+id/loginEdudziennikLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_iuczniowie" />-->
app:srcCompat="@drawable/login_logo_edudziennik" />
</FrameLayout>
</LinearLayout>
@ -247,4 +246,4 @@
</LinearLayout>
</LinearLayout>
</layout>
</layout>

View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kacper Ziubryniewicz 2019-12-23
-->
<layout 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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_edudziennik_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_edudziennik_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginEmailLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_email"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_password"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<!--<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false" />-->
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,5 +1,4 @@
<LinearLayout 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/root"
android:layout_width="match_parent"
@ -65,17 +64,18 @@
</LinearLayout>
<FrameLayout
android:id="@+id/widgetTimetableBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@drawable/widget_background_bottom">
<ListView
android:id="@+id/widgetTimetableListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/secondaryTextLight"
android:background="@drawable/widget_background_bottom"
android:dividerHeight="1.0dip" ><!--#b5ffffff-->
</ListView>
android:dividerHeight="1.0dip"
tools:listitem="@layout/row_widget_timetable_item"/><!--#b5ffffff-->
<TextView
android:id="@+id/widgetTimetableLoading"
@ -84,7 +84,34 @@
android:textColor="@color/primaryTextLight"
android:layout_gravity="center"
android:textStyle="italic"
android:text="@string/widget_loading" />
android:text="@string/widget_loading"
tools:visibility="gone"/>
<TextView
android:id="@+id/widgetTimetableNoTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="16sp"
android:text="@string/widget_timetable_no_timetable"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/widgetTimetableNoLessons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="16sp"
android:text="@string/widget_timetable_no_lessons_found"
android:visibility="gone"
tools:visibility="visible"/>
</FrameLayout>

View File

@ -1,5 +1,4 @@
<LinearLayout 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/root"
android:layout_width="match_parent"
@ -65,17 +64,18 @@
</LinearLayout>
<FrameLayout
android:id="@+id/widgetTimetableBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@drawable/widget_background_bottom">
<ListView
android:id="@+id/widgetTimetableListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/secondaryTextLight"
android:background="@drawable/widget_background_bottom"
android:dividerHeight="1.0dip" ><!--#b5ffffff-->
</ListView>
android:dividerHeight="1.0dip"
tools:listitem="@layout/row_widget_timetable_big_item"/><!--#b5ffffff-->
<TextView
android:id="@+id/widgetTimetableLoading"
@ -85,7 +85,34 @@
android:text="@string/widget_loading"
android:textColor="@color/primaryTextLight"
android:textSize="22sp"
android:textStyle="italic" />
android:textStyle="italic"
tools:visibility="gone"/>
<TextView
android:id="@+id/widgetTimetableNoTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="24sp"
android:text="@string/widget_timetable_no_timetable"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/widgetTimetableNoLessons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="24sp"
android:text="@string/widget_timetable_no_lessons_found"
android:visibility="gone"
tools:visibility="visible"/>
</FrameLayout>

View File

@ -1,5 +1,4 @@
<LinearLayout 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/root"
android:layout_width="match_parent"
@ -65,17 +64,18 @@
</LinearLayout>
<FrameLayout
android:id="@+id/widgetTimetableBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@drawable/widget_background_dark_bottom">
<ListView
android:id="@+id/widgetTimetableListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/secondaryTextDark"
android:background="@drawable/widget_background_dark_bottom"
android:dividerHeight="1.0dip" ><!--#b5ffffff-->
</ListView>
android:dividerHeight="1.0dip"
tools:listitem="@layout/row_widget_timetable_dark_item"/><!--#b5ffffff-->
<TextView
android:id="@+id/widgetTimetableLoading"
@ -84,7 +84,34 @@
android:textColor="@color/primaryTextDark"
android:layout_gravity="center"
android:textStyle="italic"
android:text="@string/widget_loading" />
android:text="@string/widget_loading"
tools:visibility="gone"/>
<TextView
android:id="@+id/widgetTimetableNoTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="16sp"
android:text="@string/widget_timetable_no_timetable"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/widgetTimetableNoLessons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextLight"
android:gravity="center"
android:textSize="16sp"
android:text="@string/widget_timetable_no_lessons_found"
android:visibility="gone"
tools:visibility="visible"/>
</FrameLayout>

View File

@ -1,5 +1,4 @@
<LinearLayout 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/root"
android:layout_width="match_parent"
@ -65,17 +64,18 @@
</LinearLayout>
<FrameLayout
android:id="@+id/widgetTimetableBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@drawable/widget_background_dark_bottom">
<ListView
android:id="@+id/widgetTimetableListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/secondaryTextDark"
android:background="@drawable/widget_background_dark_bottom"
android:dividerHeight="1.0dip" ><!--#b5ffffff-->
</ListView>
android:dividerHeight="1.0dip"
tools:listitem="@layout/row_widget_timetable_dark_big_item"/><!--#b5ffffff-->
<TextView
android:id="@+id/widgetTimetableLoading"
@ -85,7 +85,34 @@
android:text="@string/widget_loading"
android:textColor="@color/primaryTextDark"
android:textSize="22sp"
android:textStyle="italic" />
android:textStyle="italic"
tools:visibility="gone"/>
<TextView
android:id="@+id/widgetTimetableNoTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextDark"
android:gravity="center"
android:textSize="24sp"
android:text="@string/widget_timetable_no_timetable"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/widgetTimetableNoLessons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:textColor="@color/primaryTextDark"
android:gravity="center"
android:textSize="24sp"
android:text="@string/widget_timetable_no_lessons_found"
android:visibility="gone"
tools:visibility="visible"/>
</FrameLayout>

View File

@ -125,6 +125,18 @@
android:id="@+id/action_loginVulcanFragment_to_loginProgressFragment"
app:destination="@id/loginProgressFragment" />
</fragment>
<fragment
android:id="@+id/loginEdudziennikFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginEdudziennikFragment"
android:label="fragment_login_edudziennik"
tools:layout="@layout/fragment_login_edudziennik" >
<!--<action
android:id="@+id/action_loginLibrusFragment_to_loginLibrusHelpFragment"
app:destination="@id/loginLibrusHelpFragment" />-->
<action
android:id="@+id/action_loginEdudziennikFragment_to_loginProgressFragment"
app:destination="@id/loginProgressFragment" />
</fragment>
<fragment
android:id="@+id/loginVulcanHelpFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginVulcanHelpFragment"

View File

@ -76,10 +76,13 @@
<string name="bell_sync_adjust_error">Incorrect format</string>
<string name="bell_sync_cannot_now">Calibration is impossible, because there are no lessons now. Try again, i.e. in the end of the lesson or break. Remember that you should run this even before planned bell time.</string>
<string name="bell_sync_current_dialog">\n\nCurrent calibration: %s</string>
<string name="bell_sync_howto">Click OK, when the lesson ends. The counter\'s time will be calibrated to the bell time.\n\nPlanned bell time is %s</string>
<string name="bell_sync_howto">Click the bell icon, when the bell rings. The counter\'s time will be calibrated to the bell time.\n\nPlanned bell time is %s\n\nThe bell time difference at the moment is %s</string>
<string name="bell_sync_reset_confirm">Do you want to reset the calibration?</string>
<string name="bell_sync_results">The bell is inexact by %s%s</string>
<string name="bell_sync_results">The bell is inexact by %s</string>
<string name="bell_sync_title">Calibrate with the school bell</string>
<string name="bell_sync_time_title">Time of the bell to synchronize</string>
<string name="bell_sync_lesson_item">lesson %s (%s)</string>
<string name="bell_sync_break_item">break (%s)</string>
<string name="cancel">Cancel</string>
<string name="card_grades_button">Go to grades</string>
<string name="card_grades_header_title">Grades - last 7 days</string>
@ -870,4 +873,5 @@
<string name="login_summary_account_child">(child)</string>
<string name="login_summary_account_parent">(parent)</string>
<string name="toolbar_subtitle_syncing">Syncing...</string>
<string name="bell_sync_choose_howto">Choose the nearest bell to synchronize.</string>
</resources>

View File

@ -98,10 +98,14 @@
<string name="bell_sync_adjust_error">Nieprawidłowy format</string>
<string name="bell_sync_cannot_now">Synchronizacja jest niemożliwa, ponieważ teraz nie ma żadnych lekcji. Spróbuj jeszcze raz np. pod koniec lekcji albo przerwy. Pamiętaj, że powinieneś to uruchomić jeszcze przed planowanym czasem dzwonka.</string>
<string name="bell_sync_current_dialog">"\n\nAktualna kalibracja to %s"</string>
<string name="bell_sync_howto">Kliknij OK, kiedy lekcja się skończy. Licznik czasu zostanie zsynchronizowany z czasem dzwonka.\n\nCzas według planu to %s</string>
<string name="bell_sync_howto">Kliknij w ikonę dzwonka, kiedy dzwonek zadzwoni. Licznik czasu zostanie zsynchronizowany z czasem dzwonka.\n\nCzas według planu to %s\n\nAktualna różnica wynosi %s</string>
<string name="bell_sync_choose_howto">Wybierz najbliższy dzwonek, abyś mógł go zsynchronizować z aplikacją.</string>
<string name="bell_sync_reset_confirm">Czy na pewno zresetować synchronizację?</string>
<string name="bell_sync_results">Dzwonek jest niedokładny o %s%s</string>
<string name="bell_sync_results">Dzwonek jest niedokładny o %s</string>
<string name="bell_sync_title">Synchronizacja z dzwonkiem</string>
<string name="bell_sync_time_title">Godzina dzwonka do synchronizacji</string>
<string name="bell_sync_lesson_item">lekcja %s (%s)</string>
<string name="bell_sync_break_item">przerwa (%s)</string>
<string name="cancel">Anuluj</string>
<string name="card_grades_button">Przejdź do ocen</string>
<string name="card_grades_header_title">Oceny - ostatnie 7 dni</string>
@ -1109,4 +1113,20 @@
<string name="qr_scanner_dialog_title">Skanuj kod QR</string>
<string name="web_push_unpair_button">Odłącz</string>
<string name="web_push_date_paired_format">Połączono %s</string>
<string name="home_timetable_no_timetable">Nie pobrano planu lekcji</string>
<string name="home_timetable_no_timetable_text">Plan lekcji na tydzień %s nie został jeszcze pobrany.</string>
<string name="home_timetable_no_timetable_sync">Pobierz</string>
<string name="home_timetable_no_lessons">Nie ma żadnych lekcji</string>
<string name="home_timetable_no_lessons_text">Przez następne 7 dni nie ma żadnych lekcji.</string>
<string name="home_timetable_not_public">Brak planu lekcji</string>
<string name="home_timetable_not_public_text">Plan lekcji nie został opublikowany przez szkołę.\n\nSkontaktuj się z wychowawcą.</string>
<string name="widget_timetable_no_timetable">Plan lekcji nie został pobrany.\n\nOtwórz aplikację i wykonaj synchronizację, aby pobrać plan lekcji.</string>
<string name="widget_timetable_no_lessons_found">Brak lekcji przez następne 7 dni.</string>
<string name="widget_timetable_short_no_timetable">Nie pobrano planu lekcji.</string>
<string name="widget_timetable_short_no_lessons">Brak lekcji przez nast. 7 dni.</string>
<string name="no_lessons_today">Nie ma dzisiaj żadnych lekcji!</string>
<string name="lessons_finished">Nie ma dzisiaj więcej lekcji!</string>
<string name="login_edudziennik_title">Zaloguj się - Edudziennik</string>
<string name="login_edudziennik_subtitle">Użyj danych, którymi logujesz się do wersji komputerowej Edudziennika.</string>
<string name="edziennik_progress_login_edudziennik_web">Logowanie do Edudziennika</string>
</resources>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.50'
release = [
versionName: "3.9.16-dev",
versionCode: 3091600
versionName: "3.9.17-dev",
versionCode: 3091700
]
setup = [