diff --git a/app/sampledata/settings/ic_settings.xml b/app/sampledata/settings/ic_settings.xml
new file mode 100644
index 00000000..efaaa4ee
--- /dev/null
+++ b/app/sampledata/settings/ic_settings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
index 761f0dd6..0334cde6 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
@@ -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 listOfNotEmpty(vararg elements: T): List = elements.filterNot { it.isEmpty() }
-fun List.concat(delimiter: String? = null): CharSequence {
+fun List.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.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.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>()
+
+ 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>()
+
+ 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) }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
index d032bb77..5cdac97e 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
@@ -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)
-);
-*/
+}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeTimetableCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeTimetableCard.kt
index 4255f9da..65fe6237 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeTimetableCard.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/home/cards/HomeTimetableCard.kt
@@ -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()
private var events = listOf()
+ 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(
+ 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
}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java
index 1f1d1089..b9b57b72 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Time.java
@@ -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