From a8040cb21a45bed3c9be85fab12149a1a734a7f7 Mon Sep 17 00:00:00 2001 From: Caleb Morris Date: Sun, 7 Jan 2024 20:49:51 -0700 Subject: [PATCH] [track-search] Added context menu for copy and open-in-web (#10352) --- .../presentation/track/TrackerSearch.kt | 91 ++++++++++++++----- 1 file changed, 69 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt index 3ed269e2f..08738c2ee 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -22,7 +23,6 @@ import androidx.compose.foundation.layout.paddingFromBaseline import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions @@ -33,6 +33,7 @@ import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -40,7 +41,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar 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.clip @@ -48,7 +52,11 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue @@ -58,9 +66,11 @@ import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.util.system.openInBrowser import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold @@ -188,13 +198,7 @@ fun TrackerSearch( key = { it.hashCode() }, ) { SearchResultItem( - title = it.title, - coverUrl = it.cover_url, - type = it.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current), - startDate = it.start_date, - status = it.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current), - score = it.score, - description = it.summary.trim(), + trackSearch = it, selected = it == selected, onClick = { onSelectedChange(it) }, ) @@ -214,18 +218,18 @@ fun TrackerSearch( @Composable private fun SearchResultItem( - title: String, - coverUrl: String, - type: String, - startDate: String, - status: String, - score: Float, - description: String, + trackSearch: TrackSearch, selected: Boolean, onClick: () -> Unit, ) { + val context = LocalContext.current + val clipboardManager: ClipboardManager = LocalClipboardManager.current + val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) + val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) + val description = trackSearch.summary.trim() val shape = RoundedCornerShape(16.dp) val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent + var dropDownMenuExpanded by remember { mutableStateOf(false) } Box( modifier = Modifier .fillMaxWidth() @@ -237,7 +241,10 @@ private fun SearchResultItem( color = borderColor, shape = shape, ) - .selectable(selected = selected, onClick = onClick) + .combinedClickable( + onLongClick = { dropDownMenuExpanded = true }, + onClick = onClick, + ) .padding(12.dp), ) { if (selected) { @@ -251,28 +258,41 @@ private fun SearchResultItem( Column { Row { MangaCover.Book( - data = coverUrl, + data = trackSearch.cover_url, modifier = Modifier.height(96.dp), ) Spacer(modifier = Modifier.width(12.dp)) Column { Text( - text = title, + text = trackSearch.title, modifier = Modifier.padding(end = 28.dp), maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium, ) + SearchResultItemDropDownMenu( + expanded = dropDownMenuExpanded, + onCollapseMenu = { dropDownMenuExpanded = false }, + onCopyName = { + clipboardManager.setText(AnnotatedString(trackSearch.title)) + }, + onOpenInBrowser = { + val url = trackSearch.tracking_url + if (url.isNotBlank()) { + context.openInBrowser(url) + } + }, + ) if (type.isNotBlank()) { SearchResultItemDetails( title = stringResource(MR.strings.track_type), text = type, ) } - if (startDate.isNotBlank()) { + if (trackSearch.start_date.isNotBlank()) { SearchResultItemDetails( title = stringResource(MR.strings.label_started), - text = startDate, + text = trackSearch.start_date, ) } if (status.isNotBlank()) { @@ -281,10 +301,10 @@ private fun SearchResultItem( text = status, ) } - if (score != -1f) { + if (trackSearch.score != -1f) { SearchResultItemDetails( title = stringResource(MR.strings.score), - text = score.toString(), + text = trackSearch.score.toString(), ) } } @@ -304,6 +324,33 @@ private fun SearchResultItem( } } +@Composable +private fun SearchResultItemDropDownMenu( + expanded: Boolean, + onCollapseMenu: () -> Unit, + onCopyName: () -> Unit, + onOpenInBrowser: () -> Unit, +) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onCollapseMenu, + ) { + DropdownMenuItem( + text = { Text(stringResource(MR.strings.action_copy_to_clipboard)) }, + onClick = { + onCopyName() + onCollapseMenu() + }, + ) + DropdownMenuItem( + text = { Text(stringResource(MR.strings.action_open_in_browser)) }, + onClick = { + onOpenInBrowser() + }, + ) + } +} + @Composable private fun SearchResultItemDetails( title: String,