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 streaks calendar click

razeeman (Jul 1, 2023, 3:39 PM +0300) ac0a2c7d 8494671c

+119 -32
+94 -30
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/customView/SeriesCalendarView.kt
··· 5 5 import android.graphics.Canvas 6 6 import android.graphics.Color 7 7 import android.graphics.Paint 8 + import android.graphics.Rect 8 9 import android.os.Parcelable 9 10 import android.util.AttributeSet 10 11 import android.view.MotionEvent 11 12 import android.view.View 12 13 import androidx.annotation.ColorInt 14 + import com.example.util.simpletimetracker.core.utils.SingleTapDetector 13 15 import com.example.util.simpletimetracker.core.utils.SwipeDetector 14 16 import com.example.util.simpletimetracker.core.utils.isHorizontal 15 17 import com.example.util.simpletimetracker.domain.extension.orZero 18 + import com.example.util.simpletimetracker.domain.model.Coordinates 16 19 import com.example.util.simpletimetracker.feature_views.extension.dpToPx 17 20 import kotlin.math.ceil 18 21 import kotlin.math.roundToInt ··· 34 37 private val cellRadius: Float = 4f.dpToPx().toFloat() 35 38 private var cellSize: Float = 0f 36 39 private var cellColor: Int = Color.BLACK 37 - private var data: List<ViewData> = emptyList() 40 + private var data: List<Data> = emptyList() 38 41 private var dataColumnCount: Int = 0 39 42 private var panFactor: Float = 0f 40 43 private var lastPanFactor: Float = 0f 41 44 private var chartFullWidth: Float = 0f 45 + private var listener: (ViewData, Coordinates) -> Unit = { _, _ -> } 42 46 43 47 private val cellPresentPaint: Paint = Paint() 44 48 private val cellNotPresentPaint: Paint = Paint() 45 49 50 + private val singleTapDetector = SingleTapDetector( 51 + context = context, 52 + onSingleTap = ::onClick, 53 + ) 46 54 private val swipeDetector = SwipeDetector( 47 55 context = context, 48 56 onSlide = ::onSwipe, 49 - onSlideStop = ::onSwipeStop 57 + onSlideStop = ::onSwipeStop, 50 58 ) 51 59 52 60 init { ··· 96 104 MotionEvent.ACTION_DOWN -> handled = true 97 105 } 98 106 99 - return handled or swipeDetector.onTouchEvent(event) 107 + return handled or 108 + singleTapDetector.onTouchEvent(event) or 109 + swipeDetector.onTouchEvent(event) 100 110 } 101 111 102 - fun setData(data: List<ViewData>) { 112 + fun setClickListener(listener: (ViewData, Coordinates) -> Unit) { 113 + this.listener = listener 114 + } 115 + 116 + fun setData(viewData: List<ViewData>) { 103 117 if (this.data.size != data.size) { 104 118 panFactor = 0f 105 119 lastPanFactor = 0f 106 120 } 107 - this.data = data 121 + this.data = viewData.map { Data(cell = it) } 108 122 invalidate() 109 123 } 110 124 ··· 134 148 } 135 149 136 150 private fun drawCells(canvas: Canvas, w: Float, h: Float) { 151 + var boxLeft: Float 152 + var boxRight: Float 153 + var boxTop: Float 154 + var boxBottom: Float 155 + 156 + // If chart width is less than screen - center in screen. 137 157 val finalWidth = if (chartFullWidth < w) { 138 158 (w + chartFullWidth) / 2 139 159 } else { 140 160 w 141 161 } 142 162 143 - // Draw from bottom right corner so that current day would be in there. 144 - canvas.save() 145 - canvas.translate(finalWidth + panFactor, h) 146 - 147 163 data.forEachIndexed { index, point -> 148 - if (point is ViewData.Dummy) return@forEachIndexed 164 + if (point.cell is ViewData.Dummy) return@forEachIndexed 149 165 150 166 val column = index / rowsCount 151 167 val row = index % rowsCount 152 - canvas.save() 153 - canvas.translate( 154 - -(column + 1) * cellSize, 155 - -(row + 1) * cellSize, 156 - ) 157 - canvas.drawRoundRect( 158 - cellPadding, 159 - cellPadding, 160 - cellSize - cellPadding, 161 - cellSize - cellPadding, 162 - cellRadius, 163 - cellRadius, 164 - if (point is ViewData.Present) cellPresentPaint else cellNotPresentPaint, 165 - ) 166 - canvas.restore() 167 - } 168 168 169 - canvas.restore() 169 + // Draw from bottom right corner so that current day would be in there. 170 + boxLeft = finalWidth + panFactor - (column + 1) * cellSize 171 + boxTop = h - (row + 1) * cellSize 172 + boxRight = boxLeft + cellSize 173 + boxBottom = boxTop + cellSize 174 + 175 + // Save coordinates for click event. 176 + point.boxLeft = boxLeft 177 + point.boxTop = boxTop 178 + point.boxRight = boxRight 179 + point.boxBottom = boxBottom 180 + 181 + canvas.drawRoundRect( 182 + boxLeft + cellPadding, 183 + boxTop + cellPadding, 184 + boxRight - cellPadding, 185 + boxBottom - cellPadding, 186 + cellRadius, 187 + cellRadius, 188 + if (point.cell is ViewData.Present) { 189 + cellPresentPaint 190 + } else { 191 + cellNotPresentPaint 192 + }, 193 + ) 194 + } 170 195 } 171 196 172 197 @Suppress("UNUSED_PARAMETER") ··· 179 204 } 180 205 } 181 206 207 + private fun onClick(event: MotionEvent) { 208 + val x = event.x 209 + val y = event.y 210 + 211 + data.firstOrNull { 212 + it.boxLeft < x && it.boxTop < y && it.boxRight > x && it.boxBottom > y 213 + }?.let { 214 + val globalRect = Rect() 215 + getGlobalVisibleRect(globalRect) 216 + listener( 217 + it.cell, 218 + Coordinates( 219 + left = globalRect.left + it.boxLeft.toInt(), 220 + top = globalRect.top + it.boxTop.toInt(), 221 + right = globalRect.left + it.boxRight.toInt(), 222 + bottom = globalRect.top + it.boxBottom.toInt(), 223 + ) 224 + ) 225 + } 226 + } 227 + 182 228 private fun onSwipeStop() { 183 229 parent.requestDisallowInterceptTouchEvent(false) 184 230 lastPanFactor = panFactor ··· 188 234 panFactor = panFactor.coerceIn(0f, (chartFullWidth - width).coerceAtLeast(0f)) 189 235 } 190 236 237 + private inner class Data( 238 + val cell: ViewData, 239 + var boxLeft: Float = 0f, 240 + var boxTop: Float = 0f, 241 + var boxRight: Float = 0f, 242 + var boxBottom: Float = 0f, 243 + ) 244 + 191 245 sealed interface ViewData { 192 - object Present : ViewData 193 - object NotPresent : ViewData 194 - object Dummy : ViewData 246 + val rangeStart: Long 247 + 248 + data class Present( 249 + override val rangeStart: Long, 250 + ) : ViewData 251 + 252 + data class NotPresent( 253 + override val rangeStart: Long, 254 + ) : ViewData 255 + 256 + object Dummy : ViewData { 257 + override val rangeStart: Long = 0L // Not needed for dummy view. 258 + } 195 259 } 196 260 197 261 @Parcelize
+2 -2
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailStreaksInteractor.kt
··· 410 410 return dummyDays + data 411 411 .map { 412 412 if (it.second > 0) { 413 - SeriesCalendarView.ViewData.Present 413 + SeriesCalendarView.ViewData.Present(it.first) 414 414 } else { 415 - SeriesCalendarView.ViewData.NotPresent 415 + SeriesCalendarView.ViewData.NotPresent(it.first) 416 416 } 417 417 } 418 418 .reversed()
+2
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/view/StatisticsDetailFragment.kt
··· 102 102 buttonsStatisticsDetailSplitGrouping.listener = viewModel::onSplitChartGroupingClick 103 103 cardStatisticsDetailRecords.listener = throttle(viewModel::onCardClick) 104 104 cardStatisticsDetailAverage.listener = throttle(viewModel::onCardClick) 105 + chartStatisticsStreaksCalendar.setClickListener(throttle(viewModel::onStreaksCalendarClick)) 106 + chartStatisticsComparisonStreaksCalendar.setClickListener(throttle(viewModel::onComparisonStreaksCalendarClick)) 105 107 spinnerStatisticsDetail.onItemSelected = viewModel::onRangeSelected 106 108 btnStatisticsDetailPrevious.setOnClick(viewModel::onPreviousClick) 107 109 btnStatisticsDetailNext.setOnClick(viewModel::onNextClick)
+21
features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/StatisticsDetailViewModel.kt
··· 24 24 import com.example.util.simpletimetracker.domain.model.RecordsFilter 25 25 import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 26 26 import com.example.util.simpletimetracker.feature_statistics_detail.R 27 + import com.example.util.simpletimetracker.feature_statistics_detail.customView.SeriesCalendarView 27 28 import com.example.util.simpletimetracker.feature_statistics_detail.interactor.StatisticsDetailChartInteractor 28 29 import com.example.util.simpletimetracker.feature_statistics_detail.interactor.StatisticsDetailPreviewInteractor 29 30 import com.example.util.simpletimetracker.feature_statistics_detail.interactor.StatisticsDetailSplitChartInteractor ··· 297 298 fun onCustomRangeSelected(range: Range) { 298 299 rangeLength = RangeLength.Custom(range) 299 300 onRangeChanged() 301 + } 302 + 303 + fun onStreaksCalendarClick( 304 + viewData: SeriesCalendarView.ViewData, 305 + coordinates: Coordinates, 306 + ) { 307 + PopupParams( 308 + timeMapper.formatDateYear(viewData.rangeStart), 309 + coordinates, 310 + ).let(router::show) 311 + } 312 + 313 + fun onComparisonStreaksCalendarClick( 314 + viewData: SeriesCalendarView.ViewData, 315 + coordinates: Coordinates, 316 + ) { 317 + PopupParams( 318 + timeMapper.formatDateYear(viewData.rangeStart), 319 + coordinates, 320 + ).let(router::show) 300 321 } 301 322 302 323 private fun onRecordsClick() {