@@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
9
9
import androidx.compose.foundation.layout.padding
10
10
import androidx.compose.foundation.layout.size
11
11
import androidx.compose.foundation.lazy.LazyColumn
12
+ import androidx.compose.foundation.lazy.LazyListState
12
13
import androidx.compose.foundation.lazy.itemsIndexed
14
+ import androidx.compose.foundation.lazy.rememberLazyListState
13
15
import androidx.compose.material3.Icon
14
16
import androidx.compose.material3.LocalTextStyle
15
17
import androidx.compose.material3.Text
@@ -26,6 +28,12 @@ import androidx.compose.ui.text.TextStyle
26
28
import androidx.compose.ui.unit.Dp
27
29
import androidx.compose.ui.unit.dp
28
30
import androidx.compose.ui.unit.times
31
+ import com.sebastianneubauer.jsontree.search.JsonTreeSearch
32
+ import com.sebastianneubauer.jsontree.search.SearchState
33
+ import com.sebastianneubauer.jsontree.search.SearchState.SearchResult
34
+ import com.sebastianneubauer.jsontree.search.rememberSearchState
35
+ import com.sebastianneubauer.jsontree.util.rememberCollapsableText
36
+ import com.sebastianneubauer.jsontree.util.rememberPrimitiveText
29
37
import jsontree.jsontree.generated.resources.Res
30
38
import jsontree.jsontree.generated.resources.jsontree_arrow_right
31
39
import kotlinx.coroutines.Dispatchers
@@ -67,6 +75,7 @@ public fun JsonTree(
67
75
showIndices : Boolean = false,
68
76
showItemCount : Boolean = true,
69
77
expandSingleChildren : Boolean = false,
78
+ searchState : SearchState = rememberSearchState(),
70
79
onError : (Throwable ) -> Unit = {}
71
80
) {
72
81
val jsonParser = remember(json) {
@@ -76,12 +85,18 @@ public fun JsonTree(
76
85
mainDispatcher = Dispatchers .Main
77
86
)
78
87
}
79
- val coroutineScope = rememberCoroutineScope()
88
+ val jsonSearch = remember {
89
+ JsonTreeSearch (defaultDispatcher = Dispatchers .Default )
90
+ }
80
91
81
92
LaunchedEffect (jsonParser, initialState) {
93
+ searchState.reset()
82
94
jsonParser.init (initialState)
83
95
}
84
96
97
+ val lazyListState = rememberLazyListState()
98
+ val coroutineScope = rememberCoroutineScope()
99
+
85
100
when (val state = jsonParser.state.value) {
86
101
is JsonTreeParserState .Ready -> {
87
102
Box (modifier = modifier) {
@@ -94,15 +109,40 @@ public fun JsonTree(
94
109
textStyle = textStyle,
95
110
showIndices = showIndices,
96
111
showItemCount = showItemCount,
112
+ searchResult = searchState.state,
113
+ lazyListState = lazyListState,
97
114
onClick = {
98
115
coroutineScope.launch {
116
+ // reset the search state, because the result indices won't match the new list
117
+ searchState.reset()
99
118
jsonParser.expandOrCollapseItem(
100
119
item = it,
101
120
expandSingleChildren = expandSingleChildren
102
121
)
103
122
}
104
123
}
105
124
)
125
+
126
+ val searchQuery = searchState.state.query
127
+ LaunchedEffect (searchQuery) {
128
+ if (searchQuery == null ) {
129
+ searchState.reset()
130
+ } else {
131
+ val expandedList = jsonParser.expandAllItems()
132
+ val searchResult = jsonSearch.search(
133
+ searchQuery = searchQuery,
134
+ jsonTreeList = expandedList,
135
+ )
136
+ searchState.state = searchResult
137
+ }
138
+ }
139
+
140
+ val selectedListIndex = searchState.state.selectedOccurrence?.occurrence?.listIndex
141
+ LaunchedEffect (selectedListIndex) {
142
+ if (selectedListIndex != null && selectedListIndex > - 1 && ! lazyListState.isScrollInProgress) {
143
+ lazyListState.animateScrollToItem(selectedListIndex)
144
+ }
145
+ }
106
146
}
107
147
}
108
148
is JsonTreeParserState .Loading -> onLoading()
@@ -121,14 +161,24 @@ private fun JsonTreeList(
121
161
textStyle : TextStyle ,
122
162
showIndices : Boolean ,
123
163
showItemCount : Boolean ,
164
+ searchResult : SearchResult ,
165
+ lazyListState : LazyListState ,
124
166
onClick : (JsonTreeElement ) -> Unit ,
125
167
) {
126
168
LazyColumn (
169
+ state = lazyListState,
127
170
contentPadding = contentPadding
128
171
) {
129
172
itemsIndexed(items, key = { _, item -> item.id }) { index, item ->
130
173
when (item) {
131
174
is JsonTreeElement .Collapsable .Array -> {
175
+ val searchOccurrence = searchResult.occurrences[index]
176
+ val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
177
+ searchResult.selectedOccurrence.range
178
+ } else {
179
+ null
180
+ }
181
+
132
182
val coloredText = rememberCollapsableText(
133
183
type = CollapsableType .ARRAY ,
134
184
key = item.key,
@@ -138,7 +188,9 @@ private fun JsonTreeList(
138
188
isLastItem = item.isLastItem,
139
189
showIndices = showIndices,
140
190
showItemCount = showItemCount,
141
- parentType = item.parentType
191
+ searchOccurrence = searchOccurrence,
192
+ searchOccurrenceSelectedRange = selectedRange,
193
+ parentType = item.parentType,
142
194
)
143
195
144
196
Collapsable (
@@ -157,13 +209,22 @@ private fun JsonTreeList(
157
209
)
158
210
}
159
211
is JsonTreeElement .Collapsable .Object -> {
212
+ val searchOccurrence = searchResult.occurrences[index]
213
+ val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
214
+ searchResult.selectedOccurrence.range
215
+ } else {
216
+ null
217
+ }
218
+
160
219
val coloredText = rememberCollapsableText(
161
220
type = CollapsableType .OBJECT ,
162
221
key = item.key,
163
222
childItemCount = item.children.size,
164
223
state = item.state,
165
224
colors = colors,
166
225
isLastItem = item.isLastItem,
226
+ searchOccurrence = searchOccurrence,
227
+ searchOccurrenceSelectedRange = selectedRange,
167
228
showIndices = showIndices,
168
229
showItemCount = showItemCount,
169
230
parentType = item.parentType
@@ -185,11 +246,20 @@ private fun JsonTreeList(
185
246
)
186
247
}
187
248
is JsonTreeElement .Primitive -> {
249
+ val searchOccurrence = searchResult.occurrences[index]
250
+ val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
251
+ searchResult.selectedOccurrence.range
252
+ } else {
253
+ null
254
+ }
255
+
188
256
val coloredText = rememberPrimitiveText(
189
257
key = item.key,
190
258
value = item.value,
191
259
colors = colors,
192
260
isLastItem = item.isLastItem,
261
+ searchOccurrence = searchOccurrence,
262
+ searchOccurrenceSelectedRange = selectedRange,
193
263
showIndices = showIndices,
194
264
parentType = item.parentType
195
265
)
0 commit comments