v1.0.4 as published
@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
@ -1097,7 +1098,14 @@ private fun Modifier.readerLinkTapHandler(
|
||||
): Modifier = pointerInput(annotatedText, textLayout, onLinkOpen) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Main)
|
||||
val link = textLayout?.readerLinkAt(annotatedText, down.position)
|
||||
val link = textLayout?.readerLinkAt(
|
||||
text = annotatedText,
|
||||
position = down.position,
|
||||
minimumTouchWidthPx = ReaderLinkMinimumTouchWidth.toPx(),
|
||||
minimumTouchHeightPx = ReaderLinkMinimumTouchHeight.toPx(),
|
||||
horizontalPaddingPx = ReaderLinkHorizontalTouchPadding.toPx(),
|
||||
verticalPaddingPx = ReaderLinkVerticalTouchPadding.toPx(),
|
||||
)
|
||||
if (link == null) {
|
||||
waitForUpOrCancellation(pass = PointerEventPass.Main)
|
||||
return@awaitEachGesture
|
||||
@ -1112,11 +1120,78 @@ private fun Modifier.readerLinkTapHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun TextLayoutResult.readerLinkAt(text: AnnotatedString, position: Offset): String? {
|
||||
val offset = getOffsetForPosition(position)
|
||||
return text.getStringAnnotations(ReaderLinkAnnotationTag, offset, offset)
|
||||
.firstOrNull()
|
||||
private fun TextLayoutResult.readerLinkAt(
|
||||
text: AnnotatedString,
|
||||
position: Offset,
|
||||
minimumTouchWidthPx: Float,
|
||||
minimumTouchHeightPx: Float,
|
||||
horizontalPaddingPx: Float,
|
||||
verticalPaddingPx: Float,
|
||||
): String? =
|
||||
text.getStringAnnotations(ReaderLinkAnnotationTag, 0, text.length)
|
||||
.firstOrNull { annotation ->
|
||||
readerLinkTouchBounds(
|
||||
start = annotation.start,
|
||||
end = annotation.end,
|
||||
minimumTouchWidthPx = minimumTouchWidthPx,
|
||||
minimumTouchHeightPx = minimumTouchHeightPx,
|
||||
horizontalPaddingPx = horizontalPaddingPx,
|
||||
verticalPaddingPx = verticalPaddingPx,
|
||||
).any { it.contains(position) }
|
||||
}
|
||||
?.item
|
||||
|
||||
private fun TextLayoutResult.readerLinkTouchBounds(
|
||||
start: Int,
|
||||
end: Int,
|
||||
minimumTouchWidthPx: Float,
|
||||
minimumTouchHeightPx: Float,
|
||||
horizontalPaddingPx: Float,
|
||||
verticalPaddingPx: Float,
|
||||
): List<Rect> {
|
||||
if (start >= end) return emptyList()
|
||||
|
||||
val boundsByLine = linkedMapOf<Int, Rect>()
|
||||
val safeEnd = min(end, layoutInput.text.length)
|
||||
for (offset in start until safeEnd) {
|
||||
val line = getLineForOffset(offset)
|
||||
val bounds = getBoundingBox(offset)
|
||||
boundsByLine[line] = boundsByLine[line]?.union(bounds) ?: bounds
|
||||
}
|
||||
|
||||
return boundsByLine.values.map { bounds ->
|
||||
bounds.withCenteredTouchPadding(
|
||||
minimumWidth = minimumTouchWidthPx,
|
||||
minimumHeight = minimumTouchHeightPx,
|
||||
horizontalPadding = horizontalPaddingPx,
|
||||
verticalPadding = verticalPaddingPx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Rect.union(other: Rect): Rect =
|
||||
Rect(
|
||||
left = min(left, other.left),
|
||||
top = min(top, other.top),
|
||||
right = max(right, other.right),
|
||||
bottom = max(bottom, other.bottom),
|
||||
)
|
||||
|
||||
private fun Rect.withCenteredTouchPadding(
|
||||
minimumWidth: Float,
|
||||
minimumHeight: Float,
|
||||
horizontalPadding: Float,
|
||||
verticalPadding: Float,
|
||||
): Rect {
|
||||
val touchWidth = max(width + horizontalPadding * 2f, minimumWidth)
|
||||
val touchHeight = max(height + verticalPadding * 2f, minimumHeight)
|
||||
val center = center
|
||||
return Rect(
|
||||
left = center.x - touchWidth / 2f,
|
||||
top = center.y - touchHeight / 2f,
|
||||
right = center.x + touchWidth / 2f,
|
||||
bottom = center.y + touchHeight / 2f,
|
||||
)
|
||||
}
|
||||
|
||||
private fun AnnotatedString.hasReaderLinks(): Boolean =
|
||||
@ -1537,6 +1612,10 @@ private const val CombiningAcuteAccent = '\u0301'
|
||||
private const val ReadAloudStressableLetters = "аеёиоуыэюяАЕЁИОУЫЭЮЯaeiouyAEIOUY"
|
||||
private const val ReaderLinkAnnotationTag = "fb2-link"
|
||||
private val LinkTextColor = Color(0xFF0B57D0)
|
||||
private val ReaderLinkMinimumTouchWidth = 44.dp
|
||||
private val ReaderLinkMinimumTouchHeight = 40.dp
|
||||
private val ReaderLinkHorizontalTouchPadding = 10.dp
|
||||
private val ReaderLinkVerticalTouchPadding = 6.dp
|
||||
|
||||
private val ReadAloudHardcodedTextReplacements = listOf(
|
||||
ReadAloudTextReplacement(from = "Господа,", to = "Господ/а,", caseSensitive = true),
|
||||
|
||||
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 171 KiB |
BIN
screenshots/Screenshot_20260524_220504.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
screenshots/Screenshot_20260524_220656.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
screenshots/Screenshot_20260524_220732.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
screenshots/Screenshot_20260524_220803.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
screenshots/Screenshot_20260524_220818.png
Normal file
|
After Width: | Height: | Size: 203 KiB |