Skip to content

Commit 1b1e8bb

Browse files
committed
fill empty tag value statistics with previous data
1 parent 17ceae0 commit 1b1e8bb

File tree

38 files changed

+215
-2
lines changed

38 files changed

+215
-2
lines changed

domain/src/main/java/com/example/util/simpletimetracker/domain/prefs/interactor/PrefsInteractor.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,16 +1290,19 @@ class PrefsInteractor @Inject constructor(
12901290
val tagId = parts.getOrNull(0)?.toLongOrNull() ?: return@mapNotNull null
12911291
val modeInt = parts.getOrNull(1)?.toIntOrNull().orZero()
12921292
val multiplyInt = parts.getOrNull(2)?.toIntOrNull().orZero()
1293+
val fillInt = parts.getOrNull(3)?.toIntOrNull().orZero()
12931294
val chartValueMode = when (modeInt) {
12941295
0 -> ChartValueMode.TOTAL
12951296
1 -> ChartValueMode.AVERAGE
12961297
else -> ChartValueMode.TOTAL
12971298
}
12981299
val multiplyDuration = multiplyInt == 1
1300+
val fillEmptyPeriods = fillInt == 1
12991301
tagId to StatisticsDetailTagValueSettings(
13001302
tagId = tagId,
13011303
chartValueMode = chartValueMode,
13021304
multiplyDuration = multiplyDuration,
1305+
fillEmptyPeriods = fillEmptyPeriods,
13031306
)
13041307
}
13051308
}?.toMap().orEmpty()
@@ -1320,6 +1323,8 @@ class PrefsInteractor @Inject constructor(
13201323
)
13211324
append(STATISTICS_DETAIL_TAG_VALUE_DELIMITER)
13221325
append(if (settings.multiplyDuration) 1 else 0)
1326+
append(STATISTICS_DETAIL_TAG_VALUE_DELIMITER)
1327+
append(if (settings.fillEmptyPeriods) 1 else 0)
13231328
}
13241329
}.toSet()
13251330
}

domain/src/main/java/com/example/util/simpletimetracker/domain/statistics/model/StatisticsDetailTagValueSettings.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ data class StatisticsDetailTagValueSettings(
44
val tagId: Long,
55
val chartValueMode: ChartValueMode,
66
val multiplyDuration: Boolean,
7+
val fillEmptyPeriods: Boolean,
78
) {
89

910
companion object {
@@ -12,6 +13,7 @@ data class StatisticsDetailTagValueSettings(
1213
tagId = tagId,
1314
chartValueMode = ChartValueMode.TOTAL,
1415
multiplyDuration = false,
16+
fillEmptyPeriods = false,
1517
)
1618
}
1719
}

features/feature_settings/api/src/main/java/com/example/util/simpletimetracker/feature_settings/api/SettingsBlock.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,5 @@ enum class SettingsBlock {
155155

156156
StatisticsTagValuesChartValueMode,
157157
StatisticsTagValuesMultiplyDuration,
158+
StatisticsTagValuesFillEmptyPeriods,
158159
}

features/feature_statistics_detail/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ dependencies {
2323
implementation(project(":feature_records_filter:api"))
2424
implementation(libs.google.dagger)
2525
ksp(libs.kapt.dagger)
26+
27+
testImplementation(libs.test.junit)
2628
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.example.util.simpletimetracker.feature_statistics_detail.interactor
2+
3+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartBarDataDuration
4+
import javax.inject.Inject
5+
6+
class FillEmptyBarsWithPreviousValueInteractor @Inject constructor() {
7+
8+
fun invoke(
9+
data: List<ChartBarDataDuration>,
10+
previousRangeData: List<ChartBarDataDuration>?,
11+
): List<ChartBarDataDuration> {
12+
if (data.isEmpty()) return data
13+
14+
var previousNonEmptyDurations = previousRangeData
15+
?.lastOrNull { it.durations.isNotEmpty() }?.durations.orEmpty()
16+
17+
val now = System.currentTimeMillis()
18+
19+
return data.map { bar ->
20+
when {
21+
bar.durations.isNotEmpty() -> {
22+
previousNonEmptyDurations = bar.durations
23+
bar
24+
}
25+
bar.rangeStart >= now -> {
26+
bar
27+
}
28+
previousNonEmptyDurations.isNotEmpty() -> {
29+
bar.copy(durations = previousNonEmptyDurations)
30+
}
31+
else -> bar
32+
}
33+
}
34+
}
35+
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
2929
private val statisticsDetailTagValuesViewDataMapper: StatisticsDetailTagValuesViewDataMapper,
3030
private val recordTypeInteractor: RecordTypeInteractor,
3131
private val recordTagInteractor: RecordTagInteractor,
32+
private val fillEmptyBarsWithPreviousValueInteractor: FillEmptyBarsWithPreviousValueInteractor,
3233
) {
3334

3435
// TODO compare?
@@ -39,6 +40,7 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
3940
currentChartLength: ChartLength,
4041
currentChartValueMode: ChartValueMode,
4142
multiplyDuration: Boolean,
43+
fillEmptyPeriods: Boolean,
4244
rangeLength: RangeLength,
4345
rangePosition: Int,
4446
): StatisticsDetailTagValuesCompositeViewData = withContext(Dispatchers.Default) {
@@ -98,10 +100,20 @@ class StatisticsDetailTagValueInteractor @Inject constructor(
98100
multiplyDuration = multiplyDuration,
99101
splitSortMode = ChartSplitSortMode.ACTIVITY_ORDER,
100102
)
103+
val preparedPrevData = if (fillEmptyPeriods) {
104+
fillEmptyBarsWithPreviousValueInteractor.invoke(prevData, null)
105+
} else {
106+
prevData
107+
}
108+
val preparedData = if (fillEmptyPeriods) {
109+
fillEmptyBarsWithPreviousValueInteractor.invoke(data, preparedPrevData)
110+
} else {
111+
data
112+
}
101113

102114
val chartViewData = statisticsDetailTagValuesViewDataMapper.mapTagValueChartViewData(
103-
data = data,
104-
prevData = prevData,
115+
data = preparedData,
116+
prevData = preparedPrevData,
105117
rangeLength = rangeLength,
106118
availableChartGroupings = compositeData.availableChartGroupings,
107119
appliedChartGrouping = compositeData.appliedChartGrouping,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class StatisticsTagValuesSettingsViewDataInteractor @Inject constructor(
3030
title = resourceRepo.getString(R.string.statistics_detail_tag_values_multiply_duration),
3131
subtitle = "",
3232
isChecked = settings.multiplyDuration,
33+
backgroundIsVisible = false,
34+
),
35+
SettingsCheckboxViewData(
36+
block = SettingsBlock.StatisticsTagValuesFillEmptyPeriods,
37+
title = resourceRepo.getString(R.string.statistics_detail_tag_values_fill_empty_periods),
38+
subtitle = "",
39+
isChecked = settings.fillEmptyPeriods,
3340
dividerIsVisible = false,
3441
backgroundIsVisible = false,
3542
),

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/settings/viewModel/StatisticsTagValuesSettingsViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class StatisticsTagValuesSettingsViewModel @Inject constructor(
3333
tagId = params.tagId,
3434
chartValueMode = params.chartValueMode,
3535
multiplyDuration = params.multiplyDuration,
36+
fillEmptyPeriods = params.fillEmptyPeriods,
3637
)
3738
initialized = true
3839
}
@@ -54,6 +55,9 @@ class StatisticsTagValuesSettingsViewModel @Inject constructor(
5455
SettingsBlock.StatisticsTagValuesMultiplyDuration -> {
5556
newSettings.copy(multiplyDuration = !newSettings.multiplyDuration)
5657
}
58+
SettingsBlock.StatisticsTagValuesFillEmptyPeriods -> {
59+
newSettings.copy(fillEmptyPeriods = !newSettings.fillEmptyPeriods)
60+
}
5761
else -> return // Do nothing.
5862
}
5963
onSettingsChanged()

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/delegate/StatisticsDetailTagValueViewModelDelegate.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class StatisticsDetailTagValueViewModelDelegate @Inject constructor(
6262
tagId = loadedTagSettings.tagId,
6363
chartValueMode = loadedTagSettings.chartValueMode,
6464
multiplyDuration = loadedTagSettings.multiplyDuration,
65+
fillEmptyPeriods = loadedTagSettings.fillEmptyPeriods,
6566
),
6667
)
6768
}
@@ -94,6 +95,7 @@ class StatisticsDetailTagValueViewModelDelegate @Inject constructor(
9495
currentChartLength = chartLength,
9596
currentChartValueMode = settings.chartValueMode,
9697
multiplyDuration = settings.multiplyDuration,
98+
fillEmptyPeriods = settings.fillEmptyPeriods,
9799
rangeLength = parent.rangeLength,
98100
rangePosition = parent.rangePosition,
99101
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.example.util.simpletimetracker.feature_statistics_detail
2+
3+
import com.example.util.simpletimetracker.feature_statistics_detail.interactor.FillEmptyBarsWithPreviousValueInteractor
4+
import com.example.util.simpletimetracker.feature_statistics_detail.model.ChartBarDataDuration
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
7+
import org.junit.Test
8+
9+
class FillEmptyBarsWithPreviousValueInteractorTest {
10+
11+
private val interactor = FillEmptyBarsWithPreviousValueInteractor()
12+
13+
@Test
14+
fun returnsEmptyWhenDataIsEmpty() {
15+
// When
16+
val result = interactor.invoke(
17+
data = emptyList(),
18+
previousRangeData = listOf(chartBar(0, listOf(1L to 1))),
19+
)
20+
21+
// Then
22+
assertTrue(result.isEmpty())
23+
}
24+
25+
@Test
26+
fun fillsEmptyFromPreviousRange() {
27+
// Given
28+
val now = System.currentTimeMillis()
29+
val previousDuration = listOf(1L to 100)
30+
val previous = chartBar(now - 10_000, previousDuration)
31+
val data = listOf(chartBar(now - 9_000, emptyList()))
32+
33+
// When
34+
val result = interactor.invoke(
35+
data = data,
36+
previousRangeData = listOf(previous),
37+
)
38+
39+
// Then
40+
assertEquals(previousDuration, result[0].durations)
41+
}
42+
43+
@Test
44+
fun fillsFromMostRecentPreviousNonEmptyBar() {
45+
// Given
46+
val now = System.currentTimeMillis()
47+
val oldestDuration = listOf(1L to 100)
48+
val newestDuration = listOf(2L to 200)
49+
val data = listOf(chartBar(now - 9_000, emptyList()))
50+
51+
// When
52+
val result = interactor.invoke(
53+
data = data,
54+
previousRangeData = listOf(
55+
chartBar(now - 20_000, oldestDuration),
56+
chartBar(now - 18_000, emptyList()),
57+
chartBar(now - 16_000, newestDuration),
58+
),
59+
)
60+
61+
// Then
62+
assertEquals(newestDuration, result[0].durations)
63+
}
64+
65+
@Test
66+
fun fillsConsecutiveEmptyBars() {
67+
// Given
68+
val now = System.currentTimeMillis()
69+
val firstDurations = listOf(2L to 200)
70+
val data = listOf(
71+
chartBar(now - 12_000, firstDurations),
72+
chartBar(now - 10_000, emptyList()),
73+
chartBar(now - 8_000, emptyList()),
74+
)
75+
76+
// When
77+
val result = interactor.invoke(
78+
data = data,
79+
previousRangeData = null,
80+
)
81+
82+
// Then
83+
assertEquals(firstDurations, result[1].durations)
84+
assertEquals(firstDurations, result[2].durations)
85+
}
86+
87+
@Test
88+
fun doesNotFillFutureEmptyBars() {
89+
// Given
90+
val now = System.currentTimeMillis()
91+
val upcomingBar = chartBar(now + 10_000, emptyList())
92+
93+
// When
94+
val result = interactor.invoke(
95+
listOf(element = upcomingBar),
96+
listOf(chartBar(now - 20_000, listOf(3L to 300))),
97+
)
98+
99+
// Then
100+
assertTrue(result[0].durations.isEmpty())
101+
}
102+
103+
private fun chartBar(
104+
rangeStart: Long,
105+
durations: List<Pair<Long, Int>>,
106+
): ChartBarDataDuration {
107+
return ChartBarDataDuration(
108+
rangeStart = rangeStart,
109+
legend = "legend",
110+
durations = durations,
111+
)
112+
}
113+
}

0 commit comments

Comments
 (0)