@@ -12,9 +12,11 @@ import com.redmadrobot.konfeature.FeatureValueSpec
1212import com.redmadrobot.konfeature.Konfeature
1313import com.redmadrobot.konfeature.source.FeatureValueSource
1414import kotlinx.coroutines.Dispatchers
15+ import kotlinx.coroutines.FlowPreview
1516import kotlinx.coroutines.flow.Flow
1617import kotlinx.coroutines.flow.MutableStateFlow
1718import kotlinx.coroutines.flow.asStateFlow
19+ import kotlinx.coroutines.flow.debounce
1820import kotlinx.coroutines.flow.launchIn
1921import kotlinx.coroutines.flow.onEach
2022import kotlinx.coroutines.flow.update
@@ -26,14 +28,13 @@ internal class KonfeatureViewModel(
2628 private val debugPanelInterceptor : KonfeatureDebugPanelInterceptor ,
2729) : PluginViewModel() {
2830 private val _state = MutableStateFlow (KonfeatureViewState ())
31+ private val _searchQueryFlow = MutableStateFlow (" " )
2932
3033 val state: Flow <KonfeatureViewState > = _state .asStateFlow()
3134
3235 init {
33- debugPanelInterceptor
34- .valuesFlow
35- .onEach { updateItems() }
36- .launchIn(viewModelScope)
36+ observeKonfeatureValues()
37+ observeSearchQuery()
3738 }
3839
3940 fun onValueChanged (key : String , value : Any ) {
@@ -70,14 +71,7 @@ internal class KonfeatureViewModel(
7071 }
7172
7273 fun onCollapseAllClick () {
73- _state .update { state ->
74- val collapsedConfigs = state.items
75- .asSequence()
76- .filterIsInstance(KonfeatureItem .Config ::class .java)
77- .map { it.name }
78- .toSet()
79- state.copy(collapsedConfigs = collapsedConfigs)
80- }
74+ _state .update { state -> state.copy(collapsedConfigs = state.configs.keys) }
8175 }
8276
8377 fun onEditClick (key : String , value : Any , isDebugSource : Boolean ) {
@@ -89,33 +83,57 @@ internal class KonfeatureViewModel(
8983 }
9084
9185 fun onSearchQueryChanged (query : String ) {
92- _state .update { state ->
93- val filteredItems = filterItems(state.items, query)
94- state.copy(searchQuery = query, filteredItems = filteredItems)
95- }
86+ _state .update { state -> state.copy(searchQuery = query) }
87+ _searchQueryFlow .value = query
88+ }
89+
90+ private fun observeKonfeatureValues () {
91+ debugPanelInterceptor.valuesFlow
92+ .onEach { updateItems() }
93+ .launchIn(viewModelScope)
94+ }
95+
96+ @OptIn(FlowPreview ::class )
97+ private fun observeSearchQuery () {
98+ _searchQueryFlow
99+ .debounce(timeoutMillis = 500L )
100+ .onEach { query ->
101+ val values = _state .value.values
102+ val configs = _state .value.configs
103+ val filteredItems = filterItems(configs, values, query)
104+ _state .update { state -> state.copy(filteredItems = filteredItems) }
105+ }
106+ .launchIn(viewModelScope)
96107 }
97108
98109 private suspend fun updateItems () {
99- val items = withContext(Dispatchers .IO ) { getItems(konfeature) }
110+ val (configs, values) = withContext(Dispatchers .IO ) { getItems(konfeature) }
111+ val searchQuery = _searchQueryFlow .value
112+ val filteredItems = filterItems(configs, values, searchQuery)
113+
100114 _state .update { state ->
101- val filteredItems = filterItems(items, state.searchQuery)
102- state.copy(items = items, filteredItems = filteredItems)
115+ state.copy(configs = configs, values = values, filteredItems = filteredItems)
103116 }
104117 }
105118
106- private fun getItems (konfeature : Konfeature ): List <KonfeatureItem > {
107- return konfeature.spec.fold(mutableListOf<KonfeatureItem >()) { acc, configSpec ->
119+ private fun getItems (konfeature : Konfeature ): Pair <Map <String , KonfeatureItem .Config >, List<KonfeatureItem.Value>> {
120+ val configs = mutableMapOf<String , KonfeatureItem .Config >()
121+ val values = mutableListOf<KonfeatureItem .Value >()
122+
123+ konfeature.spec.fold(configs to values) { acc, configSpec ->
108124 acc.apply {
109- add( createConfigItem(configSpec) )
110- addAll( configSpec.values.map { valueSpec ->
125+ configs[configSpec.name] = createConfigItem(configSpec)
126+ configSpec.values.mapTo(values) { valueSpec ->
111127 createConfigValueItem(
112128 configName = configSpec.name,
113129 valueSpec = valueSpec,
114130 konfeature = konfeature
115131 )
116- })
132+ }
117133 }
118134 }
135+
136+ return configs to values
119137 }
120138
121139 private fun createConfigItem (config : FeatureConfigSpec ): KonfeatureItem .Config {
@@ -162,19 +180,24 @@ internal class KonfeatureViewModel(
162180 }
163181 }
164182
165- private fun filterItems (items : List <KonfeatureItem >, query : String ): List <KonfeatureItem > {
166- if (query.isBlank()) return items
167-
168- val formattedQuery = query.lowercase()
169- val matchingItems = items
170- .filterIsInstance<KonfeatureItem .Value >()
171- .filter { it.key.lowercase().contains(formattedQuery) }
172- val matchingConfigNames = matchingItems.map { it.configName }.toSet()
173-
174- return items.filter { item ->
175- when (item) {
176- is KonfeatureItem .Config -> item.name in matchingConfigNames
177- is KonfeatureItem .Value -> item.key.lowercase().contains(formattedQuery)
183+ private suspend fun filterItems (
184+ configs : Map <String , KonfeatureItem .Config >,
185+ values : List <KonfeatureItem .Value >,
186+ query : String
187+ ): List <KonfeatureItem > {
188+ return withContext(Dispatchers .Default ) {
189+ buildList {
190+ var previousValue: KonfeatureItem .Value ? = null
191+
192+ for (value in values) {
193+ if (value.key.contains(query, ignoreCase = true )) {
194+ if (previousValue?.configName != value.configName) {
195+ configs[value.configName]?.let { config -> add(config) }
196+ }
197+ add(value)
198+ previousValue = value
199+ }
200+ }
178201 }
179202 }
180203 }
0 commit comments