mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Add Quantity Badge to Upcoming Screen (#1250)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
		| @@ -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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user