Skip to content

Commit 8560e7a

Browse files
committed
add selectable stats to data distribution
1 parent 9cf2367 commit 8560e7a

File tree

15 files changed

+251
-54
lines changed

15 files changed

+251
-54
lines changed

core/src/main/java/com/example/util/simpletimetracker/core/mapper/StatisticsViewDataMapper.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class StatisticsViewDataMapper @Inject constructor(
3030
isDarkTheme: Boolean,
3131
useProportionalMinutes: Boolean,
3232
showSeconds: Boolean,
33+
hasTransitions: Boolean
3334
): List<StatisticsViewData> {
3435
val statisticsFiltered = statistics.filterNot { it.id in filteredIds }
3536
val sumDuration = statisticsFiltered.sumOf { it.data.duration }
@@ -48,6 +49,7 @@ class StatisticsViewDataMapper @Inject constructor(
4849
statisticsSize = statisticsSize,
4950
useProportionalMinutes = useProportionalMinutes,
5051
showSeconds = showSeconds,
52+
hasTransitions = hasTransitions
5153
) ?: return@mapNotNull null
5254

5355
item to statistic.data.duration
@@ -67,13 +69,18 @@ class StatisticsViewDataMapper @Inject constructor(
6769
statisticsSize: Int,
6870
useProportionalMinutes: Boolean,
6971
showSeconds: Boolean,
72+
hasTransitions: Boolean
7073
): StatisticsViewData? {
7174
val durationPercent = statisticsMapper.getDurationPercentString(
7275
sumDuration = sumDuration,
7376
duration = statistics.data.duration,
7477
statisticsSize = statisticsSize,
7578
)
76-
val transitionName = "${TransitionNames.STATISTICS_DETAIL}_shift${shift}_id${statistics.id}"
79+
val transitionName = if (hasTransitions) {
80+
"${TransitionNames.STATISTICS_DETAIL}_shift${shift}_id${statistics.id}"
81+
} else {
82+
null
83+
}
7784
val duration = mapDuration(
7885
statistics = statistics,
7986
showDuration = showDuration,

features/feature_base_adapter/src/main/java/com/example/util/simpletimetracker/feature_base_adapter/statistics/StatisticsAdapterDelegate.kt

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.example.util.simpletimetracker.feature_base_adapter.statistics
22

33
import androidx.core.view.ViewCompat
44
import com.example.util.simpletimetracker.feature_base_adapter.createRecyclerBindingAdapterDelegate
5+
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsViewData
6+
import com.example.util.simpletimetracker.feature_views.StatisticsView
57
import com.example.util.simpletimetracker.feature_views.extension.setOnClick
68
import com.example.util.simpletimetracker.feature_base_adapter.databinding.ItemStatisticsLayoutBinding as Binding
79
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsViewData as ViewData
@@ -15,21 +17,30 @@ fun createStatisticsAdapterDelegate(
1517
with(binding.viewStatisticsItem) {
1618
item as ViewData
1719

18-
itemColor = item.color
19-
itemName = item.name
20-
itemDuration = item.duration
21-
itemPercent = item.percent
20+
bind(item, onItemClick)
21+
}
22+
}
2223

23-
if (item.icon != null) {
24-
itemIconVisible = true
25-
itemIcon = item.icon
26-
} else {
27-
itemIconVisible = false
28-
}
24+
fun StatisticsView.bind(
25+
item: StatisticsViewData,
26+
onItemClick: ((ViewData, Map<Any, String>) -> Unit)?,
27+
) {
28+
itemColor = item.color
29+
itemName = item.name
30+
itemDuration = item.duration
31+
itemPercent = item.percent
32+
33+
if (item.icon != null) {
34+
itemIconVisible = true
35+
itemIcon = item.icon
36+
} else {
37+
itemIconVisible = false
38+
}
2939

30-
if (onItemClick != null) {
31-
val transitionName = item.transitionName
32-
setOnClick { onItemClick(item, mapOf(this to transitionName)) }
40+
if (onItemClick != null) {
41+
val transitionName = item.transitionName
42+
setOnClick { onItemClick(item, mapOf(this to transitionName.orEmpty())) }
43+
if (!transitionName.isNullOrEmpty()) {
3344
ViewCompat.setTransitionName(this, transitionName)
3445
}
3546
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.util.simpletimetracker.feature_base_adapter.statistics
2+
3+
import androidx.core.view.isVisible
4+
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
5+
import com.example.util.simpletimetracker.feature_base_adapter.createRecyclerBindingAdapterDelegate
6+
import com.example.util.simpletimetracker.feature_base_adapter.databinding.ItemStatisticsSelectableLayoutBinding as Binding
7+
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsSelectableViewData as ViewData
8+
9+
fun createStatisticsSelectableAdapterDelegate(
10+
onItemClick: ((StatisticsViewData, Map<Any, String>) -> Unit)?,
11+
) = createRecyclerBindingAdapterDelegate<ViewData, Binding>(
12+
Binding::inflate,
13+
) { binding, item, _ ->
14+
15+
with(binding) {
16+
item as ViewData
17+
18+
viewStatisticsSelectableItemCheck.isVisible = item.isSelected
19+
viewStatisticsItem.bind(item.data, onItemClick)
20+
}
21+
}
22+
23+
data class StatisticsSelectableViewData(
24+
val data: StatisticsViewData,
25+
val isSelected: Boolean,
26+
) : ViewHolderType {
27+
28+
override fun getUniqueId(): Long = data.id
29+
30+
override fun isValidType(other: ViewHolderType): Boolean = other is ViewData
31+
}

features/feature_base_adapter/src/main/java/com/example/util/simpletimetracker/feature_base_adapter/statistics/StatisticsViewData.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ data class StatisticsViewData(
1111
val percent: String,
1212
@ColorInt val color: Int,
1313
val icon: RecordTypeIcon?,
14-
val transitionName: String,
14+
val transitionName: String?,
1515
) : ViewHolderType {
1616

1717
override fun getUniqueId(): Long = id
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content">
7+
8+
<androidx.cardview.widget.CardView
9+
android:id="@+id/viewStatisticsSelectableItemCheck"
10+
android:layout_width="24dp"
11+
android:layout_height="0dp"
12+
android:layout_marginStart="4dp"
13+
android:visibility="gone"
14+
app:cardBackgroundColor="?colorAccent"
15+
app:cardCornerRadius="@dimen/record_type_card_corner_radius"
16+
app:cardElevation="@dimen/record_type_card_elevation"
17+
app:cardPreventCornerOverlap="false"
18+
app:cardUseCompatPadding="true"
19+
app:layout_constraintBottom_toBottomOf="parent"
20+
app:layout_constraintStart_toStartOf="parent"
21+
app:layout_constraintTop_toTopOf="parent"
22+
tools:visibility="visible" />
23+
24+
<com.example.util.simpletimetracker.feature_views.StatisticsView
25+
android:id="@+id/viewStatisticsItem"
26+
android:layout_width="0dp"
27+
android:layout_height="wrap_content"
28+
android:layout_marginStart="-4dp"
29+
android:layout_marginEnd="4dp"
30+
app:cardElevation="@dimen/record_type_card_elevation"
31+
app:layout_constraintEnd_toEndOf="parent"
32+
app:layout_constraintStart_toEndOf="@id/viewStatisticsSelectableItemCheck"
33+
app:layout_constraintTop_toTopOf="parent"
34+
app:layout_goneMarginStart="4dp"
35+
tools:itemColor="@color/red_800"
36+
tools:itemDuration="5h 23m 3s"
37+
tools:itemIcon="@drawable/ic_sports_motorsports_24px"
38+
tools:itemName="Statistics"
39+
tools:itemPercent="13%" />
40+
41+
</androidx.constraintlayout.widget.ConstraintLayout>

features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/interactor/StatisticsViewDataInteractor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class StatisticsViewDataInteractor @Inject constructor(
112112
isDarkTheme = isDarkTheme,
113113
useProportionalMinutes = useProportionalMinutes,
114114
showSeconds = showSeconds,
115+
hasTransitions = true,
115116
)
116117
// Don't show goals in the future if there is no records there.
117118
val goalsList = if (

features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/viewModel/StatisticsViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class StatisticsViewModel @Inject constructor(
9898
sharedElements: Map<Any, String>,
9999
) = viewModelScope.launch {
100100
statisticsDetailNavigationInteractor.navigate(
101-
transitionName = item.transitionName,
101+
transitionName = item.transitionName.orEmpty(),
102102
filterType = prefsInteractor.getChartFilterType(),
103103
shift = getShift(),
104104
sharedElements = sharedElements,

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/adapter/StatisticsDetailBarChartAdapterDelegate.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import com.example.util.simpletimetracker.feature_views.extension.setMargins
1010
import com.example.util.simpletimetracker.feature_statistics_detail.adapter.StatisticsDetailBarChartViewData as ViewData
1111
import com.example.util.simpletimetracker.feature_statistics_detail.databinding.StatisticsDetailBarChartItemBinding as Binding
1212

13-
fun createStatisticsDetailBarChartAdapterDelegate() = createRecyclerBindingAdapterDelegate<ViewData, Binding>(
13+
fun createStatisticsDetailBarChartAdapterDelegate(
14+
onBarClick: (StatisticsDetailBlock, Long?) -> Unit,
15+
) = createRecyclerBindingAdapterDelegate<ViewData, Binding>(
1416
Binding::inflate,
1517
) { binding, item, _ ->
1618

@@ -23,6 +25,7 @@ fun createStatisticsDetailBarChartAdapterDelegate() = createRecyclerBindingAdapt
2325
showSelectedBarOnStart(viewData.showSelectedBarOnStart)
2426
setBars(
2527
data = viewData.data,
28+
selectedBarPosition = viewData.selectedBarPosition,
2629
animate = viewData.animate.getValue().orFalse(),
2730
)
2831
setLegendTextSuffix(viewData.legendSuffix)
@@ -31,6 +34,7 @@ fun createStatisticsDetailBarChartAdapterDelegate() = createRecyclerBindingAdapt
3134
setGoalValue(viewData.goalValue)
3235
setSingleColor(item.singleColor.takeIf { viewData.useSingleColor })
3336
setDrawRoundCaps(viewData.drawRoundCaps)
37+
setOnBarClickListener { onBarClick(item.block, it) }
3438
}
3539
}
3640

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/customView/BarChartView.kt

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import kotlin.math.ceil
2828
import kotlin.math.floor
2929
import kotlin.math.max
3030
import kotlin.math.min
31+
import androidx.core.content.withStyledAttributes
3132

3233
class BarChartView @JvmOverloads constructor(
3334
context: Context,
@@ -82,7 +83,7 @@ class BarChartView @JvmOverloads constructor(
8283
private var longestTextWidth: Float = 0f
8384
private var legendLinesPixelStep: Float = 0f
8485
private var horizontalLegendsSkipCount: Int = 1
85-
private var selectedBar: Int = -1 // -1 nothing is selected
86+
private var selectedBarPosition: Int? = null
8687
private val selectedBarTextPadding: Int = 6.dpToPx()
8788
private val selectedBarBackgroundPadding: Int = 4.dpToPx()
8889
private val selectedBarBackgroundRadius: Float = 4.dpToPx().toFloat()
@@ -92,6 +93,7 @@ class BarChartView @JvmOverloads constructor(
9293
private var selectedBarWasShownOnStart: Boolean = false
9394
private var singleColor: Int? = null
9495
private var drawRoundCaps: Boolean = true
96+
private var onBarClickListener: ((Long?) -> Unit)? = null
9597

9698
private val barPaint: Paint = Paint()
9799
private val selectedBarPaint: Paint = Paint()
@@ -167,10 +169,17 @@ class BarChartView @JvmOverloads constructor(
167169

168170
fun setBars(
169171
data: List<ViewData>,
172+
selectedBarPosition: Int?,
170173
animate: Boolean,
171174
) {
172-
bars = data.takeUnless { it.isEmpty() }
173-
?: listOf(ViewData(listOf(0f to Color.BLACK), "", ""))
175+
bars = data.takeUnless { it.isEmpty() } ?: listOf(
176+
ViewData(
177+
id = 0,
178+
value = listOf(0f to Color.BLACK),
179+
legend = "",
180+
selectedBarLegend = "",
181+
)
182+
)
174183
maxPositiveValue = data
175184
.map { barData -> barData.value.map { it.first }.sum() }
176185
.filter { it >= 0f }
@@ -180,10 +189,10 @@ class BarChartView @JvmOverloads constructor(
180189
.filter { it < 0f }
181190
.minOrNull() ?: 0f
182191
if (data.isNotEmpty() && showSelectedBarOnStart && !selectedBarWasShownOnStart) {
183-
selectedBar = bars.size - 1
192+
this.selectedBarPosition = bars.size - 1
184193
selectedBarWasShownOnStart = true
185194
} else {
186-
selectedBar = -1
195+
this.selectedBarPosition = selectedBarPosition
187196
}
188197
invalidate()
189198
if (!isInEditMode && animate) animateBars()
@@ -223,17 +232,20 @@ class BarChartView @JvmOverloads constructor(
223232
invalidate()
224233
}
225234

235+
fun setOnBarClickListener(listener: (Long?) -> Unit) {
236+
onBarClickListener = listener
237+
}
238+
226239
private fun initArgs(
227240
context: Context,
228241
attrs: AttributeSet? = null,
229242
defStyleAttr: Int = 0,
230243
) {
231244
context
232-
.obtainStyledAttributes(
245+
.withStyledAttributes(
233246
attrs,
234247
R.styleable.BarChartView, defStyleAttr, 0,
235-
)
236-
.run {
248+
) {
237249
barCountInEdit =
238250
getInt(R.styleable.BarChartView_barCount, 0)
239251
barDividerMaxWidth =
@@ -262,7 +274,6 @@ class BarChartView @JvmOverloads constructor(
262274
getBoolean(R.styleable.BarChartView_shouldDrawHorizontalLegends, true)
263275
goalValue =
264276
getFloat(R.styleable.BarChartView_goalValue, 0f)
265-
recycle()
266277
}
267278
}
268279

@@ -438,6 +449,7 @@ class BarChartView @JvmOverloads constructor(
438449
}
439450
}
440451

452+
@SuppressLint("UseKtx")
441453
private fun drawBars(canvas: Canvas) {
442454
var prevColor = 0
443455
radiusArr = floatArrayOf(
@@ -521,7 +533,7 @@ class BarChartView @JvmOverloads constructor(
521533
}
522534

523535
// Draw selected bar
524-
if (index == selectedBar) {
536+
if (index == selectedBarPosition) {
525537
bounds.set(
526538
0f + barDividerWidth / 2,
527539
pixelTopBound,
@@ -570,6 +582,7 @@ class BarChartView @JvmOverloads constructor(
570582
}
571583

572584
private fun drawSelectedBarIcon(canvas: Canvas) {
585+
val selectedBar = selectedBarPosition ?: return
573586
val bar = bars.getOrNull(selectedBar) ?: return
574587
val barValue = bar.value.map { it.first }.sum()
575588
val isOnPositiveChart = barValue >= 0f || valueDownerBound == 0L
@@ -680,6 +693,7 @@ class BarChartView @JvmOverloads constructor(
680693
(segments downTo 1).toList()
681694
.map {
682695
ViewData(
696+
id = it.toLong(),
683697
value = listOf(it.toFloat() to Color.BLACK),
684698
legend = it.toString(),
685699
selectedBarLegend = it.toString(),
@@ -688,10 +702,10 @@ class BarChartView @JvmOverloads constructor(
688702
.let {
689703
setBars(
690704
data = it,
705+
selectedBarPosition = barCountInEdit / 2,
691706
animate = false,
692707
)
693708
}
694-
selectedBar = barCountInEdit / 2
695709
singleColor = Color.BLACK
696710
}
697711
}
@@ -704,17 +718,20 @@ class BarChartView @JvmOverloads constructor(
704718
bars.getOrNull(clickedAroundBar)?.let {
705719
if (y > pixelTopBound && y < pixelBottomBound) {
706720
// If clicked on the same bar - clear selection
707-
selectedBar = if (isClick && selectedBar == clickedAroundBar) {
708-
-1
721+
selectedBarPosition = if (isClick && selectedBarPosition == clickedAroundBar) {
722+
onBarClickListener?.invoke(null)
723+
null
709724
} else {
725+
onBarClickListener?.invoke(it.id)
710726
clickedAroundBar
711727
}
712728
invalidate()
713729
return
714730
}
715731
}
716732

717-
selectedBar = -1
733+
selectedBarPosition = null
734+
onBarClickListener?.invoke(null)
718735
invalidate()
719736
}
720737

@@ -754,6 +771,7 @@ class BarChartView @JvmOverloads constructor(
754771
}
755772

756773
data class ViewData(
774+
val id: Long,
757775
val value: List<Pair<Float, Int>>,
758776
val legend: String,
759777
val selectedBarLegend: String = legend,

0 commit comments

Comments
 (0)