1818
1919package org.kiwix.kiwixmobile.core.ui.components
2020
21+ import androidx.compose.foundation.background
22+ import androidx.compose.foundation.clickable
2123import androidx.compose.foundation.isSystemInDarkTheme
24+ import androidx.compose.foundation.layout.Column
25+ import androidx.compose.foundation.layout.Row
26+ import androidx.compose.foundation.layout.fillMaxWidth
27+ import androidx.compose.foundation.layout.heightIn
28+ import androidx.compose.foundation.layout.padding
2229import androidx.compose.foundation.text.KeyboardActions
2330import androidx.compose.foundation.text.KeyboardOptions
2431import androidx.compose.material3.Icon
@@ -29,22 +36,42 @@ import androidx.compose.material3.TextField
2936import androidx.compose.material3.TextFieldDefaults
3037import androidx.compose.material3.minimumInteractiveComponentSize
3138import androidx.compose.runtime.Composable
39+ import androidx.compose.runtime.MutableState
3240import androidx.compose.runtime.SideEffect
41+ import androidx.compose.runtime.mutableStateOf
42+ import androidx.compose.runtime.remember
43+ import androidx.compose.ui.Alignment
3344import androidx.compose.ui.Modifier
3445import androidx.compose.ui.focus.FocusRequester
3546import androidx.compose.ui.focus.focusRequester
47+ import androidx.compose.ui.geometry.Rect
3648import androidx.compose.ui.graphics.Color
49+ import androidx.compose.ui.layout.onGloballyPositioned
50+ import androidx.compose.ui.layout.positionInRoot
3751import androidx.compose.ui.platform.LocalSoftwareKeyboardController
3852import androidx.compose.ui.platform.testTag
3953import androidx.compose.ui.res.painterResource
4054import androidx.compose.ui.res.stringResource
4155import androidx.compose.ui.semantics.contentDescription
4256import androidx.compose.ui.semantics.semantics
57+ import androidx.compose.ui.text.AnnotatedString
58+ import androidx.compose.ui.text.SpanStyle
4359import androidx.compose.ui.text.TextStyle
60+ import androidx.compose.ui.text.buildAnnotatedString
61+ import androidx.compose.ui.text.font.FontWeight
4462import androidx.compose.ui.text.input.ImeAction
4563import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis
64+ import androidx.compose.ui.text.withStyle
65+ import androidx.compose.ui.unit.IntOffset
66+ import androidx.compose.ui.unit.toSize
67+ import androidx.compose.ui.window.Popup
4668import org.kiwix.kiwixmobile.core.R
69+ import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
4770import org.kiwix.kiwixmobile.core.utils.ComposeDimens
71+ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.EIGHT_DP
72+ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.MINIMUM_HEIGHT_OF_SEARCH_ITEM
73+ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SEARCH_ITEM_TEXT_SIZE
74+ import kotlin.math.roundToInt
4875
4976@Composable
5077fun KiwixSearchView (
@@ -53,9 +80,94 @@ fun KiwixSearchView(
5380 placeholder : String = stringResource(R .string.search_label),
5481 searchViewTextFiledTestTag : String = "",
5582 clearButtonTestTag : String = "",
83+ suggestionText : String? = null,
84+ onSuggestedWordClick : (String ) -> Unit = {},
5685 onValueChange : (String ) -> Unit ,
5786 onClearClick : () -> Unit ,
5887 onKeyboardSubmitButtonClick : (String ) -> Unit = {}
88+ ) {
89+ val textFieldBounds = remember { mutableStateOf(Rect .Zero ) }
90+ Column {
91+ SearchViewTextFiled (
92+ modifier,
93+ searchViewTextFiledTestTag,
94+ value,
95+ placeholder,
96+ onValueChange,
97+ onClearClick,
98+ clearButtonTestTag,
99+ onKeyboardSubmitButtonClick,
100+ textFieldBounds
101+ )
102+ ShowCorrectWordSuggestion (suggestionText, onSuggestedWordClick, textFieldBounds)
103+ }
104+ }
105+
106+ @Composable
107+ private fun ShowCorrectWordSuggestion (
108+ suggestionText : String? ,
109+ onSuggestedWordClick : (String ) -> Unit ,
110+ textFieldBounds : MutableState <Rect >
111+ ) {
112+ if (suggestionText.isNullOrBlank()) return
113+ Popup (
114+ alignment = Alignment .TopStart ,
115+ offset = IntOffset (
116+ x = textFieldBounds.value.left.roundToInt(),
117+ y = textFieldBounds.value.bottom.roundToInt()
118+ )
119+ ) {
120+ Row (
121+ modifier = Modifier
122+ .fillMaxWidth()
123+ .heightIn(min = MINIMUM_HEIGHT_OF_SEARCH_ITEM )
124+ .background(MaterialTheme .colorScheme.background)
125+ .clickable { onSuggestedWordClick(suggestionText) },
126+ verticalAlignment = Alignment .CenterVertically
127+ ) {
128+ Text (
129+ text = getSuggestedHighlightedText(suggestionText),
130+ modifier = Modifier
131+ .padding(horizontal = EIGHT_DP )
132+ .weight(1f ),
133+ fontSize = SEARCH_ITEM_TEXT_SIZE
134+ )
135+ Icon (
136+ painter = painterResource(id = R .drawable.ic_open_in_new_24dp),
137+ contentDescription = stringResource(id = R .string.suggested_search_icon_description),
138+ modifier = Modifier .padding(horizontal = EIGHT_DP )
139+ )
140+ }
141+ }
142+ }
143+
144+ @Composable
145+ private fun getSuggestedHighlightedText (suggestionText : String ): AnnotatedString {
146+ val rawString = stringResource(R .string.suggest_search_text)
147+ val parts = rawString.split(" %s" )
148+ val before = parts.getOrNull(ZERO ).orEmpty()
149+ val after = parts.getOrNull(ONE ).orEmpty()
150+ return buildAnnotatedString {
151+ append(before)
152+ withStyle(SpanStyle (fontWeight = FontWeight .Bold )) {
153+ append(suggestionText)
154+ }
155+ append(after)
156+ }
157+ }
158+
159+ @Suppress(" LongParameterList" , " LongMethod" )
160+ @Composable
161+ private fun SearchViewTextFiled (
162+ modifier : Modifier ,
163+ searchViewTextFiledTestTag : String ,
164+ value : String ,
165+ placeholder : String ,
166+ onValueChange : (String ) -> Unit ,
167+ onClearClick : () -> Unit ,
168+ clearButtonTestTag : String ,
169+ onKeyboardSubmitButtonClick : (String ) -> Unit ,
170+ textFieldBounds : MutableState <Rect >
59171) {
60172 val hintColor = if (isSystemInDarkTheme()) {
61173 Color .LightGray
@@ -78,7 +190,17 @@ fun KiwixSearchView(
78190 .testTag(searchViewTextFiledTestTag)
79191 .minimumInteractiveComponentSize()
80192 .focusRequester(focusRequester)
81- .semantics { contentDescription = placeholder },
193+ .semantics { contentDescription = placeholder }
194+ .onGloballyPositioned { coordinates ->
195+ val position = coordinates.positionInRoot()
196+ val size = coordinates.size.toSize()
197+ textFieldBounds.value = Rect (
198+ position.x,
199+ position.y,
200+ position.x + size.width,
201+ position.y + size.height
202+ )
203+ },
82204 singleLine = true ,
83205 value = value,
84206 placeholder = {
0 commit comments