migrated some items to viewbindings + removed Some<T>

This commit is contained in:
LagradOst 2023-07-13 23:18:37 +02:00
parent 927453d9fe
commit 05a0d3cd81
46 changed files with 1565 additions and 1264 deletions

View file

@ -28,6 +28,11 @@ android {
testOptions {
unitTests.isReturnDefaultValues = true
}
viewBinding {
enable = true
}
signingConfigs {
create("prerelease") {
if (prereleaseStoreFile != null) {

View file

@ -57,32 +57,6 @@ fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) ->
liveData.observe(this) { action(it) }
}
inline fun <reified T : Any> some(value: T?): Some<T> {
return if (value == null) {
Some.None
} else {
Some.Success(value)
}
}
sealed class Some<out T> {
data class Success<out T>(val value: T) : Some<T>()
object None : Some<Nothing>()
override fun toString(): String {
return when (this) {
is None -> "None"
is Success -> "Some(${value.toString()})"
}
}
}
sealed class ResourceSome<out T> {
data class Success<out T>(val value: T) : ResourceSome<T>()
object None : ResourceSome<Nothing>()
data class Loading(val data: Any? = null) : ResourceSome<Nothing>()
}
sealed class Resource<out T> {
data class Success<out T>(val value: T) : Resource<T>()
data class Failure(

View file

@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
@ -15,13 +16,12 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_child_downloads.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DownloadChildFragment : Fragment() {
companion object {
fun newInstance(headerName: String, folder: String) : Bundle {
fun newInstance(headerName: String, folder: String): Bundle {
return Bundle().apply {
putString("folder", folder)
putString("name", headerName)
@ -30,13 +30,21 @@ class DownloadChildFragment : Fragment() {
}
override fun onDestroyView() {
(download_child_list?.adapter as DownloadChildAdapter?)?.killAdapter()
(binding?.downloadChildList?.adapter as DownloadChildAdapter?)?.killAdapter()
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
binding = null
super.onDestroyView()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_child_downloads, container, false)
var binding: FragmentChildDownloadsBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_child_downloads, container, false)
}
private fun updateList(folder: String) = main {
@ -50,14 +58,15 @@ class DownloadChildFragment : Fragment() {
?: return@mapNotNull null
VisualDownloadChildCached(info.fileLength, info.totalBytes, it)
}
}.sortedBy { it.data.episode + (it.data.season?: 0)*100000 }
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
if (eps.isEmpty()) {
activity?.onBackPressed()
return@main
}
(download_child_list?.adapter as DownloadChildAdapter? ?: return@main).cardList = eps
download_child_list?.adapter?.notifyDataSetChanged()
(binding?.downloadChildList?.adapter as DownloadChildAdapter? ?: return@main).cardList =
eps
binding?.downloadChildList?.adapter?.notifyDataSetChanged()
}
}
@ -72,13 +81,16 @@ class DownloadChildFragment : Fragment() {
activity?.onBackPressed() // TODO FIX
return
}
context?.fixPaddingStatusbar(download_child_root)
fixPaddingStatusbar(binding?.downloadChildRoot)
download_child_toolbar.title = name
download_child_toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
download_child_toolbar.setNavigationOnClickListener {
binding?.downloadChildToolbar?.apply {
title = name
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
setNavigationOnClickListener {
activity?.onBackPressed()
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
DownloadChildAdapter(
@ -88,7 +100,7 @@ class DownloadChildFragment : Fragment() {
}
downloadDeleteEventListener = { id: Int ->
val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList
val list = (binding?.downloadChildList?.adapter as DownloadChildAdapter?)?.cardList
if (list != null) {
if (list.any { it.data.id == id }) {
updateList(folder)
@ -98,8 +110,8 @@ class DownloadChildFragment : Fragment() {
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
download_child_list.adapter = adapter
download_child_list.layoutManager = GridLayoutManager(context, 1)
binding?.downloadChildList?.adapter = adapter
binding?.downloadChildList?.layoutManager = GridLayoutManager(context, 1)
updateList(folder)
}

View file

@ -34,10 +34,10 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_downloads.*
import kotlinx.android.synthetic.main.stream_input.*
import android.text.format.Formatter.formatShortFileSize
import androidx.core.widget.doOnTextChanged
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
import com.lagradost.cloudstream3.databinding.StreamInputBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
@ -60,8 +60,8 @@ class DownloadFragment : Fragment() {
private fun setList(list: List<VisualDownloadHeaderCached>) {
main {
(download_list?.adapter as DownloadHeaderAdapter?)?.cardList = list
download_list?.adapter?.notifyDataSetChanged()
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
binding?.downloadList?.adapter?.notifyDataSetChanged()
}
}
@ -70,10 +70,13 @@ class DownloadFragment : Fragment() {
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!!
downloadDeleteEventListener = null
}
(download_list?.adapter as DownloadHeaderAdapter?)?.killAdapter()
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.killAdapter()
binding = null
super.onDestroyView()
}
var binding : FragmentDownloadsBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -82,7 +85,9 @@ class DownloadFragment : Fragment() {
downloadsViewModel =
ViewModelProvider(this)[DownloadViewModel::class.java]
return inflater.inflate(R.layout.fragment_downloads, container, false)
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false)
}
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
@ -92,36 +97,40 @@ class DownloadFragment : Fragment() {
hideKeyboard()
observe(downloadsViewModel.noDownloadsText) {
text_no_downloads.text = it
binding?.textNoDownloads?.text = it
}
observe(downloadsViewModel.headerCards) {
setList(it)
download_loading.isVisible = false
binding?.downloadLoading?.isVisible = false
}
observe(downloadsViewModel.availableBytes) {
download_free_txt?.text =
binding?.downloadFreeTxt?.text =
getString(R.string.storage_size_format).format(
getString(R.string.free_storage),
formatShortFileSize(view.context, it)
)
download_free?.setLayoutWidth(it)
binding?.downloadFree?.setLayoutWidth(it)
}
observe(downloadsViewModel.usedBytes) {
download_used_txt?.text =
binding?.apply {
downloadUsedTxt.text =
getString(R.string.storage_size_format).format(
getString(R.string.used_storage),
formatShortFileSize(view.context, it)
)
download_used?.setLayoutWidth(it)
download_storage_appbar?.isVisible = it > 0
downloadUsed.setLayoutWidth(it)
downloadStorageAppbar.isVisible = it > 0
}
}
observe(downloadsViewModel.downloadBytes) {
download_app_txt?.text =
binding?.apply {
downloadAppTxt.text =
getString(R.string.storage_size_format).format(
getString(R.string.app_storage),
formatShortFileSize(view.context, it)
)
download_app?.setLayoutWidth(it)
downloadApp.setLayoutWidth(it)
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
@ -164,7 +173,7 @@ class DownloadFragment : Fragment() {
)
downloadDeleteEventListener = { id ->
val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
if (list != null) {
if (list.any { it.data.id == id }) {
context?.let { ctx ->
@ -177,31 +186,36 @@ class DownloadFragment : Fragment() {
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
download_list?.adapter = adapter
download_list?.layoutManager = GridLayoutManager(context, 1)
binding?.downloadList?.apply {
this.adapter = adapter
layoutManager = GridLayoutManager(context, 1)
}
// Should be visible in emulator layout
download_stream_button?.isGone = isTrueTvSettings()
download_stream_button?.setOnClickListener {
binding?.downloadStreamButton?.isGone = isTrueTvSettings()
binding?.downloadStreamButton?.setOnClickListener {
val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
dialog.setContentView(R.layout.stream_input)
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
dialog.setContentView(binding.root)
dialog.show()
// If user has clicked the switch do not interfere
var preventAutoSwitching = false
dialog.hls_switch?.setOnClickListener {
binding.hlsSwitch.setOnClickListener {
preventAutoSwitching = true
}
fun activateSwitchOnHls(text: String?) {
dialog.hls_switch?.isChecked = normalSafeApiCall {
binding.hlsSwitch.isChecked = normalSafeApiCall {
URI(text).path?.substringAfterLast(".")?.contains("m3u")
} == true
}
dialog.stream_referer?.doOnTextChanged { text, _, _, _ ->
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
if (!preventAutoSwitching)
activateSwitchOnHls(text?.toString())
}
@ -210,16 +224,16 @@ class DownloadFragment : Fragment() {
0
)?.text?.toString()?.let { copy ->
val fixedText = copy.trim()
dialog.stream_url?.setText(fixedText)
binding.streamUrl.setText(fixedText)
activateSwitchOnHls(fixedText)
}
dialog.apply_btt?.setOnClickListener {
val url = dialog.stream_url.text?.toString()
binding.applyBtt.setOnClickListener {
val url = binding.streamUrl.text?.toString()
if (url.isNullOrEmpty()) {
showToast(activity, R.string.error_invalid_url, Toast.LENGTH_SHORT)
} else {
val referer = dialog.stream_referer.text?.toString()
val referer = binding.streamReferer.text?.toString()
activity?.navigate(
R.id.global_to_navigation_player,
@ -228,7 +242,7 @@ class DownloadFragment : Fragment() {
listOf(BasicLink(url)),
extract = true,
referer = referer,
isM3u8 = dialog.hls_switch?.isChecked
isM3u8 = binding.hlsSwitch.isChecked
)
)
)
@ -237,22 +251,22 @@ class DownloadFragment : Fragment() {
}
}
dialog.cancel_btt?.setOnClickListener {
binding.cancelBtt.setOnClickListener {
dialog.dismissSafe(activity)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
download_stream_button?.shrink() // hide
binding?.downloadStreamButton?.shrink() // hide
} else if (dy < -5) {
download_stream_button?.extend() // show
binding?.downloadStreamButton?.extend() // show
}
}
}
downloadsViewModel.updateList(requireContext())
context?.fixPaddingStatusbar(download_root)
fixPaddingStatusbar(binding?.downloadRoot)
}
}

View file

@ -34,12 +34,15 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
import com.lagradost.cloudstream3.databinding.HomeEpisodesExpandedBinding
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
import com.lagradost.cloudstream3.databinding.TvtypesChipsBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.*
@ -64,24 +67,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import kotlinx.android.synthetic.main.activity_main_tv.*
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.home_api_fab
import kotlinx.android.synthetic.main.fragment_home.home_change_api_loading
import kotlinx.android.synthetic.main.fragment_home.home_loading
import kotlinx.android.synthetic.main.fragment_home.home_loading_error
import kotlinx.android.synthetic.main.fragment_home.home_loading_shimmer
import kotlinx.android.synthetic.main.fragment_home.home_loading_statusbar
import kotlinx.android.synthetic.main.fragment_home.home_master_recycler
import kotlinx.android.synthetic.main.fragment_home.home_reload_connection_open_in_browser
import kotlinx.android.synthetic.main.fragment_home.home_reload_connectionerror
import kotlinx.android.synthetic.main.fragment_home.result_error_text
import kotlinx.android.synthetic.main.fragment_home_tv.*
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.home_episodes_expanded.*
import kotlinx.android.synthetic.main.tvtypes_chips.*
import kotlinx.android.synthetic.main.tvtypes_chips.view.*
import java.util.*
@ -125,22 +111,26 @@ class HomeFragment : Fragment() {
expand: HomeViewModel.ExpandableHomepageList,
deleteCallback: (() -> Unit)? = null,
expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null,
dismissCallback : (() -> Unit),
dismissCallback: (() -> Unit),
): BottomSheetDialog {
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 binding: HomeEpisodesExpandedBinding = HomeEpisodesExpandedBinding.inflate(
bottomSheetDialogBuilder.layoutInflater,
null,
false
)
bottomSheetDialogBuilder.setContentView(binding.root)
//val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!!
//title.findViewTreeLifecycleOwner().lifecycle.addObserver()
val item = expand.list
title.text = item.name
val recycle =
bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
val titleHolder =
bottomSheetDialogBuilder.findViewById<FrameLayout>(R.id.home_expanded_drag_down)!!
binding.homeExpandedText.text = item.name
// val recycle =
// bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
//val titleHolder =
// bottomSheetDialogBuilder.findViewById<FrameLayout>(R.id.home_expanded_drag_down)!!
// main {
//(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply {
@ -159,10 +149,10 @@ class HomeFragment : Fragment() {
// })
//}
// }
val delete = bottomSheetDialogBuilder.home_expanded_delete
delete.isGone = deleteCallback == null
//val delete = bottomSheetDialogBuilder.home_expanded_delete
binding.homeExpandedDelete.isGone = deleteCallback == null
if (deleteCallback != null) {
delete.setOnClickListener {
binding.homeExpandedDelete.setOnClickListener {
try {
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
val dialogClickListener =
@ -172,6 +162,7 @@ class HomeFragment : Fragment() {
deleteCallback.invoke()
bottomSheetDialogBuilder.dismissSafe(this)
}
DialogInterface.BUTTON_NEGATIVE -> {}
}
}
@ -191,16 +182,16 @@ class HomeFragment : Fragment() {
}
}
}
titleHolder.setOnClickListener {
binding.homeExpandedDragDown.setOnClickListener {
bottomSheetDialogBuilder.dismissSafe(this)
}
// Span settings
recycle.spanCount = currentSpan
binding.homeExpandedRecycler.spanCount = currentSpan
recycle.adapter = SearchAdapter(item.list.toMutableList(), recycle) { callback ->
binding.homeExpandedRecycler.adapter =
SearchAdapter(item.list.toMutableList(), binding.homeExpandedRecycler) { callback ->
handleSearchClickCallback(this, callback)
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later
@ -210,7 +201,8 @@ class HomeFragment : Fragment() {
hasNext = expand.hasNext
}
recycle.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.homeExpandedRecycler.addOnScrollListener(object :
RecyclerView.OnScrollListener() {
var expandCount = 0
val name = expand.list.name
@ -238,7 +230,7 @@ class HomeFragment : Fragment() {
})
val spanListener = { span: Int ->
recycle.spanCount = span
binding.homeExpandedRecycler.spanCount = span
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
}
@ -280,19 +272,19 @@ class HomeFragment : Fragment() {
)
}
private fun getPairList(header: ChipGroup) = getPairList(
header.home_select_anime,
header.home_select_cartoons,
header.home_select_tv_series,
header.home_select_documentaries,
header.home_select_movies,
header.home_select_asian,
header.home_select_livestreams,
header.home_select_nsfw,
header.home_select_others
private fun getPairList(header: TvtypesChipsBinding) = getPairList(
header.homeSelectAnime,
header.homeSelectCartoons,
header.homeSelectTvSeries,
header.homeSelectDocumentaries,
header.homeSelectMovies,
header.homeSelectAsian,
header.homeSelectLivestreams,
header.homeSelectNsfw,
header.homeSelectOthers
)
fun validateChips(header: ChipGroup?, validTypes: List<TvType>) {
fun validateChips(header: TvtypesChipsBinding?, validTypes: List<TvType>) {
if (header == null) return
val pairList = getPairList(header)
for ((button, types) in pairList) {
@ -301,7 +293,7 @@ class HomeFragment : Fragment() {
}
}
fun updateChips(header: ChipGroup?, selectedTypes: List<TvType>) {
fun updateChips(header: TvtypesChipsBinding?, selectedTypes: List<TvType>) {
if (header == null) return
val pairList = getPairList(header)
for ((button, types) in pairList) {
@ -311,7 +303,7 @@ class HomeFragment : Fragment() {
}
fun bindChips(
header: ChipGroup?,
header: TvtypesChipsBinding?,
selectedTypes: List<TvType>,
validTypes: List<TvType>,
callback: (List<TvType>) -> Unit
@ -344,7 +336,13 @@ class HomeFragment : Fragment() {
BottomSheetDialog(this)
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
builder.setContentView(R.layout.home_select_mainpage)
val binding: HomeSelectMainpageBinding = HomeSelectMainpageBinding.inflate(
builder.layoutInflater,
null,
false
)
builder.setContentView(binding.root)
builder.show()
builder.let { dialog ->
val isMultiLang = getApiProviderLangSettings().let { set ->
@ -408,7 +406,7 @@ class HomeFragment : Fragment() {
}
bindChips(
dialog.home_select_group,
binding.tvtypesChipsScroll.tvtypesChips,
preSelectedTypes,
validAPIs.flatMap { it.supportedTypes }.distinct()
) { list ->
@ -423,6 +421,9 @@ class HomeFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
var binding: FragmentHomeBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -433,11 +434,24 @@ class HomeFragment : Fragment() {
bottomSheetDialog?.ownShow()
val layout =
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home
return inflater.inflate(layout, container, false)
/* val binding = FragmentHomeTvBinding.inflate(layout, container, false)
binding.homeLoadingError
val binding2 = FragmentHomeBinding.inflate(layout, container, false)
binding2.homeLoadingError*/
val root = inflater.inflate(layout, container, false)
binding = FragmentHomeBinding.bind(root)
//val localBinding = FragmentHomeBinding.inflate(inflater)
//binding = localBinding
return root
//return inflater.inflate(layout, container, false)
}
override fun onDestroyView() {
bottomSheetDialog?.ownHide()
binding = null
super.onDestroyView()
}
@ -467,7 +481,7 @@ class HomeFragment : Fragment() {
fixGrid()
}
fun bookmarksUpdated(_data : Boolean) {
fun bookmarksUpdated(_data: Boolean) {
reloadStored()
}
@ -525,14 +539,18 @@ class HomeFragment : Fragment() {
private var bottomSheetDialog: BottomSheetDialog? = null
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fixGrid()
home_change_api_loading?.setOnClickListener(apiChangeClickListener)
home_api_fab?.setOnClickListener(apiChangeClickListener)
home_random?.setOnClickListener {
binding?.homeChangeApiLoading?.setOnClickListener(apiChangeClickListener)
binding?.homeChangeApiLoading?.setOnClickListener(apiChangeClickListener)
binding?.homeApiFab?.setOnClickListener(apiChangeClickListener)
binding?.homeRandom?.setOnClickListener {
if (listHomepageItems.isNotEmpty()) {
activity.loadSearchResult(listHomepageItems.random())
}
@ -542,41 +560,45 @@ class HomeFragment : Fragment() {
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
toggleRandomButton =
settingsManager.getBoolean(getString(R.string.random_button_key), false)
home_random?.visibility = View.GONE
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
) && !isTvSettings()
binding?.homeRandom?.visibility = View.GONE
}
observe(homeViewModel.preview) { preview ->
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setPreviewData(
(binding?.homeMasterRecycler?.adapter as? HomeParentItemAdapterPreview?)?.setPreviewData(
preview
)
}
observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName
home_api_fab?.text = apiName
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setApiName(
binding?.homeApiFab?.text = apiName
(binding?.homeMasterRecycler?.adapter as? HomeParentItemAdapterPreview?)?.setApiName(
apiName
)
}
observe(homeViewModel.page) { data ->
binding?.apply {
when (data) {
is Resource.Success -> {
home_loading_shimmer?.stopShimmer()
homeLoadingShimmer.stopShimmer()
val d = data.value
val mutableListOfResponse = mutableListOf<SearchResponse>()
listHomepageItems.clear()
(home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(
d.values.toMutableList(),
home_master_recycler
homeMasterRecycler
)
home_loading?.isVisible = false
home_loading_error?.isVisible = false
home_master_recycler?.isVisible = true
homeLoading.isVisible = false
homeLoadingError.isVisible = false
homeMasterRecycler.isVisible = true
//home_loaded?.isVisible = true
if (toggleRandomButton) {
//Flatten list
@ -584,19 +606,22 @@ class HomeFragment : Fragment() {
mutableListOfResponse.addAll(dlist.list.list)
}
listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url })
home_random?.isVisible = listHomepageItems.isNotEmpty()
homeRandom.isVisible = listHomepageItems.isNotEmpty()
} else {
home_random?.isGone = true
homeRandom.isGone = true
}
}
is Resource.Failure -> {
home_loading_shimmer?.stopShimmer()
result_error_text.text = data.errorString
homeLoadingShimmer.stopShimmer()
home_reload_connectionerror.setOnClickListener(apiChangeClickListener)
resultErrorText.text = data.errorString
home_reload_connection_open_in_browser.setOnClickListener { view ->
homeReloadConnectionerror.setOnClickListener(apiChangeClickListener)
homeReloadConnectionOpenInBrowser.setOnClickListener { view ->
val validAPIs = apis//.filter { api -> api.hasMainPage }
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api ->
@ -615,21 +640,23 @@ class HomeFragment : Fragment() {
}
}
home_loading?.isVisible = false
home_loading_error?.isVisible = true
home_master_recycler?.isVisible = false
homeLoading.isVisible = false
homeLoadingError.isVisible = true
homeMasterRecycler.isVisible = false
//home_loaded?.isVisible = false
}
is Resource.Loading -> {
(home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(listOf())
home_loading_shimmer?.startShimmer()
home_loading?.isVisible = true
home_loading_error?.isVisible = false
home_master_recycler?.isVisible = false
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(listOf())
homeLoadingShimmer.startShimmer()
homeLoading.isVisible = true
homeLoadingError.isVisible = false
homeMasterRecycler.isVisible = false
//home_loaded?.isVisible = false
}
}
}
}
@ -638,19 +665,19 @@ class HomeFragment : Fragment() {
HOME_BOOKMARK_VALUE_LIST,
availableWatchStatusTypes.first.map { it.internalId }.toIntArray()
)
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setAvailableWatchStatusTypes(
(binding?.homeMasterRecycler?.adapter as? HomeParentItemAdapterPreview?)?.setAvailableWatchStatusTypes(
availableWatchStatusTypes
)
}
observe(homeViewModel.bookmarks) { data ->
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setBookmarkData(
(binding?.homeMasterRecycler?.adapter as? HomeParentItemAdapterPreview?)?.setBookmarkData(
data
)
}
observe(homeViewModel.resumeWatching) { resumeWatching ->
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setResumeWatchingData(
(binding?.homeMasterRecycler?.adapter as? HomeParentItemAdapterPreview?)?.setResumeWatchingData(
resumeWatching
)
if (isTrueTvSettings()) {
@ -665,9 +692,9 @@ class HomeFragment : Fragment() {
//context?.fixPaddingStatusbarView(home_statusbar)
//context?.fixPaddingStatusbar(home_padding)
context?.fixPaddingStatusbar(home_loading_statusbar)
fixPaddingStatusbar(binding?.homeLoadingStatusbar)
home_master_recycler?.adapter =
binding?.homeMasterRecycler?.adapter =
HomeParentItemAdapterPreview(mutableListOf(), { callback ->
homeHandleSearch(callback)
}, { item ->
@ -699,17 +726,21 @@ class HomeFragment : Fragment() {
reloadStored()
loadHomePage(false)
home_master_recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding?.homeMasterRecycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
binding?.apply {
if (dy > 0) { //check for scroll down
home_api_fab?.shrink() // hide
home_random?.shrink()
homeApiFab.shrink() // hide
homeRandom.shrink()
} else if (dy < -5) {
if (!isTvSettings()) {
home_api_fab?.extend() // show
home_random?.extend()
homeApiFab.extend() // show
homeRandom.extend()
}
}
}
super.onScrolled(recyclerView, dx, dy)
}
@ -718,18 +749,20 @@ class HomeFragment : Fragment() {
// nice profile pic on homepage
//home_profile_picture_holder?.isVisible = false
// just in case
binding?.apply {
if (isTvSettings()) {
home_api_fab?.isVisible = false
homeApiFab.isVisible = false
if (isTrueTvSettings()) {
home_change_api_loading?.isVisible = true
home_change_api_loading?.isFocusable = true
home_change_api_loading?.isFocusableInTouchMode = true
homeChangeApiLoading.isVisible = true
homeChangeApiLoading.isFocusable = true
homeChangeApiLoading.isFocusableInTouchMode = true
}
// home_bookmark_select?.isFocusable = true
// home_bookmark_select?.isFocusableInTouchMode = true
} else {
home_api_fab?.isVisible = true
home_change_api_loading?.isVisible = false
homeApiFab.isVisible = true
homeChangeApiLoading.isVisible = false
}
}
//TODO READD THIS
/*for (syncApi in OAuth2Apis) {

View file

@ -544,7 +544,7 @@ class HomeParentItemAdapterPreview(
}
}
itemView.home_search?.context?.fixPaddingStatusbar(itemView.home_search)
fixPaddingStatusbar(itemView.home_search)
itemView.home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
@ -575,7 +575,7 @@ class HomeParentItemAdapterPreview(
layoutParams = params
}
} else {
itemView.home_none_padding?.context?.fixPaddingStatusbarView(itemView.home_none_padding)
fixPaddingStatusbarView(itemView.home_none_padding)
}
when (preview) {
is Resource.Success -> {

View file

@ -6,7 +6,6 @@ import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -14,6 +13,7 @@ import android.view.animation.AlphaAnimation
import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.tabs.TabLayoutMediator
import com.lagradost.cloudstream3.APIHolder
@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.mvvm.observe
@ -37,7 +38,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import kotlinx.android.synthetic.main.fragment_library.*
import kotlin.math.abs
const val LIBRARY_FOLDER = "library_folder"
@ -73,14 +73,25 @@ class LibraryFragment : Fragment() {
private val libraryViewModel: LibraryViewModel by activityViewModels()
var binding: FragmentLibraryBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_library, container, false)
): View {
val localBinding = FragmentLibraryBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_library, container, false)
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onSaveInstanceState(outState: Bundle) {
viewpager?.currentItem?.let { currentItem ->
binding?.viewpager?.currentItem?.let { currentItem ->
outState.putInt(VIEWPAGER_ITEM_KEY, currentItem)
}
super.onSaveInstanceState(outState)
@ -88,9 +99,9 @@ class LibraryFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(search_status_bar_padding)
fixPaddingStatusbar(binding?.searchStatusBarPadding)
sort_fab?.setOnClickListener {
binding?.sortFab?.setOnClickListener {
val methods = libraryViewModel.sortingMethods.map {
txt(it.stringRes).asString(view.context)
}
@ -106,7 +117,7 @@ class LibraryFragment : Fragment() {
})
}
main_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
libraryViewModel.sort(ListSorting.Query, query)
return true
@ -129,7 +140,7 @@ class LibraryFragment : Fragment() {
libraryViewModel.reloadPages(false)
list_selector?.setOnClickListener {
binding?.listSelector?.setOnClickListener {
val items = libraryViewModel.availableApiNames
val currentItem = libraryViewModel.currentApiName.value
@ -209,18 +220,20 @@ class LibraryFragment : Fragment() {
}
}
provider_selector?.setOnClickListener {
binding?.providerSelector?.setOnClickListener {
val syncName = libraryViewModel.currentSyncApi?.syncIdName ?: return@setOnClickListener
activity?.showPluginSelectionDialog(syncName.name, syncName)
}
viewpager?.setPageTransformer(LibraryScrollTransformer())
viewpager?.adapter =
viewpager.adapter ?: ViewpagerAdapter(mutableListOf(), { isScrollingDown: Boolean ->
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
binding?.viewpager?.adapter =
binding?.viewpager?.adapter ?: ViewpagerAdapter(
mutableListOf(),
{ isScrollingDown: Boolean ->
if (isScrollingDown) {
sort_fab?.shrink()
binding?.sortFab?.shrink()
} else {
sort_fab?.extend()
binding?.sortFab?.extend()
}
}) callback@{ searchClickCallback ->
// To prevent future accidents
@ -267,6 +280,7 @@ class LibraryFragment : Fragment() {
)
}
}
LibraryOpenerType.None -> {}
LibraryOpenerType.Provider ->
savedSelection.providerData?.apiName?.let { apiName ->
@ -275,8 +289,10 @@ class LibraryFragment : Fragment() {
apiName,
)
}
LibraryOpenerType.Browser ->
openBrowser(searchClickCallback.card.url)
LibraryOpenerType.Search -> {
QuickSearchFragment.pushSearch(
activity,
@ -288,22 +304,28 @@ class LibraryFragment : Fragment() {
}
}
viewpager?.offscreenPageLimit = 2
viewpager?.reduceDragSensitivity()
binding?.apply {
viewpager.offscreenPageLimit = 2
viewpager.reduceDragSensitivity()
}
val startLoading = Runnable {
gridview?.numColumns = context?.getSpanCount() ?: 3
gridview?.adapter =
binding?.apply {
gridview.numColumns = context?.getSpanCount() ?: 3
gridview.adapter =
context?.let { LoadingPosterAdapter(it, 6 * 3) }
library_loading_overlay?.isVisible = true
library_loading_shimmer?.startShimmer()
empty_list_textview?.isVisible = false
libraryLoadingOverlay.isVisible = true
libraryLoadingShimmer.startShimmer()
emptyListTextview.isVisible = false
}
}
val stopLoading = Runnable {
gridview?.adapter = null
library_loading_overlay?.isVisible = false
library_loading_shimmer?.stopShimmer()
binding?.apply {
gridview.adapter = null
libraryLoadingOverlay.isVisible = false
libraryLoadingShimmer.stopShimmer()
}
}
val handler = Handler(Looper.getMainLooper())
@ -314,18 +336,24 @@ class LibraryFragment : Fragment() {
handler.removeCallbacks(startLoading)
val pages = resource.value
val showNotice = pages.all { it.items.isEmpty() }
empty_list_textview?.isVisible = showNotice
binding?.apply {
emptyListTextview.isVisible = showNotice
if (showNotice) {
if (libraryViewModel.availableApiNames.size > 1) {
empty_list_textview?.setText(R.string.empty_library_logged_in_message)
emptyListTextview.setText(R.string.empty_library_logged_in_message)
} else {
empty_list_textview?.setText(R.string.empty_library_no_accounts_message)
emptyListTextview.setText(R.string.empty_library_no_accounts_message)
}
}
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
// Using notifyItemRangeChanged keeps the animations when sorting
viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0)
viewpager.adapter?.notifyItemRangeChanged(
0,
viewpager.adapter?.itemCount ?: 0
)
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
// Without this there would be a flashing effect:
@ -334,7 +362,7 @@ class LibraryFragment : Fragment() {
savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos ->
if (currentPos < 0) return@let
viewpager?.setCurrentItem(currentPos, false)
viewpager.setCurrentItem(currentPos, false)
// Using remove() sets the key to 0 instead of removing it
savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1)
}
@ -353,26 +381,30 @@ class LibraryFragment : Fragment() {
startOffset = distance * 100L
fillAfter = true
}
viewpager?.startAnimation(hideAnimation)
viewpager?.startAnimation(showAnimation)
viewpager.startAnimation(hideAnimation)
viewpager.startAnimation(showAnimation)
}
TabLayoutMediator(
library_tab_layout,
libraryTabLayout,
viewpager,
) { tab, position ->
tab.text = pages.getOrNull(position)?.title?.asStringNull(context)
tab.view.setOnClickListener {
val currentItem = viewpager?.currentItem ?: return@setOnClickListener
val currentItem =
binding?.viewpager?.currentItem ?: return@setOnClickListener
val distance = abs(position - currentItem)
hideViewpager(distance)
}
}.attach()
}
}
is Resource.Loading -> {
// Only start loading after 200ms to prevent loading cached lists
handler.postDelayed(startLoading, 200)
}
is Resource.Failure -> {
stopLoading.run()
// No user indication it failed :(
@ -383,7 +415,7 @@ class LibraryFragment : Fragment() {
}
override fun onConfigurationChanged(newConfig: Configuration) {
(viewpager.adapter as? ViewpagerAdapter)?.rebind()
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
super.onConfigurationChanged(newConfig)
}
}

View file

@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import kotlinx.coroutines.delay
enum class ListSorting(@StringRes val stringRes: Int) {
Query(R.string.none),

View file

@ -3,23 +3,21 @@ package com.lagradost.cloudstream3.ui.library
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
import kotlin.math.roundToInt
@ -32,8 +30,7 @@ class PageAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return LibraryItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.search_result_grid_expanded, parent, false)
SearchResultGridExpandedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
@ -57,8 +54,7 @@ class PageAdapter(
}
}
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val cardView: ImageView = itemView.imageView
inner class LibraryItemViewHolder(val binding : SearchResultGridExpandedBinding) : RecyclerView.ViewHolder(binding.root) {
private val compactView = false//itemView.context.getGridIsCompact()
private val coverHeight: Int =
@ -85,11 +81,11 @@ class PageAdapter(
val fg =
getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor))
itemView.text_rating.apply {
binding.textRating.apply {
setTextColor(ColorStateList.valueOf(fg))
}
itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg)
itemView.watchProgress?.apply {
binding.textRatingHolder.backgroundTintList = ColorStateList.valueOf(bg)
binding.watchProgress.apply {
progressTintList = ColorStateList.valueOf(fg)
progressBackgroundTintList = ColorStateList.valueOf(bg)
}
@ -99,7 +95,7 @@ class PageAdapter(
// See searchAdaptor for this, it basically fixes the height
if (!compactView) {
cardView.apply {
binding.imageView.apply {
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
coverHeight
@ -108,22 +104,22 @@ class PageAdapter(
}
val showProgress = item.episodesCompleted != null && item.episodesTotal != null
itemView.watchProgress.isVisible = showProgress
binding.watchProgress.isVisible = showProgress
if (showProgress) {
itemView.watchProgress.max = item.episodesTotal!!
itemView.watchProgress.progress = item.episodesCompleted!!
binding.watchProgress.max = item.episodesTotal!!
binding.watchProgress.progress = item.episodesCompleted!!
}
itemView.imageText.text = item.name
binding.imageText.text = item.name
val showRating = (item.personalRating ?: 0) != 0
itemView.text_rating_holder.isVisible = showRating
binding.textRatingHolder.isVisible = showRating
if (showRating) {
// We want to show 8.5 but not 8.0 hence the replace
val rating = ((item.personalRating ?: 0).toDouble() / 10).toString()
.replace(".0", "")
itemView.text_rating.text = "$rating"
binding.textRating.text = "$rating"
}
}
}

View file

@ -2,16 +2,14 @@ package com.lagradost.cloudstream3.ui.library
import android.os.Build
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnAttach
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
class ViewpagerAdapter(
var pages: List<SyncAPI.Page>,
@ -20,8 +18,7 @@ class ViewpagerAdapter(
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PageViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.library_viewpager_page, parent, false)
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
@ -34,6 +31,7 @@ class ViewpagerAdapter(
}
private val unbound = mutableSetOf<Int>()
/**
* Used to mark all pages for re-binding and forces all items to be refreshed
* Without this the pages will still use the same adapters
@ -43,43 +41,45 @@ class ViewpagerAdapter(
this.notifyItemRangeChanged(0, pages.size)
}
inner class PageViewHolder(private val itemViewTest: View) :
RecyclerView.ViewHolder(itemViewTest) {
inner class PageViewHolder(private val binding: LibraryViewpagerPageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(page: SyncAPI.Page, rebind: Boolean) {
itemView.page_recyclerview?.spanCount =
binding.pageRecyclerview.apply {
spanCount =
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
if (itemViewTest.page_recyclerview?.adapter == null || rebind) {
if (adapter == null || rebind) {
// Only add the items after it has been attached since the items rely on ItemWidth
// Which is only determined after the recyclerview is attached.
// If this fails then item height becomes 0 when there is only one item
itemViewTest.page_recyclerview?.doOnAttach {
itemViewTest.page_recyclerview?.adapter = PageAdapter(
doOnAttach {
adapter = PageAdapter(
page.items.toMutableList(),
itemViewTest.page_recyclerview,
this,
clickCallback
)
}
} else {
(itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items)
itemViewTest.page_recyclerview?.scrollToPosition(0)
(adapter as? PageAdapter)?.updateList(page.items)
scrollToPosition(0)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
val diff = scrollY - oldScrollY
if (diff == 0) return@setOnScrollChangeListener
scrollCallback.invoke(diff > 0)
}
} else {
itemViewTest.page_recyclerview.onFlingListener = object : OnFlingListener() {
onFlingListener = object : OnFlingListener() {
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
scrollCallback.invoke(velocityY > 0)
return false
}
}
}
}
}
}

View file

@ -21,6 +21,7 @@ import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.QuickSearchBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
@ -37,7 +38,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.quick_search.*
import java.util.concurrent.locks.ReentrantLock
class QuickSearchFragment : Fragment() {
@ -72,6 +72,8 @@ class QuickSearchFragment : Fragment() {
private var providers: Set<String>? = null
private lateinit var searchViewModel: SearchViewModel
var binding: QuickSearchBinding? = null
private var bottomSheetDialog: BottomSheetDialog? = null
@ -79,13 +81,21 @@ class QuickSearchFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
activity?.window?.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
)
searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java]
bottomSheetDialog?.ownShow()
return inflater.inflate(R.layout.quick_search, container, false)
val localBinding = QuickSearchBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.quick_search, container, false)
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onDestroy() {
@ -111,7 +121,7 @@ class QuickSearchFragment : Fragment() {
activity?.getSpanCount()?.let {
HomeFragment.currentSpan = it
}
quick_search_autofit_results.spanCount = HomeFragment.currentSpan
binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan
HomeFragment.currentSpan = HomeFragment.currentSpan
HomeFragment.configEvent.invoke(HomeFragment.currentSpan)
}
@ -123,7 +133,7 @@ class QuickSearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(quick_search_root)
fixPaddingStatusbar(binding?.quickSearchRoot)
fixGrid()
arguments?.getStringArray(PROVIDER_KEY)?.let {
@ -136,21 +146,22 @@ class QuickSearchFragment : Fragment() {
} else false
if (isSingleProvider) {
quick_search_autofit_results.adapter = activity?.let {
SearchAdapter(
binding?.quickSearchAutofitResults?.apply {
adapter = SearchAdapter(
ArrayList(),
quick_search_autofit_results,
this,
) { callback ->
SearchHelper.handleSearchClickCallback(activity, callback)
}
}
try {
quick_search?.queryHint = getString(R.string.search_hint_site).format(providers?.first())
binding?.quickSearch?.queryHint = getString(R.string.search_hint_site).format(providers?.first())
} catch (e: Exception) {
logError(e)
}
} else {
quick_search_master_recycler?.adapter =
binding?.quickSearchMasterRecycler?.adapter =
ParentItemAdapter(mutableListOf(), { callback ->
SearchHelper.handleSearchClickCallback(activity, callback)
//when (callback.action) {
@ -164,18 +175,17 @@ class QuickSearchFragment : Fragment() {
bottomSheetDialog = null
})
})
quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
binding?.quickSearchMasterRecycler?.layoutManager = GridLayoutManager(context, 1)
}
quick_search_autofit_results?.isVisible = isSingleProvider
quick_search_master_recycler?.isGone = isSingleProvider
binding?.quickSearchAutofitResults?.isVisible = isSingleProvider
binding?.quickSearchMasterRecycler?.isGone = isSingleProvider
val listLock = ReentrantLock()
observe(searchViewModel.currentSearch) { list ->
try {
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
listLock.lock()
(quick_search_master_recycler?.adapter as ParentItemAdapter?)?.apply {
(binding?.quickSearchMasterRecycler?.adapter as ParentItemAdapter?)?.apply {
updateList(list.map { ongoing ->
val ongoingList = HomePageList(
ongoing.apiName,
@ -192,19 +202,18 @@ class QuickSearchFragment : Fragment() {
}
val searchExitIcon =
quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
binding?.quickSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
//val searchMagIcon =
// quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
// binding.quickSearch.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
//searchMagIcon?.scaleX = 0.65f
//searchMagIcon?.scaleY = 0.65f
quick_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
binding?.quickSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (search(context, query, false))
UIHelper.hideKeyboard(quick_search)
UIHelper.hideKeyboard(binding?.quickSearch)
return true
}
@ -214,27 +223,26 @@ class QuickSearchFragment : Fragment() {
return true
}
})
quick_search_loading_bar.alpha = 0f
binding?.quickSearchLoadingBar?.alpha = 0f
observe(searchViewModel.searchResponse) {
when (it) {
is Resource.Success -> {
it.value.let { data ->
(quick_search_autofit_results?.adapter as? SearchAdapter)?.updateList(
(binding?.quickSearchAutofitResults?.adapter as? SearchAdapter)?.updateList(
context?.filterSearchResultByFilmQuality(data) ?: data
)
}
searchExitIcon?.alpha = 1f
quick_search_loading_bar?.alpha = 0f
binding?.quickSearchLoadingBar?.alpha = 0f
}
is Resource.Failure -> {
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
searchExitIcon?.alpha = 1f
quick_search_loading_bar?.alpha = 0f
binding?.quickSearchLoadingBar?.alpha = 0f
}
is Resource.Loading -> {
searchExitIcon?.alpha = 0f
quick_search_loading_bar?.alpha = 1f
binding?.quickSearchLoadingBar?.alpha = 1f
}
}
}
@ -246,13 +254,12 @@ class QuickSearchFragment : Fragment() {
// UIHelper.showInputMethod(view.findFocus())
// }
//}
quick_search_back.setOnClickListener {
binding?.quickSearchBack?.setOnClickListener {
activity?.popCurrentPage()
}
arguments?.getString(AUTOSEARCH_KEY)?.let {
quick_search?.setQuery(it, true)
binding?.quickSearch?.setQuery(it, true)
arguments?.remove(AUTOSEARCH_KEY)
}
}

View file

@ -1,20 +1,17 @@
package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.ActorData
import com.lagradost.cloudstream3.ActorRole
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.CastItemBinding
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.cast_item.view.*
class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class ActorAdaptor : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
data class ActorMetaData(
var isInverted: Boolean,
val actor: ActorData,
@ -24,7 +21,7 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.cast_item, parent, false),
CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
@ -68,15 +65,9 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private class CardViewHolder
constructor(
itemView: View,
val binding: CastItemBinding,
) :
RecyclerView.ViewHolder(itemView) {
private val actorImage: ImageView = itemView.actor_image
private val actorName: TextView = itemView.actor_name
private val actorExtra: TextView = itemView.actor_extra
private val voiceActorImage: ImageView = itemView.voice_actor_image
private val voiceActorImageHolder: View = itemView.voice_actor_image_holder
private val voiceActorName: TextView = itemView.voice_actor_name
RecyclerView.ViewHolder(binding.root) {
fun bind(actor: ActorData, isInverted: Boolean, position: Int, callback: (Int) -> Unit) {
val (mainImg, vaImage) = if (!isInverted || actor.voiceActor?.image.isNullOrBlank()) {
@ -89,6 +80,7 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
callback(position)
}
binding.apply {
actorImage.setImage(mainImg)
actorName.text = actor.actor.name
@ -98,9 +90,11 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
ActorRole.Main -> {
R.string.actor_main
}
ActorRole.Supporting -> {
R.string.actor_supporting
}
ActorRole.Background -> {
R.string.actor_background
}
@ -125,6 +119,7 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
}
}
}
}
}
class ActorDiffCallback(

View file

@ -3,34 +3,25 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding
import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.result_episode.view.*
import kotlinx.android.synthetic.main.result_episode.view.episode_text
import kotlinx.android.synthetic.main.result_episode_large.view.*
import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler
import kotlinx.android.synthetic.main.result_episode_large.view.episode_progress
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_download
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_progress_downloaded
import java.util.*
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
@ -144,26 +135,52 @@ class EpisodeAdapter(
diffResult.dispatchUpdatesTo(this)
}
var layout = R.layout.result_episode_both
fun getItem(position: Int) : ResultEpisode {
return cardList[position]
}
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
return if(item.poster.isNullOrBlank()) 0 else 1
}
// private val layout = R.layout.result_episode_both
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
R.layout.result_episode_large
else R.layout.result_episode*/
return EpisodeCardViewHolder(
LayoutInflater.from(parent.context)
.inflate(layout, parent, false),
return when(viewType) {
0 -> {
EpisodeCardViewHolderSmall(
ResultEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
hasDownloadSupport,
clickCallback,
downloadClickCallback
)
}
1 -> {
EpisodeCardViewHolderLarge(
ResultEpisodeLargeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
hasDownloadSupport,
clickCallback,
downloadClickCallback
)
}
else -> throw NotImplementedError()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is EpisodeCardViewHolder -> {
holder.bind(cardList[position])
is EpisodeCardViewHolderLarge -> {
holder.bind(getItem(position))
mBoundViewHolders.add(holder)
}
is EpisodeCardViewHolderSmall -> {
holder.bind(getItem(position))
mBoundViewHolders.add(holder)
}
}
@ -173,47 +190,34 @@ class EpisodeAdapter(
return cardList.size
}
class EpisodeCardViewHolder
class EpisodeCardViewHolderLarge
constructor(
itemView: View,
val binding : ResultEpisodeLargeBinding,
private val hasDownloadSupport: Boolean,
private val clickCallback: (EpisodeClickEvent) -> Unit,
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
) : RecyclerView.ViewHolder(binding.root), DownloadButtonViewHolder {
override var downloadButton = EasyDownloadButton()
var episodeDownloadBar: ContentLoadingProgressBar? = null
var episodeDownloadImage: ImageView? = null
// TODO TV
var localCard: ResultEpisode? = null
@SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) {
localCard = card
binding.episodeLinHolder.layoutParams.apply {
width = if(isTvSettings()) ViewGroup.LayoutParams.WRAP_CONTENT else ViewGroup.LayoutParams.MATCH_PARENT
}
val isTrueTv = isTrueTvSettings()
val (parentView, otherView) = if (card.poster == null) {
itemView.episode_holder to itemView.episode_holder_large
} else {
itemView.episode_holder_large to itemView.episode_holder
}
parentView.isVisible = true
otherView.isVisible = false
val episodeText: TextView = parentView.episode_text
val episodeFiller: MaterialButton? = parentView.episode_filler
val episodeRating: TextView? = parentView.episode_rating
val episodeDescript: TextView? = parentView.episode_descript
val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress
val episodePoster: ImageView? = parentView.episode_poster
episodeDownloadBar =
parentView.result_episode_progress_downloaded
episodeDownloadImage = parentView.result_episode_download
binding.apply {
val name =
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
episodeFiller?.isVisible = card.isFiller == true
episodeFiller.isVisible = card.isFiller == true
episodeText.text =
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
@ -221,28 +225,28 @@ class EpisodeAdapter(
if (card.videoWatchState == VideoWatchState.Watched) {
// This cannot be done in getDisplayPosition() as when you have not watched something
// the duration and position is 0
episodeProgress?.max = 1
episodeProgress?.progress = 1
episodeProgress?.isVisible = true
episodeProgress.max = 1
episodeProgress.progress = 1
episodeProgress.isVisible = true
} else {
val displayPos = card.getDisplayPosition()
episodeProgress?.max = (card.duration / 1000).toInt()
episodeProgress?.progress = (displayPos / 1000).toInt()
episodeProgress?.isVisible = displayPos > 0L
episodeProgress.max = (card.duration / 1000).toInt()
episodeProgress.progress = (displayPos / 1000).toInt()
episodeProgress.isVisible = displayPos > 0L
}
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
episodePoster.isVisible = episodePoster.setImage(card.poster) == true
if (card.rating != null) {
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)
episodeRating.text = episodeRating.context?.getString(R.string.rated_format)
?.format(card.rating.toFloat() / 10f)
} else {
episodeRating?.text = ""
episodeRating.text = ""
}
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
episodeRating.isGone = episodeRating.text.isNullOrBlank()
episodeDescript?.apply {
episodeDescript.apply {
text = card.description.html()
isGone = text.isNullOrBlank()
setOnClickListener {
@ -251,15 +255,114 @@ class EpisodeAdapter(
}
if (!isTrueTv) {
episodePoster?.setOnClickListener {
episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
episodePoster?.setOnLongClickListener {
episodePoster.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
return@setOnLongClickListener true
}
}
}
itemView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (isTrueTv) {
itemView.isFocusable = true
itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false
}
itemView.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
binding.resultEpisodeDownload.isVisible = hasDownloadSupport
binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
reattachDownloadButton()
}
override fun reattachDownloadButton() {
downloadButton.dispose()
val card = localCard
if (hasDownloadSupport && card != null) {
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
itemView.context,
card.id
)
downloadButton.setUpButton(
downloadInfo?.fileLength,
downloadInfo?.totalBytes,
binding.resultEpisodeProgressDownloaded,
binding.resultEpisodeDownload,
null,
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.poster,
card.episode,
card.season,
card.id,
card.parentId,
card.rating,
card.description,
System.currentTimeMillis(),
)
) {
if (it.action == DOWNLOAD_ACTION_DOWNLOAD) {
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
} else {
downloadClickCallback.invoke(it)
}
}
}
}
}
class EpisodeCardViewHolderSmall
constructor(
val binding : ResultEpisodeBinding,
private val hasDownloadSupport: Boolean,
private val clickCallback: (EpisodeClickEvent) -> Unit,
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root), DownloadButtonViewHolder {
override var downloadButton = EasyDownloadButton()
var localCard: ResultEpisode? = null
@SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) {
localCard = card
val isTrueTv = isTrueTvSettings()
binding.episodeHolder.layoutParams.apply {
width = if(isTvSettings()) ViewGroup.LayoutParams.WRAP_CONTENT else ViewGroup.LayoutParams.MATCH_PARENT
}
binding.apply {
val name =
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
episodeFiller.isVisible = card.isFiller == true
episodeText.text =
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
if (card.videoWatchState == VideoWatchState.Watched) {
// This cannot be done in getDisplayPosition() as when you have not watched something
// the duration and position is 0
episodeProgress.max = 1
episodeProgress.progress = 1
episodeProgress.isVisible = true
} else {
val displayPos = card.getDisplayPosition()
episodeProgress.max = (card.duration / 1000).toInt()
episodeProgress.progress = (displayPos / 1000).toInt()
episodeProgress.isVisible = displayPos > 0L
}
itemView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
@ -276,18 +379,17 @@ class EpisodeAdapter(
return@setOnLongClickListener true
}
episodeDownloadImage?.isVisible = hasDownloadSupport
episodeDownloadBar?.isVisible = hasDownloadSupport
binding.resultEpisodeDownload.isVisible = hasDownloadSupport
binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
reattachDownloadButton()
}
}
override fun reattachDownloadButton() {
downloadButton.dispose()
val card = localCard
if (hasDownloadSupport && card != null) {
if (episodeDownloadBar == null ||
episodeDownloadImage == null
) return
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
itemView.context,
card.id
@ -296,8 +398,8 @@ class EpisodeAdapter(
downloadButton.setUpButton(
downloadInfo?.fileLength,
downloadInfo?.totalBytes,
episodeDownloadBar ?: return,
episodeDownloadImage ?: return,
binding.resultEpisodeProgressDownloaded,
binding.resultEpisodeDownload,
null,
VideoDownloadHelper.DownloadEpisodeCached(
card.name,

View file

@ -1,11 +1,10 @@
package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
/*
@ -24,7 +23,6 @@ const val IMAGE_CLICK = 0
const val IMAGE_LONG_CLICK = 1
class ImageAdapter(
val layout: Int,
val clickCallback: ((Int) -> Unit)? = null,
val nextFocusUp: Int? = null,
val nextFocusDown: Int? = null,
@ -34,7 +32,9 @@ class ImageAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ImageViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false)
//result_mini_image
ResultMiniImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
// LayoutInflater.from(parent.context).inflate(layout, parent, false)
)
}
@ -66,15 +66,15 @@ class ImageAdapter(
}
class ImageViewHolder
constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
constructor(val binding: ResultMiniImageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
img: Int,
clickCallback: ((Int) -> Unit)?,
nextFocusUp: Int?,
nextFocusDown: Int?,
) {
(itemView as? ImageView?)?.apply {
binding.root.apply {
setImageResource(img)
if (nextFocusDown != null) {
this.nextFocusDownId = nextFocusDown

View file

@ -310,6 +310,7 @@ open class ResultFragment : ResultTrailerPlayer() {
result_finish_loading?.isVisible = false
result_loading_error?.isVisible = false
}
1 -> {
result_bookmark_fab?.isGone = true
result_loading?.isVisible = false
@ -317,6 +318,7 @@ open class ResultFragment : ResultTrailerPlayer() {
result_loading_error?.isVisible = true
result_reload_connection_open_in_browser?.isVisible = true
}
2 -> {
result_bookmark_fab?.isGone = isTrueTvSettings()
result_bookmark_fab?.extend()
@ -350,9 +352,9 @@ open class ResultFragment : ResultTrailerPlayer() {
viewModel.reloadEpisodes()
}
open fun updateMovie(data: ResourceSome<Pair<UiText, ResultEpisode>>) {
open fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
when (data) {
is ResourceSome.Success -> {
is Resource.Success -> {
data.value.let { (text, ep) ->
result_play_movie.setText(text)
result_play_movie?.setOnClickListener {
@ -410,6 +412,7 @@ open class ResultFragment : ResultTrailerPlayer() {
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
)
}
else -> handleDownloadClick(activity, click)
}
}
@ -417,6 +420,7 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
}
else -> {
result_movie_progress_downloaded_holder?.isVisible = false
result_play_movie?.isVisible = false
@ -424,17 +428,14 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
open fun updateEpisodes(episodes: ResourceSome<List<ResultEpisode>>) {
open fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
when (episodes) {
is ResourceSome.None -> {
result_episode_loading?.isVisible = false
result_episodes?.isVisible = false
}
is ResourceSome.Loading -> {
is Resource.Loading -> {
result_episode_loading?.isVisible = true
result_episodes?.isVisible = false
}
is ResourceSome.Success -> {
is Resource.Success -> {
result_episodes?.isVisible = true
result_episode_loading?.isVisible = false
@ -471,6 +472,11 @@ open class ResultFragment : ResultTrailerPlayer() {
result_episodes?.requestFocus()
}
}
else -> {
result_episode_loading?.isVisible = false
result_episodes?.isVisible = false
}
}
}
@ -565,7 +571,7 @@ open class ResultFragment : ResultTrailerPlayer() {
context?.updateHasTrailers()
activity?.loadCache()
activity?.fixPaddingStatusbar(result_top_bar)
fixPaddingStatusbar(result_top_bar)
//activity?.fixPaddingStatusbar(result_barstatus)
/* val backParameter = result_back.layoutParams as FrameLayout.LayoutParams
@ -588,7 +594,7 @@ open class ResultFragment : ResultTrailerPlayer() {
result_episodes?.adapter =
EpisodeAdapter(
api?.hasDownloadSupport == true,
api?.hasDownloadSupport == true && !isTvSettings(),
{ episodeClick ->
viewModel.handleAction(activity, episodeClick)
},
@ -738,10 +744,12 @@ open class ResultFragment : ResultTrailerPlayer() {
viewModel.setMeta(d, syncModel.getSyncs())
}
is Resource.Loading -> {
result_sync_max_episodes?.text =
result_sync_max_episodes?.context?.getString(R.string.sync_total_episodes_none)
}
else -> {}
}
}
@ -755,11 +763,13 @@ open class ResultFragment : ResultTrailerPlayer() {
result_sync_holder?.isVisible = false
closed = true
}
is Resource.Loading -> {
result_sync_loading_shimmer?.startShimmer()
result_sync_loading_shimmer?.isVisible = true
result_sync_holder?.isVisible = false
}
is Resource.Success -> {
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
@ -789,6 +799,7 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
}
null -> {
closed = false
}
@ -796,22 +807,23 @@ open class ResultFragment : ResultTrailerPlayer() {
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
observe(viewModel.resumeWatching) { resume ->
when (resume) {
is Some.Success -> {
observeNullable(viewModel.resumeWatching) { resume ->
if (resume == null) {
result_resume_parent?.isVisible = false
return@observeNullable
}
result_resume_parent?.isVisible = true
val value = resume.value
value.progress?.let { progress ->
resume.progress?.let { progress ->
result_resume_series_title?.apply {
isVisible = !value.isMovie
isVisible = !resume.isMovie
text =
if (value.isMovie) null else activity?.getNameFull(
value.result.name,
value.result.episode,
value.result.season
if (resume.isMovie) null else activity?.getNameFull(
resume.result.name,
resume.result.episode,
resume.result.season
)
}
result_resume_series_progress_text.setText(progress.progressLeft)
result_resume_series_progress_text?.setText(progress.progressLeft)
result_resume_series_progress?.apply {
isVisible = true
this.max = progress.maxProgress
@ -825,15 +837,15 @@ open class ResultFragment : ResultTrailerPlayer() {
result_resume_series_progress_text?.isVisible = false
}
result_resume_series_button?.isVisible = !value.isMovie
result_resume_series_button_play?.isVisible = !value.isMovie
result_resume_series_button?.isVisible = !resume.isMovie
result_resume_series_button_play?.isVisible = !resume.isMovie
val click = View.OnClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(
storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER,
value.result
resume.result
)
)
}
@ -841,13 +853,10 @@ open class ResultFragment : ResultTrailerPlayer() {
result_resume_series_button?.setOnClickListener(click)
result_resume_series_button_play?.setOnClickListener(click)
}
is Some.None -> {
result_resume_parent?.isVisible = false
}
}
}
observe(viewModel.episodes) { episodes ->
observeNullable(viewModel.episodes) { episodes ->
updateEpisodes(episodes)
}
@ -868,7 +877,7 @@ open class ResultFragment : ResultTrailerPlayer() {
setRecommendations(recommendations, null)
}
observe(viewModel.movie) { data ->
observeNullable(viewModel.movie) { data ->
updateMovie(data)
}
@ -1046,10 +1055,12 @@ open class ResultFragment : ResultTrailerPlayer() {
}*/
//}
}
is Resource.Failure -> {
result_error_text.text = storedData?.url?.plus("\n") + data.errorString
updateVisStatus(1)
}
is Resource.Loading -> {
updateVisStatus(0)
}

View file

@ -20,9 +20,9 @@ import com.google.android.gms.cast.framework.CastState
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
@ -212,7 +212,6 @@ class ResultFragmentPhone : ResultFragment() {
}*/
result_mini_sync?.adapter = ImageAdapter(
R.layout.result_mini_image,
nextFocusDown = R.id.result_sync_set_score,
clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
@ -271,41 +270,42 @@ class ResultFragmentPhone : ResultFragment() {
}
}
observe(viewModel.episodesCountText) { count ->
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
}
observe(viewModel.selectPopup) { popup ->
when (popup) {
is Some.Success -> {
observeNullable(viewModel.selectPopup) { popup ->
if (popup == null) {
popupDialog?.dismissSafe(activity)
popupDialog = null
return@observeNullable
}
popupDialog?.dismissSafe(activity)
popupDialog = activity?.let { act ->
val pop = popup.value
val options = pop.getOptions(act)
val title = pop.getTitle(act)
val options = popup.getOptions(act)
val title = popup.getTitle(act)
act.showBottomDialogInstant(
options, title, {
popupDialog = null
pop.callback(null)
popup.callback(null)
}, {
popupDialog = null
pop.callback(it)
popup.callback(it)
}
)
}
}
is Some.None -> {
popupDialog?.dismissSafe(activity)
popupDialog = null
}
}
}
observe(viewModel.loadedLinks) { load ->
when (load) {
is Some.Success -> {
if (load == null) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
return@observe
}
if (loadingDialog?.isShowing != true) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
@ -326,18 +326,12 @@ class ResultFragmentPhone : ResultFragment() {
builder
}
}
is Some.None -> {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
}
}
}
observe(viewModel.selectedSeason) { text ->
observeNullable(viewModel.selectedSeason) { text ->
result_season_button.setText(text)
selectSeason =
(if (text is Some.Success) text.value else null)?.asStringNull(result_season_button?.context)
text?.asStringNull(result_season_button?.context)
// If the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true)
if (result_resume_parent?.isVisible == true)
@ -346,7 +340,7 @@ class ResultFragmentPhone : ResultFragment() {
// setFocusUpAndDown(result_bookmark_button, result_season_button)
}
observe(viewModel.selectedDubStatus) { status ->
observeNullable(viewModel.selectedDubStatus) { status ->
result_dub_select?.setText(status)
if (result_dub_select?.isVisible == true)
@ -357,7 +351,7 @@ class ResultFragmentPhone : ResultFragment() {
// setFocusUpAndDown(result_bookmark_button, result_dub_select)
}
}
observe(viewModel.selectedRange) { range ->
observeNullable(viewModel.selectedRange) { range ->
result_episode_select.setText(range)
// If Season button is invisible then the bookmark button next focus is episode select

View file

@ -12,9 +12,9 @@ import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.ResourceSome
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter
@ -69,16 +69,16 @@ class ResultFragmentTv : ResultFragment() {
return focus == this.result_root
}
override fun updateEpisodes(episodes: ResourceSome<List<ResultEpisode>>) {
override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
super.updateEpisodes(episodes)
if (episodes is ResourceSome.Success && hasNoFocus()) {
if (episodes is Resource.Success && hasNoFocus()) {
result_episodes?.requestFocus()
}
}
override fun updateMovie(data: ResourceSome<Pair<UiText, ResultEpisode>>) {
override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
super.updateMovie(data)
if (data is ResourceSome.Success && hasNoFocus()) {
if (data is Resource.Success && hasNoFocus()) {
result_play_movie?.requestFocus()
}
}
@ -130,9 +130,9 @@ class ResultFragmentTv : ResultFragment() {
LinearListLayout(result_episodes?.context).apply {
setHorizontal()
}
(result_episodes?.adapter as EpisodeAdapter?)?.apply {
layout = R.layout.result_episode_both_tv
}
// (result_episodes?.adapter as EpisodeAdapter?)?.apply {
// layout = R.layout.result_episode_both_tv
// }
//result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE)
result_season_selection.setAdapter()
@ -140,37 +140,37 @@ class ResultFragmentTv : ResultFragment() {
result_dub_selection.setAdapter()
result_recommendations_filter_selection.setAdapter()
observe(viewModel.selectPopup) { popup ->
when (popup) {
is Some.Success -> {
observeNullable(viewModel.selectPopup) { popup ->
if(popup == null) {
popupDialog?.dismissSafe(activity)
popupDialog = null
return@observeNullable
}
popupDialog?.dismissSafe(activity)
popupDialog = activity?.let { act ->
val pop = popup.value
val options = pop.getOptions(act)
val title = pop.getTitle(act)
val options = popup.getOptions(act)
val title = popup.getTitle(act)
act.showBottomDialogInstant(
options, title, {
popupDialog = null
pop.callback(null)
popup.callback(null)
}, {
popupDialog = null
pop.callback(it)
popup.callback(it)
}
)
}
}
is Some.None -> {
popupDialog?.dismissSafe(activity)
popupDialog = null
}
}
}
observe(viewModel.loadedLinks) { load ->
when (load) {
is Some.Success -> {
observeNullable(viewModel.loadedLinks) { load ->
if(load == null) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
return@observeNullable
}
if (loadingDialog?.isShowing != true) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
@ -189,16 +189,11 @@ class ResultFragmentTv : ResultFragment() {
builder.show()
builder
}
}
is Some.None -> {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
}
}
}
observe(viewModel.episodesCountText) { count ->
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
}

View file

@ -145,15 +145,18 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
minute
)
}
hours > 0 -> txt(
R.string.next_episode_time_hour_format,
hours,
minute
)
minute > 0 -> txt(
R.string.next_episode_time_min_format,
minute
)
else -> null
}?.also {
nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
@ -305,6 +308,7 @@ fun SelectPopup.getOptions(context: Context): List<String> {
is SelectPopup.SelectArray -> {
this.options.map { it.first.asString(context) }
}
is SelectPopup.SelectText -> options.map { it.asString(context) }
}
}
@ -352,17 +356,17 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(null)
val page: LiveData<Resource<ResultData>?> = _page
private val _episodes: MutableLiveData<ResourceSome<List<ResultEpisode>>> =
MutableLiveData(ResourceSome.Loading())
val episodes: LiveData<ResourceSome<List<ResultEpisode>>> = _episodes
private val _episodes: MutableLiveData<Resource<List<ResultEpisode>>?> =
MutableLiveData(Resource.Loading())
val episodes: LiveData<Resource<List<ResultEpisode>>?> = _episodes
private val _movie: MutableLiveData<ResourceSome<Pair<UiText, ResultEpisode>>> =
MutableLiveData(ResourceSome.None)
val movie: LiveData<ResourceSome<Pair<UiText, ResultEpisode>>> = _movie
private val _movie: MutableLiveData<Resource<Pair<UiText, ResultEpisode>>?> =
MutableLiveData(null)
val movie: LiveData<Resource<Pair<UiText, ResultEpisode>>?> = _movie
private val _episodesCountText: MutableLiveData<Some<UiText>> =
MutableLiveData(Some.None)
val episodesCountText: LiveData<Some<UiText>> = _episodesCountText
private val _episodesCountText: MutableLiveData<UiText?> =
MutableLiveData(null)
val episodesCountText: LiveData<UiText?> = _episodesCountText
private val _trailers: MutableLiveData<List<ExtractedTrailerData>> =
MutableLiveData(mutableListOf())
@ -384,16 +388,16 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(emptyList())
val recommendations: LiveData<List<SearchResponse>> = _recommendations
private val _selectedRange: MutableLiveData<Some<UiText>> =
MutableLiveData(Some.None)
val selectedRange: LiveData<Some<UiText>> = _selectedRange
private val _selectedRange: MutableLiveData<UiText?> =
MutableLiveData(null)
val selectedRange: LiveData<UiText?> = _selectedRange
private val _selectedSeason: MutableLiveData<Some<UiText>> =
MutableLiveData(Some.None)
val selectedSeason: LiveData<Some<UiText>> = _selectedSeason
private val _selectedSeason: MutableLiveData<UiText?> =
MutableLiveData(null)
val selectedSeason: LiveData<UiText?> = _selectedSeason
private val _selectedDubStatus: MutableLiveData<Some<UiText>> = MutableLiveData(Some.None)
val selectedDubStatus: LiveData<Some<UiText>> = _selectedDubStatus
private val _selectedDubStatus: MutableLiveData<UiText?> = MutableLiveData(null)
val selectedDubStatus: LiveData<UiText?> = _selectedDubStatus
private val _selectedRangeIndex: MutableLiveData<Int> =
MutableLiveData(-1)
@ -406,12 +410,12 @@ class ResultViewModel2 : ViewModel() {
private val _selectedDubStatusIndex: MutableLiveData<Int> = MutableLiveData(-1)
val selectedDubStatusIndex: LiveData<Int> = _selectedDubStatusIndex
private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
private val _loadedLinks: MutableLiveData<LinkProgress?> = MutableLiveData(null)
val loadedLinks: LiveData<LinkProgress?> = _loadedLinks
private val _resumeWatching: MutableLiveData<Some<ResumeWatchingStatus>> =
MutableLiveData(Some.None)
val resumeWatching: LiveData<Some<ResumeWatchingStatus>> = _resumeWatching
private val _resumeWatching: MutableLiveData<ResumeWatchingStatus?> =
MutableLiveData(null)
val resumeWatching: LiveData<ResumeWatchingStatus?> = _resumeWatching
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
@ -800,8 +804,8 @@ class ResultViewModel2 : ViewModel() {
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE)
val watchStatus: LiveData<WatchType> get() = _watchStatus
private val _selectPopup: MutableLiveData<Some<SelectPopup>> = MutableLiveData(Some.None)
val selectPopup: LiveData<Some<SelectPopup>> get() = _selectPopup
private val _selectPopup: MutableLiveData<SelectPopup?> = MutableLiveData(null)
val selectPopup: LiveData<SelectPopup?> = _selectPopup
fun updateWatchStatus(status: WatchType) {
@ -885,23 +889,22 @@ class ResultViewModel2 : ViewModel() {
}
fun cancelLinks() {
println("called::cancelLinks")
currentLoadLinkJob?.cancel()
currentLoadLinkJob = null
_loadedLinks.postValue(Some.None)
_loadedLinks.postValue(null)
}
private fun postPopup(text: UiText, options: List<UiText>, callback: suspend (Int?) -> Unit) {
_selectPopup.postValue(
some(SelectPopup.SelectText(
SelectPopup.SelectText(
text,
options
) { value ->
viewModelScope.launchSafe {
_selectPopup.postValue(Some.None)
_selectPopup.postValue(null)
callback.invoke(value)
}
})
}
)
}
@ -912,15 +915,15 @@ class ResultViewModel2 : ViewModel() {
callback: suspend (Int?) -> Unit
) {
_selectPopup.postValue(
some(SelectPopup.SelectArray(
SelectPopup.SelectArray(
text,
options,
) { value ->
viewModelScope.launchSafe {
_selectPopup.value = Some.None
_selectPopup.postValue(null)
callback.invoke(value)
}
})
}
)
}
@ -988,7 +991,7 @@ class ResultViewModel2 : ViewModel() {
val subs: MutableSet<SubtitleData> = mutableSetOf()
fun updatePage() {
if (isVisible && isActive) {
_loadedLinks.postValue(some(LinkProgress(links.size, subs.size)))
_loadedLinks.postValue(LinkProgress(links.size, subs.size))
}
}
try {
@ -1005,7 +1008,7 @@ class ResultViewModel2 : ViewModel() {
} catch (e: Exception) {
logError(e)
} finally {
_loadedLinks.postValue(Some.None)
_loadedLinks.postValue(null)
}
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
@ -1233,6 +1236,7 @@ class ResultViewModel2 : ViewModel() {
)
}
}
ACTION_CLICK_DEFAULT -> {
activity?.let { ctx ->
if (ctx.isConnectedToChromecast()) {
@ -1249,6 +1253,7 @@ class ResultViewModel2 : ViewModel() {
}
}
}
ACTION_SHOW_DESCRIPTION -> {
_episodeSynopsis.postValue(click.data.description)
}
@ -1286,9 +1291,11 @@ class ResultViewModel2 : ViewModel() {
)
}
}
ACTION_SHOW_TOAST -> {
showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
}
ACTION_DOWNLOAD_EPISODE -> {
val response = currentResponse ?: return
downloadEpisode(
@ -1303,6 +1310,7 @@ class ResultViewModel2 : ViewModel() {
response.url
)
}
ACTION_DOWNLOAD_MIRROR -> {
val response = currentResponse ?: return
acquireSingleLink(
@ -1332,6 +1340,7 @@ class ResultViewModel2 : ViewModel() {
)
}
}
ACTION_RELOAD_EPISODE -> {
ioSafe {
loadLinks(
@ -1342,6 +1351,7 @@ class ResultViewModel2 : ViewModel() {
)
}
}
ACTION_CHROME_CAST_MIRROR -> {
acquireSingleLink(
click.data,
@ -1351,6 +1361,7 @@ class ResultViewModel2 : ViewModel() {
startChromecast(activity, click.data, result.links, result.subs, index)
}
}
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
click.data,
isCasting = true,
@ -1364,6 +1375,7 @@ class ResultViewModel2 : ViewModel() {
logError(e)
}
}
ACTION_COPY_LINK -> {
acquireSingleLink(
click.data,
@ -1380,9 +1392,11 @@ class ResultViewModel2 : ViewModel() {
showToast(act, R.string.copy_link_toast, Toast.LENGTH_SHORT)
}
}
ACTION_CHROME_CAST_EPISODE -> {
startChromecast(activity, click.data)
}
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
if (links.links.isEmpty()) {
@ -1397,6 +1411,7 @@ class ResultViewModel2 : ViewModel() {
)
}
}
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
click.data,
isCasting = true,
@ -1413,6 +1428,7 @@ class ResultViewModel2 : ViewModel() {
result.subs
)
}
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
click.data,
isCasting = true,
@ -1428,6 +1444,7 @@ class ResultViewModel2 : ViewModel() {
result.subs
)
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
val data = currentResponse?.syncData?.toList() ?: emptyList()
val list =
@ -1448,6 +1465,7 @@ class ResultViewModel2 : ViewModel() {
)
)
}
ACTION_MARK_AS_WATCHED -> {
val isWatched =
DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
@ -1672,10 +1690,10 @@ class ResultViewModel2 : ViewModel() {
private fun postMovie() {
val response = currentResponse
_episodes.postValue(ResourceSome.None)
_episodes.postValue(null)
if (response == null) {
_movie.postValue(ResourceSome.None)
_movie.postValue(null)
return
}
@ -1692,11 +1710,11 @@ class ResultViewModel2 : ViewModel() {
}
)
val data = getMovie()
_episodes.postValue(ResourceSome.None)
_episodes.postValue(null)
if (text == null || data == null) {
_movie.postValue(ResourceSome.None)
_movie.postValue(null)
} else {
_movie.postValue(ResourceSome.Success(text to data))
_movie.postValue(Resource.Success(text to data))
}
}
@ -1705,14 +1723,14 @@ class ResultViewModel2 : ViewModel() {
postMovie()
} else {
_episodes.postValue(
ResourceSome.Success(
Resource.Success(
getEpisodes(
currentIndex ?: return,
currentRange ?: return
)
)
)
_movie.postValue(ResourceSome.None)
_movie.postValue(null)
}
postResume()
}
@ -1755,14 +1773,14 @@ class ResultViewModel2 : ViewModel() {
val size = currentEpisodes[indexer]?.size
_episodesCountText.postValue(
some(
if (isMovie) null else
txt(
R.string.episode_format,
size,
txt(if (size == 1) R.string.episode else R.string.episodes),
)
)
)
_selectedSeasonIndex.postValue(
@ -1770,7 +1788,7 @@ class ResultViewModel2 : ViewModel() {
)
_selectedSeason.postValue(
some(
if (isMovie || currentSeasons.size <= 1) null else
when (indexer.season) {
0 -> txt(R.string.no_season)
@ -1792,7 +1810,7 @@ class ResultViewModel2 : ViewModel() {
}
}
}
)
)
_selectedRangeIndex.postValue(
@ -1800,13 +1818,13 @@ class ResultViewModel2 : ViewModel() {
)
_selectedRange.postValue(
some(
if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
} else {
null
}
)
)
_selectedDubStatusIndex.postValue(
@ -1814,10 +1832,10 @@ class ResultViewModel2 : ViewModel() {
)
_selectedDubStatus.postValue(
some(
if (isMovie || currentDubStatus.size <= 1) null else
txt(indexer.dubStatus)
)
)
currentId?.let { id ->
@ -1851,7 +1869,7 @@ class ResultViewModel2 : ViewModel() {
}
}*/
_episodes.postValue(ResourceSome.Success(ret))
_episodes.postValue(Resource.Success(ret))
}
}
@ -1869,7 +1887,7 @@ class ResultViewModel2 : ViewModel() {
}
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
_episodes.postValue(ResourceSome.Loading())
_episodes.postValue(Resource.Loading())
val mainId = loadResponse.getId()
currentId = mainId
@ -1924,6 +1942,7 @@ class ResultViewModel2 : ViewModel() {
}
episodes
}
is TvSeriesLoadResponse -> {
val episodes: MutableMap<EpisodeIndexer, MutableList<ResultEpisode>> =
mutableMapOf()
@ -1968,6 +1987,7 @@ class ResultViewModel2 : ViewModel() {
}
episodes
}
is MovieLoadResponse -> {
singleMap(
buildResultEpisode(
@ -1989,6 +2009,7 @@ class ResultViewModel2 : ViewModel() {
)
)
}
is LiveStreamLoadResponse -> {
singleMap(
buildResultEpisode(
@ -2010,6 +2031,7 @@ class ResultViewModel2 : ViewModel() {
)
)
}
is TorrentLoadResponse -> {
singleMap(
buildResultEpisode(
@ -2031,6 +2053,7 @@ class ResultViewModel2 : ViewModel() {
)
)
}
else -> {
mapOf()
}
@ -2088,7 +2111,7 @@ class ResultViewModel2 : ViewModel() {
}
fun postResume() {
_resumeWatching.postValue(some(resume()))
_resumeWatching.postValue(resume())
}
private fun resume(): ResumeWatchingStatus? {
@ -2196,6 +2219,7 @@ class ResultViewModel2 : ViewModel() {
}
}
}
START_ACTION_LOAD_EP -> {
val all = currentEpisodes.values.flatten()
val episode =
@ -2227,7 +2251,7 @@ class ResultViewModel2 : ViewModel() {
) =
ioSafe {
_page.postValue(Resource.Loading(url))
_episodes.postValue(ResourceSome.Loading())
_episodes.postValue(Resource.Loading())
preferDubStatus = dubStatus
currentShowFillers = showFillers
@ -2271,6 +2295,7 @@ class ResultViewModel2 : ViewModel() {
is Resource.Failure -> {
_page.postValue(data)
}
is Resource.Success -> {
if (!isActive) return@ioSafe
val loadResponse = ioWork {
@ -2307,6 +2332,7 @@ class ResultViewModel2 : ViewModel() {
if (!isActive) return@ioSafe
handleAutoStart(activity, autostart)
}
is Resource.Loading -> {
debugException { "Invalid load result" }
}

View file

@ -1,12 +1,11 @@
package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
typealias SelectData = Pair<UiText?, Any>
@ -17,7 +16,9 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return SelectViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
ResultSelectionBinding.inflate(LayoutInflater.from(parent.context), parent, false),
//LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
)
}
@ -73,10 +74,10 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
private class SelectViewHolder
constructor(
itemView: View,
binding: ResultSelectionBinding,
) :
RecyclerView.ViewHolder(itemView) {
private val item: MaterialButton = itemView as MaterialButton
RecyclerView.ViewHolder(binding.root) {
private val item: MaterialButton = binding.root
fun update(isSelected: Boolean) {
item.isSelected = isSelected

View file

@ -8,7 +8,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -162,11 +161,3 @@ fun TextView?.setTextHtml(text: UiText?) {
this.text = str.html()
}
}
fun TextView?.setTextHtml(text: Some<UiText>?) {
setTextHtml(if (text is Some.Success) text.value else null)
}
fun TextView?.setText(text: Some<UiText>?) {
setText(if (text is Some.Success) text.value else null)
}

View file

@ -78,7 +78,7 @@ class SearchAdapter(
resView: AutofitRecyclerView
) :
RecyclerView.ViewHolder(itemView) {
val cardView: ImageView = itemView.imageView
private val cardView: ImageView = itemView.imageView
private val compactView = false//itemView.context.getGridIsCompact()
private val coverHeight: Int =

View file

@ -33,6 +33,8 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
@ -56,8 +58,6 @@ 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 kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.tvtypes_chips.*
import java.util.concurrent.locks.ReentrantLock
const val SEARCH_PREF_TAGS = "search_pref_tags"
@ -89,6 +89,7 @@ class SearchFragment : Fragment() {
private val searchViewModel: SearchViewModel by activityViewModels()
private var bottomSheetDialog: BottomSheetDialog? = null
var binding: FragmentSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
@ -99,18 +100,20 @@ class SearchFragment : Fragment() {
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
)
bottomSheetDialog?.ownShow()
return inflater.inflate(
if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search,
container,
false
)
val layout = if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search
val root = inflater.inflate(layout, container, false)
binding = FragmentSearchBinding.bind(root)
return root
}
private fun fixGrid() {
activity?.getSpanCount()?.let {
currentSpan = it
}
search_autofit_results.spanCount = currentSpan
binding?.searchAutofitResults?.spanCount = currentSpan
currentSpan = currentSpan
HomeFragment.configEvent.invoke(currentSpan)
}
@ -123,6 +126,7 @@ class SearchFragment : Fragment() {
override fun onDestroyView() {
hideKeyboard()
bottomSheetDialog?.ownHide()
binding = null
super.onDestroyView()
}
@ -181,7 +185,7 @@ class SearchFragment : Fragment() {
searchViewModel.reloadRepos()
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
bindChips(
home_select_group,
binding?.tvtypesChipsScroll?.tvtypesChips,
selectedSearchTypes,
validAPIs.flatMap { api -> api.supportedTypes }.distinct()
) { list ->
@ -189,7 +193,7 @@ class SearchFragment : Fragment() {
setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
selectedSearchTypes.clear()
selectedSearchTypes.addAll(list)
search(main_search?.query?.toString())
search(binding?.mainSearch?.query?.toString())
}
}
}
@ -199,24 +203,27 @@ class SearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(searchRoot)
fixPaddingStatusbar(binding?.searchRoot)
fixGrid()
reloadRepos()
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let {
binding?.apply {
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? =
SearchAdapter(
ArrayList(),
search_autofit_results,
searchAutofitResults,
) { callback ->
SearchHelper.handleSearchClickCallback(activity, callback)
}
searchAutofitResults.adapter = adapter
searchLoadingBar.alpha = 0f
}
search_autofit_results.adapter = adapter
search_loading_bar.alpha = 0f
val searchExitIcon =
main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
// val searchMagIcon =
// main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
//searchMagIcon.scaleX = 0.65f
@ -230,7 +237,7 @@ class SearchFragment : Fragment() {
)!!.toMutableSet()
}
search_filter.setOnClickListener { searchView ->
binding?.searchFilter?.setOnClickListener { searchView ->
searchView?.context?.let { ctx ->
val validAPIs = ctx.filterProviderByPreferredMedia(hasHomePageIsRequired = false)
var currentValidApis = listOf<MainAPI>()
@ -241,7 +248,13 @@ class SearchFragment : Fragment() {
BottomSheetDialog(ctx)
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
builder.setContentView(R.layout.home_select_mainpage)
val binding: HomeSelectMainpageBinding = HomeSelectMainpageBinding.inflate(
builder.layoutInflater,
null,
false
)
builder.setContentView(binding.root)
builder.show()
builder.let { dialog ->
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
@ -303,7 +316,7 @@ class SearchFragment : Fragment() {
?: mutableListOf(TvType.Movie, TvType.TvSeries)
bindChips(
dialog.home_select_group,
binding.tvtypesChipsScroll.tvtypesChips,
selectedSearchTypes,
TvType.values().toList()
) { list ->
@ -343,15 +356,15 @@ class SearchFragment : Fragment() {
?: mutableListOf(TvType.Movie, TvType.TvSeries)
if (isTrueTvSettings()) {
search_filter.isFocusable = true
search_filter.isFocusableInTouchMode = true
binding?.searchFilter?.isFocusable = true
binding?.searchFilter?.isFocusableInTouchMode = true
}
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
search(query)
main_search?.let {
binding?.mainSearch?.let {
hideKeyboard(it)
}
@ -365,17 +378,17 @@ class SearchFragment : Fragment() {
searchViewModel.clearSearch()
searchViewModel.updateHistory()
}
search_history_holder?.isVisible = showHistory
search_master_recycler?.isVisible = !showHistory && isAdvancedSearch
search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch
binding?.apply {
searchHistoryHolder.isVisible = showHistory
searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch
searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch
}
return true
}
})
search_clear_call_history?.setOnClickListener {
binding?.searchClearCallHistory?.setOnClickListener {
activity?.let { ctx ->
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
val dialogClickListener =
@ -409,8 +422,8 @@ class SearchFragment : Fragment() {
}
observe(searchViewModel.currentHistory) { list ->
search_clear_call_history?.isVisible = list.isNotEmpty()
(search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list)
binding?.searchClearCallHistory?.isVisible = list.isNotEmpty()
(binding?.searchHistoryRecycler?.adapter as? SearchHistoryAdaptor?)?.updateList(list)
}
searchViewModel.updateHistory()
@ -420,20 +433,20 @@ class SearchFragment : Fragment() {
is Resource.Success -> {
it.value.let { data ->
if (data.isNotEmpty()) {
(search_autofit_results?.adapter as? SearchAdapter)?.updateList(data)
(binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(data)
}
}
searchExitIcon.alpha = 1f
search_loading_bar.alpha = 0f
searchExitIcon?.alpha = 1f
binding?.searchLoadingBar?.alpha = 0f
}
is Resource.Failure -> {
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
searchExitIcon.alpha = 1f
search_loading_bar.alpha = 0f
searchExitIcon?.alpha = 1f
binding?.searchLoadingBar?.alpha = 0f
}
is Resource.Loading -> {
searchExitIcon.alpha = 0f
search_loading_bar.alpha = 1f
searchExitIcon?.alpha = 0f
binding?.searchLoadingBar?.alpha = 1f
}
}
}
@ -443,7 +456,7 @@ class SearchFragment : Fragment() {
try {
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
listLock.lock()
(search_master_recycler?.adapter as ParentItemAdapter?)?.apply {
(binding?.searchMasterRecycler?.adapter as ParentItemAdapter?)?.apply {
val newItems = list.map { ongoing ->
val dataList =
if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
@ -490,8 +503,8 @@ class SearchFragment : Fragment() {
SEARCH_HISTORY_OPEN -> {
searchViewModel.clearSearch()
if (searchItem.type.isNotEmpty())
updateChips(home_select_group, searchItem.type.toMutableList())
main_search?.setQuery(searchItem.searchText, true)
updateChips(binding?.tvtypesChipsScroll?.tvtypesChips, searchItem.type.toMutableList())
binding?.mainSearch?.setQuery(searchItem.searchText, true)
}
SEARCH_HISTORY_REMOVE -> {
removeKey(SEARCH_HISTORY_KEY, searchItem.key)
@ -503,19 +516,22 @@ class SearchFragment : Fragment() {
}
}
search_history_recycler?.adapter = historyAdapter
search_history_recycler?.layoutManager = GridLayoutManager(context, 1)
binding?.apply {
searchHistoryRecycler.adapter = historyAdapter
searchHistoryRecycler.layoutManager = GridLayoutManager(context, 1)
search_master_recycler?.adapter = masterAdapter
search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
searchMasterRecycler.adapter = masterAdapter
searchMasterRecycler.layoutManager = GridLayoutManager(context, 1)
// Automatically search the specified query, this allows the app search to launch from intent
arguments?.getString(SEARCH_QUERY)?.let { query ->
if (query.isBlank()) return@let
main_search?.setQuery(query, true)
mainSearch.setQuery(query, true)
// Clear the query as to not make it request the same query every time the page is opened
arguments?.putString(SEARCH_QUERY, null)
}
}
// SubtitlesFragment.push(activity)
//searchViewModel.search("iron man")

View file

@ -10,7 +10,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import kotlinx.android.synthetic.main.search_history_item.view.*
import com.lagradost.cloudstream3.databinding.AccountSingleBinding
import com.lagradost.cloudstream3.databinding.SearchHistoryItemBinding
data class SearchHistoryItem(
@JsonProperty("searchedAt") val searchedAt: Long,
@ -34,8 +35,7 @@ class SearchHistoryAdaptor(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CardViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.search_history_item, parent, false),
SearchHistoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false),
clickCallback,
)
}
@ -65,25 +65,27 @@ class SearchHistoryAdaptor(
class CardViewHolder
constructor(
itemView: View,
val binding: SearchHistoryItemBinding,
private val clickCallback: (SearchHistoryCallback) -> Unit,
) :
RecyclerView.ViewHolder(itemView) {
private val removeButton: ImageView = itemView.home_history_remove
private val openButton: View = itemView.home_history_tab
private val title: TextView = itemView.home_history_title
RecyclerView.ViewHolder(binding.root) {
// private val removeButton: ImageView = itemView.home_history_remove
// private val openButton: View = itemView.home_history_tab
// private val title: TextView = itemView.home_history_title
fun bind(card: SearchHistoryItem) {
title.text = card.searchText
binding.apply {
homeHistoryTitle.text = card.searchText
removeButton.setOnClickListener {
homeHistoryRemove.setOnClickListener {
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE))
}
openButton.setOnClickListener {
homeHistoryTab.setOnClickListener {
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN))
}
}
}
}
}
class SearchHistoryDiffCallback(

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.search
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
@ -10,14 +9,29 @@ import androidx.cardview.widget.CardView
import androidx.core.view.isVisible
import androidx.palette.graphics.Palette
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.AnimeSearchResponse
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LiveSearchResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.home_result_grid.view.*
import kotlinx.android.synthetic.main.home_result_grid.view.background_card
import kotlinx.android.synthetic.main.home_result_grid.view.imageText
import kotlinx.android.synthetic.main.home_result_grid.view.imageView
import kotlinx.android.synthetic.main.home_result_grid.view.search_item_download_play
import kotlinx.android.synthetic.main.home_result_grid.view.text_flag
import kotlinx.android.synthetic.main.home_result_grid.view.text_is_dub
import kotlinx.android.synthetic.main.home_result_grid.view.text_is_sub
import kotlinx.android.synthetic.main.home_result_grid.view.text_quality
import kotlinx.android.synthetic.main.home_result_grid.view.title_shadow
import kotlinx.android.synthetic.main.home_result_grid.view.watchProgress
object SearchResultBuilder {
private val showCache: MutableMap<String, Boolean> = mutableMapOf()

View file

@ -3,11 +3,10 @@ package com.lagradost.cloudstream3.ui.settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.AccountSingleBinding
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -15,14 +14,15 @@ class AccountClickCallback(val action: Int, val view: View, val card: AuthAPI.Lo
class AccountAdapter(
val cardList: List<AuthAPI.LoginInfo>,
val layout: Int = R.layout.account_single,
private val clickCallback: (AccountClickCallback) -> Unit
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
AccountSingleBinding.inflate(LayoutInflater.from(parent.context), parent, false), //LayoutInflater.from(parent.context).inflate(layout, parent, false),
clickCallback
)
}
@ -43,18 +43,18 @@ class AccountAdapter(
}
class CardViewHolder
constructor(itemView: View, private val clickCallback: (AccountClickCallback) -> Unit) :
RecyclerView.ViewHolder(itemView) {
private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
constructor(val binding: AccountSingleBinding, private val clickCallback: (AccountClickCallback) -> Unit) :
RecyclerView.ViewHolder(binding.root) {
// private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
// private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
fun bind(card: AuthAPI.LoginInfo) {
// just in case name is null account index will show, should never happened
accountName.text = card.name ?: "%s %d".format(
accountName.context.getString(R.string.account),
binding.accountName.text = card.name ?: "%s %d".format(
binding.accountName.context.getString(R.string.account),
card.accountIndex
)
pfp.isVisible = pfp.setImage(card.profilePicture)
binding.accountProfilePicture.isVisible = binding.accountProfilePicture.setImage(card.profilePicture)
itemView.setOnClickListener {
clickCallback.invoke(AccountClickCallback(0, itemView, card))

View file

@ -96,7 +96,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
}
}
api.accountIndex = ogIndex
val adapter = AccountAdapter(items, R.layout.account_single) {
val adapter = AccountAdapter(items) {
dialog?.dismissSafe(activity)
api.changeAccount(it.card.accountIndex)
}

View file

@ -62,7 +62,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressed()
}
}
context.fixPaddingStatusbar(settings_toolbar)
fixPaddingStatusbar(settings_toolbar)
}
fun Fragment?.setUpToolbar(@StringRes title: Int) {
@ -74,7 +74,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressed()
}
}
context.fixPaddingStatusbar(settings_toolbar)
fixPaddingStatusbar(settings_toolbar)
}
fun getFolderSize(dir: File): Long {

View file

@ -18,8 +18,8 @@ import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
@ -97,6 +97,7 @@ class ExtensionsFragment : Fragment() {
extensionViewModel.loadRepositories()
}
}
DialogInterface.BUTTON_NEGATIVE -> {}
}
}
@ -138,10 +139,12 @@ class ExtensionsFragment : Fragment() {
// }
// }
observe(extensionViewModel.pluginStats) {
when (it) {
is Some.Success -> {
val value = it.value
observeNullable(extensionViewModel.pluginStats) { value ->
if (value == null) {
plugin_storage_appbar?.isVisible = false
return@observeNullable
}
plugin_storage_appbar?.isVisible = true
if (value.total == 0) {
@ -157,11 +160,6 @@ class ExtensionsFragment : Fragment() {
plugin_disabled_txt.setText(value.disabledText)
plugin_download_txt.setText(value.downloadedText)
}
is Some.None -> {
plugin_storage_appbar?.isVisible = false
}
}
}
plugin_storage_appbar?.setOnClickListener {
findNavController().navigate(

View file

@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
@ -40,8 +39,8 @@ class ExtensionsViewModel : ViewModel() {
private val _repositories = MutableLiveData<Array<RepositoryData>>()
val repositories: LiveData<Array<RepositoryData>> = _repositories
private val _pluginStats: MutableLiveData<Some<PluginStats>> = MutableLiveData(Some.None)
val pluginStats: LiveData<Some<PluginStats>> = _pluginStats
private val _pluginStats: MutableLiveData<PluginStats?> = MutableLiveData(null)
val pluginStats: LiveData<PluginStats?> = _pluginStats
//TODO CACHE GET REQUESTS
// DO not use viewModelScope.launchSafe, it will ANR on slow internet
@ -78,7 +77,7 @@ class ExtensionsViewModel : ViewModel() {
debugAssert({ stats.downloaded + stats.notDownloaded + stats.disabled != stats.total }) {
"downloaded(${stats.downloaded}) + notDownloaded(${stats.notDownloaded}) + disabled(${stats.disabled}) != total(${stats.total})"
}
_pluginStats.postValue(Some.Success(stats))
_pluginStats.postValue(stats)
}
private fun repos() = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)

View file

@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
@ -20,9 +21,6 @@ import com.lagradost.cloudstream3.ui.settings.appLanguages
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.fragment_plugins.*
import kotlinx.android.synthetic.main.tvtypes_chips.*
import kotlinx.android.synthetic.main.tvtypes_chips_scroll.*
const val PLUGINS_BUNDLE_NAME = "name"
const val PLUGINS_BUNDLE_URL = "url"
@ -33,11 +31,19 @@ class PluginsFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
return inflater.inflate(R.layout.fragment_plugins, container, false)
): View {
val localBinding = FragmentPluginsBinding.inflate(inflater,container,false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_plugins, container, false)
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
private val pluginViewModel: PluginsViewModel by activityViewModels()
var binding: FragmentPluginsBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -66,8 +72,8 @@ class PluginsFragment : Fragment() {
}
setUpToolbar(name)
settings_toolbar?.setOnMenuItemClickListener { menuItem ->
binding?.settingsToolbar?.apply {
setOnMenuItemClickListener { menuItem ->
when (menuItem?.itemId) {
R.id.download_all -> {
PluginsViewModel.downloadAll(activity, url, pluginViewModel)
@ -99,22 +105,16 @@ class PluginsFragment : Fragment() {
}
val searchView =
settings_toolbar?.menu?.findItem(R.id.search_button)?.actionView as? SearchView
menu?.findItem(R.id.search_button)?.actionView as? SearchView
// Don't go back if active query
settings_toolbar?.setNavigationOnClickListener {
setNavigationOnClickListener {
if (searchView?.isIconified == false) {
searchView.isIconified = true
} else {
activity?.onBackPressed()
}
}
// searchView?.onActionViewCollapsed = {
// pluginViewModel.search(null)
// }
// Because onActionViewCollapsed doesn't wanna work we need this workaround :(
searchView?.setOnQueryTextFocusChangeListener { _, hasFocus ->
if (!hasFocus) pluginViewModel.search(null)
}
@ -130,36 +130,44 @@ class PluginsFragment : Fragment() {
return true
}
})
}
// searchView?.onActionViewCollapsed = {
// pluginViewModel.search(null)
// }
// Because onActionViewCollapsed doesn't wanna work we need this workaround :(
plugin_recycler_view?.adapter =
binding?.pluginRecyclerView?.adapter =
PluginAdapter {
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
}
if (isTvSettings()) {
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
plugin_recycler_view?.setPadding(0, 0, 0, 200.toPx)
binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx)
}
observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) ->
(plugin_recycler_view?.adapter as? PluginAdapter)?.updateList(list)
(binding?.pluginRecyclerView?.adapter as? PluginAdapter)?.updateList(list)
if (scrollToTop)
plugin_recycler_view?.scrollToPosition(0)
binding?.pluginRecyclerView?.scrollToPosition(0)
}
if (isLocal) {
// No download button and no categories on local
settings_toolbar?.menu?.findItem(R.id.download_all)?.isVisible = false
settings_toolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
binding?.settingsToolbar?.menu?.findItem(R.id.download_all)?.isVisible = false
binding?.settingsToolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
pluginViewModel.updatePluginListLocal()
tv_types_scroll_view?.isVisible = false
binding?.tvtypesChipsScroll?.root?.isVisible = false
} else {
pluginViewModel.updatePluginList(context, url)
tv_types_scroll_view?.isVisible = true
binding?.tvtypesChipsScroll?.root?.isVisible = true
bindChips(home_select_group, emptyList(), TvType.values().toList()) { list ->
bindChips(binding?.tvtypesChipsScroll?.tvtypesChips, emptyList(), TvType.values().toList()) { list ->
pluginViewModel.tvTypes.clear()
pluginViewModel.tvTypes.addAll(list.map { it.name })
pluginViewModel.updateFilteredPlugins()

View file

@ -8,21 +8,16 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentSetupExtensionsBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel
import com.lagradost.cloudstream3.ui.settings.extensions.RepoAdapter
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_extensions.blank_repo_screen
import kotlinx.android.synthetic.main.fragment_extensions.repo_recycler_view
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
import kotlinx.android.synthetic.main.fragment_setup_media.prev_btt
import kotlinx.android.synthetic.main.fragment_setup_media.setup_root
class SetupFragmentExtensions : Fragment() {
@ -39,13 +34,24 @@ class SetupFragmentExtensions : Fragment() {
}
}
var binding: FragmentSetupExtensionsBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setup_extensions, container, false)
): View {
val localBinding = FragmentSetupExtensionsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_setup_extensions, container, false)
}
override fun onResume() {
super.onResume()
afterRepositoryLoadedEvent += ::setRepositories
@ -60,12 +66,12 @@ class SetupFragmentExtensions : Fragment() {
main {
val repositories = RepositoryManager.getRepositories() + PREBUILT_REPOSITORIES
val hasRepos = repositories.isNotEmpty()
repo_recycler_view?.isVisible = hasRepos
blank_repo_screen?.isVisible = !hasRepos
binding?.repoRecyclerView?.isVisible = hasRepos
binding?.blankRepoScreen?.isVisible = !hasRepos
// view_public_repositories_button?.isVisible = hasRepos
if (hasRepos) {
repo_recycler_view?.adapter = RepoAdapter(true, {}, {
binding?.repoRecyclerView?.adapter = RepoAdapter(true, {}, {
PluginsViewModel.downloadAll(activity, it.url, null)
}).apply { updateList(repositories) }
}
@ -80,23 +86,23 @@ class SetupFragmentExtensions : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
fixPaddingStatusbar(binding?.setupRoot)
val isSetup = arguments?.getBoolean(SETUP_EXTENSION_BUNDLE_IS_SETUP) ?: false
// view_public_repositories_button?.setOnClickListener {
// openBrowser(PUBLIC_REPOSITORIES_LIST, isTvSettings(), this)
// }
with(context) {
if (this == null) return
normalSafeApiCall {
// val ctx = context ?: return@normalSafeApiCall
setRepositories()
binding?.apply {
if (!isSetup) {
next_btt.setText(R.string.setup_done)
nextBtt.setText(R.string.setup_done)
}
prev_btt?.isVisible = isSetup
prevBtt.isVisible = isSetup
next_btt?.setOnClickListener {
nextBtt.setOnClickListener {
// Continue setup
if (isSetup)
if (
@ -111,11 +117,12 @@ class SetupFragmentExtensions : Fragment() {
findNavController().navigate(R.id.navigation_home)
}
prev_btt?.setOnClickListener {
prevBtt.setOnClickListener {
findNavController().navigate(R.id.navigation_setup_language)
}
}
}
}
}

View file

@ -13,40 +13,49 @@ import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentSetupLanguageBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.settings.appLanguages
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_setup_language.*
import kotlinx.android.synthetic.main.fragment_setup_media.listview1
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
const val HAS_DONE_SETUP_KEY = "HAS_DONE_SETUP"
class SetupFragmentLanguage : Fragment() {
var binding: FragmentSetupLanguageBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_setup_language, container, false)
): View {
val localBinding = FragmentSetupLanguageBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_setup_language, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
// We don't want a crash for all users
normalSafeApiCall {
with(context) {
if (this == null) return@normalSafeApiCall
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
fixPaddingStatusbar(binding?.setupRoot)
val ctx = context ?: return@normalSafeApiCall
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
val arrayAdapter =
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
binding?.apply {
// Icons may crash on some weird android versions?
normalSafeApiCall {
val drawable = when {
@ -54,10 +63,10 @@ class SetupFragmentLanguage : Fragment() {
BuildConfig.BUILD_TYPE == "prerelease" -> R.drawable.cloud_2_gradient_beta
else -> R.drawable.cloud_2_gradient
}
app_icon_image?.setImageDrawable(ContextCompat.getDrawable(this, drawable))
appIconImage.setImageDrawable(ContextCompat.getDrawable(ctx, drawable))
}
val current = getCurrentLocale(this)
val current = getCurrentLocale(ctx)
val languageCodes = appLanguages.map { it.third }
val languageNames = appLanguages.map { (emoji, name, iso) ->
val flag = emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" }
@ -66,18 +75,19 @@ class SetupFragmentLanguage : Fragment() {
val index = languageCodes.indexOf(current)
arrayAdapter.addAll(languageNames)
listview1?.adapter = arrayAdapter
listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
listview1?.setItemChecked(index, true)
listview1.adapter = arrayAdapter
listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE
listview1.setItemChecked(index, true)
listview1?.setOnItemClickListener { _, _, position, _ ->
listview1.setOnItemClickListener { _, _, position, _ ->
val code = languageCodes[position]
CommonActivity.setLocale(activity, code)
settingsManager.edit().putString(getString(R.string.locale_key), code).apply()
settingsManager.edit().putString(getString(R.string.locale_key), code)
.apply()
activity?.recreate()
}
next_btt?.setOnClickListener {
nextBtt.setOnClickListener {
// If no plugins go to plugins page
val nextDestination = if (
PluginManager.getPluginsOnline().isEmpty()
@ -92,10 +102,11 @@ class SetupFragmentLanguage : Fragment() {
)
}
skip_btt?.setOnClickListener {
skipBtt.setOnClickListener {
findNavController().navigate(R.id.navigation_home)
}
}
}
}

View file

@ -10,30 +10,39 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentSetupLayoutBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_setup_layout.*
import kotlinx.android.synthetic.main.fragment_setup_media.listview1
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
import kotlinx.android.synthetic.main.fragment_setup_media.prev_btt
import kotlinx.android.synthetic.main.fragment_setup_media.setup_root
import org.acra.ACRA
class SetupFragmentLayout : Fragment() {
var binding: FragmentSetupLayoutBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setup_layout, container, false)
): View {
val localBinding = FragmentSetupLayoutBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_setup_layout, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
fixPaddingStatusbar(binding?.setupRoot)
with(context) {
if (this == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
normalSafeApiCall {
val ctx = context ?: return@normalSafeApiCall
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
val prefNames = resources.getStringArray(R.array.app_layout)
val prefValues = resources.getIntArray(R.array.app_layout_values)
@ -42,48 +51,48 @@ class SetupFragmentLayout : Fragment() {
settingsManager.getInt(getString(R.string.app_layout_key), -1)
val arrayAdapter =
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
arrayAdapter.addAll(prefNames.toList())
listview1?.adapter = arrayAdapter
listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
listview1?.setItemChecked(
binding?.apply {
listview1.adapter = arrayAdapter
listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE
listview1.setItemChecked(
prefValues.indexOf(currentLayout), true
)
listview1?.setOnItemClickListener { _, _, position, _ ->
listview1.setOnItemClickListener { _, _, position, _ ->
settingsManager.edit()
.putInt(getString(R.string.app_layout_key), prefValues[position])
.apply()
activity?.recreate()
}
acra_switch?.setOnCheckedChangeListener { _, enableCrashReporting ->
acraSwitch.setOnCheckedChangeListener { _, enableCrashReporting ->
// Use same pref as in settings
settingsManager.edit().putBoolean(ACRA.PREF_DISABLE_ACRA, !enableCrashReporting)
.apply()
val text =
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
crash_reporting_text?.text = getText(text)
crashReportingText.text = getText(text)
}
val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true)
acra_switch.isChecked = enableCrashReporting
crash_reporting_text.text =
acraSwitch.isChecked = enableCrashReporting
crashReportingText.text =
getText(
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
)
next_btt?.setOnClickListener {
nextBtt.setOnClickListener {
findNavController().navigate(R.id.navigation_home)
}
prev_btt?.setOnClickListener {
prevBtt.setOnClickListener {
findNavController().popBackStack()
}
}
}
}
}

View file

@ -10,38 +10,51 @@ import androidx.core.util.forEach
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_setup_media.*
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
class SetupFragmentMedia : Fragment() {
var binding: FragmentSetupMediaBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setup_media, container, false)
): View {
val localBinding = FragmentSetupMediaBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_setup_media, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
normalSafeApiCall {
fixPaddingStatusbar(binding?.setupRoot)
with(context) {
if (this == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val ctx = context ?: return@normalSafeApiCall
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
val arrayAdapter =
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
val names = enumValues<TvType>().sorted().map { it.name }
val selected = mutableListOf<Int>()
arrayAdapter.addAll(names)
listview1?.let {
binding?.apply {
listview1.let {
it.adapter = arrayAdapter
it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
@ -54,7 +67,8 @@ class SetupFragmentMedia : Fragment() {
}
}
val prefValues = selected.mapNotNull { pos ->
val item = it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null
val item =
it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null
val itemVal = TvType.valueOf(item)
itemVal.ordinal.toString()
}.toSet()
@ -67,15 +81,14 @@ class SetupFragmentMedia : Fragment() {
}
}
next_btt?.setOnClickListener {
nextBtt.setOnClickListener {
findNavController().navigate(R.id.navigation_setup_media_to_navigation_setup_layout)
}
prev_btt?.setOnClickListener {
prevBtt.setOnClickListener {
findNavController().popBackStack()
}
}
}
}
}

View file

@ -14,31 +14,43 @@ import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentSetupProviderLanguagesBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_setup_media.*
class SetupFragmentProviderLanguage : Fragment() {
var binding: FragmentSetupProviderLanguagesBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_setup_provider_languages, container, false)
): View {
val localBinding = FragmentSetupProviderLanguagesBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root
//return inflater.inflate(R.layout.fragment_setup_provider_languages, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
fixPaddingStatusbar(binding?.setupRoot)
with(context) {
if (this == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
normalSafeApiCall {
val ctx = context ?: return@normalSafeApiCall
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
val arrayAdapter =
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
val current = this.getApiProviderLangSettings()
val current = ctx.getApiProviderLangSettings()
val langs = APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
@ -56,31 +68,31 @@ class SetupFragmentProviderLanguage : Fragment() {
}
arrayAdapter.addAll(languageNames)
listview1?.adapter = arrayAdapter
listview1?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
binding?.apply {
listview1.adapter = arrayAdapter
listview1.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
currentList.forEach {
listview1.setItemChecked(it, true)
}
listview1?.setOnItemClickListener { _, _, _, _ ->
listview1.setOnItemClickListener { _, _, _, _ ->
val currentLanguages = mutableListOf<String>()
listview1?.checkedItemPositions?.forEach { key, value ->
listview1.checkedItemPositions?.forEach { key, value ->
if (value) currentLanguages.add(langs[key])
}
settingsManager.edit().putStringSet(
this.getString(R.string.provider_lang_key),
ctx.getString(R.string.provider_lang_key),
currentLanguages.toSet()
).apply()
}
next_btt?.setOnClickListener {
nextBtt.setOnClickListener {
findNavController().navigate(R.id.navigation_setup_provider_languages_to_navigation_setup_media)
}
prev_btt?.setOnClickListener {
prevBtt.setOnClickListener {
findNavController().popBackStack()
}
} }
}
}

View file

@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.Event
@ -31,7 +32,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.subtitle_settings.*
const val CHROME_SUBTITLE_KEY = "chome_subtitle_settings"
@ -137,12 +137,21 @@ class ChromecastSubtitlesFragment : Fragment() {
//subtitle_text?.setStyle(fromSaveToStyle(state))
}
var binding : ChromecastSubtitleSettingsBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
return inflater.inflate(R.layout.chromecast_subtitle_settings, container, false)
): View {
val localBinding = ChromecastSubtitleSettingsBinding.inflate(inflater, container, false)
binding = localBinding
return localBinding.root//inflater.inflate(R.layout.chromecast_subtitle_settings, container, false)
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
private lateinit var state: SaveChromeCaptionStyle
@ -159,7 +168,7 @@ class ChromecastSubtitlesFragment : Fragment() {
onColorSelectedEvent += ::onColorSelected
onDialogDismissedEvent += ::onDialogDismissed
context?.fixPaddingStatusbar(subs_root)
fixPaddingStatusbar(binding?.subsRoot)
state = getCurrentSavedStyle()
context?.updateState()
@ -190,17 +199,20 @@ class ChromecastSubtitlesFragment : Fragment() {
}
}
subs_text_color.setup(0)
subs_outline_color.setup(1)
subs_background_color.setup(2)
binding?.apply {
subsTextColor.setup(0)
subsOutlineColor.setup(1)
subsBackgroundColor.setup(2)
}
val dismissCallback = {
if (hide)
activity?.hideSystemUI()
}
subs_edge_type.setFocusableInTv()
subs_edge_type.setOnClickListener { textView ->
binding?.subsEdgeType?.setFocusableInTv()
binding?.subsEdgeType?.setOnClickListener { textView ->
val edgeTypes = listOf(
Pair(
EDGE_TYPE_NONE,
@ -237,15 +249,15 @@ class ChromecastSubtitlesFragment : Fragment() {
}
}
subs_edge_type.setOnLongClickListener {
binding?.subsEdgeType?.setOnLongClickListener {
state.edgeType = defaultState.edgeType
it.context.updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
}
subs_font_size.setFocusableInTv()
subs_font_size.setOnClickListener { textView ->
binding?.subsFontSize?.setFocusableInTv()
binding?.subsFontSize?.setOnClickListener { textView ->
val fontSizes = listOf(
Pair(0.75f, "75%"),
Pair(0.80f, "80%"),
@ -278,24 +290,26 @@ class ChromecastSubtitlesFragment : Fragment() {
}
}
subs_font_size.setOnLongClickListener { _ ->
binding?.subsFontSize?.setOnLongClickListener { _ ->
state.fontScale = defaultState.fontScale
//textView.context.updateState() // font size not changed
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
}
subs_font.setFocusableInTv()
subs_font.setOnClickListener { textView ->
binding?.subsFont?.setFocusableInTv()
binding?.subsFont?.setOnClickListener { textView ->
val fontTypes = listOf(
Pair(null, textView.context.getString(R.string.normal)),
Pair("Droid Sans", "Droid Sans"),
Pair("Droid Sans Mono", "Droid Sans Mono"),
Pair("Droid Serif Regular", "Droid Serif Regular"),
Pair("Cutive Mono", "Cutive Mono"),
Pair("Short Stack", "Short Stack"),
Pair("Quintessential", "Quintessential"),
Pair("Alegreya Sans SC", "Alegreya Sans SC"),
null to textView.context.getString(R.string.normal),
"Droid Sans" to "Droid Sans",
"Droid Sans Mono" to "Droid Sans Mono",
"Droid Serif Regular" to "Droid Serif Regular",
"Cutive Mono" to "Cutive Mono",
"Short Stack" to "Short Stack",
"Quintessential" to "Quintessential",
"Alegreya Sans SC" to "Alegreya Sans SC",
)
//showBottomDialog
@ -310,35 +324,35 @@ class ChromecastSubtitlesFragment : Fragment() {
textView.context.updateState()
}
}
subs_font.setOnLongClickListener { textView ->
binding?.subsFont?.setOnLongClickListener { textView ->
state.fontFamily = defaultState.fontFamily
textView.context.updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
}
cancel_btt.setOnClickListener {
binding?.cancelBtt?.setOnClickListener {
activity?.popCurrentPage()
}
apply_btt.setOnClickListener {
binding?.applyBtt?.setOnClickListener {
it.context.saveStyle(state)
applyStyleEvent.invoke(state)
//it.context.fromSaveToStyle(state)
activity?.popCurrentPage()
}
subtitle_text.setCues(
binding?.subtitleText?.apply {
setCues(
listOf(
Cue.Builder()
.setTextSize(
getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(),
Cue.TEXT_SIZE_TYPE_ABSOLUTE
)
.setText(subtitle_text.context.getString(R.string.subtitles_example_text))
.setText(context.getString(R.string.subtitles_example_text))
.build()
)
)
}
}
}

View file

@ -238,7 +238,7 @@ class SubtitlesFragment : Fragment() {
context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts"
)
context?.fixPaddingStatusbar(subs_root)
fixPaddingStatusbar(subs_root)
state = getCurrentSavedStyle()
context?.updateState()

View file

@ -397,21 +397,22 @@ object UIHelper {
return result
}
fun Context?.fixPaddingStatusbar(v: View?) {
if (v == null || this == null) return
fun fixPaddingStatusbar(v: View?) {
if (v == null) return
val ctx = v.context ?: return
v.setPadding(
v.paddingLeft,
v.paddingTop + getStatusBarHeight(),
v.paddingTop + ctx.getStatusBarHeight(),
v.paddingRight,
v.paddingBottom
)
}
fun Context.fixPaddingStatusbarView(v: View?) {
fun fixPaddingStatusbarView(v: View?) {
if (v == null) return
val ctx = v.context ?: return
val params = v.layoutParams
params.height = getStatusBarHeight()
params.height = ctx.getStatusBarHeight()
v.layoutParams = params
}

View file

@ -172,4 +172,15 @@
app:icon="@drawable/ic_baseline_filter_list_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/home_random"
style="@style/ExtendedFloatingActionButton"
android:layout_gravity="bottom|start"
android:text="@string/home_random"
android:textColor="?attr/textColor"
android:visibility="gone"
app:icon="@drawable/ic_baseline_play_arrow_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</FrameLayout>

View file

@ -25,7 +25,7 @@
app:titleTextColor="?attr/textColor"
tools:title="Overlord" />
<include layout="@layout/tvtypes_chips_scroll" />
<include layout="@layout/tvtypes_chips_scroll" android:id="@+id/tvtypes_chips_scroll" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView

View file

@ -88,7 +88,7 @@
app:tint="?attr/textColor" />
</FrameLayout>
<include layout="@layout/tvtypes_chips_scroll" />
<include layout="@layout/tvtypes_chips_scroll" android:id="@+id/tvtypes_chips_scroll" />
</LinearLayout>

View file

@ -89,7 +89,7 @@
app:tint="?attr/textColor" />
</FrameLayout>
<include layout="@layout/tvtypes_chips_scroll" />
<include layout="@layout/tvtypes_chips_scroll" android:id="@+id/tvtypes_chips_scroll" />
</LinearLayout>

View file

@ -26,7 +26,7 @@
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="60dp">
<include layout="@layout/tvtypes_chips_scroll" />
<include layout="@layout/tvtypes_chips_scroll" android:id="@+id/tvtypes_chips_scroll" />
<LinearLayout
android:id="@+id/apply_btt_holder"
android:layout_width="match_parent"

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<com.lagradost.cloudstream3.ui.AutofitRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/page_recyclerview"

View file

@ -3,27 +3,28 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_holder_large"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/rounded_image_radius"
android:layout_marginBottom="10dp"
android:nextFocusRight="@id/result_episode_download"
app:cardBackgroundColor="?attr/boxItemBackground"
android:layout_marginBottom="10dp">
app:cardCornerRadius="@dimen/rounded_image_radius">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:padding="10dp">
<LinearLayout
android:id="@+id/episode_lin_holder"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="horizontal">
<!--app:cardCornerRadius="@dimen/roundedImageRadius"-->
<androidx.cardview.widget.CardView
android:layout_width="126dp"
@ -31,121 +32,121 @@
android:foreground="@drawable/outline_drawable">
<ImageView
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_poster"
tools:src="@drawable/example_poster"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/episode_poster_img_des" />
android:contentDescription="@string/episode_poster_img_des"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:nextFocusRight="@id/result_episode_download"
android:scaleType="centerCrop"
tools:src="@drawable/example_poster" />
<ImageView
android:src="@drawable/play_button"
android:layout_gravity="center"
android:layout_width="36dp"
android:layout_height="36dp"
android:contentDescription="@string/play_episode" />
android:layout_gravity="center"
android:contentDescription="@string/play_episode"
android:src="@drawable/play_button" />
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginBottom="-1.5dp"
android:id="@+id/episode_progress"
android:progressTint="?attr/colorPrimary"
android:progressBackgroundTint="?attr/colorPrimary"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
tools:progress="50"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:layout_height="5dp" />
android:layout_marginBottom="-1.5dp"
android:progressBackgroundTint="?attr/colorPrimary"
android:progressTint="?attr/colorPrimary"
tools:progress="50" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_marginStart="15dp"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="15dp"
android:layout_marginEnd="50dp"
android:layout_height="wrap_content">
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:layout_gravity="start"
android:id="@+id/episode_filler"
style="@style/SmallBlackButton"
android:layout_gravity="start"
android:layout_marginEnd="10dp"
android:text="@string/filler"
android:id="@+id/episode_filler" />
android:text="@string/filler" />
<TextView
android:layout_gravity="center_vertical"
android:id="@+id/episode_text"
tools:text="1. Jobless"
android:textStyle="bold"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:textStyle="bold"
tools:text="1. Jobless" />
</LinearLayout>
<TextView
android:id="@+id/episode_rating"
tools:text="Rated: 8.8"
android:textColor="?attr/grayTextColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor"
tools:text="Rated: 8.8" />
</LinearLayout>
<FrameLayout
android:layout_marginStart="-50dp"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginStart="-50dp">
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:id="@+id/result_episode_progress_downloaded"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/result_episode_progress_downloaded"
android:indeterminate="false"
android:progressDrawable="@drawable/circular_progress_bar"
android:background="@drawable/circle_shape"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:layout_margin="5dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="@drawable/circle_shape"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/circular_progress_bar"
android:visibility="visible" />
<ImageView
android:nextFocusLeft="@id/episode_poster"
android:id="@+id/result_episode_download"
android:layout_width="50dp"
android:visibility="visible"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="10dp"
android:layout_width="50dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/download"
android:nextFocusLeft="@id/episode_poster"
android:padding="10dp"
android:src="@drawable/ic_baseline_play_arrow_24"
android:visibility="visible"
app:tint="?attr/white" />
</FrameLayout>
</LinearLayout>
<TextView
android:maxLines="4"
android:id="@+id/episode_descript"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="4"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:id="@+id/episode_descript"
android:textColor="?attr/grayTextColor"
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart."
android:layout_width="match_parent"
android:layout_height="wrap_content" />
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart." />
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -6,5 +6,5 @@
android:requiresFadingEdge="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<include layout="@layout/tvtypes_chips" />
<include layout="@layout/tvtypes_chips" android:id="@+id/tvtypes_chips" />
</HorizontalScrollView>