Compare commits

...

8 Commits

29 changed files with 615 additions and 108 deletions

View File

@ -0,0 +1,13 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-25.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,8A4,4 0,0 1,16 12A4,4 0,0 1,12 16A4,4 0,0 1,8 12A4,4 0,0 1,12 8M12,10A2,2 0,0 0,10 12A2,2 0,0 0,12 14A2,2 0,0 0,14 12A2,2 0,0 0,12 10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"/>
</vector>

View File

@ -36,7 +36,6 @@ import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.R
import pl.szczodrzynski.navlib.getColorFromRes
import java.text.SimpleDateFormat
import java.util.*
@ -379,13 +378,13 @@ fun CharSequence?.asItalicSpannable(): Spannable {
*/
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
fun List<CharSequence?>.concat(delimiter: String? = null): CharSequence {
if (this.isEmpty()) {
return ""
}
if (this.size == 1) {
return this[0]
return this[0] ?: ""
}
var spanned = false
@ -400,6 +399,8 @@ fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
if (spanned) {
val ssb = SpannableStringBuilder()
for (piece in this) {
if (piece == null)
continue
if (!first && delimiter != null)
ssb.append(delimiter)
first = false
@ -409,6 +410,8 @@ fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
} else {
val sb = StringBuilder()
for (piece in this) {
if (piece == null)
continue
if (!first && delimiter != null)
sb.append(delimiter)
first = false
@ -533,3 +536,54 @@ operator fun Time?.compareTo(other: Time?): Int {
operator fun StringBuilder.plusAssign(str: String?) {
this.append(str)
}
fun Context.timeTill(time: Int, delimiter: String = " "): 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
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
fun Context.timeLeft(time: Int, delimiter: String = " "): 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
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}

View File

@ -64,6 +64,7 @@ const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzP
const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia"
const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia"
const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe"
const val IDZIENNIK_WEB_HOMEWORK = "mod_panelRodzica/pracaDomowa/WS_pracaDomowa.asmx/pobierzPraceDomowe"
const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia"
const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia"
const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia"

View File

@ -11,6 +11,7 @@ const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030
const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040
const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050
const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060
const val ENDPOINT_IDZIENNIK_WEB_HOMEWORK = 1061
const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070
const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080
const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090
@ -34,6 +35,10 @@ val IdziennikFeatures = listOf(
ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_HOMEWORK, listOf(
ENDPOINT_IDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),

View File

@ -55,6 +55,10 @@ class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
IdziennikWebExams(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
IdziennikWebHomework(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
IdziennikWebNotices(data, onSuccess)

View File

@ -94,6 +94,7 @@ open class IdziennikWeb(open val data: DataIdziennik) {
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
is Boolean -> json.addProperty(name, value)
}
}
setJsonBody(json)

View File

@ -5,21 +5,21 @@
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.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.lessons.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebExams(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebExams"
}
@ -34,14 +34,15 @@ class IdziennikWebExams(override val data: DataIdziennik,
}
private fun getExams() {
val param = JsonObject()
param.addProperty("strona", 1)
param.addProperty("iloscNaStrone", "99")
param.addProperty("iloscRekordow", -1)
param.addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
param.addProperty("kierunekSort", 0)
param.addProperty("maxIloscZaznaczonych", 0)
param.addProperty("panelFiltrow", 0)
val param = JsonObject().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", "99")
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf(
"idP" to data.registerId,
@ -55,28 +56,33 @@ class IdziennikWebExams(override val data: DataIdziennik,
return@webApiGet
}
for (jExamEl in json.getAsJsonArray("ListK")) {
val jExam = jExamEl.asJsonObject
// jExam
val eventId = jExam.get("_recordId").asLong
val rSubject = data.getSubject(jExam.get("przedmiot").asString, -1, "")
val rTeacher = data.getTeacherByLastFirst(jExam.get("wpisal").asString)
val examDate = Date.fromY_m_d(jExam.get("data").asString)
val lessonObject = Lesson.getByWeekDayAndSubject(data.lessonList, examDate.weekDay, rSubject.id)
val examTime = lessonObject?.startTime
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { exam ->
val id = exam.getLong("_recordId") ?: return@forEach
val examDate = Date.fromY_m_d(exam.getString("data") ?: return@forEach)
val subjectId = data.getSubject(exam.getString("przedmiot") ?: return@forEach,
-1, "").id
val teacherId = data.getTeacherByLastFirst(exam.getString("wpisal")
?: return@forEach).id
val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val topic = exam.getString("zakres") ?: ""
val eventType = when (exam.getString("rodzaj")) {
"sprawdzian/praca klasowa" -> Event.TYPE_EXAM
else -> Event.TYPE_SHORT_QUIZ
}
val eventType = if (jExam.get("rodzaj").asString == "sprawdzian/praca klasowa") Event.TYPE_EXAM else Event.TYPE_SHORT_QUIZ
val eventObject = Event(
profileId,
eventId,
id,
examDate,
examTime,
jExam.get("zakres").asString,
startTime,
topic,
-1,
eventType,
false,
rTeacher.id,
rSubject.id,
teacherId,
subjectId,
data.teamClass?.id ?: -1
)
@ -106,9 +112,11 @@ class IdziennikWebExams(override val data: DataIdziennik,
examsNextMonthChecked = true
getExams()
} else {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS)
onSuccess()
}
}
}
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-25
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.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.utils.models.Date
class IdziennikWebHomework(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebHomework"
}
init {
val param = JsonObject().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", 997)
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "DataZadania")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_HOMEWORK, mapOf(
"idP" to data.registerId,
"data" to Date.getToday().stringY_m_d,
"wszystkie" to true,
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { homework ->
val id = homework.getLong("_recordId") ?: return@forEach
val eventDate = Date.fromY_m_d(homework.getString("dataO") ?: return@forEach)
val subjectId = data.getSubject(homework.getString("przed") ?: return@forEach,
-1, "").id
val teacherId = data.getTeacherByLastFirst(homework.getString("usr")
?: return@forEach).id
val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime
val topic = homework.getString("tytul") ?: ""
val seen = when (profile?.empty) {
true -> true
else -> eventDate < Date.getToday()
}
val eventObject = Event(
profileId,
id,
eventDate,
startTime,
topic,
-1,
Event.TYPE_HOMEWORK,
false,
teacherId,
subjectId,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_HOMEWORK,
eventObject.id,
seen,
seen,
System.currentTimeMillis()
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.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
@ -69,6 +70,8 @@ class LibrusApiEvents(override val data: DataLibrus,
))
}
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS)
onSuccess()
}

View File

@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.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
@ -55,6 +56,8 @@ class LibrusApiHomework(override val data: DataLibrus,
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS)
onSuccess()
}

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.api.v2.POST
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
@ -55,19 +56,18 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () ->
val id = "/podglad/([0-9]+)'".toRegex().find(
elements[9].select("input").attr("onclick")
)?.get(1)?.toLong() ?: return@forEachIndexed
val startTime = data.lessonList.singleOrNull {
it.weekDay == eventDate.weekDay && it.subjectId == subjectId
}?.startTime
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
val moreInfo = graphElements[2 * i + 1].select("td[title]")
.attr("title").trim()
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim()
val notified = when (profile?.empty) {
val seen = when (profile?.empty) {
true -> true
false -> Date.getToday() < eventDate
else -> false
else -> eventDate < Date.getToday()
}
val eventObject = Event(
@ -89,13 +89,15 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () ->
profileId,
Metadata.TYPE_HOMEWORK,
id,
notified,
notified,
seen,
seen,
addedDate
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
// because this requires a synergia login (2 more requests) sync this every two hours or if explicit :D
data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 2 * HOUR, DRAWER_ITEM_HOMEWORK)
onSuccess()

View File

@ -101,7 +101,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
val loginElement = doc.createElement("login")
loginElement.appendChild(doc.createTextNode(data.apiLogin))
dataElement.appendChild(loginElement)
val passwordElement = doc.createElement("login")
val passwordElement = doc.createElement("password")
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
dataElement.appendChild(passwordElement)
val keyStrokeElement = doc.createElement("KeyStroke")
@ -150,6 +150,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
sessionId = sessionId?.replace("MAINT", "") // dunno what's this
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import androidx.core.util.contains
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
@ -74,5 +75,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
}
}
}

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import androidx.core.util.contains
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
@ -53,5 +54,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List<String>) {
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
}
}
}

View File

@ -283,6 +283,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
when (model) {
is DataRemoveModel.Timetable -> model.commit(profileId, db.timetableDao())
is DataRemoveModel.Grades -> model.commit(profileId, db.gradeDao())
is DataRemoveModel.Events -> model.commit(profileId, db.eventDao())
}
}
@ -305,7 +306,6 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.gradeDao().addAll(gradeList)
}
if (eventList.isNotEmpty()) {
db.eventDao().removeFuture(profile.id, Date.getToday())
db.eventDao().addAll(eventList)
}
if (noticeList.isNotEmpty()) {

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.data.db.modules.events.EventDao
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeDao
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao
import pl.szczodrzynski.edziennik.utils.models.Date
@ -15,21 +16,23 @@ open class DataRemoveModel {
fun to(dateTo: Date) = Timetable(null, dateTo)
fun between(dateFrom: Date, dateTo: Date) = Timetable(dateFrom, dateTo)
}
fun commit(profileId: Int, dao: TimetableDao) {
if (dateFrom != null && dateTo != null) {
dao.clearBetweenDates(profileId, dateFrom, dateTo)
}
else {
} else {
dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) }
dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) }
}
}
}
class Grades(val all: Boolean, val semester: Int?) : DataRemoveModel() {
class Grades(private val all: Boolean, private val semester: Int?) : DataRemoveModel() {
companion object {
fun all() = Grades(true, null)
fun semester(semester: Int) = Grades(false, semester)
}
fun commit(profileId: Int, dao: GradeDao) {
if (all) {
dao.clear(profileId)
@ -37,4 +40,16 @@ open class DataRemoveModel {
semester?.let { dao.clearForSemester(profileId, it) }
}
}
}
class Events(private val type: Int?, private val exceptType: Int?) : DataRemoveModel() {
companion object {
fun futureExceptType(exceptType: Int) = Events(null, exceptType)
fun futureWithType(type: Int) = Events(type, null)
}
fun commit(profileId: Int, dao: EventDao) {
type?.let { dao.removeFutureWithType(profileId, Date.getToday(), it) }
exceptType?.let { dao.removeFutureExceptType(profileId, Date.getToday(), it) }
}
}
}

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_EVENTS
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_EVENTS
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_HOMEWORK
@ -91,8 +92,14 @@ class VulcanApiEvents(override val data: DataVulcan, private val isHomework: Boo
}
when (isHomework) {
true -> data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS)
false -> data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS)
true -> {
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS)
}
false -> {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS)
}
}
onSuccess()
}

View File

@ -1,16 +1,17 @@
package pl.szczodrzynski.edziennik.data.db.modules.events;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
@ -130,6 +131,12 @@ public abstract class EventDao {
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate")
public abstract void removeFuture(int profileId, Date todayDate);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
public abstract void removeFutureWithType(int profileId, Date todayDate, int type);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
public abstract void removeFutureExceptType(int profileId, Date todayDate, int exceptType);
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = "+TYPE_EVENT+" OR thingType = "+TYPE_LESSON_CHANGE+" OR thingType = "+TYPE_HOMEWORK+") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
public abstract void setSeenByDate(int profileId, Date date, boolean seen);

View File

@ -6,10 +6,10 @@ import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
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_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.*
@ -74,6 +74,7 @@ class ProfileFull : Profile {
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)
@ -87,9 +88,7 @@ class ProfileFull : Profile {
return fragmentIds
}
constructor() : super() {
}
constructor() : super()
constructor(profile: Profile, loginStore: LoginStore) {
@ -122,7 +121,7 @@ class ProfileFull : Profile {
}*/
}
constructor(context: Context) : super(context) {}
constructor(context: Context) : super(context)
fun canChangeLoginPassword(): Boolean {
return loginStoreType == LOGIN_TYPE_MOBIDZIENNIK || loginStoreType == LOGIN_TYPE_LIBRUS || loginStoreType == LOGIN_TYPE_IUCZNIOWIE

View File

@ -59,6 +59,11 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
return startTime ?: oldStartTime
}
val isCancelled
get() = type == TYPE_CANCELLED || type == TYPE_SHIFTED_SOURCE
val isChange
get() = type == TYPE_CHANGE || type == TYPE_SHIFTED_TARGET
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
override fun toString(): String {
@ -110,7 +115,7 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
return true
}
override fun hashCode(): Int {
override fun hashCode(): Int { // intentionally ignoring ID and display* here
var result = profileId
result = 31 * result + type
result = 31 * result + (date?.hashCode() ?: 0)
@ -131,32 +136,4 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
result = 31 * result + (oldClassroom?.hashCode() ?: 0)
return result
}
}
/*
DROP TABLE lessons;
DROP TABLE lessonChanges;
CREATE TABLE lessons (
profileId INTEGER NOT NULL,
type INTEGER NOT NULL,
date TEXT DEFAULT NULL,
lessonNumber INTEGER DEFAULT NULL,
startTime TEXT DEFAULT NULL,
endTime TEXT DEFAULT NULL,
teacherId INTEGER DEFAULT NULL,
subjectId INTEGER DEFAULT NULL,
teamId INTEGER DEFAULT NULL,
classroom TEXT DEFAULT NULL,
oldDate TEXT DEFAULT NULL,
oldLessonNumber INTEGER DEFAULT NULL,
oldStartTime TEXT DEFAULT NULL,
oldEndTime TEXT DEFAULT NULL,
oldTeacherId INTEGER DEFAULT NULL,
oldSubjectId INTEGER DEFAULT NULL,
oldTeamId INTEGER DEFAULT NULL,
oldClassroom TEXT DEFAULT NULL,
PRIMARY KEY(profileId)
);
*/
}

View File

@ -52,6 +52,13 @@ interface TimetableDao {
""")
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
ORDER BY id, type
""")
fun getForDateNow(profileId: Int, date: Date) : List<LessonFull>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId

View File

@ -5,11 +5,15 @@
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
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.utils.sizeDp
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
@ -22,6 +26,8 @@ import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import pl.szczodrzynski.navlib.colorAttr
import kotlin.coroutines.CoroutineContext
class HomeTimetableCard(
@ -48,6 +54,17 @@ class HomeTimetableCard(
private var lessons = listOf<LessonFull>()
private var events = listOf<Event>()
private var bellSyncDiffMillis = 0L
private val syncedNow: Time
get() = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
private var counterJob: Job? = null
private var counterStart: Time? = null
private var counterEnd: Time? = null
private var subjectSpannable: CharSequence? = null
private val ignoreCancelled = true
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
holder.root.removeAllViews()
b = CardHomeTimetableBinding.inflate(LayoutInflater.from(holder.root.context))
@ -56,6 +73,17 @@ class HomeTimetableCard(
}
holder.root += b.root
b.settings.setImageDrawable(IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_settings_outline)
.colorAttr(activity, R.attr.colorIcon)
.sizeDp(20))
// get current bell-sync params
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get all lessons within the search bounds
app.db.timetableDao().getBetweenDates(today, searchEnd).observe(fragment, Observer {
allLessons = it
@ -65,36 +93,177 @@ class HomeTimetableCard(
private fun update() { launch {
val deferred = async(Dispatchers.Default) {
// get current bell-sync params
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get the current bell-synced time
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
val now = syncedNow
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.displayEndTime > now && it.type != Lesson.TYPE_NO_LESSONS }
lessons = allLessons.filter {
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)
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
lessons = allLessons.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.type != Lesson.TYPE_NO_LESSONS
&& !(it.isCancelled && ignoreCancelled)
}
checkedDays++
}
timetableDate
}
deferred.await()
val timetableDate = deferred.await()
val text = StringBuilder()
for (lesson in lessons) {
text += lesson.displayStartTime?.stringHM+" "+lesson.displaySubjectName+"\n"
val isToday = today == timetableDate
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
val now = syncedNow
val firstLesson = lessons.firstOrNull()
val lastLesson = lessons.lastOrNull()
if (isToday) {
// today
b.dayInfo.setText(R.string.home_timetable_today)
counterStart = firstLesson?.displayStartTime
counterEnd = firstLesson?.displayEndTime
val isOngoing = counterStart <= now && now <= counterEnd
val lessonRes = if (isOngoing)
R.string.home_timetable_lesson_ongoing
else
R.string.home_timetable_lesson_not_started
b.lessonBig.setText(lessonRes, firstLesson.subjectSpannable)
firstLesson?.displayClassroom?.let {
b.classroom.visibility = View.VISIBLE
b.classroom.text = it
} ?: run {
b.classroom.visibility = View.GONE
}
subjectSpannable = firstLesson.subjectSpannable
counterJob = startCoroutineTimer(repeatMillis = 1000) {
count()
}
}
b.text.text = text.toString()
else {
val isTomorrow = today.clone().stepForward(0, 0, 1) == timetableDate
val dayInfoRes = if (isTomorrow) {
// tomorrow
R.string.home_timetable_tomorrow
}
else {
val todayWeekStart = today.weekStart
val dateWeekStart = timetableDate.weekStart
if (todayWeekStart == dateWeekStart) {
// this week
R.string.home_timetable_date_this_week
}
else {
// future: not this week
R.string.home_timetable_date_future
}
}
b.dayInfo.setText(dayInfoRes, Week.getFullDayName(timetableDate.weekDay), timetableDate.formattedString)
b.lessonInfo.setText(
R.string.home_timetable_lessons_info,
lessons.size,
firstLesson?.displayStartTime?.stringHM ?: "?",
lastLesson?.displayEndTime?.stringHM ?: "?"
)
b.lessonBig.setText(R.string.home_timetable_lesson_first, firstLesson.subjectSpannable)
firstLesson?.displayClassroom?.let {
b.classroom.visibility = View.VISIBLE
b.classroom.text = it
} ?: run {
b.classroom.visibility = View.GONE
}
}
val text = mutableListOf<CharSequence>(
activity.getString(R.string.home_timetable_later)
)
var first = true
for (lesson in lessons) {
if (first) { first = false; continue }
text += listOf(
lesson.displayStartTime?.stringHM,
lesson.subjectSpannable
).concat(" ")
}
if (text.size == 1)
text += activity.getString(R.string.home_timetable_later_no_lessons)
b.nextLessons.text = text.concat("\n")
}}
private val LessonFull?.subjectSpannable: CharSequence
get() = if (this == null) "?" else when {
isCancelled -> displaySubjectName.asStrikethroughSpannable()
isChange -> displaySubjectName.asItalicSpannable()
else -> displaySubjectName ?: "?"
}
private fun count() {
val counterStart = counterStart
val counterEnd = counterEnd
if (counterStart == null || counterEnd == null) {
// there is no lesson to count
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
this.counterJob?.cancel()
return
}
val now = syncedNow
if (now > counterEnd) {
// the lesson is already over
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
this.counterJob?.cancel()
this.counterStart = null
this.counterEnd = null
update() // check for new lessons to display
return
}
val isOngoing = counterStart <= now && now <= counterEnd
val lessonRes = if (isOngoing)
R.string.home_timetable_lesson_ongoing
else
R.string.home_timetable_lesson_not_started
b.lessonBig.setText(lessonRes, subjectSpannable ?: "")
if (now < counterStart) {
// the lesson hasn't yet started
b.progress.visibility = View.GONE
b.counter.visibility = View.VISIBLE
val diff = counterStart - now
b.counter.text = activity.timeTill(diff.toInt(), "\n")
}
else {
// the lesson is right now
b.progress.visibility = View.VISIBLE
b.counter.visibility = View.VISIBLE
val lessonLength = counterEnd - counterStart
val timePassed = now - counterStart
val timeLeft = counterEnd - now
b.counter.text = activity.timeLeft(timeLeft.toInt(), "\n")
b.progress.max = lessonLength.toInt()
b.progress.progress = timePassed.toInt()
}
}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -157,8 +157,10 @@ public class PermissionChecker {
public Intent intentApkInstall() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return new Intent("android.settings.MANAGE_UNKNOWN_APP_SOURCES",
Intent intent = new Intent("android.settings.MANAGE_UNKNOWN_APP_SOURCES",
Uri.parse("package:" + mContext.getPackageName()));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
return null;
}

View File

@ -3,6 +3,8 @@ package pl.szczodrzynski.edziennik.utils.models;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.Calendar;
public class Time implements Comparable<Time> {
@ -114,6 +116,10 @@ public class Time implements Comparable<Time> {
return new Time(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
}
public long getInUnix() {
return getInMillis() / 1000;
}
public int getValue()
{
return hour * 10000 + minute * 100 + second;
@ -202,4 +208,8 @@ public class Time implements Comparable<Time> {
result = 31 * result + second;
return result;
}
public long minus(@NotNull Time other) {
return getInUnix() - other.getInUnix();
}
}

View File

@ -12,10 +12,112 @@
android:orientation="vertical"
tools:layout_margin="8dp">
<TextView
android:id="@+id/text"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
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" />
</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:max="2700"
android: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>
</layout>

View File

@ -8,4 +8,5 @@
<attr name="timetable_lesson_change_color" format="color" />
<attr name="timetable_lesson_shifted_source_color" format="color" />
<attr name="timetable_lesson_shifted_target_color" format="color" />
<attr name="colorIcon" format="color" />
</resources>

View File

@ -1044,4 +1044,14 @@
<string name="home_lucky_number_today">%d to dzisiejszy szczęśliwy numerek.</string>
<string name="home_lucky_number_tomorrow">%d to szczęśliwy numerek na jutro.</string>
<string name="home_lucky_number_no_number">Nie ma dzisiaj szczęśliwego numerka.</string>
<string name="home_timetable_tomorrow">Jutro (%1$s)</string>
<string name="home_timetable_date_this_week">%1$s, %2$s</string>
<string name="home_timetable_date_future">%1$s, %2$s</string>
<string name="home_timetable_lessons_info">%d lekcji - %s do %s</string>
<string name="home_timetable_lesson_first">Pierwsza: %s</string>
<string name="home_timetable_later">Później:</string>
<string name="home_timetable_later_no_lessons">brak lekcji</string>
<string name="home_timetable_today">Dzisiaj</string>
<string name="home_timetable_lesson_ongoing">Teraz: %s</string>
<string name="home_timetable_lesson_not_started">Za chwilę: %s</string>
</resources>

View File

@ -82,6 +82,7 @@
<item name="colorFab">#4CAF50</item>
<item name="colorFabIcon">#c8e6c9</item>
<item name="colorOnFab">#ffffff</item>
<item name="colorIcon">#8a000000</item>
<item name="md_dark_theme">false</item>
<item name="md_title_color">?android:textColorPrimary</item>
@ -114,6 +115,7 @@
<item name="colorFab">#4CAF50</item>
<item name="colorFabIcon">#c8e6c9</item>
<item name="colorOnFab">#ffffff</item>
<item name="colorIcon">#b4ffffff</item>
<item name="md_dark_theme">true</item>
<item name="md_title_color">?android:textColorPrimary</item>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.50'
release = [
versionName: "3.9.11-dev",
versionCode: 3091100
versionName: "3.9.12-dev",
versionCode: 3091200
]
setup = [