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.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),
|
||||||
|
|||||||
|
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 |