package com.lagradost.cloudstream3.ui.home import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.* import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.* import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.showToast 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.mvvm.observeNullable import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.ownHide import com.lagradost.cloudstream3.utils.AppUtils.ownShow import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso 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.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import java.util.* const val HOME_BOOKMARK_VALUE_LIST = "home_bookmarked_last_list" const val HOME_PREF_HOMEPAGE = "home_pref_homepage" class HomeFragment : Fragment() { companion object { val configEvent = Event() var currentSpan = 1 val listHomepageItems = mutableListOf() private val errorProfilePics = listOf( R.drawable.monke_benene, R.drawable.monke_burrito, R.drawable.monke_coco, R.drawable.monke_cookie, R.drawable.monke_flusdered, R.drawable.monke_funny, R.drawable.monke_like, R.drawable.monke_party, R.drawable.monke_sob, R.drawable.monke_drink, ) val errorProfilePic = errorProfilePics.random() //fun Activity.loadHomepageList( // item: HomePageList, // deleteCallback: (() -> Unit)? = null, //) { // loadHomepageList( // expand = HomeViewModel.ExpandableHomepageList(item, 1, false), // deleteCallback = deleteCallback, // expandCallback = null // ) //} // returns a BottomSheetDialog that will be hidden with OwnHidden upon hide, and must be saved to be able call ownShow in onCreateView fun Activity.loadHomepageList( expand: HomeViewModel.ExpandableHomepageList, deleteCallback: (() -> Unit)? = null, expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null, dismissCallback: (() -> Unit), ): BottomSheetDialog { val context = this val bottomSheetDialogBuilder = BottomSheetDialog(context) val binding: HomeEpisodesExpandedBinding = HomeEpisodesExpandedBinding.inflate( bottomSheetDialogBuilder.layoutInflater, null, false ) bottomSheetDialogBuilder.setContentView(binding.root) //val title = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_text)!! //title.findViewTreeLifecycleOwner().lifecycle.addObserver() val item = expand.list binding.homeExpandedText.text = item.name // val recycle = // bottomSheetDialogBuilder.findViewById(R.id.home_expanded_recycler)!! //val titleHolder = // bottomSheetDialogBuilder.findViewById(R.id.home_expanded_drag_down)!! // main { //(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply { // println("GOT LIFE: lifecycle $this") // this.lifecycle.addObserver(object : DefaultLifecycleObserver { // override fun onResume(owner: LifecycleOwner) { // super.onResume(owner) // println("onResume!!!!") // bottomSheetDialogBuilder?.ownShow() // } // override fun onStop(owner: LifecycleOwner) { // super.onStop(owner) // bottomSheetDialogBuilder?.ownHide() // } // }) //} // } //val delete = bottomSheetDialogBuilder.home_expanded_delete binding.homeExpandedDelete.isGone = deleteCallback == null if (deleteCallback != null) { binding.homeExpandedDelete.setOnClickListener { try { val builder: AlertDialog.Builder = AlertDialog.Builder(context) val dialogClickListener = DialogInterface.OnClickListener { _, which -> when (which) { DialogInterface.BUTTON_POSITIVE -> { deleteCallback.invoke() bottomSheetDialogBuilder.dismissSafe(this) } DialogInterface.BUTTON_NEGATIVE -> {} } } builder.setTitle(R.string.delete_file) .setMessage( context.getString(R.string.delete_message).format( item.name ) ) .setPositiveButton(R.string.delete, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) .show().setDefaultFocus() } catch (e: Exception) { logError(e) // ye you somehow fucked up formatting did you? } } } binding.homeExpandedDragDown.setOnClickListener { bottomSheetDialogBuilder.dismissSafe(this) } // Span settings binding.homeExpandedRecycler.spanCount = currentSpan binding.homeExpandedRecycler.adapter = SearchAdapter(item.list.toMutableList(), binding.homeExpandedRecycler) { callback -> handleSearchClickCallback(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 //bottomSheetDialogBuilder.dismissSafe(this) } }.apply { hasNext = expand.hasNext } binding.homeExpandedRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { var expandCount = 0 val name = expand.list.name override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) val adapter = recyclerView.adapter if (adapter !is SearchAdapter) return val count = adapter.itemCount val currentHasNext = adapter.hasNext //!recyclerView.canScrollVertically(1) if (!recyclerView.isRecyclerScrollable() && currentHasNext && expandCount != count) { expandCount = count ioSafe { expandCallback?.invoke(name)?.let { newExpand -> (recyclerView.adapter as? SearchAdapter?)?.apply { hasNext = newExpand.hasNext updateList(newExpand.list.list) } } } } } }) val spanListener = { span: Int -> binding.homeExpandedRecycler.spanCount = span //(recycle.adapter as SearchAdapter).notifyDataSetChanged() } configEvent += spanListener bottomSheetDialogBuilder.setOnDismissListener { dismissCallback.invoke() configEvent -= spanListener } //(recycle.adapter as SearchAdapter).notifyDataSetChanged() bottomSheetDialogBuilder.show() return bottomSheetDialogBuilder } fun getPairList( anime: Chip?, cartoons: Chip?, tvs: Chip?, docs: Chip?, movies: Chip?, asian: Chip?, livestream: Chip?, nsfw: Chip?, others: Chip?, ): List>> { // This list should be same order as home screen to aid navigation return listOf( Pair(movies, listOf(TvType.Movie, TvType.Torrent)), Pair(tvs, listOf(TvType.TvSeries)), Pair(anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)), Pair(asian, listOf(TvType.AsianDrama)), Pair(cartoons, listOf(TvType.Cartoon)), Pair(docs, listOf(TvType.Documentary)), Pair(livestream, listOf(TvType.Live)), Pair(nsfw, listOf(TvType.NSFW)), Pair(others, listOf(TvType.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: TvtypesChipsBinding?, validTypes: List) { if (header == null) return val pairList = getPairList(header) for ((button, types) in pairList) { val isValid = validTypes.any { types.contains(it) } button?.isVisible = isValid } } fun updateChips(header: TvtypesChipsBinding?, selectedTypes: List) { if (header == null) return val pairList = getPairList(header) for ((button, types) in pairList) { button?.isChecked = button?.isVisible == true && selectedTypes.any { types.contains(it) } } } fun bindChips( header: TvtypesChipsBinding?, selectedTypes: List, validTypes: List, callback: (List) -> Unit ) { bindChips(header, selectedTypes, validTypes, callback, null, null) } fun bindChips( header: TvtypesChipsBinding?, selectedTypes: List, validTypes: List, callback: (List) -> Unit, nextFocusDown: Int?, nextFocusUp: Int? ) { if (header == null) return val pairList = getPairList(header) for ((button, types) in pairList) { val isValid = validTypes.any { types.contains(it) } button?.isVisible = isValid button?.isChecked = isValid && selectedTypes.any { types.contains(it) } button?.isFocusable = true if (isTrueTvSettings()) { button?.isFocusableInTouchMode = true } if (nextFocusDown != null) button?.nextFocusDownId = nextFocusDown if (nextFocusUp != null) button?.nextFocusUpId = nextFocusUp button?.setOnCheckedChangeListener { _, _ -> val list = ArrayList() for ((sbutton, vvalidTypes) in pairList) { if (sbutton?.isChecked == true) list.addAll(vvalidTypes) } callback(list) } } } fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) { val validAPIs = filterProviderByPreferredMedia().toMutableList() validAPIs.add(0, randomApi) validAPIs.add(0, noneApi) //val builder: AlertDialog.Builder = AlertDialog.Builder(this) //builder.setView(R.layout.home_select_mainpage) val builder = BottomSheetDialog(this) builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED val binding: HomeSelectMainpageBinding = HomeSelectMainpageBinding.inflate( builder.layoutInflater, null, false ) builder.setContentView(binding.root) builder.show() builder.let { dialog -> val isMultiLang = getApiProviderLangSettings().let { set -> set.size > 1 || set.contains(AllLanguagesName) } //dialog.window?.setGravity(Gravity.BOTTOM) var currentApiName = selectedApiName var currentValidApis: MutableList = mutableListOf() val preSelectedTypes = this.getKey>(HOME_PREF_HOMEPAGE) ?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } ?.toMutableList() ?: mutableListOf(TvType.Movie, TvType.TvSeries) binding.cancelBtt.setOnClickListener { dialog.dismissSafe() } binding.applyBtt.setOnClickListener { if (currentApiName != selectedApiName) { currentApiName?.let(callback) } dialog.dismissSafe() } val listView = dialog.findViewById(R.id.listview1) val arrayAdapter = ArrayAdapter(this, R.layout.sort_bottom_single_choice) listView?.adapter = arrayAdapter listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE listView?.setOnItemClickListener { _, _, i, _ -> if (currentValidApis.isNotEmpty()) { currentApiName = currentValidApis[i].name //to switch to apply simply remove this currentApiName?.let(callback) dialog.dismissSafe() } } fun updateList() { this.setKey(HOME_PREF_HOMEPAGE, preSelectedTypes) arrayAdapter.clear() currentValidApis = validAPIs.filter { api -> api.hasMainPage && api.supportedTypes.any { preSelectedTypes.contains(it) } }.sortedBy { it.name.lowercase() }.toMutableList() currentValidApis.addAll(0, validAPIs.subList(0, 2)) val names = currentValidApis.map { if (isMultiLang) "${getFlagFromIso(it.lang)?.plus(" ") ?: ""}${it.name}" else it.name } val index = currentValidApis.map { it.name }.indexOf(currentApiName) listView?.setItemChecked(index, true) arrayAdapter.addAll(names) arrayAdapter.notifyDataSetChanged() } bindChips( binding.tvtypesChipsScroll.tvtypesChips, preSelectedTypes, validAPIs.flatMap { it.supportedTypes }.distinct() ) { list -> preSelectedTypes.clear() preSelectedTypes.addAll(list) updateList() } updateList() } } } private val homeViewModel: HomeViewModel by activityViewModels() var binding: FragmentHomeBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { //homeViewModel = // ViewModelProvider(this).get(HomeViewModel::class.java) bottomSheetDialog?.ownShow() val layout = if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home val root = inflater.inflate(layout, container, false) binding = try { FragmentHomeBinding.bind(root) } catch (t: Throwable) { showToast(txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG) logError(t) null } return root } override fun onDestroyView() { bottomSheetDialog?.ownHide() binding = null super.onDestroyView() } private fun fixGrid() { activity?.getSpanCount()?.let { currentSpan = it } configEvent.invoke(currentSpan) } private val apiChangeClickListener = View.OnClickListener { view -> view.context.selectHomepage(currentApiName) { api -> homeViewModel.loadAndCancel(api, forceReload = true, fromUI = true) } /*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf() validAPIs.add(0, randomApi) validAPIs.add(0, noneApi) view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) { homeViewModel.loadAndCancel(validAPIs[itemId].name) }*/ } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) //(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.notifyDataSetChanged() fixGrid() } private var currentApiName: String? = null private var toggleRandomButton = false private var bottomSheetDialog: BottomSheetDialog? = null @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) fixGrid() binding?.apply { homeChangeApiLoading.setOnClickListener(apiChangeClickListener) //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) homeApiFab.setOnClickListener(apiChangeClickListener) homeRandom.setOnClickListener { if (listHomepageItems.isNotEmpty()) { activity.loadSearchResult(listHomepageItems.random()) } } homeMasterRecycler.adapter = HomeParentItemAdapterPreview( mutableListOf(), homeViewModel ) fixPaddingStatusbar(homeLoadingStatusbar) if (isTvSettings()) { homeApiFab.isVisible = false if (isTrueTvSettings()) { homeChangeApiLoading.isVisible = true homeChangeApiLoading.isFocusable = true homeChangeApiLoading.isFocusableInTouchMode = true } // home_bookmark_select?.isFocusable = true // home_bookmark_select?.isFocusableInTouchMode = true } else { homeApiFab.isVisible = true homeChangeApiLoading.isVisible = false } homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy > 0) { //check for scroll down homeApiFab.shrink() // hide homeRandom.shrink() } else if (dy < -5) { if (!isTvSettings()) { homeApiFab.extend() // show homeRandom.extend() } } super.onScrolled(recyclerView, dx, dy) } }) } //Load value for toggling Random button. Hide at startup context?.let { val settingsManager = PreferenceManager.getDefaultSharedPreferences(it) toggleRandomButton = settingsManager.getBoolean( getString(R.string.random_button_key), false ) && !isTvSettings() binding?.homeRandom?.visibility = View.GONE } observe(homeViewModel.apiName) { apiName -> currentApiName = apiName binding?.homeApiFab?.text = apiName } observe(homeViewModel.page) { data -> binding?.apply { when (data) { is Resource.Success -> { homeLoadingShimmer.stopShimmer() val d = data.value val mutableListOfResponse = mutableListOf() listHomepageItems.clear() (homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList( d.values.toMutableList(), homeMasterRecycler ) homeLoading.isVisible = false homeLoadingError.isVisible = false homeMasterRecycler.isVisible = true //home_loaded?.isVisible = true if (toggleRandomButton) { //Flatten list d.values.forEach { dlist -> mutableListOfResponse.addAll(dlist.list.list) } listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url }) homeRandom.isVisible = listHomepageItems.isNotEmpty() } else { homeRandom.isGone = true } } is Resource.Failure -> { homeLoadingShimmer.stopShimmer() resultErrorText.text = data.errorString homeReloadConnectionerror.setOnClickListener(apiChangeClickListener) homeReloadConnectionOpenInBrowser.setOnClickListener { view -> val validAPIs = apis//.filter { api -> api.hasMainPage } view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair( index, api.name ) }) { try { val i = Intent(Intent.ACTION_VIEW) i.data = Uri.parse(validAPIs[itemId].mainUrl) startActivity(i) } catch (e: Exception) { logError(e) } } } homeLoading.isVisible = false homeLoadingError.isVisible = true homeMasterRecycler.isVisible = false //home_loaded?.isVisible = false } is Resource.Loading -> { (homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(listOf()) homeLoadingShimmer.startShimmer() homeLoading.isVisible = true homeLoadingError.isVisible = false homeMasterRecycler.isVisible = false //home_loaded?.isVisible = false } } } } //context?.fixPaddingStatusbarView(home_statusbar) //context?.fixPaddingStatusbar(home_padding) observeNullable(homeViewModel.popup) { item -> if (item == null) { bottomSheetDialog?.dismissSafe() bottomSheetDialog = null return@observeNullable } // don't recreate if (bottomSheetDialog != null) { return@observeNullable } bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = { homeViewModel.expandAndReturn(it) }, dismissCallback = { homeViewModel.popup(null) bottomSheetDialog = null }) } homeViewModel.reloadStored() homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false) //loadHomePage(false) // nice profile pic on homepage //home_profile_picture_holder?.isVisible = false // just in case //TODO READD THIS /*for (syncApi in OAuth2Apis) { val login = syncApi.loginInfo() val pic = login?.profilePicture if (home_profile_picture?.setImage( pic, errorImageDrawable = errorProfilePic ) == true ) { home_profile_picture_holder?.isVisible = true break } }*/ } }