Require native Git runtime and bundle full manpages
- Remove the app fallback path when native Git is unavailable and show a startup blocker instead - Fix native level setup for staged file counting and cherry-pick parity - Improve manpage loading/search and bundle full Git documentation assets - Integrate Git cross-compilation into AndroidProjectTooling.sh and remove debug AAB support
This commit is contained in:
@@ -27,6 +27,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -38,6 +41,13 @@ data class ManPageState(
|
||||
val content: String,
|
||||
)
|
||||
|
||||
private data class ManPageMatch(
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val lineIndex: Int,
|
||||
val columnIndex: Int,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ManPageDialog(
|
||||
state: ManPageState,
|
||||
@@ -52,21 +62,52 @@ fun ManPageDialog(
|
||||
if (searchQuery.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
contentLines.mapIndexedNotNull { index, line ->
|
||||
index.takeIf { line.contains(searchQuery, ignoreCase = true) }
|
||||
val result = mutableListOf<ManPageMatch>()
|
||||
var absoluteLineStart = 0
|
||||
contentLines.forEachIndexed { lineIndex, line ->
|
||||
var searchFrom = 0
|
||||
while (searchFrom <= line.length) {
|
||||
val columnIndex = line.indexOf(searchQuery, searchFrom, ignoreCase = true)
|
||||
if (columnIndex == -1) break
|
||||
val start = absoluteLineStart + columnIndex
|
||||
result += ManPageMatch(
|
||||
start = start,
|
||||
end = start + searchQuery.length,
|
||||
lineIndex = lineIndex,
|
||||
columnIndex = columnIndex,
|
||||
)
|
||||
searchFrom = columnIndex + searchQuery.length.coerceAtLeast(1)
|
||||
}
|
||||
absoluteLineStart += line.length + 1
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
val matchCount = matches.size
|
||||
val currentMatchNumber = if (matchCount == 0) 0 else selectedMatch + 1
|
||||
val highlightedContent = remember(state.content, matches, selectedMatch) {
|
||||
AnnotatedString.Builder(state.content).apply {
|
||||
matches.forEachIndexed { index, match ->
|
||||
addStyle(
|
||||
SpanStyle(
|
||||
color = if (index == selectedMatch) Color.White else Color.Black,
|
||||
background = if (index == selectedMatch) Color(0xFFD32F2F) else Color(0xFFFFEB3B),
|
||||
),
|
||||
match.start,
|
||||
match.end,
|
||||
)
|
||||
}
|
||||
}.toAnnotatedString()
|
||||
}
|
||||
|
||||
LaunchedEffect(searchQuery, matchCount) {
|
||||
selectedMatch = selectedMatch.coerceIn(0, (matchCount - 1).coerceAtLeast(0))
|
||||
}
|
||||
|
||||
LaunchedEffect(selectedMatch, matches) {
|
||||
val lineIndex = matches.getOrNull(selectedMatch) ?: return@LaunchedEffect
|
||||
verticalScrollState.animateScrollTo((lineIndex * 18).coerceAtMost(verticalScrollState.maxValue))
|
||||
val match = matches.getOrNull(selectedMatch) ?: return@LaunchedEffect
|
||||
verticalScrollState.animateScrollTo((match.lineIndex * 18).coerceAtMost(verticalScrollState.maxValue))
|
||||
horizontalScrollState.animateScrollTo((match.columnIndex * 8).coerceAtMost(horizontalScrollState.maxValue))
|
||||
}
|
||||
|
||||
Dialog(onDismissRequest = onClose) {
|
||||
@@ -170,7 +211,7 @@ fun ManPageDialog(
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = state.content,
|
||||
text = highlightedContent,
|
||||
modifier = Modifier.widthIn(min = 1200.dp),
|
||||
color = TextPrimary,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
|
||||
Reference in New Issue
Block a user