package eu.kanade.presentation.updates import android.text.format.DateUtils import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.domain.updates.model.UpdatesWithRelations import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadIndicator import eu.kanade.presentation.components.ListGroupHeader import eu.kanade.presentation.components.MangaCover import eu.kanade.presentation.manga.components.DotSeparatorText import eu.kanade.presentation.util.ReadItemAlpha import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.selectedBackground import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.updates.UpdatesItem import java.util.Date import kotlin.time.Duration.Companion.minutes fun LazyListScope.updatesLastUpdatedItem( lastUpdated: Long, ) { item(key = "updates-lastUpdated") { val time = remember(lastUpdated) { val now = Date().time if (now - lastUpdated < 1.minutes.inWholeMilliseconds) { null } else { DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS) } } Box( modifier = Modifier .animateItemPlacement() .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), ) { Text( text = if (time.isNullOrEmpty()) { stringResource(R.string.updates_last_update_info, stringResource(R.string.updates_last_update_info_just_now)) } else { stringResource(R.string.updates_last_update_info, time) }, fontStyle = FontStyle.Italic, ) } } } fun LazyListScope.updatesUiItems( uiModels: List, selectionMode: Boolean, onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, onClickCover: (UpdatesItem) -> Unit, onClickUpdate: (UpdatesItem) -> Unit, onDownloadChapter: (List, ChapterDownloadAction) -> Unit, ) { items( items = uiModels, contentType = { when (it) { is UpdatesUiModel.Header -> "header" is UpdatesUiModel.Item -> "item" } }, key = { when (it) { is UpdatesUiModel.Header -> "updatesHeader-${it.hashCode()}" is UpdatesUiModel.Item -> "updates-${it.item.update.mangaId}-${it.item.update.chapterId}" } }, ) { item -> when (item) { is UpdatesUiModel.Header -> { ListGroupHeader( modifier = Modifier.animateItemPlacement(), text = item.date, ) } is UpdatesUiModel.Item -> { val updatesItem = item.item UpdatesUiItem( modifier = Modifier.animateItemPlacement(), update = updatesItem.update, selected = updatesItem.selected, readProgress = updatesItem.update.lastPageRead .takeIf { !updatesItem.update.read && it > 0L } ?.let { stringResource( R.string.chapter_progress, it + 1, ) }, onLongClick = { onUpdateSelected(updatesItem, !updatesItem.selected, true, true) }, onClick = { when { selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false) else -> onClickUpdate(updatesItem) } }, onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode }, onDownloadChapter = { action: ChapterDownloadAction -> onDownloadChapter(listOf(updatesItem), action) }.takeIf { !selectionMode }, downloadStateProvider = updatesItem.downloadStateProvider, downloadProgressProvider = updatesItem.downloadProgressProvider, ) } } } } @Composable fun UpdatesUiItem( modifier: Modifier, update: UpdatesWithRelations, selected: Boolean, readProgress: String?, onClick: () -> Unit, onLongClick: () -> Unit, onClickCover: (() -> Unit)?, onDownloadChapter: ((ChapterDownloadAction) -> Unit)?, // Download Indicator downloadStateProvider: () -> Download.State, downloadProgressProvider: () -> Int, ) { val haptic = LocalHapticFeedback.current Row( modifier = modifier .selectedBackground(selected) .combinedClickable( onClick = onClick, onLongClick = { onLongClick() haptic.performHapticFeedback(HapticFeedbackType.LongPress) }, ) .height(56.dp) .padding(horizontal = MaterialTheme.padding.medium), verticalAlignment = Alignment.CenterVertically, ) { MangaCover.Square( modifier = Modifier .padding(vertical = 6.dp) .fillMaxHeight(), data = update.coverData, onClick = onClickCover, ) Column( modifier = Modifier .padding(horizontal = MaterialTheme.padding.medium) .weight(1f), ) { val bookmark = remember(update.bookmark) { update.bookmark } val read = remember(update.read) { update.read } val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f } Text( text = update.mangaTitle, maxLines = 1, style = MaterialTheme.typography.bodyMedium, overflow = TextOverflow.Ellipsis, modifier = Modifier.alpha(textAlpha), ) Row(verticalAlignment = Alignment.CenterVertically) { var textHeight by remember { mutableStateOf(0) } if (bookmark) { Icon( imageVector = Icons.Filled.Bookmark, contentDescription = stringResource(R.string.action_filter_bookmarked), modifier = Modifier .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }), tint = MaterialTheme.colorScheme.primary, ) Spacer(modifier = Modifier.width(2.dp)) } Text( text = update.chapterName, maxLines = 1, style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, modifier = Modifier .weight(weight = 1f, fill = false) .alpha(textAlpha), ) if (readProgress != null) { DotSeparatorText() Text( text = readProgress, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.alpha(ReadItemAlpha), ) } } } ChapterDownloadIndicator( enabled = onDownloadChapter != null, modifier = Modifier.padding(start = 4.dp), downloadStateProvider = downloadStateProvider, downloadProgressProvider = downloadProgressProvider, onClick = { onDownloadChapter?.invoke(it) }, ) } }