<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.focus.searchsuggestions

import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations.map
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan

sealed class State {
    data class Disabled(val givePrompt: Boolean) : State()
    data class NoSuggestionsAPI(val givePrompt: Boolean) : State()
    object ReadyForSuggestions : State()
}

class SearchSuggestionsViewModel(application: Application) : AndroidViewModel(application) {
    private val fetcher: SearchSuggestionsFetcher
    private val preferences: SearchSuggestionsPreferences = SearchSuggestionsPreferences(application)

    private val _selectedSearchSuggestion = MutableLiveData<String>()
    val selectedSearchSuggestion: LiveData<String> = _selectedSearchSuggestion

    private val _searchQuery = MutableLiveData<String>()
    val searchQuery: LiveData<String> = _searchQuery

    private val _state = MutableLiveData<State>()
    val state: LiveData<State> = _state

    val suggestions: LiveData<List<SpannableStringBuilder>>
    var alwaysSearch = false
        private set

    init {
        fetcher = SearchSuggestionsFetcher(preferences.getSearchEngine())

        suggestions = map(fetcher.results) { result ->
            val style = StyleSpan(Typeface.BOLD)
            val endIndex = result.query.length

            result.suggestions.map { suggestion ->
                SpannableStringBuilder(suggestion).apply {
                    setSpan(style, 0, minOf(endIndex, suggestion.length), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
            }
        }
    }

    fun selectSearchSuggestion(suggestion: String, alwaysSearch: Boolean = false) {
        this.alwaysSearch = alwaysSearch
        _selectedSearchSuggestion.postValue(suggestion)
    }

    fun setSearchQuery(query: String) {
        _searchQuery.value = query

        if (state.value === State.ReadyForSuggestions) {
            fetcher.requestSuggestions(query)
        }
    }

    fun enableSearchSuggestions() {
        preferences.enableSearchSuggestions()
        updateState()
    }

    fun disableSearchSuggestions() {
        fetcher.cancelJobs()
        preferences.disableSearchSuggestions()
        updateState()
    }

    fun dismissNoSuggestionsMessage() {
        preferences.dismissNoSuggestionsMessage()
        updateState()
    }

    fun refresh() {
        val engine = preferences.getSearchEngine()
        fetcher.updateSearchEngine(engine)
        updateState()
    }

    private fun updateState() {
        val enabled = preferences.searchSuggestionsEnabled()

        val state = if (enabled) {
            if (fetcher.canProvideSearchSuggestions) {
                State.ReadyForSuggestions
            } else {
                val givePrompt = !preferences.userHasDismissedNoSuggestionsMessage()
                State.NoSuggestionsAPI(givePrompt)
            }
        } else {
            val givePrompt = !preferences.hasUserToggledSearchSuggestions()
            State.Disabled(givePrompt)
        }

        _state.value = state
    }
}
<commit_msg>Fixes #3904 - fetch suggestions immediately after enabling search suggestions<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.focus.searchsuggestions

import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations.map
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan

sealed class State {
    data class Disabled(val givePrompt: Boolean) : State()
    data class NoSuggestionsAPI(val givePrompt: Boolean) : State()
    object ReadyForSuggestions : State()
}

class SearchSuggestionsViewModel(application: Application) : AndroidViewModel(application) {
    private val fetcher: SearchSuggestionsFetcher
    private val preferences: SearchSuggestionsPreferences = SearchSuggestionsPreferences(application)

    private val _selectedSearchSuggestion = MutableLiveData<String>()
    val selectedSearchSuggestion: LiveData<String> = _selectedSearchSuggestion

    private val _searchQuery = MutableLiveData<String>()
    val searchQuery: LiveData<String> = _searchQuery

    private val _state = MutableLiveData<State>()
    val state: LiveData<State> = _state

    val suggestions: LiveData<List<SpannableStringBuilder>>
    var alwaysSearch = false
        private set

    init {
        fetcher = SearchSuggestionsFetcher(preferences.getSearchEngine())

        suggestions = map(fetcher.results) { result ->
            val style = StyleSpan(Typeface.BOLD)
            val endIndex = result.query.length

            result.suggestions.map { suggestion ->
                SpannableStringBuilder(suggestion).apply {
                    setSpan(style, 0, minOf(endIndex, suggestion.length), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                }
            }
        }
    }

    fun selectSearchSuggestion(suggestion: String, alwaysSearch: Boolean = false) {
        this.alwaysSearch = alwaysSearch
        _selectedSearchSuggestion.postValue(suggestion)
    }

    fun setSearchQuery(query: String) {
        _searchQuery.value = query

        if (state.value === State.ReadyForSuggestions) {
            fetcher.requestSuggestions(query)
        }
    }

    fun enableSearchSuggestions() {
        preferences.enableSearchSuggestions()
        updateState()
        setSearchQuery(searchQuery.value ?: "")
    }

    fun disableSearchSuggestions() {
        fetcher.cancelJobs()
        preferences.disableSearchSuggestions()
        updateState()
    }

    fun dismissNoSuggestionsMessage() {
        preferences.dismissNoSuggestionsMessage()
        updateState()
    }

    fun refresh() {
        val engine = preferences.getSearchEngine()
        fetcher.updateSearchEngine(engine)
        updateState()
    }

    private fun updateState() {
        val enabled = preferences.searchSuggestionsEnabled()

        val state = if (enabled) {
            if (fetcher.canProvideSearchSuggestions) {
                State.ReadyForSuggestions
            } else {
                val givePrompt = !preferences.userHasDismissedNoSuggestionsMessage()
                State.NoSuggestionsAPI(givePrompt)
            }
        } else {
            val givePrompt = !preferences.hasUserToggledSearchSuggestions()
            State.Disabled(givePrompt)
        }

        _state.value = state
    }
}

{'fraction_non_alphanumeric': 0.07768756998880179, 'fraction_numerical': 0.001959686450167973, 'mean_word_length': 2.958448753462604, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3960341', 'n_tokens_mistral': 1966, 'n_tokens_neox': 1835, 'n_words': 411}