package eu.kanade.presentation.manga import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton 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.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.TrackLogoIcon import eu.kanade.presentation.components.VerticalDivider import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.util.system.copyToClipboard import java.text.DateFormat private const val UnsetStatusTextAlpha = 0.5F @Composable fun TrackInfoDialogHome( trackItems: List, dateFormat: DateFormat, onStatusClick: (TrackItem) -> Unit, onChapterClick: (TrackItem) -> Unit, onScoreClick: (TrackItem) -> Unit, onStartDateEdit: (TrackItem) -> Unit, onEndDateEdit: (TrackItem) -> Unit, onNewSearch: (TrackItem) -> Unit, onOpenInBrowser: (TrackItem) -> Unit, onRemoved: (TrackItem) -> Unit, ) { Column( modifier = Modifier .animateContentSize() .fillMaxWidth() .verticalScroll(rememberScrollState()) .padding(16.dp) .windowInsetsPadding(WindowInsets.systemBars), verticalArrangement = Arrangement.spacedBy(24.dp), ) { trackItems.forEach { item -> if (item.track != null) { val supportsScoring = item.service.getScoreList().isNotEmpty() val supportsReadingDates = item.service.supportsReadingDates TrackInfoItem( title = item.track.title, service = item.service, status = item.service.getStatus(item.track.status), onStatusClick = { onStatusClick(item) }, chapters = "${item.track.last_chapter_read.toInt()}".let { val totalChapters = item.track.total_chapters if (totalChapters > 0) { // Add known total chapter count "$it / $totalChapters" } else { it } }, onChaptersClick = { onChapterClick(item) }, score = item.service.displayScore(item.track) .takeIf { supportsScoring && item.track.score != 0F }, onScoreClick = { onScoreClick(item) } .takeIf { supportsScoring }, startDate = remember(item.track.started_reading_date) { dateFormat.format(item.track.started_reading_date) } .takeIf { supportsReadingDates && item.track.started_reading_date != 0L }, onStartDateClick = { onStartDateEdit(item) } // TODO .takeIf { supportsReadingDates }, endDate = dateFormat.format(item.track.finished_reading_date) .takeIf { supportsReadingDates && item.track.finished_reading_date != 0L }, onEndDateClick = { onEndDateEdit(item) } .takeIf { supportsReadingDates }, onNewSearch = { onNewSearch(item) }, onOpenInBrowser = { onOpenInBrowser(item) }, onRemoved = { onRemoved(item) }, ) } else { TrackInfoItemEmpty( service = item.service, onNewSearch = { onNewSearch(item) }, ) } } } } @Composable private fun TrackInfoItem( title: String, service: TrackService, status: String, onStatusClick: () -> Unit, chapters: String, onChaptersClick: () -> Unit, score: String?, onScoreClick: (() -> Unit)?, startDate: String?, onStartDateClick: (() -> Unit)?, endDate: String?, onEndDateClick: (() -> Unit)?, onNewSearch: () -> Unit, onOpenInBrowser: () -> Unit, onRemoved: () -> Unit, ) { val context = LocalContext.current Column { Row( verticalAlignment = Alignment.CenterVertically, ) { TrackLogoIcon( service = service, onClick = onOpenInBrowser, ) Box( modifier = Modifier .height(48.dp) .weight(1f) .combinedClickable( onClick = onNewSearch, onLongClick = { context.copyToClipboard(title, title) }, ) .padding(start = 16.dp), contentAlignment = Alignment.CenterStart, ) { Text( text = title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium, ) } VerticalDivider() TrackInfoItemMenu( onOpenInBrowser = onOpenInBrowser, onRemoved = onRemoved, ) } Box( modifier = Modifier .padding(top = 12.dp) .clip(MaterialTheme.shapes.medium) .background(MaterialTheme.colorScheme.surface) .padding(8.dp) .clip(RoundedCornerShape(6.dp)), ) { Column { Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1f), text = status, onClick = onStatusClick, ) VerticalDivider() TrackDetailsItem( modifier = Modifier.weight(1f), text = chapters, onClick = onChaptersClick, ) if (onScoreClick != null) { VerticalDivider() TrackDetailsItem( modifier = Modifier .weight(1f) .alpha(if (score == null) UnsetStatusTextAlpha else 1f), text = score ?: stringResource(R.string.score), onClick = onScoreClick, ) } } if (onStartDateClick != null && onEndDateClick != null) { Divider() Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1F), text = startDate, placeholder = stringResource(R.string.track_started_reading_date), onClick = onStartDateClick, ) VerticalDivider() TrackDetailsItem( modifier = Modifier.weight(1F), text = endDate, placeholder = stringResource(R.string.track_finished_reading_date), onClick = onEndDateClick, ) } } } } } } @Composable private fun TrackDetailsItem( modifier: Modifier = Modifier, text: String?, placeholder: String = "", onClick: () -> Unit, ) { Box( modifier = modifier .clickable(onClick = onClick) .alpha(if (text == null) UnsetStatusTextAlpha else 1f) .fillMaxHeight() .padding(12.dp), contentAlignment = Alignment.Center, ) { Text( text = text ?: placeholder, maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, ) } } @Composable private fun TrackInfoItemEmpty( service: TrackService, onNewSearch: () -> Unit, ) { Row( verticalAlignment = Alignment.CenterVertically, ) { TrackLogoIcon(service) TextButton( onClick = onNewSearch, modifier = Modifier .padding(start = 16.dp) .weight(1f), ) { Text(text = stringResource(R.string.add_tracking)) } } } @Composable private fun TrackInfoItemMenu( onOpenInBrowser: () -> Unit, onRemoved: () -> Unit, ) { var expanded by remember { mutableStateOf(false) } Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) { IconButton(onClick = { expanded = true }) { Icon( imageVector = Icons.Default.MoreVert, contentDescription = stringResource(R.string.label_more), ) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, ) { DropdownMenuItem( text = { Text(stringResource(R.string.action_open_in_browser)) }, onClick = { onOpenInBrowser() expanded = false }, ) DropdownMenuItem( text = { Text(stringResource(R.string.action_remove)) }, onClick = { onRemoved() expanded = false }, ) } } }