mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-24 14:15:54 +03: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
4 changed files with 73 additions and 12 deletions
|
@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
|
|||
import kotlin.contracts.contract
|
||||
|
||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||
generator: (T?, T?) -> R?,
|
||||
generator: (before: T?, after: T?) -> R?,
|
||||
): List<R> {
|
||||
if (isEmpty()) return emptyList()
|
||||
val newList = mutableListOf<R>()
|
||||
|
@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||
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) {
|
||||
if (shouldAdd) {
|
||||
add(value)
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package mihon.feature.upcoming
|
||||
|
||||
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.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
|
@ -27,9 +34,9 @@ import tachiyomi.core.common.Constants
|
|||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.time.LocalDate
|
||||
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
|
||||
private fun UpcomingScreenSmallImpl(
|
||||
listState: LazyListState,
|
||||
|
@ -140,7 +174,10 @@ private fun UpcomingScreenSmallImpl(
|
|||
)
|
||||
}
|
||||
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 -> {
|
||||
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 cafe.adriel.voyager.core.model.StateScreenModel
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
@ -33,7 +33,7 @@ class UpcomingScreenModel(
|
|||
val upcomingItems = it.toUpcomingUIModels()
|
||||
state.copy(
|
||||
items = upcomingItems,
|
||||
events = it.toEvents(),
|
||||
events = upcomingItems.toEvents(),
|
||||
headerIndexes = upcomingItems.getHeaderIndexes(),
|
||||
)
|
||||
}
|
||||
|
@ -42,13 +42,16 @@ class UpcomingScreenModel(
|
|||
}
|
||||
|
||||
private fun List<Manga>.toUpcomingUIModels(): ImmutableList<UpcomingUIModel> {
|
||||
var mangaCount = 0
|
||||
return fastMap { UpcomingUIModel.Item(it) }
|
||||
.insertSeparators { before, after ->
|
||||
.insertSeparatorsReversed { before, after ->
|
||||
if (after != null) mangaCount++
|
||||
|
||||
val beforeDate = before?.manga?.expectedNextUpdate?.toLocalDate()
|
||||
val afterDate = after?.manga?.expectedNextUpdate?.toLocalDate()
|
||||
|
||||
if (beforeDate != afterDate && afterDate != null) {
|
||||
UpcomingUIModel.Header(afterDate)
|
||||
UpcomingUIModel.Header(afterDate, mangaCount).also { mangaCount = 0 }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -56,9 +59,9 @@ class UpcomingScreenModel(
|
|||
.toImmutableList()
|
||||
}
|
||||
|
||||
private fun List<Manga>.toEvents(): ImmutableMap<LocalDate, Int> {
|
||||
return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX }
|
||||
.mapValues { it.value.size }
|
||||
private fun List<UpcomingUIModel>.toEvents(): ImmutableMap<LocalDate, Int> {
|
||||
return filterIsInstance<UpcomingUIModel.Header>()
|
||||
.associate { it.date to it.mangaCount }
|
||||
.toImmutableMap()
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@ import tachiyomi.domain.manga.model.Manga
|
|||
import java.time.LocalDate
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue