Skip to content

Commit 66b6ac5

Browse files
authored
update SystemSearchActivity to reuse installed apps suggestions (#6968)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1211704100686772?focus=true ### Description Removes the code that populated the installed apps search suggestions in the `SystemSearchActivity` and instead takes the suggestions directly from the autocomplete results. ### Steps to test this PR - [x] Ensure that Search & Duck.ai is disabled (Settings -> AI Features). - [x] Add a widget (regular or favorites, **not** search-only) to the home screen. - [x] Click on the widget, verify that the input activity opens. - [x] Search for apps on your device and verify they show up. - [x] Verify that you see "Ask Duck.ai" at the bottom of suggestions list. - [x] Add a search-only widget to the home screen. - [x] Click on the widget, verify that the input activity opens. - [x] Search for apps on your device and verify they show up. - [x] Verify that you **don't see** "Ask Duck.ai" at the bottom of suggestions list.
1 parent d2d4182 commit 66b6ac5

File tree

7 files changed

+103
-251
lines changed

7 files changed

+103
-251
lines changed

app/src/main/java/com/duckduckgo/app/systemsearch/DeviceAppSuggestionsAdapter.kt

Lines changed: 0 additions & 74 deletions
This file was deleted.

app/src/main/java/com/duckduckgo/app/systemsearch/SystemSearchActivity.kt

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ class SystemSearchActivity : DuckDuckGoActivity() {
136136
private val viewModel: SystemSearchViewModel by bindViewModel()
137137
private val binding: ActivitySystemSearchBinding by viewBinding()
138138
private lateinit var autocompleteSuggestionsAdapter: BrowserAutoCompleteSuggestionsAdapter
139-
private lateinit var deviceAppSuggestionsAdapter: DeviceAppSuggestionsAdapter
140139
private lateinit var quickAccessAdapter: FavoritesQuickAccessAdapter
141140
private lateinit var itemTouchHelper: ItemTouchHelper
142141

@@ -211,7 +210,6 @@ class SystemSearchActivity : DuckDuckGoActivity() {
211210
configureFlowCollectors()
212211
configureOnboarding()
213212
configureAutoComplete()
214-
configureDeviceAppSuggestions()
215213
configureDaxButton()
216214
configureTextInput()
217215
configureQuickAccessGrid()
@@ -376,15 +374,6 @@ class SystemSearchActivity : DuckDuckGoActivity() {
376374
)
377375
}
378376

379-
private fun configureDeviceAppSuggestions() {
380-
binding.deviceAppSuggestions.layoutManager = LinearLayoutManager(this)
381-
deviceAppSuggestionsAdapter =
382-
DeviceAppSuggestionsAdapter {
383-
viewModel.userSelectedApp(it)
384-
}
385-
binding.deviceAppSuggestions.adapter = deviceAppSuggestionsAdapter
386-
}
387-
388377
private fun configureQuickAccessGrid() {
389378
val quickAccessRecyclerView = binding.quickAccessRecyclerView
390379
val numOfColumns = gridViewColumnCalculator.calculateNumberOfColumns(QUICK_ACCESS_ITEM_MAX_SIZE_DP, QUICK_ACCESS_GRID_MAX_COLUMNS)
@@ -495,11 +484,8 @@ class SystemSearchActivity : DuckDuckGoActivity() {
495484
if (viewState.autocompleteResults.suggestions.isEmpty()) {
496485
viewModel.autoCompleteSuggestionsGone()
497486
}
498-
deviceAppSuggestionsAdapter.updateData(viewState.appResults)
499487

500488
binding.autocompleteSuggestions.isVisible = !viewState.autocompleteResults.suggestions.isEmpty()
501-
binding.listDivider.isVisible = viewState.appResults.isNotEmpty()
502-
binding.deviceAppSuggestions.isVisible = !viewState.appResults.isEmpty()
503489
}
504490

505491
private fun renderOmnibarState(viewState: SystemSearchViewModel.OmnibarViewState) {
@@ -694,10 +680,10 @@ class SystemSearchActivity : DuckDuckGoActivity() {
694680

695681
private fun launchDeviceApp(command: LaunchDeviceApplication) {
696682
try {
697-
startActivity(command.deviceApp.launchIntent)
683+
startActivity(command.deviceAppSuggestion.launchIntent)
698684
finish()
699685
} catch (error: ActivityNotFoundException) {
700-
viewModel.appNotFound(command.deviceApp)
686+
viewModel.appNotFound(command.deviceAppSuggestion)
701687
}
702688
}
703689

app/src/main/java/com/duckduckgo/app/systemsearch/SystemSearchViewModel.kt

Lines changed: 31 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggesti
3737
import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggestion.AutoCompleteHistoryRelatedSuggestion.AutoCompleteHistorySuggestion
3838
import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggestion.AutoCompleteHistoryRelatedSuggestion.AutoCompleteInAppMessageSuggestion
3939
import com.duckduckgo.browser.api.autocomplete.AutoComplete.AutoCompleteSuggestion.AutoCompleteUrlSuggestion.AutoCompleteSwitchToTabSuggestion
40+
import com.duckduckgo.browser.api.autocomplete.AutoCompleteFactory
4041
import com.duckduckgo.browser.api.autocomplete.AutoCompleteSettings
4142
import com.duckduckgo.browser.ui.omnibar.OmnibarPosition
4243
import com.duckduckgo.common.utils.DispatcherProvider
@@ -55,7 +56,6 @@ import com.duckduckgo.voice.api.VoiceSearchAvailability
5556
import kotlinx.coroutines.CoroutineScope
5657
import kotlinx.coroutines.ExperimentalCoroutinesApi
5758
import kotlinx.coroutines.FlowPreview
58-
import kotlinx.coroutines.flow.Flow
5959
import kotlinx.coroutines.flow.MutableSharedFlow
6060
import kotlinx.coroutines.flow.MutableStateFlow
6161
import kotlinx.coroutines.flow.SharingStarted
@@ -67,6 +67,7 @@ import kotlinx.coroutines.flow.flatMapLatest
6767
import kotlinx.coroutines.flow.flow
6868
import kotlinx.coroutines.flow.flowOn
6969
import kotlinx.coroutines.flow.map
70+
import kotlinx.coroutines.flow.onEach
7071
import kotlinx.coroutines.flow.stateIn
7172
import kotlinx.coroutines.flow.update
7273
import kotlinx.coroutines.launch
@@ -76,19 +77,13 @@ import logcat.asLog
7677
import logcat.logcat
7778
import javax.inject.Inject
7879

79-
data class SystemSearchResult(
80-
val autocomplete: AutoCompleteResult,
81-
val deviceApps: List<DeviceApp>,
82-
)
83-
8480
@ContributesViewModel(ActivityScope::class)
8581
class SystemSearchViewModel @Inject constructor(
8682
private val duckAiFeatureState: DuckAiFeatureState,
8783
private val voiceSearchAvailability: VoiceSearchAvailability,
8884
private val duckChat: DuckChat,
8985
private var userStageStore: UserStageStore,
90-
private val autoComplete: AutoComplete,
91-
private val deviceAppLookup: DeviceAppLookup,
86+
autoCompleteFactory: AutoCompleteFactory,
9287
private val pixel: Pixel,
9388
private val savedSitesRepository: SavedSitesRepository,
9489
private val appSettingsPreferencesStore: SettingsDataStore,
@@ -116,7 +111,6 @@ class SystemSearchViewModel @Inject constructor(
116111
sealed class Suggestions {
117112
data class SystemSearchResultsViewState(
118113
val autocompleteResults: AutoCompleteResult = AutoCompleteResult("", emptyList()),
119-
val appResults: List<DeviceApp> = emptyList(),
120114
) : Suggestions()
121115

122116
data class QuickAccessItems(
@@ -155,7 +149,7 @@ class SystemSearchViewModel @Inject constructor(
155149
) : Command()
156150

157151
data class LaunchDeviceApplication(
158-
val deviceApp: DeviceApp,
152+
val deviceAppSuggestion: AutoCompleteSuggestion.AutoCompleteDeviceAppSuggestion,
159153
) : Command()
160154

161155
data class ShowAppNotFoundMessage(
@@ -191,17 +185,33 @@ class SystemSearchViewModel @Inject constructor(
191185
private var hasUserSeenHistory = false
192186
private var omnibarPosition: OmnibarPosition = appSettingsPreferencesStore.omnibarPosition
193187

188+
private val autoComplete: AutoComplete = autoCompleteFactory.create(
189+
AutoComplete.Config(showInstalledApps = true),
190+
)
191+
194192
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
195193
val suggestionsViewState =
196194
combine(queryFlow, refreshTrigger) { query, _ -> query.trim() }
197195
.debounce(DEBOUNCE_TIME_MS)
198196
.distinctUntilChanged()
199197
.flatMapLatest { query ->
200-
buildResultsFlow(query)
198+
autoComplete.autoComplete(query)
201199
}.flowOn(dispatchers.io())
202200
.catch { t: Throwable? -> logcat(WARN) { "Failed to get search results: ${t?.asLog()}" } }
203-
.map { results ->
204-
updateResults(results)
201+
.onEach { results ->
202+
if (results.suggestions.contains(AutoCompleteInAppMessageSuggestion)) {
203+
hasUserSeenHistory = true
204+
}
205+
}
206+
.map {
207+
val result = it.copy(
208+
suggestions = if (isSearchOnly.value) {
209+
it.suggestions.filterNot { suggestion -> suggestion is AutoCompleteSuggestion.AutoCompleteDuckAIPrompt }
210+
} else {
211+
it.suggestions
212+
},
213+
)
214+
Suggestions.SystemSearchResultsViewState(autocompleteResults = result)
205215
}.stateIn(viewModelScope, SharingStarted.Lazily, Suggestions.SystemSearchResultsViewState())
206216

207217
val favoritesViewState =
@@ -235,7 +245,6 @@ class SystemSearchViewModel @Inject constructor(
235245

236246
init {
237247
resetViewState()
238-
refreshAppList()
239248
}
240249

241250
fun setLaunchedFromSearchOnlyWidget(launchedFromSearchOnlyWidget: Boolean) {
@@ -264,17 +273,6 @@ class SystemSearchViewModel @Inject constructor(
264273
}
265274
}
266275

267-
private fun buildResultsFlow(query: String): Flow<SystemSearchResult> =
268-
combine(
269-
autoComplete.autoComplete(query),
270-
flow { emit(deviceAppLookup.query(query)) },
271-
) { autocompleteResult, appsResult ->
272-
if (autocompleteResult.suggestions.contains(AutoCompleteInAppMessageSuggestion)) {
273-
hasUserSeenHistory = true
274-
}
275-
SystemSearchResult(autocompleteResult, appsResult)
276-
}
277-
278276
fun onOmnibarConfigured(position: OmnibarPosition) {
279277
omnibarPosition = position
280278
}
@@ -323,19 +321,6 @@ class SystemSearchViewModel @Inject constructor(
323321
}
324322
}
325323

326-
private fun updateResults(results: SystemSearchResult): Suggestions.SystemSearchResultsViewState {
327-
val suggestions = results.autocomplete.suggestions
328-
val appResults = results.deviceApps
329-
val hasMultiResults = suggestions.isNotEmpty() && appResults.isNotEmpty()
330-
331-
val updatedSuggestions = if (hasMultiResults) suggestions.take(RESULTS_MAX_RESULTS_PER_GROUP) else suggestions
332-
val updatedApps = if (hasMultiResults) appResults.take(RESULTS_MAX_RESULTS_PER_GROUP) else appResults
333-
return Suggestions.SystemSearchResultsViewState(
334-
autocompleteResults = AutoCompleteResult(results.autocomplete.query, updatedSuggestions),
335-
appResults = updatedApps,
336-
)
337-
}
338-
339324
private fun inputCleared() {
340325
queryFlow.update { "" }
341326
}
@@ -369,15 +354,20 @@ class SystemSearchViewModel @Inject constructor(
369354
when (suggestion) {
370355
is AutoCompleteSwitchToTabSuggestion -> {
371356
command.value = Command.LaunchBrowserAndSwitchToTab(suggestion.phrase, suggestion.tabId)
357+
pixel.fire(INTERSTITIAL_LAUNCH_BROWSER_QUERY)
372358
}
373359
is AutoCompleteSuggestion.AutoCompleteDuckAIPrompt -> {
374360
onDuckAiRequested(suggestion.phrase)
375361
}
362+
is AutoCompleteSuggestion.AutoCompleteDeviceAppSuggestion -> {
363+
command.value = Command.LaunchDeviceApplication(deviceAppSuggestion = suggestion)
364+
pixel.fire(INTERSTITIAL_LAUNCH_DEVICE_APP)
365+
}
376366
else -> {
377367
command.value = Command.LaunchBrowser(suggestion.phrase)
368+
pixel.fire(INTERSTITIAL_LAUNCH_BROWSER_QUERY)
378369
}
379370
}
380-
pixel.fire(INTERSTITIAL_LAUNCH_BROWSER_QUERY)
381371
}
382372

383373
fun userLongPressedAutocomplete(suggestion: AutoCompleteSuggestion) {
@@ -418,21 +408,8 @@ class SystemSearchViewModel @Inject constructor(
418408
}
419409
}
420410

421-
fun userSelectedApp(app: DeviceApp) {
422-
command.value = Command.LaunchDeviceApplication(app)
423-
pixel.fire(INTERSTITIAL_LAUNCH_DEVICE_APP)
424-
}
425-
426-
fun appNotFound(app: DeviceApp) {
427-
command.value = Command.ShowAppNotFoundMessage(app.shortName)
428-
429-
refreshAppList()
430-
}
431-
432-
private fun refreshAppList() {
433-
viewModelScope.launch(dispatchers.io()) {
434-
deviceAppLookup.refreshAppList()
435-
}
411+
fun appNotFound(deviceAppSuggestion: AutoCompleteSuggestion.AutoCompleteDeviceAppSuggestion) {
412+
command.value = Command.ShowAppNotFoundMessage(deviceAppSuggestion.shortName)
436413
}
437414

438415
fun onQuickAccessListChanged(newList: List<FavoritesQuickAccessAdapter.QuickAccessFavorite>) {
@@ -462,7 +439,6 @@ class SystemSearchViewModel @Inject constructor(
462439

463440
companion object {
464441
private const val DEBOUNCE_TIME_MS = 200L
465-
private const val RESULTS_MAX_RESULTS_PER_GROUP = 4
466442
}
467443

468444
override fun onFavouriteEdited(favorite: Favorite) {

app/src/main/res/layout/activity_system_search.xml

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -251,30 +251,6 @@
251251
tools:itemCount="4"
252252
tools:listitem="@layout/item_autocomplete_search_suggestion" />
253253

254-
<View
255-
android:id="@+id/listDivider"
256-
android:layout_width="match_parent"
257-
android:layout_height="0.5dp"
258-
android:background="?attr/daxColorShade"
259-
android:layout_marginVertical="@dimen/keyline_2"
260-
android:visibility="gone"
261-
app:layout_constraintEnd_toEndOf="parent"
262-
app:layout_constraintStart_toStartOf="parent"
263-
app:layout_constraintTop_toBottomOf="@id/autocompleteSuggestions"
264-
app:layout_constraintBottom_toTopOf="@id/deviceAppSuggestions"/>
265-
266-
<androidx.recyclerview.widget.RecyclerView
267-
android:id="@+id/deviceAppSuggestions"
268-
android:layout_width="0dp"
269-
android:layout_height="wrap_content"
270-
android:layout_marginTop="@dimen/keyline_2"
271-
android:overScrollMode="never"
272-
app:layout_constraintEnd_toEndOf="parent"
273-
app:layout_constraintStart_toStartOf="parent"
274-
app:layout_constraintTop_toBottomOf="@id/listDivider"
275-
tools:itemCount="4"
276-
tools:listitem="@layout/item_device_app_suggestion" />
277-
278254
<include
279255
android:id="@+id/includeSystemSearchOnboarding"
280256
layout="@layout/include_system_search_onboarding" />

app/src/main/res/layout/include_system_search_onboarding.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
android:visibility="gone"
3131
app:layout_constraintEnd_toEndOf="parent"
3232
app:layout_constraintStart_toStartOf="parent"
33-
app:layout_constraintTop_toBottomOf="@id/deviceAppSuggestions"
33+
app:layout_constraintTop_toBottomOf="@id/autocompleteSuggestions"
3434
tools:context="com.duckduckgo.app.systemsearch.SystemSearchActivity"
3535
tools:visibility="visible">
3636

0 commit comments

Comments
 (0)