mirror of
https://github.com/mihonapp/mihon.git
synced 2025-01-28 02:44:56 +01:00
Add Quantity Badge to Upcoming Screen (#1250)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
parent
7c7af72f8c
commit
6b2bba4e54
@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
|
|||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
generator: (T?, T?) -> R?,
|
generator: (before: T?, after: T?) -> R?,
|
||||||
): List<R> {
|
): List<R> {
|
||||||
if (isEmpty()) return emptyList()
|
if (isEmpty()) return emptyList()
|
||||||
val newList = mutableListOf<R>()
|
val newList = mutableListOf<R>()
|
||||||
@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||||||
return newList
|
return newList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
|
||||||
|
*/
|
||||||
|
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
|
||||||
|
generator: (before: T?, after: T?) -> R?,
|
||||||
|
): List<R> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
val newList = mutableListOf<R>()
|
||||||
|
for (i in size downTo 0) {
|
||||||
|
val after = getOrNull(i)
|
||||||
|
after?.let(newList::add)
|
||||||
|
val before = getOrNull(i - 1)
|
||||||
|
val separator = generator.invoke(before, after)
|
||||||
|
separator?.let(newList::add)
|
||||||
|
}
|
||||||
|
return newList.asReversed()
|
||||||
|
}
|
||||||
|
|
||||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
package mihon.feature.upcoming
|
package mihon.feature.upcoming
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
|
import androidx.compose.material3.Badge
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@ -27,9 +34,9 @@ import tachiyomi.core.common.Constants
|
|||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
|
||||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
@ -99,6 +106,33 @@ private fun UpcomingToolbar() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DateHeading(
|
||||||
|
date: LocalDate,
|
||||||
|
mangaCount: Int,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = relativeDateText(date),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(MaterialTheme.padding.small)
|
||||||
|
.padding(start = MaterialTheme.padding.small),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
Badge(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
) {
|
||||||
|
Text("$mangaCount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UpcomingScreenSmallImpl(
|
private fun UpcomingScreenSmallImpl(
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
@ -140,7 +174,10 @@ private fun UpcomingScreenSmallImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpcomingUIModel.Header -> {
|
is UpcomingUIModel.Header -> {
|
||||||
ListGroupHeader(text = relativeDateText(item.date))
|
DateHeading(
|
||||||
|
date = item.date,
|
||||||
|
mangaCount = item.mangaCount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,7 +225,10 @@ private fun UpcomingScreenLargeImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpcomingUIModel.Header -> {
|
is UpcomingUIModel.Header -> {
|
||||||
ListGroupHeader(text = relativeDateText(item.date))
|
DateHeading(
|
||||||
|
date = item.date,
|
||||||
|
mangaCount = item.mangaCount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.ui.util.fastMap
|
|||||||
import androidx.compose.ui.util.fastMapIndexedNotNull
|
import androidx.compose.ui.util.fastMapIndexedNotNull
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparatorsReversed
|
||||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
@ -33,7 +33,7 @@ class UpcomingScreenModel(
|
|||||||
val upcomingItems = it.toUpcomingUIModels()
|
val upcomingItems = it.toUpcomingUIModels()
|
||||||
state.copy(
|
state.copy(
|
||||||
items = upcomingItems,
|
items = upcomingItems,
|
||||||
events = it.toEvents(),
|
events = upcomingItems.toEvents(),
|
||||||
headerIndexes = upcomingItems.getHeaderIndexes(),
|
headerIndexes = upcomingItems.getHeaderIndexes(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -42,13 +42,16 @@ class UpcomingScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> {
|
private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> {
|
||||||
|
var mangaCount = 0
|
||||||
return fastMap { UpcomingUIModel.Item(it) }
|
return fastMap { UpcomingUIModel.Item(it) }
|
||||||
.insertSeparators { before, after ->
|
.insertSeparatorsReversed { before, after ->
|
||||||
|
if (after != null) mangaCount++
|
||||||
|
|
||||||
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
|
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
|
||||||
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
|
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
|
||||||
|
|
||||||
if (beforeDate != afterDate && afterDate != null) {
|
if (beforeDate != afterDate && afterDate != null) {
|
||||||
UpcomingUIModel.Header(afterDate)
|
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -56,9 +59,9 @@ class UpcomingScreenModel(
|
|||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Manga>.toEvents(): ImmutableMap<LocalDate, Int> {
|
private fun List<UpcomingUIModel>.toEvents(): ImmutableMap<LocalDate, Int> {
|
||||||
return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX }
|
return filterIsInstance<UpcomingUIModel.Header>()
|
||||||
.mapValues { it.value.size }
|
.associate { it.date to it.mangaCount }
|
||||||
.toImmutableMap()
|
.toImmutableMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@ import tachiyomi.domain.manga.model.Manga
|
|||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
sealed interface UpcomingUIModel {
|
sealed interface UpcomingUIModel {
|
||||||
data class Header(val date: LocalDate) : UpcomingUIModel
|
data class Header(val date: LocalDate, val mangaCount: Int) : UpcomingUIModel
|
||||||
data class Item(val manga: Manga) : UpcomingUIModel
|
data class Item(val manga: Manga) : UpcomingUIModel
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user