A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

add scroll to streaks calendar

razeeman (Jun 25, 2023, 8:19 PM +0300) ef606da2 a483c5fa

+91 -9
+2 -2
core/src/main/java/com/example/util/simpletimetracker/core/utils/TouchDetectors.kt
··· 127 127 startSliding() 128 128 initialDirection = Direction.LEFT 129 129 } 130 - onSlide(e2.rawY - e1.rawY, initialDirection, e2) 130 + onSlide(e2.rawX - e1.rawX, initialDirection, e2) 131 131 return true 132 132 } 133 133 ··· 147 147 startSliding() 148 148 initialDirection = Direction.RIGHT 149 149 } 150 - onSlide(e2.rawY - e1.rawY, initialDirection, e2) 150 + onSlide(e2.rawX - e1.rawX, initialDirection, e2) 151 151 return true 152 152 } 153 153
+89 -7
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/customView/SeriesCalendarView.kt
··· 1 1 package com.example.util.simpletimetracker.feature_statistics_detail.customView 2 2 3 + import android.annotation.SuppressLint 3 4 import android.content.Context 4 5 import android.graphics.Canvas 5 6 import android.graphics.Color 6 7 import android.graphics.Paint 8 + import android.os.Parcelable 7 9 import android.util.AttributeSet 10 + import android.view.MotionEvent 8 11 import android.view.View 9 12 import androidx.annotation.ColorInt 13 + import com.example.util.simpletimetracker.core.utils.SwipeDetector 14 + import com.example.util.simpletimetracker.core.utils.isHorizontal 15 + import com.example.util.simpletimetracker.domain.extension.orZero 10 16 import com.example.util.simpletimetracker.feature_views.extension.dpToPx 11 17 import kotlin.math.ceil 12 18 import kotlin.math.roundToInt 19 + import kotlinx.parcelize.Parcelize 13 20 14 21 class SeriesCalendarView @JvmOverloads constructor( 15 22 context: Context, ··· 28 35 private var cellSize: Float = 0f 29 36 private var cellColor: Int = Color.BLACK 30 37 private var data: List<ViewData> = emptyList() 38 + private var dataColumnCount: Int = 0 39 + private var panFactor: Float = 0f 40 + private var lastPanFactor: Float = 0f 41 + private var chartFullWidth: Float = 0f 31 42 32 43 private val cellPresentPaint: Paint = Paint() 33 44 private val cellNotPresentPaint: Paint = Paint() 34 45 46 + private val swipeDetector = SwipeDetector( 47 + context = context, 48 + onSlide = ::onSwipe, 49 + onSlideStop = ::onSwipeStop 50 + ) 51 + 35 52 init { 36 53 initPaint() 37 54 } 38 55 56 + override fun onSaveInstanceState(): Parcelable { 57 + val superState = super.onSaveInstanceState() 58 + return SavedState( 59 + superSavedState = superState, 60 + panFactor = panFactor, 61 + lastPanFactor = lastPanFactor, 62 + ) 63 + } 64 + 65 + override fun onRestoreInstanceState(state: Parcelable?) { 66 + val savedState = state as? SavedState 67 + super.onRestoreInstanceState(savedState?.superSavedState ?: state) 68 + panFactor = savedState?.panFactor.orZero() 69 + lastPanFactor = savedState?.lastPanFactor.orZero() 70 + } 71 + 39 72 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 40 73 val w = resolveSize(0, widthMeasureSpec) 41 74 cellSize = w / columnsCount.toFloat() ··· 48 81 override fun onDraw(canvas: Canvas?) { 49 82 if (canvas == null) return 50 83 51 - drawCells(canvas, width.toFloat(), height.toFloat()) 84 + val w = width.toFloat() 85 + val h = height.toFloat() 86 + 87 + calculateDimensions() 88 + drawCells(canvas, w, h) 89 + } 90 + 91 + @SuppressLint("ClickableViewAccessibility") 92 + override fun onTouchEvent(event: MotionEvent): Boolean { 93 + var handled = false 94 + 95 + when (event.action) { 96 + MotionEvent.ACTION_DOWN -> handled = true 97 + } 98 + 99 + return handled or swipeDetector.onTouchEvent(event) 52 100 } 53 101 54 102 fun setData(data: List<ViewData>) { 103 + if (this.data.size != data.size) { 104 + panFactor = 0f 105 + lastPanFactor = 0f 106 + } 55 107 this.data = data 56 108 invalidate() 57 109 } ··· 76 128 } 77 129 } 78 130 131 + private fun calculateDimensions() { 132 + dataColumnCount = ceil(data.size.toFloat() / rowsCount).toInt() 133 + chartFullWidth = dataColumnCount * cellSize 134 + } 135 + 79 136 private fun drawCells(canvas: Canvas, w: Float, h: Float) { 80 - val dataColumnCount = ceil(data.size.toFloat() / rowsCount).toInt() 81 - val finalWidth = if (dataColumnCount < columnsCount) { 82 - (w + dataColumnCount * cellSize) / 2 137 + val finalWidth = if (chartFullWidth < w) { 138 + (w + chartFullWidth) / 2 83 139 } else { 84 140 w 85 141 } 86 142 87 143 // Draw from bottom right corner so that current day would be in there. 88 144 canvas.save() 89 - canvas.translate(finalWidth, h) 145 + canvas.translate(finalWidth + panFactor, h) 90 146 91 147 data.forEachIndexed { index, point -> 92 148 if (point is ViewData.Dummy) return@forEachIndexed ··· 95 151 val row = index % rowsCount 96 152 canvas.save() 97 153 canvas.translate( 98 - - (column + 1) * cellSize, 99 - - (row + 1) * cellSize, 154 + -(column + 1) * cellSize, 155 + -(row + 1) * cellSize, 100 156 ) 101 157 canvas.drawRoundRect( 102 158 cellPadding, ··· 113 169 canvas.restore() 114 170 } 115 171 172 + @Suppress("UNUSED_PARAMETER") 173 + private fun onSwipe(offset: Float, direction: SwipeDetector.Direction, event: MotionEvent) { 174 + if (direction.isHorizontal()) { 175 + parent.requestDisallowInterceptTouchEvent(true) 176 + panFactor = lastPanFactor + offset 177 + coercePan() 178 + invalidate() 179 + } 180 + } 181 + 182 + private fun onSwipeStop() { 183 + parent.requestDisallowInterceptTouchEvent(false) 184 + lastPanFactor = panFactor 185 + } 186 + 187 + private fun coercePan() { 188 + panFactor = panFactor.coerceIn(0f, (chartFullWidth - width).coerceAtLeast(0f)) 189 + } 190 + 116 191 sealed interface ViewData { 117 192 object Present : ViewData 118 193 object NotPresent : ViewData 119 194 object Dummy : ViewData 120 195 } 196 + 197 + @Parcelize 198 + private class SavedState( 199 + val superSavedState: Parcelable?, 200 + val panFactor: Float, 201 + val lastPanFactor: Float, 202 + ) : BaseSavedState(superSavedState) 121 203 }