diff --git a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt index 211aebe1..8a10a125 100644 --- a/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt +++ b/core/src/main/java/com/alamkanak/weekview/HeaderRenderer.kt @@ -11,14 +11,19 @@ import android.text.TextPaint import android.util.SparseArray import androidx.collection.ArrayMap import androidx.core.content.ContextCompat -import java.util.Calendar +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.temporal.ChronoUnit +import java.util.* import kotlin.math.roundToInt internal class HeaderRenderer( context: Context, viewState: ViewState, eventChipsCacheProvider: EventChipsCacheProvider, - onHeaderHeightChanged: () -> Unit + onHeaderHeightChanged: () -> Unit, + private var beginWeekCalendar: Calendar? = null ) : Renderer, DateFormatterDependent { private val allDayEventLabels = ArrayMap() @@ -48,7 +53,8 @@ internal class HeaderRenderer( private val headerDrawer = HeaderDrawer( context = context, - viewState = viewState + viewState = viewState, + beginWeekCalendar = this.beginWeekCalendar ) override fun onSizeChanged(width: Int, height: Int) { @@ -139,43 +145,20 @@ private class DateLabelsDrawer( ) : Drawer { override fun draw(canvas: Canvas) { - if (viewState.numberOfVisibleDays > 1) { - canvas.drawDateLabelInMultiDayView() - } else { - canvas.drawDateLabelInSingleDayView() - } - } - - private fun Canvas.drawDateLabelInSingleDayView() { - val bounds = viewState.weekNumberBounds - val date = viewState.dateRange.first() - - val key = date.toEpochDays() - val textLayout = dateLabelLayouts[key] - - withTranslation( - x = bounds.centerX(), - y = viewState.headerPadding, - ) { - draw(textLayout) - } - } - - private fun Canvas.drawDateLabelInMultiDayView() { - drawInBounds(viewState.headerBounds) { + canvas.drawInBounds(viewState.headerBounds) { viewState.dateRangeWithStartPixels.forEach { (date, startPixel) -> drawLabel(date, startPixel) } } } - private fun Canvas.drawLabel(date: Calendar, startPixel: Float) { - val key = date.toEpochDays() + private fun Canvas.drawLabel(day: Calendar, startPixel: Float) { + val key = day.toEpochDays() val textLayout = dateLabelLayouts[key] withTranslation( x = startPixel + viewState.dayWidth / 2f, - y = viewState.headerPadding, + y = viewState.headerPadding ) { draw(textLayout) } @@ -341,7 +324,8 @@ internal class AllDayEventsDrawer( private class HeaderDrawer( context: Context, - private val viewState: ViewState + private val viewState: ViewState, + private val beginWeekCalendar: Calendar? ) : Drawer { private val upArrow: Drawable by lazy { @@ -363,12 +347,8 @@ private class HeaderDrawer( canvas.drawRect(0f, 0f, width, viewState.headerHeight, backgroundPaint) - if (viewState.showWeekNumber && viewState.numberOfVisibleDays > 1) { - canvas.drawWeekNumber() - } - - if (viewState.showTimeColumnSeparator) { - canvas.drawTimeColumnSeparatorExtension() + if (viewState.showWeekNumber) { + canvas.drawWeekNumber(beginWeekCalendar = this.beginWeekCalendar) } if (viewState.showAllDayEventsToggleArrow) { @@ -381,8 +361,40 @@ private class HeaderDrawer( } } - private fun Canvas.drawWeekNumber() { - val weekNumber = viewState.dateRange.first().weekOfYear.toString() + private fun Canvas.drawWeekNumber(beginWeekCalendar : Calendar ? = null) { + + var weekNumber = viewState.dateRange.first().weekOfYear.toString() + // 自己适配周数 + beginWeekCalendar?.apply { + var now = viewState.dateRange.first().copy() + now.apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + var begin2 = Calendar.getInstance() + begin2.apply { + set(Calendar.YEAR, now.get(Calendar.YEAR)) + set(Calendar.DAY_OF_MONTH, beginWeekCalendar.get(Calendar.DAY_OF_MONTH)) + set(Calendar.MONTH, beginWeekCalendar.get(Calendar.MONTH)) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + add(Calendar.DAY_OF_YEAR, (get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY) * -1) + } + if (begin2.compareTo(now) > 0) { + begin2.apply { + set(Calendar.YEAR, get(Calendar.YEAR) - 1) + } + } + + val between_days: Long = (now.timeInMillis - begin2.timeInMillis) / (1000 * 3600 * 24) + var i: Int = (between_days / 7).toInt() + 1 + weekNumber = i.toString() + } val bounds = viewState.weekNumberBounds val textPaint = viewState.weekNumberTextPaint @@ -409,18 +421,6 @@ private class HeaderDrawer( drawText(weekNumber, bounds.centerX(), bounds.centerY() + textOffset, textPaint) } - private fun Canvas.drawTimeColumnSeparatorExtension() { - val startX = if (viewState.isLtr) { - viewState.timeColumnWidth - viewState.timeColumnSeparatorPaint.strokeWidth / 2 - } else { - viewState.viewWidth - viewState.timeColumnWidth - } - - val startY = viewState.headerPadding - val stopY = viewState.headerHeight - - drawLine(startX, startY, startX, stopY, viewState.timeColumnSeparatorPaint) - } private fun Canvas.drawAllDayEventsToggleArrow() = with(viewState) { val bottom = (headerHeight - headerPadding).roundToInt() diff --git a/core/src/main/java/com/alamkanak/weekview/WeekView.kt b/core/src/main/java/com/alamkanak/weekview/WeekView.kt index 8104bf7e..9027356c 100644 --- a/core/src/main/java/com/alamkanak/weekview/WeekView.kt +++ b/core/src/main/java/com/alamkanak/weekview/WeekView.kt @@ -14,7 +14,7 @@ import android.view.accessibility.AccessibilityManager import androidx.annotation.RequiresApi import androidx.core.view.ViewCompat import java.util.Calendar -import kotlin.math.abs +import kotlin.math.ceil import kotlin.math.min import kotlin.math.roundToInt @@ -27,8 +27,14 @@ class WeekView @JvmOverloads constructor( private val viewState: ViewState by lazy { ViewStateFactory.create(context, attrs) } + @PublicApi + var beginWeekCalendar: Calendar?= null + get() = field + set(value) { + field = value + invalidate() + } - private val eventsCacheProvider: EventsCacheProvider = { adapter?.eventsCache } private val eventChipsCacheProvider: EventChipsCacheProvider = { adapter?.eventChipsCache } private val touchHandler = WeekViewTouchHandler(viewState) @@ -42,15 +48,10 @@ class WeekView @JvmOverloads constructor( notifyRangeChangedListener() } - override fun onVerticalScrollPositionChanged(distance: Float) { - notifyVerticalScrollChanged(distance) + override fun onVerticalScrollPositionChanged() { invalidate() } - override fun onVerticalScrollingFinished() { - notifyVerticalScrollFinished() - } - override fun requestInvalidation() { ViewCompat.postInvalidateOnAnimation(this@WeekView) } @@ -58,21 +59,11 @@ class WeekView @JvmOverloads constructor( private val navigator = Navigator(viewState = viewState, listener = navigationListener) - private val dragHandler = DragHandler( - viewState = viewState, - touchHandler = touchHandler, - navigator = navigator, - dragListener = { id -> adapter?.handleDragAndDrop(id) }, - eventsCacheProvider = eventsCacheProvider, - eventsProcessorProvider = { adapter?.eventsProcessor }, - ) - private val gestureHandler = WeekViewGestureHandler( context = context, viewState = viewState, touchHandler = touchHandler, - navigator = navigator, - dragHandler = dragHandler, + navigator = navigator ) private var accessibilityTouchHelper = WeekViewAccessibilityTouchHelper( @@ -85,7 +76,7 @@ class WeekView @JvmOverloads constructor( private val renderers: List = listOf( TimeColumnRenderer(viewState), CalendarRenderer(viewState, eventChipsCacheProvider), - HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate) + HeaderRenderer(context, viewState, eventChipsCacheProvider, onHeaderHeightChanged = this::invalidate, beginWeekCalendar = this.beginWeekCalendar) ) // We use width and height instead of view.isLaidOut(), because the latter seems to @@ -106,7 +97,7 @@ class WeekView @JvmOverloads constructor( ViewCompat.setAccessibilityDelegate(this, accessibilityTouchHelper) } - setLayerType(LAYER_TYPE_HARDWARE, null) + setLayerType(LAYER_TYPE_SOFTWARE, null) } override fun onConfigurationChanged(newConfig: Configuration) { @@ -195,7 +186,7 @@ class WeekView @JvmOverloads constructor( val didFirstVisibleDateChange = !currentFirstVisibleDate.isSameDate(newFirstVisibleDate) viewState.firstVisibleDate = newFirstVisibleDate - if (didFirstVisibleDateChange) { + if (didFirstVisibleDateChange && navigator.isNotRunning) { val newLastVisibleDate = newDateRange.last() adapter?.onRangeChanged( firstVisibleDate = newFirstVisibleDate, @@ -204,19 +195,6 @@ class WeekView @JvmOverloads constructor( } } - private fun notifyVerticalScrollChanged(distance: Float) { - if (abs(distance) >= 1f) { - adapter?.onVerticalScrollPositionChanged( - currentOffset = verticalScrollOffset, - distance = distance - ) - } - } - - private fun notifyVerticalScrollFinished() { - adapter?.onVerticalScrollFinished(currentOffset = verticalScrollOffset) - } - /* *********************************************************************************************** * @@ -248,11 +226,6 @@ class WeekView @JvmOverloads constructor( * Returns whether the first day of the week should be displayed at the start position * when WeekView is rendered for the first time. */ - @Deprecated( - message = "Use stickToWeekInWeekView instead.", - replaceWith = ReplaceWith(expression = "stickToWeekInWeekView"), - level = DeprecationLevel.ERROR, - ) @PublicApi var showFirstDayOfWeekFirst: Boolean get() = viewState.showFirstDayOfWeekFirst @@ -260,19 +233,6 @@ class WeekView @JvmOverloads constructor( viewState.showFirstDayOfWeekFirst = value } - /** - * Returns whether weeks should always be scrolled as a whole when showing a week view, meaning - * that the [firstVisibleDate] will actually be the first date of the currently displayed week - * (as per [Calendar.getFirstDayOfWeek]). If not, any day of the week can be the - * [firstVisibleDate]. - */ - @PublicApi - var stickToActualWeek: Boolean - get() = viewState.stickToActualWeek - set(value) { - viewState.stickToActualWeek = value - } - /** * Returns whether all-day events are arranged vertically. If false, all-day events are shown * in a horizontal arrangement, occupying only a single row. @@ -1143,6 +1103,35 @@ class WeekView @JvmOverloads constructor( *********************************************************************************************** */ + /** + * Returns the scrolling speed factor in horizontal direction. + */ + @PublicApi + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + var xScrollingSpeed: Float + get() = viewState.xScrollingSpeed + set(value) { + viewState.xScrollingSpeed = value + } + + /** + * Returns whether WeekView can fling horizontally. + */ + @PublicApi + @Deprecated( + message = "Use isHorizontalScrollingEnabled instead.", + replaceWith = ReplaceWith("isHorizontalScrollingEnabled"), + level = DeprecationLevel.ERROR + ) + var isHorizontalFlingEnabled: Boolean + get() = viewState.horizontalFlingEnabled + set(value) { + viewState.horizontalFlingEnabled = value + } + /** * Returns whether WeekView can scroll horizontally. */ @@ -1153,6 +1142,31 @@ class WeekView @JvmOverloads constructor( viewState.horizontalScrollingEnabled = value } + /** + * Returns whether WeekView can fling vertically. + */ + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + @PublicApi + var isVerticalFlingEnabled: Boolean + get() = viewState.verticalFlingEnabled + set(value) { + viewState.verticalFlingEnabled = value + } + + @PublicApi + @Deprecated( + message = "This value is no longer being taken into account.", + level = DeprecationLevel.ERROR + ) + var scrollDuration: Int + get() = viewState.scrollDuration + set(value) { + viewState.scrollDuration = value + } + @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean = gestureHandler.onTouchEvent(event) @@ -1242,6 +1256,51 @@ class WeekView @JvmOverloads constructor( navigator.scrollVerticallyTo(offset = finalOffset) } + /** + * Scrolls to the current date. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDate() instead.", + replaceWith = ReplaceWith(expression = "scrollToDate"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToToday() { + scrollToDate(today()) + } + + /** + * Scrolls to the current date and time. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDateTime() instead.", + replaceWith = ReplaceWith(expression = "scrollToDateTime"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToCurrentTime() { + internalScrollToDate( + date = now(), + onComplete = { scrollToTime(hour = it.hour, minute = it.minute) } + ) + } + + /** + * Scrolls to a specific date. If the date is before [minDate] or after [maxDate], [WeekView] + * will scroll to them instead. + * + * @param date The date to show. + */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToDate() instead.", + replaceWith = ReplaceWith(expression = "scrollToDate"), + level = DeprecationLevel.ERROR + ) + @PublicApi + fun goToDate(date: Calendar) { + scrollToDate(date) + } + private fun internalScrollToDate(date: Calendar, onComplete: (Calendar) -> Unit = {}) { val adjustedDate = viewState.getStartDateInAllowedRange(date) if (adjustedDate.toEpochDays() == viewState.firstVisibleDate.toEpochDays()) { @@ -1258,50 +1317,38 @@ class WeekView @JvmOverloads constructor( return } - navigator.scrollHorizontallyTo(date = adjustedDate) { - onComplete(adjustedDate) - } + navigator.scrollHorizontallyTo(date = date, onFinished = { onComplete(adjustedDate) }) } /** - * Returns the first hour that is visible on the screen. - */ - @PublicApi - val firstVisibleHour: Int - get() = viewState.firstVisibleHour - - /** - * Returns the first hour that is fully visible on the screen. - */ - @PublicApi - val firstFullyVisibleHour: Int - get() = viewState.firstFullyVisibleHour - - /** - * Returns the last hour that is visible on the screen. + * Scrolls to a specific hour. If the hour is before [minHour] or after [maxHour], [WeekView] + * will scroll to them instead. + * + * @param hour The hour to scroll to, in 24-hour format. Supported values are 0-24. */ + @Deprecated( + message = "This method will be removed in a future release. Use scrollToTime() instead.", + replaceWith = ReplaceWith(expression = "scrollToTime"), + level = DeprecationLevel.ERROR + ) @PublicApi - val lastVisibleHour: Int - get() = viewState.lastVisibleHour + fun goToHour(hour: Int) { + scrollToTime(hour = hour, minute = 0) + } /** - * Returns the last hour that is fully visible on the screen. + * Returns the first hour that is visible on the screen. */ @PublicApi - val lastFullyVisibleHour: Int - get() = viewState.lastFullyVisibleHour + val firstVisibleHour: Int + get() = viewState.minHour + (viewState.currentOrigin.y * -1 / viewState.hourHeight).toInt() /** - * Returns the current vertical offset (in pixels) from the top. This is 0 if WeekView is - * scrolled up all the way to [minHour]. + * Returns the first hour that is fully visible on the screen. */ @PublicApi - val verticalScrollOffset: Float - get() { - // Invert the current origin, as it feels more natural - // to have a positive verticalScrollOffset. - return viewState.currentOrigin.y * -1 - } + val firstFullyVisibleHour: Int + get() = viewState.minHour + ceil(viewState.currentOrigin.y * -1 / viewState.hourHeight).toInt() /* *********************************************************************************************** @@ -1345,6 +1392,22 @@ class WeekView @JvmOverloads constructor( invalidate() } + @PublicApi + @Deprecated( + message = "Use setDateFormatter() and setTimeFormatter() instead.", + level = DeprecationLevel.ERROR + ) + var dateTimeInterpreter: DateTimeInterpreter + get() = object : DateTimeInterpreter { + override fun interpretDate(date: Calendar): String = viewState.dateFormatter(date) + override fun interpretTime(hour: Int): String = viewState.timeFormatter(hour) + } + set(value) { + setDateFormatter { value.interpretDate(it) } + setTimeFormatter { value.interpretTime(it) } + invalidate() + } + @PublicApi fun setDateFormatter(formatter: DateFormatter) { viewState.dateFormatter = formatter @@ -1404,16 +1467,22 @@ class WeekView @JvmOverloads constructor( internal fun handleClick(x: Float, y: Float): Boolean { val eventChip = findHitEvent(x, y) ?: return false - val data = findEventData(id = eventChip.eventId) ?: return false + val data = findEventData(id = eventChip.originalEvent.id) ?: return false + + onEventClick(data) onEventClick(data, eventChip.bounds) + return true } - internal fun handleLongClick(x: Float, y: Float): LongClickResult? { - val eventChip = findHitEvent(x, y) ?: return null - val data = findEventData(id = eventChip.eventId) ?: return null - val handled = onEventLongClick(data, eventChip.bounds) - return LongClickResult(eventChip = eventChip, handled = handled) + internal fun handleLongClick(x: Float, y: Float): Boolean { + val eventChip = findHitEvent(x, y) ?: return false + val data = findEventData(id = eventChip.originalEvent.id) ?: return false + + onEventLongClick(data) + onEventLongClick(data, eventChip.bounds) + + return true } private fun findHitEvent(x: Float, y: Float): EventChip? { @@ -1437,11 +1506,13 @@ class WeekView @JvmOverloads constructor( internal fun onEventClick(id: Long, bounds: RectF) { val data = findEventData(id) ?: return + onEventClick(data) onEventClick(data, bounds) } - internal fun handleLongClick(id: Long, bounds: RectF) { + internal fun onEventLongClick(id: Long, bounds: RectF) { val data = findEventData(id) ?: return + onEventLongClick(data) onEventLongClick(data, bounds) } @@ -1459,7 +1530,11 @@ class WeekView @JvmOverloads constructor( * @param item The item of type [T] that was submitted to [WeekView] * @return A [WeekViewEntity] that will be rendered in [WeekView] */ - abstract fun onCreateEntity(item: T): WeekViewEntity + open fun onCreateEntity(item: T): WeekViewEntity { + throw RuntimeException( + "You called submitList() on WeekView's adapter, but didn't implement onCreateEntity()." + ) + } /** * Returns the data of the [WeekViewEntity.Event] that the user clicked on. @@ -1490,31 +1565,8 @@ class WeekView @JvmOverloads constructor( * * @param data The data of the [WeekViewEntity.Event] * @param bounds The [RectF] representing the bounds of the event's [EventChip] - * @return Whether the long click has been handled. If false, this will trigger drag-&-drop - * to activate. */ - open fun onEventLongClick(data: T, bounds: RectF): Boolean = false - - internal fun handleDragAndDrop(id: Long) { - val data = findEventData(id) ?: return - val match = eventsCache[id] ?: return - - onDragAndDropFinished( - data = data, - newStartTime = match.startTime, - newEndTime = match.endTime, - ) - } - - /** - * Called when a drag-&-drop gesture has finished to inform the caller of the dragged event's - * new start and end time. - * - * @param data The [T] entity that is associated with the dragged event - * @param newStartTime The new start time that the event was dragged to - * @param newEndTime THe new end time that the event was dragged to - */ - open fun onDragAndDropFinished(data: T, newStartTime: Calendar, newEndTime: Calendar) = Unit + open fun onEventLongClick(data: T, bounds: RectF) = Unit /** * Returns the date and time of the location that the user clicked on. @@ -1539,23 +1591,6 @@ class WeekView @JvmOverloads constructor( * @param lastVisibleDate A [Calendar] representing the last visible date */ open fun onRangeChanged(firstVisibleDate: Calendar, lastVisibleDate: Calendar) = Unit - - /** - * Called whenever the vertical scroll position in [WeekView] changes. A [distance] > 0 - * indicates that the user is scrolling down towards later hours; a [distance] < 0 that the - * user is scrolling up towards earlier hours. - * - * @param currentOffset The current vertical offset. - * @param distance The distance that the user scrolled vertically. - */ - open fun onVerticalScrollPositionChanged(currentOffset: Float, distance: Float) = Unit - - /** - * Called when the vertical scrolling in [WeekView] finished. - * - * @param currentOffset The current vertical offset. - */ - open fun onVerticalScrollFinished(currentOffset: Float) = Unit } /** @@ -1572,6 +1607,25 @@ class WeekView @JvmOverloads constructor( override val eventsCache = SimpleEventsCache() + /** + * Submits a new list of [WeekViewDisplayable] elements to the adapter. These events are + * processed on a background thread and then presented in [WeekView]. Previously submitted + * events are replaced completely. + * + * @param events The [WeekViewDisplayable] elements that are to be displayed in [WeekView] + */ + @PublicApi + @Deprecated( + message = "Use submitList() to submit a list of elements of type T instead. Then, overwrite the adapter's onCreateEntity() method to create a WeekViewEntity.", + replaceWith = ReplaceWith(expression = "submitList"), + level = DeprecationLevel.ERROR + ) + fun submit(events: List>) { + val viewState = weekView?.viewState ?: return + val entities = events.map { it.toWeekViewEntity(context) } + eventsProcessor.submit(entities, viewState, onFinished = this::updateObserver) + } + /** * Submits a new list of elements to the adapter. These events are processed on a background * thread and then presented in [WeekView]. Previously submitted events are replaced @@ -1605,6 +1659,24 @@ class WeekView @JvmOverloads constructor( override val eventsCache = PaginatedEventsCache() + /** + * Submits a new list of [WeekViewDisplayable] elements to the adapter. These events are + * processed on a background thread and then presented in [WeekView]. + * + * @param events The [WeekViewDisplayable] elements that are to be displayed in [WeekView] + */ + @PublicApi + @Deprecated( + message = "Use submitList() to submit a list of elements of type T instead. Then, overwrite the adapter's onCreateEntity() method to create a WeekViewEntity.", + replaceWith = ReplaceWith(expression = "submitList"), + level = DeprecationLevel.ERROR + ) + fun submit(events: List>) { + val viewState = weekView?.viewState ?: return + val entities = events.map { it.toWeekViewEntity(context) } + eventsProcessor.submit(entities, viewState, onFinished = this::updateObserver) + } + /** * Submits a new list of elements of type [T] to the adapter. These events are processed on * a background thread and then presented in [WeekView].