feat: voice search (#1623)

This commit is contained in:
Datj0000
2025-03-30 01:49:32 +07:00
committed by GitHub
parent 7e148a4d6a
commit 1e69418044
5 changed files with 213 additions and 128 deletions

View File

@ -1,7 +1,11 @@
package com.lagradost.cloudstream3.ui.search
import android.app.Activity
import android.content.Intent
import android.content.DialogInterface
import android.content.res.Configuration
import android.speech.RecognizerIntent
import android.speech.SpeechRecognizer
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
@ -21,6 +25,7 @@ import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
@ -29,6 +34,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.AnimeSearchResponse
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.MainActivity
@ -69,6 +75,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
class SearchFragment : Fragment() {
@ -99,6 +106,18 @@ class SearchFragment : Fragment() {
private var bottomSheetDialog: BottomSheetDialog? = null
var binding: FragmentSearchBinding? = null
private val speechRecognizerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val matches = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
if (!matches.isNullOrEmpty()) {
val recognizedText = matches[0]
binding?.mainSearch?.setQuery(recognizedText, true)
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -234,6 +253,25 @@ class SearchFragment : Fragment() {
searchLoadingBar.alpha = 0f
}
binding?.voiceSearch?.setOnClickListener { searchView ->
searchView?.context?.let { ctx ->
try {
if (!SpeechRecognizer.isRecognitionAvailable(ctx)) {
showToast(R.string.speech_recognition_unavailable)
} else {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
putExtra(RecognizerIntent.EXTRA_PROMPT, ctx.getString(R.string.begin_speaking))
}
speechRecognizerLauncher.launch(intent)
}
} catch (_ : Throwable) {
// launch may throw
showToast(R.string.speech_recognition_unavailable)
}
}
}
val searchExitIcon =
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
@ -576,4 +614,4 @@ class SearchFragment : Fragment() {
.commit()*/
}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,14c1.66,0 3,-1.34 3,-3v-4c0,-1.66 -1.34,-3 -3,-3 -1.66,0 -3,1.34 -3,3v4c0,1.66 1.34,3 3,3zM19,11h-2c0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5h-2c0,3.45 2.68,6.29 6,6.92v2.08h-3v2h8v-2h-3v-2.08c3.32,-0.63 6,-3.47 6,-6.92z"/>
</vector>

View File

@ -16,78 +16,96 @@
android:orientation="vertical"
android:paddingBottom="10dp">
<FrameLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="30dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/search_loading_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginStart="-35dp"
android:foregroundTint="@color/white"
android:progressTint="@color/white">
</androidx.core.widget.ContentLoadingProgressBar>
<!--app:queryHint="@string/search_hint"
android:background="@color/grayBackground" @color/itemBackground
app:searchHintIcon="@drawable/search_white"
-->
</androidx.appcompat.widget.SearchView>
</FrameLayout>
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_scrollFlags="scroll|enterAlways">
<ImageView
android:id="@+id/search_filter"
android:id="@+id/voice_search"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:src="@drawable/ic_baseline_tune_24"
android:src="@drawable/ic_mic"
app:tint="?attr/textColor" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="30dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/search_loading_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginStart="-35dp"
android:foregroundTint="@color/white"
android:progressTint="@color/white">
</androidx.core.widget.ContentLoadingProgressBar>
<!--app:queryHint="@string/search_hint"
android:background="@color/grayBackground" @color/itemBackground
app:searchHintIcon="@drawable/search_white"
-->
</androidx.appcompat.widget.SearchView>
</FrameLayout>
<ImageView
android:id="@+id/search_filter"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:src="@drawable/ic_baseline_tune_24"
app:tint="?attr/textColor" />
</FrameLayout>
</LinearLayout>
<include
android:id="@+id/tvtypes_chips_scroll"

View File

@ -17,79 +17,97 @@
android:orientation="vertical"
android:paddingBottom="10dp">
<FrameLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="30dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/search_loading_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginStart="-35dp"
android:foregroundTint="@color/white"
android:progressTint="@color/white">
</androidx.core.widget.ContentLoadingProgressBar>
<!--app:queryHint="@string/search_hint"
android:background="@color/grayBackground" @color/itemBackground
app:searchHintIcon="@drawable/search_white"
-->
</androidx.appcompat.widget.SearchView>
</FrameLayout>
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_scrollFlags="scroll|enterAlways">
<ImageView
android:id="@+id/search_filter"
android:id="@+id/voice_search"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/tvtypes_chips_scroll"
android:tag = "@string/tv_no_focus_tag"
android:src="@drawable/ic_baseline_tune_24"
android:src="@drawable/ic_mic"
app:tint="?attr/textColor" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="30dp">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/search_autofit_results"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/search_loading_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginStart="-35dp"
android:foregroundTint="@color/white"
android:progressTint="@color/white">
</androidx.core.widget.ContentLoadingProgressBar>
<!--app:queryHint="@string/search_hint"
android:background="@color/grayBackground" @color/itemBackground
app:searchHintIcon="@drawable/search_white"
-->
</androidx.appcompat.widget.SearchView>
</FrameLayout>
<ImageView
android:id="@+id/search_filter"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:nextFocusUp="@id/nav_rail_view"
android:nextFocusDown="@id/tvtypes_chips_scroll"
android:tag = "@string/tv_no_focus_tag"
android:src="@drawable/ic_baseline_tune_24"
app:tint="?attr/textColor" />
</FrameLayout>
</LinearLayout>
<include
android:id="@+id/tvtypes_chips_scroll"

View File

@ -115,6 +115,8 @@
<string name="title_settings">Settings</string>
<string name="search_hint">Search…</string>
<string name="search_hint_site" formatted="true">Search %s…</string>
<string name="speech_recognition_unavailable">Speech recognition is not available</string>
<string name="begin_speaking">Begin Speaking…</string>
<string name="no_data">No Data</string>
<string name="episode_more_options_des">More Options</string>
<string name="next_episode">Next episode</string>