forked from recloudstream/cloudstream
made inf scrolling better + fixed search bug
This commit is contained in:
parent
625ff8c910
commit
d3b3091fe0
10 changed files with 179 additions and 47 deletions
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.animeproviders
|
|||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import java.net.URLDecoder
|
||||
|
@ -75,7 +74,6 @@ class AniflixProvider : MainAPI() {
|
|||
val token = getToken()
|
||||
val url = "$mainUrl/_next/data/$token/search.json?keyword=$query"
|
||||
val response = app.get(url)
|
||||
println("resp: $url ===> ${response.text}")
|
||||
val searchResponse =
|
||||
response.parsedSafe<Search>()
|
||||
?: throw ErrorLoadingException("No Media")
|
||||
|
|
|
@ -24,6 +24,7 @@ class HomeChildItemAdapter(
|
|||
) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var isHorizontal: Boolean = false
|
||||
var hasNext : Boolean = false
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val layout = overrideLayout
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.*
|
||||
|
@ -46,6 +47,8 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallba
|
|||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
|
@ -119,11 +122,27 @@ class HomeFragment : Fragment() {
|
|||
|
||||
val errorProfilePic = errorProfilePics.random()
|
||||
|
||||
fun Activity.loadHomepageList(item: HomePageList, deleteCallback: (() -> Unit)? = null) {
|
||||
fun Activity.loadHomepageList(
|
||||
item: HomePageList,
|
||||
deleteCallback: (() -> Unit)? = null,
|
||||
) {
|
||||
loadHomepageList(
|
||||
expand = HomeViewModel.ExpandableHomepageList(item, 1, false),
|
||||
deleteCallback = deleteCallback,
|
||||
expandCallback = null
|
||||
)
|
||||
}
|
||||
|
||||
fun Activity.loadHomepageList(
|
||||
expand: HomeViewModel.ExpandableHomepageList,
|
||||
deleteCallback: (() -> Unit)? = null,
|
||||
expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null
|
||||
) {
|
||||
val context = this
|
||||
val bottomSheetDialogBuilder = BottomSheetDialog(context)
|
||||
bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded)
|
||||
val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!!
|
||||
val item = expand.list
|
||||
title.text = item.name
|
||||
val recycle =
|
||||
bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
|
||||
|
@ -176,8 +195,36 @@ class HomeFragment : Fragment() {
|
|||
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
|
||||
bottomSheetDialogBuilder.dismissSafe(this)
|
||||
}
|
||||
}.apply {
|
||||
hasNext = expand.hasNext
|
||||
}
|
||||
|
||||
recycle.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
var expandCount = 0
|
||||
val name = expand.list.name
|
||||
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
val adapter = recyclerView.adapter
|
||||
if (adapter !is SearchAdapter) return
|
||||
|
||||
val count = adapter.itemCount
|
||||
val currentHasNext = adapter.hasNext
|
||||
if (!recyclerView.canScrollVertically(1) && currentHasNext && expandCount != count) {
|
||||
expandCount = count
|
||||
ioSafe {
|
||||
expandCallback?.invoke(name)?.let { newExpand ->
|
||||
(recyclerView.adapter as? SearchAdapter?)?.apply {
|
||||
hasNext = newExpand.hasNext
|
||||
updateList(newExpand.list.list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val spanListener = { span: Int ->
|
||||
recycle.spanCount = span
|
||||
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
||||
|
@ -537,7 +584,10 @@ class HomeFragment : Fragment() {
|
|||
listHomepageItems.clear()
|
||||
|
||||
// println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}")
|
||||
(home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(d.values.toMutableList())
|
||||
(home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(
|
||||
d.values.toMutableList(),
|
||||
home_master_recycler
|
||||
)
|
||||
|
||||
home_loading?.isVisible = false
|
||||
home_loading_error?.isVisible = false
|
||||
|
@ -843,15 +893,18 @@ class HomeFragment : Fragment() {
|
|||
ParentItemAdapter(mutableListOf(), { callback ->
|
||||
homeHandleSearch(callback)
|
||||
}, { item ->
|
||||
activity?.loadHomepageList(item)
|
||||
activity?.loadHomepageList(item, expandCallback = {
|
||||
homeViewModel.expandAndReturn(it)
|
||||
})
|
||||
}, { name ->
|
||||
homeViewModel.expand(name)
|
||||
})
|
||||
home_master_recycler?.setMaxViewPoolSize(0, Int.MAX_VALUE)
|
||||
home_master_recycler.layoutManager = object : LinearLayoutManager(context) {
|
||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||
return false
|
||||
}
|
||||
}; // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
|
||||
} // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
|
||||
|
||||
if (context?.isTvSettings() == false) {
|
||||
LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap
|
||||
|
|
|
@ -6,10 +6,12 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.HomePageList
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import kotlinx.android.synthetic.main.homepage_parent.view.*
|
||||
|
||||
|
@ -17,11 +19,12 @@ import kotlinx.android.synthetic.main.homepage_parent.view.*
|
|||
class ParentItemAdapter(
|
||||
private var items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
private val moreInfoClickCallback: (HomePageList) -> Unit,
|
||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||
private val expandCallback: ((String) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder {
|
||||
//println("onCreateViewHolder $i")
|
||||
val layout =
|
||||
if (parent.context.isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent
|
||||
return ParentViewHolder(
|
||||
|
@ -33,6 +36,8 @@ class ParentItemAdapter(
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
//println("onBindViewHolder $position")
|
||||
|
||||
when (holder) {
|
||||
is ParentViewHolder -> {
|
||||
holder.bind(items[position])
|
||||
|
@ -55,14 +60,25 @@ class ParentItemAdapter(
|
|||
}
|
||||
|
||||
@JvmName("updateListExpandableHomepageList")
|
||||
fun updateList(newList: MutableList<HomeViewModel.ExpandableHomepageList>) {
|
||||
fun updateList(
|
||||
newList: MutableList<HomeViewModel.ExpandableHomepageList>,
|
||||
recyclerView: RecyclerView? = null
|
||||
) {
|
||||
// this
|
||||
// 1. prevents deep copy that makes this.items == newList
|
||||
// 2. filters out undesirable results
|
||||
// 3. moves empty results to the bottom (sortedBy is a stable sort)
|
||||
val new =
|
||||
newList.map { it.copy(list = it.list.copy(list = it.list.list.filterSearchResponse())) }
|
||||
.sortedBy { it.list.list.isEmpty() }
|
||||
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
SearchDiffCallback(items, newList)
|
||||
SearchDiffCallback(items, new)
|
||||
)
|
||||
items.clear()
|
||||
items.addAll(newList.map { it.copy(list = it.list.copy()) }) // I have to do this otherwise it is a "copy" and dispatchUpdatesTo wont work
|
||||
items.addAll(new)
|
||||
|
||||
/*val mAdapter = this
|
||||
val mAdapter = this
|
||||
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
mAdapter.notifyItemRangeInserted(position, count)
|
||||
|
@ -77,18 +93,44 @@ class ParentItemAdapter(
|
|||
}
|
||||
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
// I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind
|
||||
recyclerView?.apply {
|
||||
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
|
||||
val missingUpdates = (position until (position + count)).toMutableSet()
|
||||
for (i in 0 until mAdapter.itemCount) {
|
||||
val viewHolder = getChildViewHolder(getChildAt(i))
|
||||
val absolutePosition = viewHolder.absoluteAdapterPosition
|
||||
if (absolutePosition >= position && absolutePosition < position + count) {
|
||||
val expand = items.getOrNull(absolutePosition) ?: continue
|
||||
if (viewHolder is ParentViewHolder) {
|
||||
missingUpdates -= absolutePosition
|
||||
if (viewHolder.title.text == expand.list.name) {
|
||||
viewHolder.update(expand)
|
||||
} else {
|
||||
viewHolder.bind(expand)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// just in case some item did not get updated
|
||||
for (i in missingUpdates) {
|
||||
mAdapter.notifyItemChanged(i, payload)
|
||||
}
|
||||
} ?: run { // in case we don't have a nice
|
||||
mAdapter.notifyItemRangeChanged(position, count, payload)
|
||||
}
|
||||
})*/
|
||||
}
|
||||
})
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
//diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
class ParentViewHolder
|
||||
constructor(
|
||||
itemView: View,
|
||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
private val moreInfoClickCallback: (HomePageList) -> Unit,
|
||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||
private val expandCallback: ((String) -> Unit)? = null,
|
||||
) :
|
||||
RecyclerView.ViewHolder(itemView) {
|
||||
|
@ -96,6 +138,23 @@ class ParentItemAdapter(
|
|||
val recyclerView: RecyclerView = itemView.home_child_recyclerview
|
||||
private val moreInfo: FrameLayout? = itemView.home_child_more_info
|
||||
|
||||
fun update(expand: HomeViewModel.ExpandableHomepageList) {
|
||||
val info = expand.list
|
||||
(recyclerView.adapter as? HomeChildItemAdapter?)?.apply {
|
||||
updateList(info.list.toMutableList())
|
||||
hasNext = expand.hasNext
|
||||
} ?: run {
|
||||
recyclerView.adapter = HomeChildItemAdapter(
|
||||
info.list.toMutableList(),
|
||||
clickCallback = clickCallback,
|
||||
nextFocusUp = recyclerView.nextFocusUpId,
|
||||
nextFocusDown = recyclerView.nextFocusDownId,
|
||||
).apply {
|
||||
isHorizontal = info.isHorizontalImages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(expand: HomeViewModel.ExpandableHomepageList) {
|
||||
val info = expand.list
|
||||
recyclerView.adapter = HomeChildItemAdapter(
|
||||
|
@ -105,6 +164,7 @@ class ParentItemAdapter(
|
|||
nextFocusDown = recyclerView.nextFocusDownId,
|
||||
).apply {
|
||||
isHorizontal = info.isHorizontalImages
|
||||
hasNext = expand.hasNext
|
||||
}
|
||||
|
||||
title.text = info.name
|
||||
|
@ -116,8 +176,12 @@ class ParentItemAdapter(
|
|||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
val count = recyclerView.adapter?.itemCount ?: return
|
||||
if (!recyclerView.canScrollHorizontally(1) && expand.hasNext && expandCount != count) {
|
||||
val adapter = recyclerView.adapter
|
||||
if (adapter !is HomeChildItemAdapter) return
|
||||
|
||||
val count = adapter.itemCount
|
||||
val hasNext = adapter.hasNext
|
||||
if (!recyclerView.canScrollHorizontally(1) && hasNext && expandCount != count) {
|
||||
expandCount = count
|
||||
expandCallback?.invoke(name)
|
||||
}
|
||||
|
@ -127,7 +191,7 @@ class ParentItemAdapter(
|
|||
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||
|
||||
moreInfo?.setOnClickListener {
|
||||
moreInfoClickCallback.invoke(info)
|
||||
moreInfoClickCallback.invoke(expand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,9 +158,8 @@ class HomeViewModel : ViewModel() {
|
|||
|
||||
val lock: MutableSet<String> = mutableSetOf()
|
||||
|
||||
// this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive:
|
||||
fun expand(name: String) = viewModelScope.launch {
|
||||
if (lock.contains(name)) return@launch
|
||||
suspend fun expandAndReturn(name: String) : ExpandableHomepageList? {
|
||||
if (lock.contains(name)) return null
|
||||
lock += name
|
||||
|
||||
repo?.apply {
|
||||
|
@ -183,11 +182,8 @@ class HomeViewModel : ViewModel() {
|
|||
"Expanded contained an item that was previously already in the list\n${list.name} = ${this.list.list}\n${newList.name} = ${newList.list}"
|
||||
}
|
||||
|
||||
val before = list.list.size
|
||||
this.list.list += newList.list
|
||||
this.list.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason
|
||||
expandable[key] = this
|
||||
val after = list.list.size
|
||||
} ?: debugWarning {
|
||||
"Expanded an item not in main load named $key, current list is ${expandable.keys}"
|
||||
}
|
||||
|
@ -201,6 +197,13 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
lock -= name
|
||||
|
||||
return expandable[name]
|
||||
}
|
||||
|
||||
// this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive:
|
||||
fun expand(name: String) = viewModelScope.launch {
|
||||
expandAndReturn(name)
|
||||
}
|
||||
|
||||
private fun load(api: MainAPI?) = viewModelScope.launch {
|
||||
|
|
|
@ -27,7 +27,6 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageLis
|
|||
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
|
||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||
import com.lagradost.cloudstream3.ui.search.SearchViewModel
|
||||
import com.lagradost.cloudstream3.utils.UIHelper
|
||||
|
@ -173,7 +172,7 @@ class QuickSearchFragment : Fragment() {
|
|||
updateList(list.map { ongoing ->
|
||||
val ongoingList = HomePageList(
|
||||
ongoing.apiName,
|
||||
if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList()
|
||||
if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
|
||||
)
|
||||
ongoingList
|
||||
})
|
||||
|
|
|
@ -27,6 +27,7 @@ class SearchAdapter(
|
|||
private val resView: AutofitRecyclerView,
|
||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var hasNext : Boolean = false
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid
|
||||
|
|
|
@ -426,7 +426,7 @@ class SearchFragment : Fragment() {
|
|||
val newItems = list.map { ongoing ->
|
||||
val ongoingList = HomePageList(
|
||||
ongoing.apiName,
|
||||
if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList()
|
||||
if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
|
||||
)
|
||||
ongoingList
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ class SearchViewModel : ViewModel() {
|
|||
_currentSearch.postValue(emptyList())
|
||||
}
|
||||
|
||||
private var currentSearchIndex = 0
|
||||
private var onGoingSearch: Job? = null
|
||||
fun searchAndCancel(
|
||||
query: String,
|
||||
|
@ -50,6 +51,7 @@ class SearchViewModel : ViewModel() {
|
|||
ignoreSettings: Boolean = false,
|
||||
isQuickSearch: Boolean = false,
|
||||
) {
|
||||
currentSearchIndex++
|
||||
onGoingSearch?.cancel()
|
||||
onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch)
|
||||
}
|
||||
|
@ -70,6 +72,7 @@ class SearchViewModel : ViewModel() {
|
|||
isQuickSearch: Boolean = false,
|
||||
) =
|
||||
viewModelScope.launch {
|
||||
val currentIndex = currentSearchIndex
|
||||
if (query.length <= 1) {
|
||||
clearSearch()
|
||||
return@launch
|
||||
|
@ -91,21 +94,24 @@ class SearchViewModel : ViewModel() {
|
|||
|
||||
_searchResponse.postValue(Resource.Loading())
|
||||
|
||||
val currentList = ArrayList<OnGoingSearch>()
|
||||
|
||||
_currentSearch.postValue(ArrayList())
|
||||
|
||||
withContext(Dispatchers.IO) { // This interrupts UI otherwise
|
||||
val currentList = ArrayList<OnGoingSearch>()
|
||||
|
||||
repos.filter { a ->
|
||||
(ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch)
|
||||
}.apmap { a -> // Parallel
|
||||
val search = if (isQuickSearch) a.quickSearch(query) else a.search(query)
|
||||
if(currentSearchIndex != currentIndex) return@apmap
|
||||
currentList.add(OnGoingSearch(a.name, search))
|
||||
_currentSearch.postValue(currentList)
|
||||
}
|
||||
}
|
||||
_currentSearch.postValue(currentList)
|
||||
|
||||
if(currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug
|
||||
|
||||
_currentSearch.postValue(currentList)
|
||||
val list = ArrayList<SearchResponse>()
|
||||
val nestedList =
|
||||
currentList.map { it.data }
|
||||
|
@ -128,3 +134,4 @@ class SearchViewModel : ViewModel() {
|
|||
_searchResponse.postValue(Resource.Success(list))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.tvprovider.media.tv.PreviewChannelHelper
|
||||
import androidx.tvprovider.media.tv.TvContractCompat
|
||||
import androidx.tvprovider.media.tv.WatchNextProgram
|
||||
|
@ -52,6 +53,11 @@ import java.net.URL
|
|||
import java.net.URLDecoder
|
||||
|
||||
object AppUtils {
|
||||
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
||||
for (i in 0..maxViewTypeId)
|
||||
recycledViewPool.setMaxRecycledViews(i, maxPoolSize)
|
||||
}
|
||||
|
||||
//fun Context.deleteFavorite(data: SearchResponse) {
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
// normalSafeApiCall {
|
||||
|
|
Loading…
Reference in a new issue