Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ data class OcrSelection(
)

private val borderColor = Color(0, 170, 255, 180)
private val highlightColor = Color(130, 150, 200, 100)

@Composable
fun OcrBlockCanvas(
Expand All @@ -51,6 +50,8 @@ fun OcrBlockCanvas(
onEmptyTap: () -> Unit,
modifier: Modifier = Modifier,
) {
val highlightColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.45f)

Canvas(
modifier = modifier
.fillMaxSize()
Expand Down Expand Up @@ -89,6 +90,7 @@ fun OcrBlockCanvas(
selection = selection,
boxScaleX = boxScaleX,
boxScaleY = boxScaleY,
highlightColor = highlightColor,
)
}
}
Expand Down Expand Up @@ -163,6 +165,7 @@ private fun DrawScope.drawMatchHighlight(
selection: OcrSelection,
boxScaleX: Float,
boxScaleY: Float,
highlightColor: Color,
) {
val orderedIndices = block.orderedLineIndices()
val orderedSentence = orderedIndices.joinToString("") { block.lines[it] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ import eu.kanade.tachiyomi.ui.reader.viewer.fullText
import eu.kanade.tachiyomi.ui.reader.viewer.isLookupStartChar
import eu.kanade.tachiyomi.util.view.setComposeContent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import tachiyomi.core.common.i18n.stringResource as contextStringResource
import tachiyomi.i18n.MR
Expand All @@ -74,6 +78,8 @@ internal class ScreenLookupOverlayController(
private var cachedProfile: chimahon.anki.AnkiProfile? = null
private var overlayBackHandler: (() -> Boolean)? = null
private var overlayBackCallback: Any? = null
private val lookupWarmupScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var lookupWarmupJob: Job? = null

val isShowing: Boolean
get() = overlayView != null
Expand All @@ -88,6 +94,10 @@ internal class ScreenLookupOverlayController(
val webView = cachedWebView
?: prepareDictionaryWebViewShell(context, languageCode = profile.languageCode)
.also { cachedWebView = it }
lookupWarmupJob?.cancel()
lookupWarmupJob = lookupWarmupScope.launch {
Injekt.get<DictionaryRepository>().warmUp(getDictionaryPaths(context, profile), profile.id)
}

val owner = OverlayLifecycleOwner().also {
it.performCreate()
Expand Down Expand Up @@ -165,6 +175,8 @@ internal class ScreenLookupOverlayController(

fun release() {
dismiss(recycleScreenshot = true, notify = false)
lookupWarmupJob?.cancel()
lookupWarmupJob = null
cachedWebView?.runCatching { destroy() }
cachedWebView = null
cachedProfile = null
Expand Down Expand Up @@ -318,7 +330,12 @@ internal fun ScreenLookupOverlay(
onBlockTapped = { tapped, tapX, tapY ->
val charOffset = tapped.screenLookupCharOffset(tapX, tapY)
val text = tapped.fullText
if (charOffset in text.indices && isLookupStartChar(text[charOffset])) {
if (selection?.block == tapped && selection?.sentenceOffset == charOffset) {
selection = null
showTapHint = false
matchedCharCount = 0
matchOffset = 0
} else if (charOffset in text.indices && isLookupStartChar(text[charOffset])) {
val lookupString = extractOcrLookupString(text, charOffset)
if (lookupString.isNotBlank()) {
lookupNonce++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ fun PlayerControls(
label = "controls_transparent_overlay",
)
val openSubtitleLookup: (SubtitleLookupSelection) -> Unit = openSubtitleLookup@{ subtitleLookup ->
if (subtitleLookupRequest?.matchesTap(subtitleLookup) == true) {
subtitleLookupRequest = null
viewModel.unpause()
return@openSubtitleLookup
}
val currentPanel = viewModel.panelShown.value
if (
viewModel.sheetShown.value != Sheets.None ||
Expand Down Expand Up @@ -900,6 +905,7 @@ private fun PlayerSubtitleTextLayer(
val fontSizeSp = (subtitleFontSize * subtitleScale * fontSizeFactor).coerceIn(minFontSize, maxFontSize)
val resolvedBottomPadding = bottomPadding ?: (28f + (100 - subtitlePos).coerceIn(0, 100) * 2.2f).dp
val outlineWidth = borderSize.coerceAtLeast(1) * 1.8f
val lookupHighlightColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.45f)
val boxBackgroundColor = Color(backgroundColor).let {
if (it.alpha == 0f && borderStyle != SubtitlesBorderStyle.OutlineAndShadow) {
Color.Black.copy(alpha = 0.78f)
Expand Down Expand Up @@ -954,16 +960,17 @@ private fun PlayerSubtitleTextLayer(
}
}

val activeRequest = request?.takeIf { it.fullText == subtitleText } ?: return@drawBehind
val activeRequest = request?.takeIf {
it.fullText == subtitleText && it.matchedCharCount > 0
} ?: return@drawBehind
val start = (activeRequest.charOffset + activeRequest.matchOffset)
.coerceIn(0, subtitleText.length)
if (start >= subtitleText.length) return@drawBehind
val fallbackCount = activeRequest.lookupString.length.coerceAtLeast(1)
val count = activeRequest.matchedCharCount.takeIf { it > 0 } ?: fallbackCount
val count = activeRequest.matchedCharCount
val end = (start + count).coerceIn(start + 1, subtitleText.length)
layout.highlightRects(subtitleText, start, end).forEach { rect ->
drawRoundRect(
color = Color(130, 150, 200, 0x8A),
color = lookupHighlightColor,
topLeft = Offset(rect.left, rect.top),
size = Size(rect.width, rect.height),
cornerRadius = CornerRadius(6f, 6f),
Expand Down Expand Up @@ -1086,6 +1093,21 @@ private fun SubtitleLookupSelection.offsetBy(offset: Offset): SubtitleLookupSele
)
}

private fun SubtitleLookupRequest.matchesTap(selection: SubtitleLookupSelection): Boolean {
if (fullText != selection.fullText || lineIndex != selection.lineIndex) return false

val start = (charOffset + matchOffset).coerceIn(0, fullText.length)
val count = matchedCharCount.takeIf { it > 0 } ?: 1
val end = (start + count).coerceIn(start + 1, fullText.length)

return selection.tapCharOffset in start until end ||
(
selection.lookupString == lookupString &&
selection.tapCharOffset == tapCharOffset &&
selection.lineStartOffset == lineStartOffset
)
}

private fun TextLayoutResult.lookupOffsetForPosition(text: String, position: Offset): Int? {
if (text.isBlank()) return null
val lineIndex = getLineForVerticalPosition(position.y).coerceIn(0, lineCount - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.webkit.WebView
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
Expand All @@ -13,8 +14,11 @@ import chimahon.DictionaryRepository
import chimahon.MediaInfo
import eu.kanade.tachiyomi.ui.dictionary.DictionaryPopupWebViewWarmup
import eu.kanade.tachiyomi.ui.dictionary.DictionaryPreferences
import eu.kanade.tachiyomi.ui.dictionary.getDictionaryPaths
import eu.kanade.tachiyomi.ui.player.PlayerViewModel
import eu.kanade.tachiyomi.ui.reader.viewer.OcrLookupPopup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
Expand Down Expand Up @@ -49,8 +53,6 @@ internal fun PlayerSubtitleLookupPopup(
onTermMatched: (Int, Int) -> Unit,
modifier: Modifier = Modifier,
) {
if (request == null) return

val context = LocalContext.current
val dictionaryPreferences = remember { Injekt.get<DictionaryPreferences>() }
val repository = remember { Injekt.get<DictionaryRepository>() }
Expand All @@ -67,26 +69,34 @@ internal fun PlayerSubtitleLookupPopup(
DictionaryPopupWebViewWarmup.acquire(context, activeProfile.languageCode)
}

LaunchedEffect(activeProfile) {
withContext(Dispatchers.IO) {
repository.warmUp(getDictionaryPaths(context, activeProfile), activeProfile.id)
}
}

DisposableEffect(webView) {
onDispose {
DictionaryPopupWebViewWarmup.recycle(context, webView)
}
}

BackHandler(onBack = onDismiss)
BackHandler(enabled = request != null, onBack = onDismiss)

val visible = request != null

OcrLookupPopup(
visible = true,
lookupString = request.lookupString,
fullText = request.fullText,
charOffset = request.charOffset,
visible = visible,
lookupString = request?.lookupString.orEmpty(),
fullText = request?.fullText.orEmpty(),
charOffset = request?.charOffset ?: 0,
onDismiss = onDismiss,
webView = webView,
repository = repository,
anchorX = request.anchorX,
anchorY = request.anchorY,
anchorWidth = request.anchorWidth,
anchorHeight = request.anchorHeight,
anchorX = request?.anchorX ?: 0f,
anchorY = request?.anchorY ?: 0f,
anchorWidth = request?.anchorWidth ?: 0f,
anchorHeight = request?.anchorHeight ?: 0f,
isVertical = false,
activeProfile = activeProfile,
type = "anime",
Expand All @@ -99,8 +109,8 @@ internal fun PlayerSubtitleLookupPopup(
},
onRequestSentenceAudio = {
viewModel.captureSubtitleAudioForAnki(
startSeconds = request.cueStartSeconds,
endSeconds = request.cueEndSeconds,
startSeconds = request?.cueStartSeconds,
endSeconds = request?.cueEndSeconds,
)
},
usePopup = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.ui.dictionary.OcrBlockCanvas
import eu.kanade.tachiyomi.ui.dictionary.OcrSelection
import eu.kanade.tachiyomi.ui.dictionary.OcrStatusOverlay
import eu.kanade.tachiyomi.ui.dictionary.OcrTapHint
import eu.kanade.tachiyomi.ui.dictionary.getDictionaryPaths
import eu.kanade.tachiyomi.ui.dictionary.screenLookupCharOffset
import eu.kanade.tachiyomi.ui.dictionary.toScreenLookupBlocks
import eu.kanade.tachiyomi.ui.player.PlayerViewModel
Expand Down Expand Up @@ -79,6 +80,12 @@ internal fun PlayerVideoOcrOverlay(
val boxScaleX = dictionaryPreferences.ocrBoxScaleX().get()
val boxScaleY = dictionaryPreferences.ocrBoxScaleY().get()

LaunchedEffect(activeProfile) {
withContext(Dispatchers.IO) {
repository.warmUp(getDictionaryPaths(context, activeProfile), activeProfile.id)
}
}

DisposableEffect(webView) {
onDispose {
DictionaryPopupWebViewWarmup.recycle(context, webView)
Expand Down Expand Up @@ -146,7 +153,12 @@ internal fun PlayerVideoOcrOverlay(
val charOffset = tapped.screenLookupCharOffset(tapX, tapY)
val orderedCharOffset = tapped.toOrderedOffset(charOffset)
val text = tapped.orderedFullText
if (orderedCharOffset in text.indices && isLookupStartChar(text[orderedCharOffset])) {
if (selection?.block == tapped && selection?.sentenceOffset == orderedCharOffset) {
selection = null
showTapHint = false
matchedCharCount = 0
matchOffset = 0
} else if (orderedCharOffset in text.indices && isLookupStartChar(text[orderedCharOffset])) {
val lookupString = extractOcrLookupString(text, orderedCharOffset)
if (lookupString.isNotBlank()) {
lookupNonce++
Expand Down
Loading