[Timetable] Implement Librus timetable with lesson changes and shifts. Update UI.

This commit is contained in:
Kuba Szczodrzyński 2019-11-10 22:57:19 +01:00
parent 5fa7409317
commit 4eeaa54a47
12 changed files with 231 additions and 39 deletions

View File

@ -390,5 +390,5 @@ fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
} }
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) { fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
text = context.getString(resid, formatArgs) text = context.getString(resid, *formatArgs)
} }

View File

@ -80,6 +80,7 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_timetable) data.startProgress(R.string.edziennik_progress_endpoint_timetable)
LibrusApiTimetables(data, onSuccess) LibrusApiTimetables(data, onSuccess)
} }
ENDPOINT_LIBRUS_API_NORMAL_GRADES -> { ENDPOINT_LIBRUS_API_NORMAL_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(data, onSuccess) LibrusApiGrades(data, onSuccess)

View File

@ -1,54 +1,37 @@
/* /*
* Copyright (c) Kacper Ziubryniewicz 2019-11-10 * Copyright (c) Kuba Szczodrzyński 2019-11-10.
*/ */
package pl.szczodrzynski.edziennik.api.v2.librus.data.api package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiTimetables(override val data: DataLibrus, class LibrusApiTimetables(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) { val onSuccess: () -> Unit) : LibrusApi(data) {
companion object { companion object {
const val TAG = "LibrusApiTimetables" const val TAG = "LibrusApiTimetables"
} }
init { init {
apiGet(TAG, "Timetables") { json -> apiGet(TAG, "Timetables") { json ->
json.getJsonObject("Timetable")?.also { timetables -> val days = json.getJsonObject("Timetable")
timetables.keySet().forEach { dateStr ->
val date = Date.fromY_m_d(dateStr)
timetables.getJsonArray(dateStr)?.asJsonObjectList()?.forEach { lesson -> days?.entrySet()?.forEach { (dateString, dayEl) ->
val lessonNumber = lesson.getInt("LessonNo") val lessonDate = dateString?.let { Date.fromY_m_d(it) } ?: return@forEach
val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } val day = dayEl?.asJsonArray
val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } day?.forEach { lessonRangeEl ->
val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id") val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
val subjectId = lesson.getJsonObject("Subject")?.getLong("Id") lessonRange?.forEachIndexed { index, lesson ->
val classId = lesson.getJsonObject("Class")?.getLong("Id") parseLesson(lessonDate, lesson)
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
val teamId = virtualClassId ?: classId
val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id")
val classroom = data.classrooms.singleOrNull { it.id == classroomId }?.name
val lessonObject = Lesson(profileId).apply {
this.date = date
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.teacherId = teacherId
this.subjectId = subjectId
this.teamId = teamId
this.classroom = classroom
}
// TODO add to the database
} }
} }
} }
@ -57,4 +40,126 @@ class LibrusApiTimetables(override val data: DataLibrus,
onSuccess() onSuccess()
} }
} }
private fun parseLesson(lessonDate: Date, lesson: JsonObject) {
val lessonObject = Lesson(profileId, lesson.hashCode().toLong())
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
val isCancelled = lesson.getBoolean("IsCanceled") ?: false
val lessonNo = lesson.getInt("LessonNo") ?: return
val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } ?: return
val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } ?: return
val subjectId = lesson.getJsonObject("Subject")?.getLong("Id")
val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id")
val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id") ?: -1
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId
if (isSubstitution && isCancelled) {
// shifted lesson - source
val newDate = lesson.getString("NewDate")?.let { Date.fromY_m_d(it) } ?: return
val newLessonNo = lesson.getInt("NewLessonNo") ?: return
val newStartTime = lesson.getString("NewHourFrom")?.let { Time.fromH_m(it) } ?: return
val newEndTime = lesson.getString("NewHourTo")?.let { Time.fromH_m(it) } ?: return
val newSubjectId = lesson.getJsonObject("NewSubject")?.getLong("Id")
val newTeacherId = lesson.getJsonObject("NewTeacher")?.getLong("Id")
val newClassroomId = lesson.getJsonObject("NewClassroom")?.getLong("Id") ?: -1
val newVirtualClassId = lesson.getJsonObject("NewVirtualClass")?.getLong("Id")
val newTeamId = lesson.getJsonObject("NewClass")?.getLong("Id") ?: newVirtualClassId
lessonObject.let {
it.type = Lesson.TYPE_SHIFTED_SOURCE
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
it.date = newDate
it.lessonNumber = newLessonNo
it.startTime = newStartTime
it.endTime = newEndTime
it.subjectId = newSubjectId
it.teacherId = newTeacherId
it.teamId = newTeamId
it.classroom = data.classrooms[newClassroomId]?.name
}
}
else if (isSubstitution) {
// lesson change OR shifted lesson - target
val oldDate = lesson.getString("OrgDate")?.let { Date.fromY_m_d(it) } ?: return
val oldLessonNo = lesson.getInt("OrgLessonNo") ?: return
val oldStartTime = lesson.getString("OrgHourFrom")?.let { Time.fromH_m(it) } ?: return
val oldEndTime = lesson.getString("OrgHourTo")?.let { Time.fromH_m(it) } ?: return
val oldSubjectId = lesson.getJsonObject("OrgSubject")?.getLong("Id")
val oldTeacherId = lesson.getJsonObject("OrgTeacher")?.getLong("Id")
val oldClassroomId = lesson.getJsonObject("OrgClassroom")?.getLong("Id") ?: -1
val oldVirtualClassId = lesson.getJsonObject("OrgVirtualClass")?.getLong("Id")
val oldTeamId = lesson.getJsonObject("OrgClass")?.getLong("Id") ?: oldVirtualClassId
lessonObject.let {
it.type = if (lessonDate == oldDate && lessonNo == oldLessonNo) Lesson.TYPE_CHANGE else Lesson.TYPE_SHIFTED_TARGET
it.oldDate = oldDate
it.oldLessonNumber = oldLessonNo
it.oldStartTime = oldStartTime
it.oldEndTime = oldEndTime
it.oldSubjectId = oldSubjectId
it.oldTeacherId = oldTeacherId
it.oldTeamId = oldTeamId
it.oldClassroom = data.classrooms[oldClassroomId]?.name
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
else if (isCancelled) {
lessonObject.let {
it.type = Lesson.TYPE_CANCELLED
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
}
}
else {
lessonObject.let {
it.type = Lesson.TYPE_NORMAL
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
if (lessonObject.type != Lesson.TYPE_NORMAL) {
data.metadataList.add(
Metadata(
data.profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
data.profile?.empty ?: false,
data.profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.lessonNewList += lessonObject
}
} }

View File

@ -17,6 +17,12 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
return oldDate return oldDate
return date ?: oldDate return date ?: oldDate
} }
val displayLessonNumber: Int?
get() {
if (type == TYPE_SHIFTED_SOURCE)
return oldLessonNumber
return lessonNumber ?: oldLessonNumber
}
val displayStartTime: Time? val displayStartTime: Time?
get() { get() {
if (type == TYPE_SHIFTED_SOURCE) if (type == TYPE_SHIFTED_SOURCE)

View File

@ -35,7 +35,8 @@ interface TimetableDao {
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR (type = 3 AND oldDate = :date) WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)
ORDER BY type
""") """)
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>> fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
} }

View File

@ -122,7 +122,13 @@ class TimetableDayFragment(val date: Date) : Fragment() {
}.concat(arrowRight) }.concat(arrowRight)
lb.subjectName.text = lesson.displaySubjectName?.let { if (lesson.type == Lesson.TYPE_CANCELLED) it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) else it } lb.lessonNumber = lesson.displayLessonNumber
lb.subjectName.text = lesson.displaySubjectName?.let {
if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
it.asStrikethroughSpannable().asColoredSpannable(colorSecondary)
else
it
}
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
@ -165,7 +171,55 @@ class TimetableDayFragment(val date: Date) : Fragment() {
} }
lb.annotation.background.colorFilter = PorterDuffColorFilter( lb.annotation.background.colorFilter = PorterDuffColorFilter(
getColorFromAttr(activity, R.attr.timetable_lesson_cancelled_color), getColorFromAttr(activity, R.attr.timetable_lesson_change_color),
PorterDuff.Mode.SRC_ATOP
)
}
Lesson.TYPE_SHIFTED_SOURCE -> {
lb.annotationVisible = true
if (lesson.date != lesson.oldDate) {
lb.annotation.setText(
R.string.timetable_lesson_shifted_other_day,
lesson.date?.stringY_m_d ?: "?",
lesson.startTime?.stringHM ?: "?"
)
}
else if (lesson.startTime != lesson.oldStartTime) {
lb.annotation.setText(
R.string.timetable_lesson_shifted_same_day,
lesson.startTime?.stringHM ?: "?"
)
}
else {
lb.annotation.setText(R.string.timetable_lesson_shifted)
}
lb.annotation.background.colorFilter = PorterDuffColorFilter(
getColorFromAttr(activity, R.attr.timetable_lesson_shifted_source_color),
PorterDuff.Mode.SRC_ATOP
)
}
Lesson.TYPE_SHIFTED_TARGET -> {
lb.annotationVisible = true
if (lesson.date != lesson.oldDate) {
lb.annotation.setText(
R.string.timetable_lesson_shifted_from_other_day,
lesson.oldDate?.stringY_m_d ?: "?",
lesson.oldStartTime?.stringHM ?: "?"
)
}
else if (lesson.startTime != lesson.oldStartTime) {
lb.annotation.setText(
R.string.timetable_lesson_shifted_from_same_day,
lesson.oldStartTime?.stringHM ?: "?"
)
}
else {
lb.annotation.setText(R.string.timetable_lesson_shifted_from)
}
lb.annotation.background.colorFilter = PorterDuffColorFilter(
getColorFromAttr(activity, R.attr.timetable_lesson_shifted_target_color),
PorterDuff.Mode.SRC_ATOP PorterDuff.Mode.SRC_ATOP
) )
} }

View File

@ -1,6 +1,7 @@
package pl.szczodrzynski.edziennik.utils.models; package pl.szczodrzynski.edziennik.utils.models;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Calendar; import java.util.Calendar;
@ -218,6 +219,11 @@ public class Date implements Comparable<Date> {
return this.getValue() - o.getValue(); return this.getValue() - o.getValue();
} }
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof Date && this.getValue() == ((Date) obj).getValue();
}
@Override @Override
public String toString() { public String toString() {
return "Date{" + return "Date{" +

View File

@ -1,5 +1,7 @@
package pl.szczodrzynski.edziennik.utils.models; package pl.szczodrzynski.edziennik.utils.models;
import androidx.annotation.Nullable;
import java.util.Calendar; import java.util.Calendar;
public class Time { public class Time {
@ -173,6 +175,11 @@ public class Time {
return (currentTime.getValue() >= startTime.getValue() && currentTime.getValue() <= endTime.getValue()); return (currentTime.getValue() >= startTime.getValue() && currentTime.getValue() <= endTime.getValue());
} }
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof Time && this.getValue() == ((Time) obj).getValue();
}
@Override @Override
public String toString() { public String toString() {
return "Time{" + return "Time{" +

View File

@ -8,6 +8,9 @@
<variable <variable
name="annotationVisible" name="annotationVisible"
type="boolean"/> type="boolean"/>
<variable
name="lessonNumber"
type="Integer" />
</data> </data>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -91,11 +94,13 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-condensed-light" android:fontFamily="sans-serif-condensed-light"
android:includeFontPadding="false" android:includeFontPadding="false"
android:layout_marginBottom="-4dp"
android:paddingStart="4dp" android:paddingStart="4dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:text="9" android:text="@{Integer.toString(lessonNumber)}"
tools:textSize="28sp"/> android:textSize="28sp"
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
tools:text="3"/>
<!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}" <!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}"
android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"--> android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"-->

View File

@ -6,5 +6,6 @@
<attr name="timetable_lesson_bg" format="reference" /> <attr name="timetable_lesson_bg" format="reference" />
<attr name="timetable_lesson_cancelled_color" format="color" /> <attr name="timetable_lesson_cancelled_color" format="color" />
<attr name="timetable_lesson_change_color" format="color" /> <attr name="timetable_lesson_change_color" format="color" />
<attr name="timetable_lesson_shifted_color" format="color" /> <attr name="timetable_lesson_shifted_source_color" format="color" />
<attr name="timetable_lesson_shifted_target_color" format="color" />
</resources> </resources>

View File

@ -991,4 +991,8 @@
<string name="timetable_lesson_change_format">Zastępstwo: zamiast %s</string> <string name="timetable_lesson_change_format">Zastępstwo: zamiast %s</string>
<string name="timetable_lesson_shifted_same_day">Lekcja przeniesiona na godz. %s</string> <string name="timetable_lesson_shifted_same_day">Lekcja przeniesiona na godz. %s</string>
<string name="timetable_lesson_shifted_other_day">Lekcja przeniesiona na %s, godz. %s</string> <string name="timetable_lesson_shifted_other_day">Lekcja przeniesiona na %s, godz. %s</string>
<string name="timetable_lesson_shifted_from_same_day">Lekcja przeniesiona z godz. %s</string>
<string name="timetable_lesson_shifted_from_other_day">Lekcja przeniesiona z dnia %s, godz. %s</string>
<string name="timetable_lesson_shifted">Lekcja przeniesiona na inny termin</string>
<string name="timetable_lesson_shifted_from">Lekcja przeniesiona z innego terminu</string>
</resources> </resources>

View File

@ -99,7 +99,8 @@
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_light</item> <item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_light</item>
<item name="timetable_lesson_cancelled_color">#9f9f9f</item> <item name="timetable_lesson_cancelled_color">#9f9f9f</item>
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_color">#4caf50</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item>
</style> </style>
<style name="AppTheme.Dark" parent="NavView.Dark"> <style name="AppTheme.Dark" parent="NavView.Dark">
<item name="colorPrimary">#64b5f6</item> <item name="colorPrimary">#64b5f6</item>
@ -128,7 +129,8 @@
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_dark</item> <item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_dark</item>
<item name="timetable_lesson_cancelled_color">#838383</item> <item name="timetable_lesson_cancelled_color">#838383</item>
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_color">#4caf50</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item>
</style> </style>