Skip to content

Commit 457e5f9

Browse files
AnimeboynzAntsyLich
authored andcommitted
Add Quantity Badge to Upcoming Screen (#1250)
Co-authored-by: AntsyLich <[email protected]> (cherry picked from commit 6b2bba4)
1 parent 2bd9a91 commit 457e5f9

File tree

4 files changed

+73
-12
lines changed

4 files changed

+73
-12
lines changed

app/src/main/java/eu/kanade/core/util/CollectionUtils.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
55
import kotlin.contracts.contract
66

77
fun <T : R, R : Any> List<T>.insertSeparators(
8-
generator: (T?, T?) -> R?,
8+
generator: (before: T?, after: T?) -> R?,
99
): List<R> {
1010
if (isEmpty()) return emptyList()
1111
val newList = mutableListOf<R>()
@@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
1919
return newList
2020
}
2121

22+
/**
23+
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
24+
*/
25+
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
26+
generator: (before: T?, after: T?) -> R?,
27+
): List<R> {
28+
if (isEmpty()) return emptyList()
29+
val newList = mutableListOf<R>()
30+
for (i in size downTo 0) {
31+
val after = getOrNull(i)
32+
after?.let(newList::add)
33+
val before = getOrNull(i - 1)
34+
val separator = generator.invoke(before, after)
35+
separator?.let(newList::add)
36+
}
37+
return newList.asReversed()
38+
}
39+
2240
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
2341
if (shouldAdd) {
2442
add(value)

app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
package mihon.feature.upcoming
22

33
import androidx.compose.foundation.layout.PaddingValues
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.fillMaxWidth
46
import androidx.compose.foundation.layout.padding
57
import androidx.compose.foundation.lazy.LazyListState
68
import androidx.compose.foundation.lazy.items
79
import androidx.compose.foundation.lazy.rememberLazyListState
810
import androidx.compose.material.icons.Icons
911
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
12+
import androidx.compose.material3.Badge
1013
import androidx.compose.material3.Icon
1114
import androidx.compose.material3.IconButton
15+
import androidx.compose.material3.MaterialTheme
16+
import androidx.compose.material3.Text
1217
import androidx.compose.runtime.Composable
1318
import androidx.compose.runtime.rememberCoroutineScope
19+
import androidx.compose.ui.Alignment
1420
import androidx.compose.ui.Modifier
1521
import androidx.compose.ui.platform.LocalUriHandler
22+
import androidx.compose.ui.text.font.FontWeight
1623
import cafe.adriel.voyager.navigator.LocalNavigator
1724
import cafe.adriel.voyager.navigator.currentOrThrow
1825
import eu.kanade.presentation.components.AppBar
@@ -27,9 +34,9 @@ import tachiyomi.core.common.Constants
2734
import tachiyomi.domain.manga.model.Manga
2835
import tachiyomi.i18n.MR
2936
import tachiyomi.presentation.core.components.FastScrollLazyColumn
30-
import tachiyomi.presentation.core.components.ListGroupHeader
3137
import tachiyomi.presentation.core.components.TwoPanelBox
3238
import tachiyomi.presentation.core.components.material.Scaffold
39+
import tachiyomi.presentation.core.components.material.padding
3340
import tachiyomi.presentation.core.i18n.stringResource
3441
import java.time.LocalDate
3542
import java.time.YearMonth
@@ -99,6 +106,33 @@ private fun UpcomingToolbar() {
99106
)
100107
}
101108

109+
@Composable
110+
private fun DateHeading(
111+
date: LocalDate,
112+
mangaCount: Int,
113+
) {
114+
Row(
115+
verticalAlignment = Alignment.CenterVertically,
116+
modifier = Modifier.fillMaxWidth(),
117+
) {
118+
Text(
119+
text = relativeDateText(date),
120+
modifier = Modifier
121+
.padding(MaterialTheme.padding.small)
122+
.padding(start = MaterialTheme.padding.small),
123+
color = MaterialTheme.colorScheme.onSurfaceVariant,
124+
fontWeight = FontWeight.SemiBold,
125+
style = MaterialTheme.typography.bodyMedium,
126+
)
127+
Badge(
128+
containerColor = MaterialTheme.colorScheme.primary,
129+
contentColor = MaterialTheme.colorScheme.onPrimary,
130+
) {
131+
Text("$mangaCount")
132+
}
133+
}
134+
}
135+
102136
@Composable
103137
private fun UpcomingScreenSmallImpl(
104138
listState: LazyListState,
@@ -140,7 +174,10 @@ private fun UpcomingScreenSmallImpl(
140174
)
141175
}
142176
is UpcomingUIModel.Header -> {
143-
ListGroupHeader(text = relativeDateText(item.date))
177+
DateHeading(
178+
date = item.date,
179+
mangaCount = item.mangaCount,
180+
)
144181
}
145182
}
146183
}
@@ -188,7 +225,10 @@ private fun UpcomingScreenLargeImpl(
188225
)
189226
}
190227
is UpcomingUIModel.Header -> {
191-
ListGroupHeader(text = relativeDateText(item.date))
228+
DateHeading(
229+
date = item.date,
230+
mangaCount = item.mangaCount,
231+
)
192232
}
193233
}
194234
}

app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import androidx.compose.ui.util.fastMap
44
import androidx.compose.ui.util.fastMapIndexedNotNull
55
import cafe.adriel.voyager.core.model.StateScreenModel
66
import cafe.adriel.voyager.core.model.screenModelScope
7-
import eu.kanade.core.util.insertSeparators
7+
import eu.kanade.core.util.insertSeparatorsReversed
88
import eu.kanade.tachiyomi.util.lang.toLocalDate
99
import kotlinx.collections.immutable.ImmutableList
1010
import kotlinx.collections.immutable.ImmutableMap
@@ -33,7 +33,7 @@ class UpcomingScreenModel(
3333
val upcomingItems = it.toUpcomingUIModels()
3434
state.copy(
3535
items = upcomingItems,
36-
events = it.toEvents(),
36+
events = upcomingItems.toEvents(),
3737
headerIndexes = upcomingItems.getHeaderIndexes(),
3838
)
3939
}
@@ -42,23 +42,26 @@ class UpcomingScreenModel(
4242
}
4343

4444
private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> {
45+
var mangaCount = 0
4546
return fastMap { UpcomingUIModel.Item(it) }
46-
.insertSeparators { before, after ->
47+
.insertSeparatorsReversed { before, after ->
48+
if (after != null) mangaCount++
49+
4750
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
4851
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
4952

5053
if (beforeDate != afterDate && afterDate != null) {
51-
UpcomingUIModel.Header(afterDate)
54+
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
5255
} else {
5356
null
5457
}
5558
}
5659
.toImmutableList()
5760
}
5861

59-
private fun List<Manga>.toEvents(): ImmutableMap<LocalDate, Int> {
60-
return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX }
61-
.mapValues { it.value.size }
62+
private fun List<UpcomingUIModel>.toEvents(): ImmutableMap<LocalDate, Int> {
63+
return filterIsInstance<UpcomingUIModel.Header>()
64+
.associate { it.date to it.mangaCount }
6265
.toImmutableMap()
6366
}
6467

app/src/main/java/mihon/feature/upcoming/UpcomingUIModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import tachiyomi.domain.manga.model.Manga
44
import java.time.LocalDate
55

66
sealed interface UpcomingUIModel {
7-
data class Header(val date: LocalDate) : UpcomingUIModel
7+
data class Header(val date: LocalDate, val mangaCount: Int) : UpcomingUIModel
88
data class Item(val manga: Manga) : UpcomingUIModel
99
}

0 commit comments

Comments
 (0)