diff --git a/app/src/main/java/de/tobiasschuerg/weekview/sample/EventCreator.kt b/app/src/main/java/de/tobiasschuerg/weekview/sample/EventCreator.kt index 3dbba05..17fe1f6 100644 --- a/app/src/main/java/de/tobiasschuerg/weekview/sample/EventCreator.kt +++ b/app/src/main/java/de/tobiasschuerg/weekview/sample/EventCreator.kt @@ -3,6 +3,7 @@ package de.tobiasschuerg.weekview.sample import android.graphics.Color import de.tobiasschuerg.weekview.data.Event import de.tobiasschuerg.weekview.data.WeekData +import de.tobiasschuerg.weekview.util.TimeSpan import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate import org.threeten.bp.LocalTime @@ -52,8 +53,7 @@ object EventCreator { title = name, shortTitle = name, subTitle = subTitle, - startTime = startTime, - endTime = endTime, + timeSpan = TimeSpan(startTime, endTime), textColor = Color.WHITE, backgroundColor = randomColor() ) diff --git a/app/src/main/java/de/tobiasschuerg/weekview/sample/SampleActivity.kt b/app/src/main/java/de/tobiasschuerg/weekview/sample/SampleActivity.kt index e1cc25a..94ebdac 100644 --- a/app/src/main/java/de/tobiasschuerg/weekview/sample/SampleActivity.kt +++ b/app/src/main/java/de/tobiasschuerg/weekview/sample/SampleActivity.kt @@ -12,8 +12,10 @@ import androidx.appcompat.app.AppCompatActivity import com.jakewharton.threetenabp.AndroidThreeTen import de.tobiasschuerg.weekview.data.Event import de.tobiasschuerg.weekview.data.EventConfig +import de.tobiasschuerg.weekview.util.TimeSpan import de.tobiasschuerg.weekview.view.EventView import de.tobiasschuerg.weekview.view.WeekView +import org.threeten.bp.Duration import org.threeten.bp.LocalDate import org.threeten.bp.LocalTime import org.threeten.bp.temporal.ChronoUnit @@ -38,8 +40,7 @@ class SampleActivity : AppCompatActivity() { date = LocalDate.now(), title = "Current hour", shortTitle = "Now", - startTime = LocalTime.now().truncatedTo(ChronoUnit.HOURS), - endTime = LocalTime.now().truncatedTo(ChronoUnit.HOURS).plusMinutes(59), + timeSpan = TimeSpan.of(LocalTime.now().truncatedTo(ChronoUnit.HOURS), Duration.ofHours(1)), backgroundColor = Color.RED, textColor = Color.WHITE ) diff --git a/library/src/main/java/de/tobiasschuerg/weekview/data/Event.kt b/library/src/main/java/de/tobiasschuerg/weekview/data/Event.kt index 8760eab..04ea840 100644 --- a/library/src/main/java/de/tobiasschuerg/weekview/data/Event.kt +++ b/library/src/main/java/de/tobiasschuerg/weekview/data/Event.kt @@ -1,8 +1,8 @@ package de.tobiasschuerg.weekview.data +import de.tobiasschuerg.weekview.util.TimeSpan import org.threeten.bp.Duration import org.threeten.bp.LocalDate -import org.threeten.bp.LocalTime sealed class Event { @@ -18,8 +18,7 @@ sealed class Event { override val shortTitle: String, val subTitle: String? = null, - val startTime: LocalTime, - val endTime: LocalTime, + val timeSpan: TimeSpan, val upperText: String? = null, val lowerText: String? = null, @@ -27,7 +26,7 @@ sealed class Event { val textColor: Int, val backgroundColor: Int ) : Event() { - val duration: Duration = Duration.between(startTime, endTime) + val duration: Duration = timeSpan.duration } data class AllDay( diff --git a/library/src/main/java/de/tobiasschuerg/weekview/data/WeekData.kt b/library/src/main/java/de/tobiasschuerg/weekview/data/WeekData.kt index b1dae79..08c09b5 100644 --- a/library/src/main/java/de/tobiasschuerg/weekview/data/WeekData.kt +++ b/library/src/main/java/de/tobiasschuerg/weekview/data/WeekData.kt @@ -1,16 +1,22 @@ package de.tobiasschuerg.weekview.data +import de.tobiasschuerg.weekview.util.TimeSpan import org.threeten.bp.LocalTime class WeekData { private val singleEvents: MutableList = mutableListOf() - private val allDays: MutableList = mutableListOf() - - var earliestStart: LocalTime = LocalTime.MAX - - var latestEnd: LocalTime = LocalTime.MIN + private var earliestStart: LocalTime? = null + private var latestEnd: LocalTime? = null + + fun getTimeSpan(): TimeSpan? { + return if (earliestStart != null && latestEnd != null) { + TimeSpan(earliestStart!!, latestEnd!!) + } else { + null + } + } fun add(item: Event.AllDay) { allDays.add(item) @@ -19,12 +25,12 @@ class WeekData { fun add(item: Event.Single) { singleEvents.add(item) - if (item.startTime.isBefore(earliestStart)) { - earliestStart = item.startTime + if (earliestStart == null || item.timeSpan.start.isBefore(earliestStart)) { + earliestStart = item.timeSpan.start } - if (item.endTime.isAfter(latestEnd)) { - latestEnd = item.endTime + if (latestEnd == null || item.timeSpan.endExclusive.isAfter(latestEnd)) { + latestEnd = item.timeSpan.endExclusive } } diff --git a/library/src/main/java/de/tobiasschuerg/weekview/util/TimeSpan.kt b/library/src/main/java/de/tobiasschuerg/weekview/util/TimeSpan.kt new file mode 100644 index 0000000..d861dbb --- /dev/null +++ b/library/src/main/java/de/tobiasschuerg/weekview/util/TimeSpan.kt @@ -0,0 +1,27 @@ +package de.tobiasschuerg.weekview.util + +import org.threeten.bp.Duration +import org.threeten.bp.LocalTime + +/** + * Holds a duration of time. + */ +data class TimeSpan( + val start: LocalTime, + val endExclusive: LocalTime +) { + + init { + require(start.isBefore(endExclusive)) { + "Start time $start must be before end time $endExclusive!" + } + } + + val duration: Duration by lazy { Duration.between(start, endExclusive) } + + companion object { + fun of(start: LocalTime, duration: Duration): TimeSpan { + return TimeSpan(start, start.plus(duration)) + } + } +} diff --git a/library/src/main/java/de/tobiasschuerg/weekview/view/EventView.kt b/library/src/main/java/de/tobiasschuerg/weekview/view/EventView.kt index ae36955..8f27479 100644 --- a/library/src/main/java/de/tobiasschuerg/weekview/view/EventView.kt +++ b/library/src/main/java/de/tobiasschuerg/weekview/view/EventView.kt @@ -100,14 +100,14 @@ class EventView( // start time if (config.showTimeStart) { - val startText = event.startTime.toLocalString() + val startText = event.timeSpan.start.toLocalString() textPaint.getTextBounds(startText, 0, startText.length, textBounds) canvas.drawText(startText, (textBounds.left + paddingLeft).toFloat(), (textBounds.height() + paddingTop).toFloat(), textPaint) } // end time if (config.showTimeEnd) { - val endText = event.endTime.toLocalString() + val endText = event.timeSpan.endExclusive.toLocalString() textPaint.getTextBounds(endText, 0, endText.length, textBounds) canvas.drawText(endText, (width - (textBounds.right + paddingRight)).toFloat(), (height - paddingBottom).toFloat(), textPaint) } diff --git a/library/src/main/java/de/tobiasschuerg/weekview/view/WeekBackgroundView.kt b/library/src/main/java/de/tobiasschuerg/weekview/view/WeekBackgroundView.kt index 815f552..47aa6f0 100644 --- a/library/src/main/java/de/tobiasschuerg/weekview/view/WeekBackgroundView.kt +++ b/library/src/main/java/de/tobiasschuerg/weekview/view/WeekBackgroundView.kt @@ -8,6 +8,7 @@ import android.graphics.Rect import android.util.Log import android.view.View import de.tobiasschuerg.weekview.util.DayOfWeekUtil +import de.tobiasschuerg.weekview.util.TimeSpan import de.tobiasschuerg.weekview.util.dipToPixelF import de.tobiasschuerg.weekview.util.dipToPixelI import de.tobiasschuerg.weekview.util.toLocalString @@ -113,7 +114,13 @@ internal class WeekBackgroundView constructor(context: Context) : View(context) // final String timeString = localTime.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)); val timeString = localTime.toLocalString() - drawMultiLineText(this, timeString, context.dipToPixelF(25f), y + context.dipToPixelF(20f), mPaintLabels) + drawMultiLineText( + this, + timeString, + context.dipToPixelF(25f), + y + context.dipToPixelF(20f), + mPaintLabels + ) last = localTime localTime = localTime.plusHours(1) @@ -151,10 +158,21 @@ internal class WeekBackgroundView constructor(context: Context) : View(context) private fun Canvas.drawWeekDayName(day: DayOfWeek, column: Int) { val shortName = day.getDisplayName(TextStyle.SHORT, Locale.getDefault()) val xLabel = (getColumnStart(column, false) + getColumnEnd(column, false)) / 2 - drawText(shortName, xLabel.toFloat(), topOffsetPx / 2 + mPaintLabels.descent(), mPaintLabels) - } - - private fun drawMultiLineText(canvas: Canvas, text: String, initialX: Float, initialY: Float, paint: Paint) { + drawText( + shortName, + xLabel.toFloat(), + topOffsetPx / 2 + mPaintLabels.descent(), + mPaintLabels + ) + } + + private fun drawMultiLineText( + canvas: Canvas, + text: String, + initialX: Float, + initialY: Float, + paint: Paint + ) { var currentY = initialY text.split(" ") .dropLastWhile(String::isEmpty) @@ -190,8 +208,10 @@ internal class WeekBackgroundView constructor(context: Context) : View(context) } override fun onMeasure(widthMeasureSpec: Int, hms: Int) { - val height = topOffsetPx + context.dipToPixelF(getDurationMinutes() * scalingFactor) + paddingBottom - val heightMeasureSpec2 = MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY) + val height = + topOffsetPx + context.dipToPixelF(getDurationMinutes() * scalingFactor) + paddingBottom + val heightMeasureSpec2 = + MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY) super.onMeasure(widthMeasureSpec, heightMeasureSpec2) } @@ -199,18 +219,15 @@ internal class WeekBackgroundView constructor(context: Context) : View(context) isInScreenshotMode = screenshotMode } - fun updateTimes(startTime: LocalTime, endTime: LocalTime) { - if (startTime.isAfter(endTime)) { - throw IllegalArgumentException("Start time $startTime must be before end time $endTime") - } + fun updateTimes(timeSpan: TimeSpan) { var timesHaveChanged = false - if (startTime.isBefore(this.startTime)) { - this.startTime = startTime.truncatedTo(ChronoUnit.HOURS) + if (timeSpan.start.isBefore(startTime)) { + startTime = timeSpan.start.truncatedTo(ChronoUnit.HOURS) timesHaveChanged = true } - if (endTime.isAfter(this.endTime)) { - if (endTime.isBefore(LocalTime.of(23, 0))) { - this.endTime = endTime.truncatedTo(ChronoUnit.HOURS).plusHours(1) + if (timeSpan.endExclusive.isAfter(endTime)) { + if (timeSpan.endExclusive.isBefore(LocalTime.of(23, 0))) { + this.endTime = timeSpan.endExclusive.truncatedTo(ChronoUnit.HOURS).plusHours(1) } else { this.endTime = LocalTime.MAX } diff --git a/library/src/main/java/de/tobiasschuerg/weekview/view/WeekView.kt b/library/src/main/java/de/tobiasschuerg/weekview/view/WeekView.kt index 258de91..5e4aa70 100644 --- a/library/src/main/java/de/tobiasschuerg/weekview/view/WeekView.kt +++ b/library/src/main/java/de/tobiasschuerg/weekview/view/WeekView.kt @@ -123,7 +123,9 @@ class WeekView(context: Context, attributeSet: AttributeSet) : fun addEvents(weekData: WeekData) { Log.d(TAG, "Adding ${weekData.getSingleEvents().size} weekData to week view") - backgroundView.updateTimes(weekData.earliestStart, weekData.latestEnd) + weekData.getTimeSpan()?.let { + backgroundView.updateTimes(it) + } for (event in weekData.getSingleEvents()) { addEvent(event) @@ -158,12 +160,12 @@ class WeekView(context: Context, attributeSet: AttributeSet) : } val lv = EventView(context, event, eventConfig, weekViewConfig.scalingFactor) - backgroundView.updateTimes(event.startTime, event.endTime) + backgroundView.updateTimes(event.timeSpan) // mark active event val now = LocalTime.now() if (LocalDate.now().dayOfWeek == event.date.dayOfWeek && // this day - event.startTime < now && event.endTime > now + event.timeSpan.start < now && event.timeSpan.endExclusive > now ) { lv.animation = Animation.createBlinkAnimation() } @@ -246,7 +248,7 @@ class WeekView(context: Context, attributeSet: AttributeSet) : eventView.scalingFactor = weekViewConfig.scalingFactor val startTime = backgroundView.startTime - val lessonStart = eventView.event.startTime + val lessonStart = eventView.event.timeSpan.start val offset = Duration.between(startTime, lessonStart) val yOffset = offset.toMinutes() * weekViewConfig.scalingFactor @@ -262,16 +264,16 @@ class WeekView(context: Context, attributeSet: AttributeSet) : } private fun overlaps(left: EventView, right: EventView): Boolean { - val rightStartsAfterLeftStarts = right.event.startTime >= left.event.startTime - val rightStartsBeforeLeftEnds = right.event.startTime < left.event.endTime + val rightStartsAfterLeftStarts = right.event.timeSpan.start >= left.event.timeSpan.start + val rightStartsBeforeLeftEnds = right.event.timeSpan.start < left.event.timeSpan.endExclusive val lessonStartsWithing = rightStartsAfterLeftStarts && rightStartsBeforeLeftEnds - val leftStartsBeforeRightEnds = left.event.startTime < right.event.endTime - val rightEndsBeforeOrWithLeftEnds = right.event.endTime <= left.event.endTime + val leftStartsBeforeRightEnds = left.event.timeSpan.start < right.event.timeSpan.endExclusive + val rightEndsBeforeOrWithLeftEnds = right.event.timeSpan.endExclusive <= left.event.timeSpan.endExclusive val lessonEndsWithing = leftStartsBeforeRightEnds && rightEndsBeforeOrWithLeftEnds - val leftStartsAfterRightStarts = left.event.startTime > right.event.startTime - val rightEndsAfterLeftEnds = right.event.endTime > left.event.endTime + val leftStartsAfterRightStarts = left.event.timeSpan.start > right.event.timeSpan.start + val rightEndsAfterLeftEnds = right.event.timeSpan.start > left.event.timeSpan.endExclusive val lessonWithin = leftStartsAfterRightStarts && rightEndsAfterLeftEnds return lessonStartsWithing || lessonEndsWithing || lessonWithin diff --git a/library/src/test/java/de/tobiasschuerg/weekview/util/TimeSpanTest.kt b/library/src/test/java/de/tobiasschuerg/weekview/util/TimeSpanTest.kt new file mode 100644 index 0000000..82edf4a --- /dev/null +++ b/library/src/test/java/de/tobiasschuerg/weekview/util/TimeSpanTest.kt @@ -0,0 +1,30 @@ +package de.tobiasschuerg.weekview.util + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.threeten.bp.Duration +import org.threeten.bp.LocalTime + +/** + * Created by Tobias Schrg on 11.02.2022. + */ +class TimeSpanTest { + + @Test + fun testDuration() { + val timeSpan = TimeSpan( + start = LocalTime.of(10, 15), + endExclusive = LocalTime.of(10, 45) + ) + assertEquals(Duration.ofMinutes(30), timeSpan.duration) + } + + @Test + fun testDuration2() { + val timeSpan = TimeSpan.of( + start = LocalTime.of(10, 15), + duration = Duration.ofMinutes(30) + ) + assertEquals(Duration.ofMinutes(30), timeSpan.duration) + } +}