Skip to content

Commit a2ea4ef

Browse files
committed
add tag value calculation mode to detailed statistics
1 parent acba353 commit a2ea4ef

File tree

13 files changed

+149
-41
lines changed

13 files changed

+149
-41
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum class StatisticsDetailBlock : ButtonsRowItemViewData.ButtonsRowId {
4242
TagValuesChartData,
4343
TagValuesChartGrouping,
4444
TagValuesChartLength,
45+
TagValuesChartMode,
4546
TagValuesRangeAverages,
4647
TagValuesTotals,
4748
DataDistributionHint,

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.google.android.flexbox.FlexWrap
1919
import com.google.android.flexbox.FlexboxItemDecoration
2020
import com.google.android.flexbox.FlexboxLayoutManager
2121
import com.google.android.flexbox.JustifyContent
22+
import androidx.core.content.withStyledAttributes
2223

2324
class StatisticsDetailCardView @JvmOverloads constructor(
2425
context: Context,
@@ -46,7 +47,7 @@ class StatisticsDetailCardView @JvmOverloads constructor(
4647
private val binding = StatisticsDetailCardViewBinding
4748
.inflate(layoutInflater, this, true)
4849

49-
private var itemsCount: Int
50+
private var itemsCount: Int = 0
5051
private val typesAdapter: BaseRecyclerAdapter by lazy {
5152
BaseRecyclerAdapter(
5253
createStatisticsDetailCardInternalAdapterDelegate(
@@ -70,19 +71,18 @@ class StatisticsDetailCardView @JvmOverloads constructor(
7071
setDrawable(dividerDrawable)
7172
}
7273

73-
context.obtainStyledAttributes(
74+
context.withStyledAttributes(
7475
attrs,
7576
R.styleable.StatisticsDetailCardView,
7677
defStyleAttr,
7778
0,
78-
).run {
79+
) {
7980
itemsCount = getInt(
8081
R.styleable.StatisticsDetailCardView_itemCount, DEFAULT_ITEM_COUNT,
8182
)
8283
itemsDescription = getString(
8384
R.styleable.StatisticsDetailCardView_itemDescription,
8485
).orEmpty()
85-
recycle()
8686
}
8787

8888
binding.rvStatisticsDetailCard.apply {
@@ -93,6 +93,7 @@ class StatisticsDetailCardView @JvmOverloads constructor(
9393
addItemDecoration(itemDecoration)
9494
}
9595
adapter = typesAdapter
96+
itemAnimator = null // Cards are flashing on scroll and rebind.
9697
}
9798

9899
if (isInEditMode) {

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailChartInteractor.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartG
2828
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartLength
2929
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartMode
3030
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartSplitSortMode
31+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartValueMode
3132
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailChartCompositeViewData
3233
import kotlinx.coroutines.Dispatchers
3334
import kotlinx.coroutines.withContext
@@ -99,6 +100,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
99100
val canSplitByActivity = canSplitByActivity(filter)
100101
val canComparisonSplitByActivity = canSplitByActivity(compare)
101102
val chartMode = ChartMode.DURATIONS
103+
val chartValueMode = ChartValueMode.TOTAL
102104

103105
val compositeData = getChartRangeSelectionData(
104106
currentChartGrouping = currentChartGrouping,
@@ -121,6 +123,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
121123
typesMap = typesMap,
122124
isDarkTheme = isDarkTheme,
123125
chartMode = chartMode,
126+
chartValueMode = chartValueMode,
124127
splitByActivity = splitByActivity && canSplitByActivity,
125128
splitSortMode = splitSortMode,
126129
)
@@ -136,6 +139,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
136139
typesMap = typesMap,
137140
isDarkTheme = isDarkTheme,
138141
chartMode = chartMode,
142+
chartValueMode = chartValueMode,
139143
splitSortMode = splitSortMode,
140144
)
141145
val compareData = getChartData(
@@ -145,6 +149,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
145149
typesMap = typesMap,
146150
isDarkTheme = isDarkTheme,
147151
chartMode = chartMode,
152+
chartValueMode = chartValueMode,
148153
splitByActivity = splitByActivity && canComparisonSplitByActivity,
149154
splitSortMode = splitSortMode,
150155
)
@@ -185,6 +190,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
185190
typesMap: Map<Long, RecordType>,
186191
isDarkTheme: Boolean,
187192
chartMode: ChartMode,
193+
chartValueMode: ChartValueMode,
188194
splitByActivity: Boolean,
189195
splitSortMode: ChartSplitSortMode,
190196
): List<ChartBarDataDuration> {
@@ -200,21 +206,26 @@ class StatisticsDetailChartInteractor @Inject constructor(
200206

201207
fun mapRangesToValue(list: List<RecordBase>, range: Range): Long? {
202208
return when (chartMode) {
203-
ChartMode.DURATIONS -> {
209+
is ChartMode.DURATIONS -> {
204210
list.map { record ->
205211
rangeMapper.clampToRange(record, range)
206212
}.let(rangeMapper::mapToDuration)
207213
}
208-
ChartMode.COUNTS -> {
214+
is ChartMode.COUNTS -> {
209215
list.size.toLong()
210216
}
211-
ChartMode.TAG_VALUE -> {
212-
list.mapNotNull { record ->
217+
is ChartMode.TAG_VALUE -> {
218+
val tagsValues = list.mapNotNull { record ->
219+
// Only one valued tag should be available.
213220
record.tags
214-
.mapNotNull { it.numericValue }
215-
.takeUnless { it.isEmpty() }
216-
?.sumOf { it * TAG_VALUE_PRECISION }
217-
}.takeUnless { it.isEmpty() }?.sum()?.roundToLong()
221+
.firstOrNull { it.tagId == chartMode.tagId }
222+
?.numericValue
223+
?.times(TAG_VALUE_PRECISION)
224+
}.takeUnless { it.isEmpty() }
225+
when (chartValueMode) {
226+
ChartValueMode.TOTAL -> tagsValues?.sum()?.roundToLong()
227+
ChartValueMode.AVERAGE -> tagsValues?.sum()?.div(tagsValues.size)?.roundToLong()
228+
}
218229
}
219230
}
220231
}
@@ -397,6 +408,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
397408
typesMap: Map<Long, RecordType>,
398409
isDarkTheme: Boolean,
399410
chartMode: ChartMode,
411+
chartValueMode: ChartValueMode,
400412
splitSortMode: ChartSplitSortMode,
401413
): List<ChartBarDataDuration> {
402414
return if (rangeLength != RangeLength.All) {
@@ -416,6 +428,7 @@ class StatisticsDetailChartInteractor @Inject constructor(
416428
isDarkTheme = isDarkTheme,
417429
splitByActivity = false,
418430
chartMode = chartMode,
431+
chartValueMode = chartValueMode,
419432
splitSortMode = splitSortMode,
420433
)
421434
} else {

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailGoalsInteractor.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.example.util.simpletimetracker.feature_statistics_detail.mapper.Stati
1818
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartGrouping
1919
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartLength
2020
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartSplitSortMode
21+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartValueMode
2122
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailGoalsCompositeViewData
2223
import kotlinx.coroutines.Dispatchers
2324
import kotlinx.coroutines.withContext
@@ -64,6 +65,7 @@ class StatisticsDetailGoalsInteractor @Inject constructor(
6465
rangeLength = mapToRange(compositeData.appliedChartGrouping),
6566
)
6667
val chartMode = statisticsDetailViewDataMapper.mapToChartMode(chartGoal)
68+
val chartValueMode = ChartValueMode.TOTAL
6769
val ranges = chartInteractor.getRanges(
6870
compositeData = compositeData,
6971
rangeLength = rangeLength,
@@ -79,6 +81,7 @@ class StatisticsDetailGoalsInteractor @Inject constructor(
7981
typesMap = typesMap,
8082
isDarkTheme = isDarkTheme,
8183
chartMode = chartMode,
84+
chartValueMode = chartValueMode,
8285
splitByActivity = false,
8386
splitSortMode = ChartSplitSortMode.ACTIVITY_ORDER,
8487
)
@@ -94,6 +97,7 @@ class StatisticsDetailGoalsInteractor @Inject constructor(
9497
typesMap = typesMap,
9598
isDarkTheme = isDarkTheme,
9699
chartMode = chartMode,
100+
chartValueMode = chartValueMode,
97101
splitSortMode = ChartSplitSortMode.ACTIVITY_ORDER,
98102
)
99103

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/interactor/StatisticsDetailTagValueInteractor.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartG
1616
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartLength
1717
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartMode
1818
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartSplitSortMode
19+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartValueMode
1920
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailTagValuesCompositeViewData
2021
import kotlinx.coroutines.Dispatchers
2122
import kotlinx.coroutines.withContext
@@ -36,6 +37,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
3637
filter: List<RecordsFilter>,
3738
currentChartGrouping: ChartGrouping,
3839
currentChartLength: ChartLength,
40+
currentChartValueMode: ChartValueMode,
3941
rangeLength: RangeLength,
4042
rangePosition: Int,
4143
): StatisticsDetailTagValuesCompositeViewData = withContext(Dispatchers.Default) {
@@ -56,7 +58,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
5658
val types = recordTypeInteractor.getAll()
5759
val typesMap = types.associateBy(RecordType::id)
5860
val typesOrder = types.map(RecordType::id)
59-
val chartMode = ChartMode.TAG_VALUE
61+
val chartMode = ChartMode.TAG_VALUE(valuedTag.id)
6062

6163
val compositeData = chartInteractor.getChartRangeSelectionData(
6264
currentChartGrouping = currentChartGrouping,
@@ -79,6 +81,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
7981
typesMap = typesMap,
8082
isDarkTheme = isDarkTheme,
8183
chartMode = chartMode,
84+
chartValueMode = currentChartValueMode,
8285
splitByActivity = false,
8386
splitSortMode = ChartSplitSortMode.ACTIVITY_ORDER,
8487
)
@@ -94,6 +97,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
9497
typesMap = typesMap,
9598
isDarkTheme = isDarkTheme,
9699
chartMode = chartMode,
100+
chartValueMode = currentChartValueMode,
97101
splitSortMode = ChartSplitSortMode.ACTIVITY_ORDER,
98102
)
99103

@@ -106,6 +110,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
106110
availableChartLengths = compositeData.availableChartLengths,
107111
appliedChartLength = compositeData.appliedChartLength,
108112
chartMode = chartMode,
113+
chartValueMode = currentChartValueMode,
109114
valueSuffix = valuedTag.valueSuffix,
110115
useProportionalMinutes = useProportionalMinutes,
111116
showSeconds = showSeconds,

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/mapper/StatisticsDetailGoalsViewDataMapper.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,12 @@ class StatisticsDetailGoalsViewDataMapper @Inject constructor(
224224
interval: Long,
225225
): String {
226226
return when (chartMode) {
227-
ChartMode.DURATIONS -> timeMapper.formatInterval(
227+
is ChartMode.DURATIONS -> timeMapper.formatInterval(
228228
interval = interval,
229229
forceSeconds = showSeconds,
230230
useProportionalMinutes = useProportionalMinutes,
231231
)
232-
ChartMode.COUNTS, ChartMode.TAG_VALUE -> interval.toString()
232+
is ChartMode.COUNTS, is ChartMode.TAG_VALUE -> interval.toString()
233233
}
234234
}
235235

@@ -328,12 +328,12 @@ class StatisticsDetailGoalsViewDataMapper @Inject constructor(
328328
interval: Long,
329329
): String {
330330
return when (chartMode) {
331-
ChartMode.DURATIONS -> timeMapper.formatInterval(
331+
is ChartMode.DURATIONS -> timeMapper.formatInterval(
332332
interval = interval,
333333
forceSeconds = showSeconds,
334334
useProportionalMinutes = useProportionalMinutes,
335335
)
336-
ChartMode.COUNTS, ChartMode.TAG_VALUE -> interval.toString()
336+
is ChartMode.COUNTS, is ChartMode.TAG_VALUE -> interval.toString()
337337
}
338338
}
339339

@@ -350,8 +350,8 @@ class StatisticsDetailGoalsViewDataMapper @Inject constructor(
350350
.map { rangeMapper.clampRecordToRange(it, range) }
351351
}
352352
val currentValue = when (chartMode) {
353-
ChartMode.DURATIONS -> recordsFromRange.sumOf(RecordBase::duration)
354-
ChartMode.COUNTS, ChartMode.TAG_VALUE -> recordsFromRange.size.toLong()
353+
is ChartMode.DURATIONS -> recordsFromRange.sumOf(RecordBase::duration)
354+
is ChartMode.COUNTS, is ChartMode.TAG_VALUE -> recordsFromRange.size.toLong()
355355
}
356356
val percentage = if (goalValue != 0L) {
357357
currentValue * 100f / goalValue

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/mapper/StatisticsDetailTagValuesViewDataMapper.kt

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartB
1515
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartGrouping
1616
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartLength
1717
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartMode
18+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartValueMode
1819
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailCardInternalViewData
1920
import javax.inject.Inject
2021

@@ -32,6 +33,7 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
3233
availableChartLengths: List<ChartLength>,
3334
appliedChartLength: ChartLength,
3435
chartMode: ChartMode,
36+
chartValueMode: ChartValueMode,
3537
valueSuffix: String,
3638
useProportionalMinutes: Boolean,
3739
showSeconds: Boolean,
@@ -68,8 +70,16 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
6870
availableChartLengths = availableChartLengths,
6971
appliedChartLength = appliedChartLength,
7072
)
73+
val chartValueModeViewData = statisticsDetailViewDataMapper.mapToChartValueModeViewData(
74+
availableChartValueModes = listOf(
75+
ChartValueMode.TOTAL,
76+
ChartValueMode.AVERAGE,
77+
),
78+
chartValueMode = chartValueMode,
79+
)
7180
val totals = mapTagValuesTotals(
7281
goalData = data,
82+
chartValueMode = chartValueMode,
7383
)
7484

7585
if (chartData.visible) {
@@ -103,16 +113,21 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
103113
}
104114

105115
if (chartLengthViewData.isNotEmpty()) {
106-
// Update margin top depending if has buttons before.
107-
val hasButtonsBefore = items.lastOrNull() is ButtonsRowItemViewData
108-
val marginTopDp = if (hasButtonsBefore) -10 else 4
109116
items += ButtonsRowItemViewData(
110117
block = StatisticsDetailBlock.TagValuesChartLength,
111-
marginTopDp = marginTopDp,
118+
marginTopDp = getTopMargin(items),
112119
data = chartLengthViewData,
113120
)
114121
}
115122

123+
if (chartValueModeViewData.isNotEmpty()) {
124+
items += ButtonsRowItemViewData(
125+
block = StatisticsDetailBlock.TagValuesChartMode,
126+
marginTopDp = getTopMargin(items),
127+
data = chartValueModeViewData,
128+
)
129+
}
130+
116131
if (rangeAverages.isNotEmpty()) {
117132
items += StatisticsDetailCardViewData(
118133
block = StatisticsDetailBlock.TagValuesRangeAverages,
@@ -136,6 +151,7 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
136151

137152
private fun mapTagValuesTotals(
138153
goalData: List<ChartBarDataDuration>,
154+
chartValueMode: ChartValueMode,
139155
): List<StatisticsDetailCardInternalViewData> {
140156
val emptyValue by lazy { resourceRepo.getString(R.string.statistics_detail_empty) }
141157

@@ -144,7 +160,7 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
144160
val maxValue = barValues.maxOrNull()?.toFloat()?.div(TAG_VALUE_PRECISION)
145161
val total = barValues.takeUnless { it.isEmpty() }?.sum()?.toFloat()?.div(TAG_VALUE_PRECISION)
146162

147-
return listOf(
163+
return listOfNotNull(
148164
StatisticsDetailCardInternalViewData(
149165
value = minValue?.toString()?.removeTrailingZeroes() ?: emptyValue,
150166
valueChange = StatisticsDetailCardInternalViewData.ValueChange.None,
@@ -160,7 +176,9 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
160176
description = resourceRepo.getString(R.string.statistics_detail_total_duration),
161177
titleTextSizeSp = 14,
162178
subtitleTextSizeSp = 12,
163-
),
179+
).takeUnless {
180+
chartValueMode == ChartValueMode.AVERAGE
181+
},
164182
StatisticsDetailCardInternalViewData(
165183
value = maxValue?.toString()?.removeTrailingZeroes() ?: emptyValue,
166184
valueChange = StatisticsDetailCardInternalViewData.ValueChange.None,
@@ -171,4 +189,11 @@ class StatisticsDetailTagValuesViewDataMapper @Inject constructor(
171189
),
172190
)
173191
}
192+
193+
194+
private fun getTopMargin(currentItems: List<ViewHolderType>): Int {
195+
// Update margin top depending if has buttons before.
196+
val hasButtonsBefore = currentItems.lastOrNull() is ButtonsRowItemViewData
197+
return if (hasButtonsBefore) -10 else 4
198+
}
174199
}

0 commit comments

Comments
 (0)