mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-07-13 00:13:17 +00:00
Compare commits
2 commits
master
...
reposearch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13a2b447a2 |
||
|
|
d1a5d0a39f |
6 changed files with 180 additions and 48 deletions
|
|
@ -16,7 +16,6 @@ import com.lagradost.cloudstream3.plugins.PluginManager.getPluginSanitizedFileNa
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager.unloadPlugin
|
import com.lagradost.cloudstream3.plugins.PluginManager.unloadPlugin
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
|
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
|
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
|
|
@ -26,7 +25,7 @@ import java.nio.file.AtomicMoveNotSupportedException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comes with the app, always available in the app, non removable.
|
* Comes with the app, always available in the app, non removable.
|
||||||
|
|
@ -136,17 +135,15 @@ object RepositoryManager {
|
||||||
suspend fun parseRepository(url: String): Repository? {
|
suspend fun parseRepository(url: String): Repository? {
|
||||||
return safeAsync {
|
return safeAsync {
|
||||||
// Take manifestVersion and such into account later
|
// Take manifestVersion and such into account later
|
||||||
app.get(convertRawGitUrl(url)).parsedSafe<Repository>()
|
app.get(convertRawGitUrl(url), cacheTime = 5, cacheUnit = TimeUnit.MINUTES).parsedSafe<Repository>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parsePlugins(pluginUrls: String): List<SitePlugin> {
|
private suspend fun parsePlugins(pluginUrls: String): List<SitePlugin> {
|
||||||
// Take manifestVersion and such into account later
|
// Take manifestVersion and such into account later
|
||||||
return try {
|
return try {
|
||||||
val response = app.get(convertRawGitUrl(pluginUrls))
|
app.get(convertRawGitUrl(pluginUrls), cacheTime = 5, cacheUnit = TimeUnit.MINUTES)
|
||||||
// Normal parsed function not working?
|
.parsed<Array<SitePlugin>>().toList()
|
||||||
// return response.parsedSafe()
|
|
||||||
tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList()
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logError(t)
|
logError(t)
|
||||||
emptyList()
|
emptyList()
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
|
|
@ -26,6 +28,7 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||||
import com.lagradost.cloudstream3.ui.BaseFragment
|
import com.lagradost.cloudstream3.ui.BaseFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
|
import com.lagradost.cloudstream3.ui.setRecycledViewPool
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setSystemBarsPadding
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setSystemBarsPadding
|
||||||
|
|
@ -43,6 +46,7 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val extensionViewModel: ExtensionsViewModel by activityViewModels()
|
private val extensionViewModel: ExtensionsViewModel by activityViewModels()
|
||||||
|
private val pluginViewModel: PluginsViewModel by activityViewModels()
|
||||||
|
|
||||||
private fun View.setLayoutWidth(weight: Int) {
|
private fun View.setLayoutWidth(weight: Int) {
|
||||||
val param = LinearLayout.LayoutParams(
|
val param = LinearLayout.LayoutParams(
|
||||||
|
|
@ -126,7 +130,10 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
when (which) {
|
when (which) {
|
||||||
DialogInterface.BUTTON_POSITIVE -> {
|
DialogInterface.BUTTON_POSITIVE -> {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
RepositoryManager.removeRepository(uiContext.applicationContext, repo)
|
RepositoryManager.removeRepository(
|
||||||
|
uiContext.applicationContext,
|
||||||
|
repo
|
||||||
|
)
|
||||||
extensionViewModel.loadStats()
|
extensionViewModel.loadStats()
|
||||||
extensionViewModel.loadRepositories()
|
extensionViewModel.loadRepositories()
|
||||||
}
|
}
|
||||||
|
|
@ -145,10 +152,11 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(extensionViewModel.repositories) {
|
observe(extensionViewModel.repositories) { repos ->
|
||||||
binding.repoRecyclerView.isVisible = it.isNotEmpty()
|
binding.repoRecyclerView.isVisible = repos.isNotEmpty()
|
||||||
binding.blankRepoScreen.isVisible = it.isEmpty()
|
binding.blankRepoScreen.isVisible = repos.isEmpty()
|
||||||
(binding.repoRecyclerView.adapter as? RepoAdapter)?.submitList(it.toList())
|
(binding.repoRecyclerView.adapter as? RepoAdapter)?.submitList(repos.toList())
|
||||||
|
pluginViewModel.updatePluginList(binding.root.context, repos.map { it.url })
|
||||||
}
|
}
|
||||||
|
|
||||||
observeNullable(extensionViewModel.pluginStats) { value ->
|
observeNullable(extensionViewModel.pluginStats) { value ->
|
||||||
|
|
@ -185,6 +193,70 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.pluginRecyclerView.apply {
|
||||||
|
setLinearListLayout(
|
||||||
|
isHorizontal = false,
|
||||||
|
nextDown = FOCUS_SELF,
|
||||||
|
nextRight = FOCUS_SELF,
|
||||||
|
)
|
||||||
|
setRecycledViewPool(PluginAdapter.sharedPool)
|
||||||
|
adapter =
|
||||||
|
PluginAdapter {
|
||||||
|
val urls = extensionViewModel.repositories.value?.map { repo -> repo.url }
|
||||||
|
?: emptyList()
|
||||||
|
pluginViewModel.handlePluginAction(activity, urls, it, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) ->
|
||||||
|
(binding.pluginRecyclerView.adapter as? PluginAdapter)?.submitList(list)
|
||||||
|
if (scrollToTop) {
|
||||||
|
binding.pluginRecyclerView.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.settingsToolbar.apply {
|
||||||
|
val searchItem = menu?.findItem(R.id.search_button)
|
||||||
|
val searchView = searchItem?.actionView as? SearchView
|
||||||
|
|
||||||
|
searchItem?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||||
|
override fun onMenuItemActionCollapse(p0: MenuItem): Boolean {
|
||||||
|
binding.pluginRecyclerView.isVisible = false
|
||||||
|
binding.repoRecyclerView.isVisible = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionExpand(p0: MenuItem): Boolean {
|
||||||
|
binding.pluginRecyclerView.isVisible = true
|
||||||
|
binding.repoRecyclerView.isVisible = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Don't go back if active query
|
||||||
|
setNavigationOnClickListener {
|
||||||
|
if (searchView?.isIconified == false) {
|
||||||
|
searchView.isIconified = true
|
||||||
|
} else {
|
||||||
|
dispatchBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
pluginViewModel.search(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
pluginViewModel.search(newText)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val addRepositoryClick = View.OnClickListener {
|
val addRepositoryClick = View.OnClickListener {
|
||||||
val ctx = context ?: return@OnClickListener
|
val ctx = context ?: return@OnClickListener
|
||||||
val binding = AddRepoInputBinding.inflate(LayoutInflater.from(ctx), null, false)
|
val binding = AddRepoInputBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
|
|
@ -199,7 +271,10 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
)?.text?.toString()?.let { copiedText ->
|
)?.text?.toString()?.let { copiedText ->
|
||||||
if (copiedText.contains(RepoAdapter.SHAREABLE_REPO_SEPARATOR)) {
|
if (copiedText.contains(RepoAdapter.SHAREABLE_REPO_SEPARATOR)) {
|
||||||
// text is of format <repository name> : <repository url>
|
// text is of format <repository name> : <repository url>
|
||||||
val (name, url) = copiedText.split(RepoAdapter.SHAREABLE_REPO_SEPARATOR, limit = 2)
|
val (name, url) = copiedText.split(
|
||||||
|
RepoAdapter.SHAREABLE_REPO_SEPARATOR,
|
||||||
|
limit = 2
|
||||||
|
)
|
||||||
binding.repoUrlInput.setText(url.trim())
|
binding.repoUrlInput.setText(url.trim())
|
||||||
binding.repoNameInput.setText(name.trim())
|
binding.repoNameInput.setText(name.trim())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -227,7 +302,7 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
|
|
||||||
val fixedName = if (!name.isNullOrBlank()) name
|
val fixedName = if (!name.isNullOrBlank()) name
|
||||||
else repository.name
|
else repository.name
|
||||||
val newRepo = RepositoryData(repository.iconUrl,fixedName, url)
|
val newRepo = RepositoryData(repository.iconUrl, fixedName, url)
|
||||||
RepositoryManager.addRepository(newRepo)
|
RepositoryManager.addRepository(newRepo)
|
||||||
extensionViewModel.loadStats()
|
extensionViewModel.loadStats()
|
||||||
extensionViewModel.loadRepositories()
|
extensionViewModel.loadRepositories()
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.lagradost.cloudstream3.AllLanguagesName
|
import com.lagradost.cloudstream3.AllLanguagesName
|
||||||
import com.lagradost.cloudstream3.BuildConfig
|
import com.lagradost.cloudstream3.BuildConfig
|
||||||
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
|
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.BaseFragment
|
import com.lagradost.cloudstream3.ui.BaseFragment
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
|
|
@ -34,8 +34,7 @@ const val PLUGINS_BUNDLE_LOCAL = "isLocal"
|
||||||
class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
BaseFragment.BindingCreator.Inflate(FragmentPluginsBinding::inflate)
|
BaseFragment.BindingCreator.Inflate(FragmentPluginsBinding::inflate)
|
||||||
) {
|
) {
|
||||||
|
private lateinit var pluginViewModel: PluginsViewModel
|
||||||
private val pluginViewModel: PluginsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
pluginViewModel.clear() // clear for the next observe
|
pluginViewModel.clear() // clear for the next observe
|
||||||
|
|
@ -47,6 +46,8 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentPluginsBinding) {
|
override fun onBindingCreated(binding: FragmentPluginsBinding) {
|
||||||
|
pluginViewModel = ViewModelProvider(this)[PluginsViewModel::class.java]
|
||||||
|
|
||||||
// Since the ViewModel is getting reused the tvTypes must be cleared between uses
|
// Since the ViewModel is getting reused the tvTypes must be cleared between uses
|
||||||
pluginViewModel.tvTypes.clear()
|
pluginViewModel.tvTypes.clear()
|
||||||
pluginViewModel.selectedLanguages = listOf()
|
pluginViewModel.selectedLanguages = listOf()
|
||||||
|
|
@ -130,9 +131,6 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
dispatchBackPressed()
|
dispatchBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
searchView?.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
|
||||||
if (!hasFocus) pluginViewModel.search(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
|
@ -161,7 +159,7 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
setRecycledViewPool(PluginAdapter.sharedPool)
|
setRecycledViewPool(PluginAdapter.sharedPool)
|
||||||
adapter =
|
adapter =
|
||||||
PluginAdapter {
|
PluginAdapter {
|
||||||
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
|
pluginViewModel.handlePluginAction(activity, listOf(url), it, isLocal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +183,7 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
|
|
||||||
binding.tvtypesChipsScroll.root.isVisible = false
|
binding.tvtypesChipsScroll.root.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
pluginViewModel.updatePluginList(context, url)
|
pluginViewModel.updatePluginList(context, listOf(url))
|
||||||
binding.tvtypesChipsScroll.root.isVisible = true
|
binding.tvtypesChipsScroll.root.isVisible = true
|
||||||
// not needed for users but may be useful for devs
|
// not needed for users but may be useful for devs
|
||||||
downloadAllButton?.isVisible = BuildConfig.DEBUG
|
downloadAllButton?.isVisible = BuildConfig.DEBUG
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
),
|
),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
viewModel?.updatePluginListPrivate(activity, repositoryUrl)
|
viewModel?.updatePluginListPrivate(activity, listOf(repositoryUrl))
|
||||||
} else if (list.isNotEmpty()) {
|
} else if (list.isNotEmpty()) {
|
||||||
showToast(R.string.download_failed, Toast.LENGTH_SHORT)
|
showToast(R.string.download_failed, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
@ -157,11 +157,11 @@ class PluginsViewModel : ViewModel() {
|
||||||
* */
|
* */
|
||||||
fun handlePluginAction(
|
fun handlePluginAction(
|
||||||
activity: Activity?,
|
activity: Activity?,
|
||||||
repositoryUrl: String,
|
repositoryUrls: List<String>,
|
||||||
plugin: Plugin,
|
plugin: Plugin,
|
||||||
isLocal: Boolean
|
isLocal: Boolean
|
||||||
) = ioSafe {
|
) = ioSafe {
|
||||||
Log.i(TAG, "handlePluginAction = $repositoryUrl, $plugin, $isLocal")
|
Log.i(TAG, "handlePluginAction = ${repositoryUrls}, $plugin, $isLocal")
|
||||||
|
|
||||||
if (activity == null) return@ioSafe
|
if (activity == null) return@ioSafe
|
||||||
val (repo, metadata) = plugin
|
val (repo, metadata) = plugin
|
||||||
|
|
@ -198,15 +198,18 @@ class PluginsViewModel : ViewModel() {
|
||||||
if (isLocal)
|
if (isLocal)
|
||||||
updatePluginListLocal()
|
updatePluginListLocal()
|
||||||
else
|
else
|
||||||
updatePluginListPrivate(activity, repositoryUrl)
|
updatePluginListPrivate(activity, repositoryUrls)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
|
private suspend fun updatePluginListPrivate(context: Context, repositoryUrls: List<String>) {
|
||||||
val isAdult = PreferenceManager.getDefaultSharedPreferences(context)
|
val isAdult = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getStringSet(context.getString(R.string.prefer_media_type_key), emptySet())
|
.getStringSet(context.getString(R.string.prefer_media_type_key), emptySet())
|
||||||
?.contains(TvType.NSFW.ordinal.toString()) == true
|
?.contains(TvType.NSFW.ordinal.toString()) == true
|
||||||
|
|
||||||
val plugins = getPlugins(repositoryUrl)
|
val plugins = repositoryUrls.flatMap { repositoryUrl ->
|
||||||
|
getPlugins(repositoryUrl)
|
||||||
|
}
|
||||||
|
|
||||||
val list = plugins.filter {
|
val list = plugins.filter {
|
||||||
// Show all non-nsfw plugins or all if nsfw is enabled
|
// Show all non-nsfw plugins or all if nsfw is enabled
|
||||||
it.second.tvTypes?.contains(TvType.NSFW.name) != true || isAdult
|
it.second.tvTypes?.contains(TvType.NSFW.name) != true || isAdult
|
||||||
|
|
@ -241,16 +244,28 @@ class PluginsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<PluginViewData>.sortByQuery(query: String?): List<PluginViewData> {
|
private fun List<PluginViewData>.sortByQuery(query: String?): List<PluginViewData> {
|
||||||
return if (query == null) {
|
return if (query.isNullOrBlank()) {
|
||||||
// Return list to base state if no query
|
// Return list to base state if no query
|
||||||
this.sortedBy { it.plugin.second.name }
|
this.sortedBy { it.plugin.second.name }
|
||||||
} else {
|
} else {
|
||||||
this.sortedBy {
|
this.mapNotNull {
|
||||||
-Levenshtein.partialRatio(
|
// Try matching name
|
||||||
|
val score = Levenshtein.partialRatio(
|
||||||
it.plugin.second.name.lowercase(),
|
it.plugin.second.name.lowercase(),
|
||||||
query.lowercase()
|
query.lowercase()
|
||||||
|
).takeIf { score -> score > 80 } ?:
|
||||||
|
// Fallback to description, but limit characters to reduce lag
|
||||||
|
it.plugin.second.description?.lowercase()?.take(64)
|
||||||
|
?.let { description ->
|
||||||
|
Levenshtein.partialRatio(
|
||||||
|
description,
|
||||||
|
query.lowercase()
|
||||||
)
|
)
|
||||||
}
|
}?.takeIf { score -> score > 80 } ?: return@mapNotNull null
|
||||||
|
it to score
|
||||||
|
}.sortedBy {
|
||||||
|
-it.second
|
||||||
|
}.map { it.first }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,16 +282,17 @@ class PluginsViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePluginList(context: Context?, repositoryUrl: String) = viewModelScope.launchSafe {
|
fun updatePluginList(context: Context?, repositoryUrls: List<String>) =
|
||||||
|
viewModelScope.launchSafe {
|
||||||
if (context == null) return@launchSafe
|
if (context == null) return@launchSafe
|
||||||
Log.i(TAG, "updatePluginList = $repositoryUrl")
|
Log.i(TAG, "updatePluginList = $repositoryUrls")
|
||||||
updatePluginListPrivate(context, repositoryUrl)
|
updatePluginListPrivate(context, repositoryUrls)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String?) {
|
fun search(query: String?) {
|
||||||
currentQuery = query
|
currentQuery = query
|
||||||
_filteredPlugins.postValue(
|
_filteredPlugins.postValue(
|
||||||
true to (filteredPlugins.value?.second?.sortByQuery(query) ?: emptyList())
|
true to plugins.filterTvTypes().filterLang().sortByQuery(query)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,39 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/extensions_root"
|
android:id="@+id/extensions_root"
|
||||||
android:background="?attr/primaryGrayBackground"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<include layout="@layout/standard_toolbar" />
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/settings_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
android:paddingTop="@dimen/navbar_height"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:menu="@menu/repository_search"
|
||||||
|
app:navigationIconTint="?attr/iconColor"
|
||||||
|
app:titleTextColor="?attr/textColor"
|
||||||
|
tools:title="Overlord" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<!-- <include layout="@layout/standard_toolbar" />-->
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:clipToPadding="false"
|
|
||||||
android:id="@+id/repo_recycler_view"
|
android:id="@+id/repo_recycler_view"
|
||||||
android:background="?attr/primaryBlackBackground"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginBottom="80dp"
|
android:layout_marginBottom="80dp"
|
||||||
|
android:background="?attr/primaryBlackBackground"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:nextFocusUp="@id/settings_toolbar"
|
android:nextFocusUp="@id/settings_toolbar"
|
||||||
android:nextFocusDown="@id/plugin_storage_appbar"
|
android:nextFocusDown="@id/plugin_storage_appbar"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
|
@ -27,11 +46,25 @@
|
||||||
tools:listitem="@layout/repository_item"
|
tools:listitem="@layout/repository_item"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/blank_repo_screen"
|
android:visibility="gone"
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:id="@+id/plugin_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/primaryBlackBackground"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
|
||||||
|
android:nextFocusLeft="@id/nav_rail_view"
|
||||||
|
android:nextFocusUp="@id/tvtypes_chips"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:listitem="@layout/repository_item" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/blank_repo_screen"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/primaryBlackBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:visibility="gone">
|
tools:visibility="gone">
|
||||||
|
|
@ -64,14 +97,14 @@
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:background="?attr/primaryGrayBackground"
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
android:baselineAligned="false"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="@drawable/outline_drawable"
|
android:foreground="@drawable/outline_drawable"
|
||||||
android:nextFocusRight="@id/add_repo_button_imageview"
|
android:nextFocusRight="@id/add_repo_button_imageview"
|
||||||
android:nextFocusUp="@id/repo_recycler_view"
|
android:nextFocusUp="@id/repo_recycler_view"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:visibility="visible"
|
android:visibility="visible">
|
||||||
android:baselineAligned="false">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
||||||
13
app/src/main/res/menu/repository_search.xml
Normal file
13
app/src/main/res/menu/repository_search.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_button"
|
||||||
|
android:title="@string/title_search"
|
||||||
|
android:icon="@drawable/search_icon"
|
||||||
|
android:searchIcon="@drawable/search_icon"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
|
app:showAsAction="always|collapseActionView"
|
||||||
|
tools:ignore="AppCompatResource" />
|
||||||
|
</menu>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue