v1.0.4 as published

This commit is contained in:
Sergey Chernov 2026-05-25 00:10:15 +03:00
parent 17c885b3f5
commit 65925f2a07
11 changed files with 210588 additions and 5 deletions

View File

@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
@ -1097,7 +1098,14 @@ private fun Modifier.readerLinkTapHandler(
): Modifier = pointerInput(annotatedText, textLayout, onLinkOpen) { ): Modifier = pointerInput(annotatedText, textLayout, onLinkOpen) {
awaitEachGesture { awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Main) 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) { if (link == null) {
waitForUpOrCancellation(pass = PointerEventPass.Main) waitForUpOrCancellation(pass = PointerEventPass.Main)
return@awaitEachGesture return@awaitEachGesture
@ -1112,11 +1120,78 @@ private fun Modifier.readerLinkTapHandler(
} }
} }
private fun TextLayoutResult.readerLinkAt(text: AnnotatedString, position: Offset): String? { private fun TextLayoutResult.readerLinkAt(
val offset = getOffsetForPosition(position) text: AnnotatedString,
return text.getStringAnnotations(ReaderLinkAnnotationTag, offset, offset) position: Offset,
.firstOrNull() 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 ?.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 = private fun AnnotatedString.hasReaderLinks(): Boolean =
@ -1537,6 +1612,10 @@ private const val CombiningAcuteAccent = '\u0301'
private const val ReadAloudStressableLetters = "аеёиоуыэюяАЕЁИОУЫЭЮЯaeiouyAEIOUY" private const val ReadAloudStressableLetters = "аеёиоуыэюяАЕЁИОУЫЭЮЯaeiouyAEIOUY"
private const val ReaderLinkAnnotationTag = "fb2-link" private const val ReaderLinkAnnotationTag = "fb2-link"
private val LinkTextColor = Color(0xFF0B57D0) 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( private val ReadAloudHardcodedTextReplacements = listOf(
ReadAloudTextReplacement(from = "Господа,", to = "Господ/а,", caseSensitive = true), ReadAloudTextReplacement(from = "Господа,", to = "Господ/а,", caseSensitive = true),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

File diff suppressed because one or more lines are too long