diff --git a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt index 8dab8a054..baf369e39 100644 --- a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt +++ b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt @@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract fun List.insertSeparators( - generator: (T?, T?) -> R?, + generator: (before: T?, after: T?) -> R?, ): List { if (isEmpty()) return emptyList() val newList = mutableListOf() @@ -19,6 +19,24 @@ fun List.insertSeparators( return newList } +/** + * Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element + */ +fun List.insertSeparatorsReversed( + generator: (before: T?, after: T?) -> R?, +): List { + if (isEmpty()) return emptyList() + val newList = mutableListOf() + 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 HashSet.addOrRemove(value: E, shouldAdd: Boolean) { if (shouldAdd) { add(value) diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt index 0baa1c8ee..479d02be6 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt @@ -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, + ) } } } diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt index b404a7f96..e72bb548d 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt @@ -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.toUpcomingUIModels(): ImmutableList { + 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.toEvents(): ImmutableMap { - return groupBy { it.expectedNextUpdate?.toLocalDate() ?: LocalDate.MAX } - .mapValues { it.value.size } + private fun List.toEvents(): ImmutableMap { + return filterIsInstance() + .associate { it.date to it.mangaCount } .toImmutableMap() } diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingUIModel.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingUIModel.kt index c394f45f6..758e1686e 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingUIModel.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingUIModel.kt @@ -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 }