From 05a0d3cd817c5484a44acd9bb6e8199e52ced387 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:18:37 +0200 Subject: [PATCH 01/31] migrated some items to viewbindings + removed Some --- app/build.gradle.kts | 5 + .../cloudstream3/mvvm/ArchComponentExt.kt | 26 -- .../ui/download/DownloadChildFragment.kt | 44 ++- .../ui/download/DownloadFragment.kt | 98 +++--- .../cloudstream3/ui/home/HomeFragment.kt | 325 ++++++++++-------- .../ui/home/HomeParentItemAdapterPreview.kt | 4 +- .../ui/library/LibraryFragment.kt | 198 ++++++----- .../ui/library/LibraryViewModel.kt | 1 - .../cloudstream3/ui/library/PageAdapter.kt | 30 +- .../ui/library/ViewpagerAdapter.kt | 70 ++-- .../ui/quicksearch/QuickSearchFragment.kt | 65 ++-- .../cloudstream3/ui/result/ActorAdaptor.kt | 77 ++--- .../cloudstream3/ui/result/EpisodeAdapter.kt | 296 ++++++++++------ .../cloudstream3/ui/result/ImageAdapter.kt | 14 +- .../cloudstream3/ui/result/ResultFragment.kt | 133 +++---- .../ui/result/ResultFragmentPhone.kt | 106 +++--- .../ui/result/ResultFragmentTv.kt | 77 ++--- .../ui/result/ResultViewModel2.kt | 194 ++++++----- .../cloudstream3/ui/result/SelectAdaptor.kt | 13 +- .../cloudstream3/ui/result/UiText.kt | 9 - .../cloudstream3/ui/search/SearchAdaptor.kt | 2 +- .../cloudstream3/ui/search/SearchFragment.kt | 128 ++++--- .../ui/search/SearchHistoryAdaptor.kt | 30 +- .../ui/search/SearchResultBuilder.kt | 20 +- .../ui/settings/AccountAdapter.kt | 22 +- .../ui/settings/SettingsAccount.kt | 2 +- .../ui/settings/SettingsFragment.kt | 4 +- .../settings/extensions/ExtensionsFragment.kt | 42 ++- .../extensions/ExtensionsViewModel.kt | 7 +- .../ui/settings/extensions/PluginsFragment.kt | 74 ++-- .../ui/setup/SetupFragmentExtensions.kt | 79 +++-- .../ui/setup/SetupFragmentLanguage.kt | 53 +-- .../ui/setup/SetupFragmentLayout.kt | 101 +++--- .../ui/setup/SetupFragmentMedia.kt | 89 +++-- .../ui/setup/SetupFragmentProviderLanguage.kt | 50 ++- .../subtitles/ChromecastSubtitlesFragment.kt | 90 +++-- .../ui/subtitles/SubtitlesFragment.kt | 2 +- .../lagradost/cloudstream3/utils/UIHelper.kt | 13 +- app/src/main/res/layout/fragment_home_tv.xml | 11 + app/src/main/res/layout/fragment_plugins.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 2 +- .../main/res/layout/fragment_search_tv.xml | 2 +- .../main/res/layout/home_select_mainpage.xml | 2 +- .../res/layout/library_viewpager_page.xml | 2 - .../main/res/layout/result_episode_large.xml | 213 ++++++------ .../main/res/layout/tvtypes_chips_scroll.xml | 2 +- 46 files changed, 1565 insertions(+), 1264 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ebde6187..86d91147 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,6 +28,11 @@ android { testOptions { unitTests.isReturnDefaultValues = true } + + viewBinding { + enable = true + } + signingConfigs { create("prerelease") { if (prereleaseStoreFile != null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index bb15bc85..28b552d1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -57,32 +57,6 @@ fun LifecycleOwner.observeNullable(liveData: LiveData, action: (t: T) -> liveData.observe(this) { action(it) } } -inline fun some(value: T?): Some { - return if (value == null) { - Some.None - } else { - Some.Success(value) - } -} - -sealed class Some { - data class Success(val value: T) : Some() - object None : Some() - - override fun toString(): String { - return when (this) { - is None -> "None" - is Success -> "Some(${value.toString()})" - } - } -} - -sealed class ResourceSome { - data class Success(val value: T) : ResourceSome() - object None : ResourceSome() - data class Loading(val data: Any? = null) : ResourceSome() -} - sealed class Resource { data class Success(val value: T) : Resource() data class Failure( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 477a18e0..0cef49b1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -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,14 +81,17 @@ 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 { - activity?.onBackPressed() + binding?.downloadChildToolbar?.apply { + title = name + setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) + setNavigationOnClickListener { + activity?.onBackPressed() + } } + val adapter: RecyclerView.Adapter = DownloadChildAdapter( ArrayList(), @@ -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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e80a8fa5..629ab11a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -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) { 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 = - 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 + binding?.apply { + downloadUsedTxt.text = + getString(R.string.storage_size_format).format( + getString(R.string.used_storage), + formatShortFileSize(view.context, it) + ) + downloadUsed.setLayoutWidth(it) + downloadStorageAppbar.isVisible = it > 0 + } } observe(downloadsViewModel.downloadBytes) { - download_app_txt?.text = - getString(R.string.storage_size_format).format( - getString(R.string.app_storage), - formatShortFileSize(view.context, it) - ) - download_app?.setLayoutWidth(it) + binding?.apply { + downloadAppTxt.text = + getString(R.string.storage_size_format).format( + getString(R.string.app_storage), + formatShortFileSize(view.context, it) + ) + downloadApp.setLayoutWidth(it) + } } val adapter: RecyclerView.Adapter = @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 5cf6fc8e..99ce7c3b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -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(R.id.home_expanded_text)!! + 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 - title.text = item.name - val recycle = - bottomSheetDialogBuilder.findViewById(R.id.home_expanded_recycler)!! - val titleHolder = - bottomSheetDialogBuilder.findViewById(R.id.home_expanded_drag_down)!! + 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 { @@ -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,26 +182,27 @@ 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 -> - 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 - //bottomSheetDialogBuilder.dismissSafe(this) + 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 + //bottomSheetDialogBuilder.dismissSafe(this) + } + }.apply { + hasNext = expand.hasNext } - }.apply { - 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) { + fun validateChips(header: TvtypesChipsBinding?, validTypes: List) { 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) { + fun updateChips(header: TvtypesChipsBinding?, selectedTypes: List) { 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, validTypes: List, callback: (List) -> 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,91 +560,100 @@ 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 -> - when (data) { - is Resource.Success -> { - home_loading_shimmer?.stopShimmer() + binding?.apply { + when (data) { + is Resource.Success -> { + homeLoadingShimmer.stopShimmer() - val d = data.value - val mutableListOfResponse = mutableListOf() - listHomepageItems.clear() + val d = data.value + val mutableListOfResponse = mutableListOf() + listHomepageItems.clear() - (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList( - d.values.toMutableList(), - home_master_recycler - ) + (homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList( + d.values.toMutableList(), + homeMasterRecycler + ) - home_loading?.isVisible = false - home_loading_error?.isVisible = false - home_master_recycler?.isVisible = true - //home_loaded?.isVisible = true - if (toggleRandomButton) { - //Flatten list - d.values.forEach { dlist -> - mutableListOfResponse.addAll(dlist.list.list) + 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 } - listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url }) - home_random?.isVisible = listHomepageItems.isNotEmpty() - } else { - home_random?.isGone = true } - } - is Resource.Failure -> { - home_loading_shimmer?.stopShimmer() - result_error_text.text = data.errorString + is Resource.Failure -> { - home_reload_connectionerror.setOnClickListener(apiChangeClickListener) + homeLoadingShimmer.stopShimmer() - home_reload_connection_open_in_browser.setOnClickListener { view -> - val validAPIs = apis//.filter { api -> api.hasMainPage } + resultErrorText.text = data.errorString - 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) + 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 } - home_loading?.isVisible = false - home_loading_error?.isVisible = true - home_master_recycler?.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 - //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 + } } } } @@ -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,18 +726,22 @@ 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) { - if (dy > 0) { //check for scroll down - home_api_fab?.shrink() // hide - home_random?.shrink() - } else if (dy < -5) { - if (!isTvSettings()) { - home_api_fab?.extend() // show - home_random?.extend() + + binding?.apply { + 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) } }) @@ -718,18 +749,20 @@ class HomeFragment : Fragment() { // nice profile pic on homepage //home_profile_picture_holder?.isVisible = false // just in case - if (isTvSettings()) { - home_api_fab?.isVisible = false - if (isTrueTvSettings()) { - home_change_api_loading?.isVisible = true - home_change_api_loading?.isFocusable = true - home_change_api_loading?.isFocusableInTouchMode = true + binding?.apply { + 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 } - // home_bookmark_select?.isFocusable = true - // home_bookmark_select?.isFocusableInTouchMode = true - } else { - home_api_fab?.isVisible = true - home_change_api_loading?.isVisible = false } //TODO READD THIS /*for (syncApi in OAuth2Apis) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 94a1a526..715f1867 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -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 -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index d7c06c4e..a20cd5c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -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,20 +220,22 @@ 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 -> - if (isScrollingDown) { - sort_fab?.shrink() - } else { - sort_fab?.extend() - } - }) callback@{ searchClickCallback -> + binding?.viewpager?.setPageTransformer(LibraryScrollTransformer()) + binding?.viewpager?.adapter = + binding?.viewpager?.adapter ?: ViewpagerAdapter( + mutableListOf(), + { isScrollingDown: Boolean -> + if (isScrollingDown) { + binding?.sortFab?.shrink() + } else { + binding?.sortFab?.extend() + } + }) callback@{ searchClickCallback -> // To prevent future accidents debugAssert({ searchClickCallback.card !is SyncAPI.LibraryItem @@ -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 = - context?.let { LoadingPosterAdapter(it, 6 * 3) } - library_loading_overlay?.isVisible = true - library_loading_shimmer?.startShimmer() - empty_list_textview?.isVisible = false + binding?.apply { + gridview.numColumns = context?.getSpanCount() ?: 3 + gridview.adapter = + context?.let { LoadingPosterAdapter(it, 6 * 3) } + 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,65 +336,75 @@ class LibraryFragment : Fragment() { handler.removeCallbacks(startLoading) val pages = resource.value val showNotice = pages.all { it.items.isEmpty() } - empty_list_textview?.isVisible = showNotice - if (showNotice) { - if (libraryViewModel.availableApiNames.size > 1) { - empty_list_textview?.setText(R.string.empty_library_logged_in_message) - } else { - empty_list_textview?.setText(R.string.empty_library_no_accounts_message) + + + binding?.apply { + emptyListTextview.isVisible = showNotice + if (showNotice) { + if (libraryViewModel.availableApiNames.size > 1) { + emptyListTextview.setText(R.string.empty_library_logged_in_message) + } else { + 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 + ) + + // Only stop loading after 300ms to hide the fade effect the viewpager produces when updating + // Without this there would be a flashing effect: + // loading -> show old viewpager -> black screen -> show new viewpager + handler.postDelayed(stopLoading, 300) + + savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> + if (currentPos < 0) return@let + viewpager.setCurrentItem(currentPos, false) + // Using remove() sets the key to 0 instead of removing it + savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1) + } + + // Since the animation to scroll multiple items is so much its better to just hide + // the viewpager a bit while the fastest animation is running + fun hideViewpager(distance: Int) { + if (distance < 3) return + + val hideAnimation = AlphaAnimation(1f, 0f).apply { + duration = distance * 50L + fillAfter = true + } + val showAnimation = AlphaAnimation(0f, 1f).apply { + duration = distance * 50L + startOffset = distance * 100L + fillAfter = true + } + viewpager.startAnimation(hideAnimation) + viewpager.startAnimation(showAnimation) + } + + TabLayoutMediator( + libraryTabLayout, + viewpager, + ) { tab, position -> + tab.text = pages.getOrNull(position)?.title?.asStringNull(context) + tab.view.setOnClickListener { + val currentItem = + binding?.viewpager?.currentItem ?: return@setOnClickListener + val distance = abs(position - currentItem) + hideViewpager(distance) + } + }.attach() } - - (viewpager.adapter as? ViewpagerAdapter)?.pages = pages - // Using notifyItemRangeChanged keeps the animations when sorting - 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: - // loading -> show old viewpager -> black screen -> show new viewpager - handler.postDelayed(stopLoading, 300) - - savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> - if (currentPos < 0) return@let - viewpager?.setCurrentItem(currentPos, false) - // Using remove() sets the key to 0 instead of removing it - savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1) - } - - // Since the animation to scroll multiple items is so much its better to just hide - // the viewpager a bit while the fastest animation is running - fun hideViewpager(distance: Int) { - if (distance < 3) return - - val hideAnimation = AlphaAnimation(1f, 0f).apply { - duration = distance * 50L - fillAfter = true - } - val showAnimation = AlphaAnimation(0f, 1f).apply { - duration = distance * 50L - startOffset = distance * 100L - fillAfter = true - } - viewpager?.startAnimation(hideAnimation) - viewpager?.startAnimation(showAnimation) - } - - TabLayoutMediator( - library_tab_layout, - viewpager, - ) { tab, position -> - tab.text = pages.getOrNull(position)?.title?.asStringNull(context) - tab.view.setOnClickListener { - val currentItem = 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) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index 5f64880c..14d31356 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -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), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt index 2435f8be..05b05f44 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt @@ -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" } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 33a40386..441d6adc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -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, @@ -20,8 +18,7 @@ class ViewpagerAdapter( ) : RecyclerView.Adapter() { 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() + /** * 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,44 +41,46 @@ 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 = - this@PageViewHolder.itemView.context.getSpanCount() ?: 3 - - if (itemViewTest.page_recyclerview?.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( - page.items.toMutableList(), - itemViewTest.page_recyclerview, - clickCallback - ) + binding.pageRecyclerview.apply { + spanCount = + this@PageViewHolder.itemView.context.getSpanCount() ?: 3 + 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 + doOnAttach { + adapter = PageAdapter( + page.items.toMutableList(), + this, + clickCallback + ) + } + } else { + (adapter as? PageAdapter)?.updateList(page.items) + scrollToPosition(0) } - } else { - (itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items) - itemViewTest.page_recyclerview?.scrollToPosition(0) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> - val diff = scrollY - oldScrollY - if (diff == 0) return@setOnScrollChangeListener + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + 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() { - override fun onFling(velocityX: Int, velocityY: Int): Boolean { - scrollCallback.invoke(velocityY > 0) - return false + scrollCallback.invoke(diff > 0) + } + } else { + onFlingListener = object : OnFlingListener() { + override fun onFling(velocityX: Int, velocityY: Int): Boolean { + scrollCallback.invoke(velocityY > 0) + return false + } } } } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index ba57d2de..9e5ca6ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -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? = 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(androidx.appcompat.R.id.search_close_btn) + binding?.quickSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) //val searchMagIcon = - // quick_search?.findViewById(androidx.appcompat.R.id.search_mag_icon) + // binding.quickSearch.findViewById(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) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt index 92cecc37..7b415d78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt @@ -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() { +class ActorAdaptor : RecyclerView.Adapter() { data class ActorMetaData( var isInverted: Boolean, val actor: ActorData, @@ -24,7 +21,7 @@ class ActorAdaptor() : RecyclerView.Adapter() { 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() { 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,39 +80,43 @@ class ActorAdaptor() : RecyclerView.Adapter() { callback(position) } - actorImage.setImage(mainImg) + binding.apply { + actorImage.setImage(mainImg) - actorName.text = actor.actor.name - actor.role?.let { - actorExtra.context?.getString( - when (it) { - ActorRole.Main -> { - R.string.actor_main - } - ActorRole.Supporting -> { - R.string.actor_supporting - } - ActorRole.Background -> { - R.string.actor_background + actorName.text = actor.actor.name + actor.role?.let { + actorExtra.context?.getString( + when (it) { + ActorRole.Main -> { + R.string.actor_main + } + + ActorRole.Supporting -> { + R.string.actor_supporting + } + + ActorRole.Background -> { + R.string.actor_background + } } + )?.let { text -> + actorExtra.isVisible = true + actorExtra.text = text } - )?.let { text -> + } ?: actor.roleString?.let { actorExtra.isVisible = true - actorExtra.text = text + actorExtra.text = it + } ?: run { + actorExtra.isVisible = false } - } ?: actor.roleString?.let { - actorExtra.isVisible = true - actorExtra.text = it - } ?: run { - actorExtra.isVisible = false - } - if (actor.voiceActor == null) { - voiceActorImageHolder.isVisible = false - voiceActorName.isVisible = false - } else { - voiceActorName.text = actor.voiceActor.name - voiceActorImageHolder.isVisible = voiceActorImage.setImage(vaImage) + if (actor.voiceActor == null) { + voiceActorImageHolder.isVisible = false + voiceActorName.isVisible = false + } else { + voiceActorName.text = actor.voiceActor.name + voiceActorImageHolder.isVisible = voiceActorImage.setImage(vaImage) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 0932b001..216d95a4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -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), - hasDownloadSupport, - clickCallback, - downloadClickCallback - ) + 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,94 +190,81 @@ 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 + binding.apply { - 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 + 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 - episodeDownloadBar = - parentView.result_episode_progress_downloaded - episodeDownloadImage = parentView.result_episode_download - - 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 - } - - episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true - - if (card.rating != null) { - episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format) - ?.format(card.rating.toFloat() / 10f) - } else { - episodeRating?.text = "" - } - - episodeRating?.isGone = episodeRating?.text.isNullOrBlank() - - episodeDescript?.apply { - text = card.description.html() - isGone = text.isNullOrBlank() - setOnClickListener { - clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) - } - } - - if (!isTrueTv) { - episodePoster?.setOnClickListener { - clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) + 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 } - episodePoster?.setOnLongClickListener { - clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card)) - return@setOnLongClickListener true + episodePoster.isVisible = episodePoster.setImage(card.poster) == true + + if (card.rating != null) { + episodeRating.text = episodeRating.context?.getString(R.string.rated_format) + ?.format(card.rating.toFloat() / 10f) + } else { + episodeRating.text = "" + } + + episodeRating.isGone = episodeRating.text.isNullOrBlank() + + episodeDescript.apply { + text = card.description.html() + isGone = text.isNullOrBlank() + setOnClickListener { + clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) + } + } + + if (!isTrueTv) { + episodePoster.setOnClickListener { + clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) + } + + episodePoster.setOnLongClickListener { + clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card)) + return@setOnLongClickListener true + } } } - itemView.setOnClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) } @@ -276,8 +280,8 @@ class EpisodeAdapter( return@setOnLongClickListener true } - episodeDownloadImage?.isVisible = hasDownloadSupport - episodeDownloadBar?.isVisible = hasDownloadSupport + binding.resultEpisodeDownload.isVisible = hasDownloadSupport + binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport reattachDownloadButton() } @@ -285,9 +289,6 @@ class EpisodeAdapter( 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 +297,109 @@ class EpisodeAdapter( downloadButton.setUpButton( downloadInfo?.fileLength, downloadInfo?.totalBytes, - episodeDownloadBar ?: return, - episodeDownloadImage ?: return, + 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)) + } + + 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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt index ebd6a658..ca2934ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 5a3e28b4..68fc87e6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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>) { + open fun updateMovie(data: Resource>?) { 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>) { + open fun updateEpisodes(episodes: Resource>?) { 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,58 +807,56 @@ 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 -> { - result_resume_parent?.isVisible = true - val value = resume.value - value.progress?.let { progress -> - result_resume_series_title?.apply { - isVisible = !value.isMovie - text = - if (value.isMovie) null else activity?.getNameFull( - value.result.name, - value.result.episode, - value.result.season - ) - } - result_resume_series_progress_text.setText(progress.progressLeft) - result_resume_series_progress?.apply { - isVisible = true - this.max = progress.maxProgress - this.progress = progress.progress - } - result_resume_progress_holder?.isVisible = true - } ?: run { - result_resume_progress_holder?.isVisible = false - result_resume_series_progress?.isVisible = false - result_resume_series_title?.isVisible = false - result_resume_series_progress_text?.isVisible = false - } - - result_resume_series_button?.isVisible = !value.isMovie - result_resume_series_button_play?.isVisible = !value.isMovie - - val click = View.OnClickListener { - viewModel.handleAction( - activity, - EpisodeClickEvent( - storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER, - value.result - ) - ) - } - - result_resume_series_button?.setOnClickListener(click) - result_resume_series_button_play?.setOnClickListener(click) - } - is Some.None -> { - result_resume_parent?.isVisible = false - } + observeNullable(viewModel.resumeWatching) { resume -> + if (resume == null) { + result_resume_parent?.isVisible = false + return@observeNullable } + result_resume_parent?.isVisible = true + resume.progress?.let { progress -> + result_resume_series_title?.apply { + isVisible = !resume.isMovie + text = + 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?.apply { + isVisible = true + this.max = progress.maxProgress + this.progress = progress.progress + } + result_resume_progress_holder?.isVisible = true + } ?: run { + result_resume_progress_holder?.isVisible = false + result_resume_series_progress?.isVisible = false + result_resume_series_title?.isVisible = false + result_resume_series_progress_text?.isVisible = false + } + + 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, + resume.result + ) + ) + } + + result_resume_series_button?.setOnClickListener(click) + result_resume_series_button_play?.setOnClickListener(click) } - 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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 2f232995..571d4b0b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -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,73 +270,68 @@ 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 -> { - popupDialog?.dismissSafe(activity) - - popupDialog = activity?.let { act -> - val pop = popup.value - val options = pop.getOptions(act) - val title = pop.getTitle(act) - - act.showBottomDialogInstant( - options, title, { - popupDialog = null - pop.callback(null) - }, { - popupDialog = null - pop.callback(it) - } - ) - } - } - is Some.None -> { - popupDialog?.dismissSafe(activity) - popupDialog = null - } + observeNullable(viewModel.selectPopup) { popup -> + if (popup == null) { + popupDialog?.dismissSafe(activity) + popupDialog = null + return@observeNullable } + popupDialog?.dismissSafe(activity) + + popupDialog = activity?.let { act -> + val options = popup.getOptions(act) + val title = popup.getTitle(act) + + act.showBottomDialogInstant( + options, title, { + popupDialog = null + popup.callback(null) + }, { + popupDialog = null + popup.callback(it) + } + ) + } + + } observe(viewModel.loadedLinks) { load -> - when (load) { - is Some.Success -> { - if (loadingDialog?.isShowing != true) { - loadingDialog?.dismissSafe(activity) - loadingDialog = null - } - loadingDialog = loadingDialog ?: context?.let { ctx -> - val builder = - BottomSheetDialog(ctx) - builder.setContentView(R.layout.bottom_loading) - builder.setOnDismissListener { - loadingDialog = null - viewModel.cancelLinks() - } - //builder.setOnCancelListener { - // it?.dismiss() - //} - builder.setCanceledOnTouchOutside(true) - builder.show() - builder - } - } - is Some.None -> { - loadingDialog?.dismissSafe(activity) + if (load == null) { + loadingDialog?.dismissSafe(activity) + loadingDialog = null + return@observe + } + if (loadingDialog?.isShowing != true) { + loadingDialog?.dismissSafe(activity) + loadingDialog = null + } + loadingDialog = loadingDialog ?: context?.let { ctx -> + val builder = + BottomSheetDialog(ctx) + builder.setContentView(R.layout.bottom_loading) + builder.setOnDismissListener { loadingDialog = null + viewModel.cancelLinks() } + //builder.setOnCancelListener { + // it?.dismiss() + //} + builder.setCanceledOnTouchOutside(true) + builder.show() + builder } } - 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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 71ecb0e9..91354117 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -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>) { + override fun updateEpisodes(episodes: Resource>?) { super.updateEpisodes(episodes) - if (episodes is ResourceSome.Success && hasNoFocus()) { + if (episodes is Resource.Success && hasNoFocus()) { result_episodes?.requestFocus() } } - override fun updateMovie(data: ResourceSome>) { + override fun updateMovie(data: Resource>?) { 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 -> { - popupDialog?.dismissSafe(activity) + observeNullable(viewModel.selectPopup) { popup -> + if(popup == null) { + popupDialog?.dismissSafe(activity) + popupDialog = null + return@observeNullable + } - popupDialog = activity?.let { act -> - val pop = popup.value - val options = pop.getOptions(act) - val title = pop.getTitle(act) + popupDialog?.dismissSafe(activity) - act.showBottomDialogInstant( - options, title, { - popupDialog = null - pop.callback(null) - }, { - popupDialog = null - pop.callback(it) - } - ) + popupDialog = activity?.let { act -> + val options = popup.getOptions(act) + val title = popup.getTitle(act) + + act.showBottomDialogInstant( + options, title, { + popupDialog = null + popup.callback(null) + }, { + popupDialog = null + 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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 46a8c9f6..dbd1dd40 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -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 { 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?> = _page - private val _episodes: MutableLiveData>> = - MutableLiveData(ResourceSome.Loading()) - val episodes: LiveData>> = _episodes + private val _episodes: MutableLiveData>?> = + MutableLiveData(Resource.Loading()) + val episodes: LiveData>?> = _episodes - private val _movie: MutableLiveData>> = - MutableLiveData(ResourceSome.None) - val movie: LiveData>> = _movie + private val _movie: MutableLiveData>?> = + MutableLiveData(null) + val movie: LiveData>?> = _movie - private val _episodesCountText: MutableLiveData> = - MutableLiveData(Some.None) - val episodesCountText: LiveData> = _episodesCountText + private val _episodesCountText: MutableLiveData = + MutableLiveData(null) + val episodesCountText: LiveData = _episodesCountText private val _trailers: MutableLiveData> = MutableLiveData(mutableListOf()) @@ -384,16 +388,16 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(emptyList()) val recommendations: LiveData> = _recommendations - private val _selectedRange: MutableLiveData> = - MutableLiveData(Some.None) - val selectedRange: LiveData> = _selectedRange + private val _selectedRange: MutableLiveData = + MutableLiveData(null) + val selectedRange: LiveData = _selectedRange - private val _selectedSeason: MutableLiveData> = - MutableLiveData(Some.None) - val selectedSeason: LiveData> = _selectedSeason + private val _selectedSeason: MutableLiveData = + MutableLiveData(null) + val selectedSeason: LiveData = _selectedSeason - private val _selectedDubStatus: MutableLiveData> = MutableLiveData(Some.None) - val selectedDubStatus: LiveData> = _selectedDubStatus + private val _selectedDubStatus: MutableLiveData = MutableLiveData(null) + val selectedDubStatus: LiveData = _selectedDubStatus private val _selectedRangeIndex: MutableLiveData = MutableLiveData(-1) @@ -406,12 +410,12 @@ class ResultViewModel2 : ViewModel() { private val _selectedDubStatusIndex: MutableLiveData = MutableLiveData(-1) val selectedDubStatusIndex: LiveData = _selectedDubStatusIndex - private val _loadedLinks: MutableLiveData> = MutableLiveData(Some.None) - val loadedLinks: LiveData> = _loadedLinks + private val _loadedLinks: MutableLiveData = MutableLiveData(null) + val loadedLinks: LiveData = _loadedLinks - private val _resumeWatching: MutableLiveData> = - MutableLiveData(Some.None) - val resumeWatching: LiveData> = _resumeWatching + private val _resumeWatching: MutableLiveData = + MutableLiveData(null) + val resumeWatching: LiveData = _resumeWatching private val _episodeSynopsis: MutableLiveData = MutableLiveData(null) val episodeSynopsis: LiveData = _episodeSynopsis @@ -800,8 +804,8 @@ class ResultViewModel2 : ViewModel() { private val _watchStatus: MutableLiveData = MutableLiveData(WatchType.NONE) val watchStatus: LiveData get() = _watchStatus - private val _selectPopup: MutableLiveData> = MutableLiveData(Some.None) - val selectPopup: LiveData> get() = _selectPopup + private val _selectPopup: MutableLiveData = MutableLiveData(null) + val selectPopup: LiveData = _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, 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 = 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), - ) - ) + + if (isMovie) null else + txt( + R.string.episode_format, + size, + txt(if (size == 1) R.string.episode else R.string.episodes), + ) + ) _selectedSeasonIndex.postValue( @@ -1770,29 +1788,29 @@ class ResultViewModel2 : ViewModel() { ) _selectedSeason.postValue( - some( - if (isMovie || currentSeasons.size <= 1) null else - when (indexer.season) { - 0 -> txt(R.string.no_season) - else -> { - val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames - val seasonData = seasonNames.getSeason(indexer.season) - // If displaySeason is null then only show the name! - if (seasonData?.name != null && seasonData.displaySeason == null) { - txt(seasonData.name) - } else { - val suffix = seasonData?.name?.let { " $it" } ?: "" - txt( - R.string.season_format, - txt(R.string.season), - seasonData?.displaySeason ?: indexer.season, - suffix - ) - } + if (isMovie || currentSeasons.size <= 1) null else + when (indexer.season) { + 0 -> txt(R.string.no_season) + else -> { + val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames + val seasonData = seasonNames.getSeason(indexer.season) + + // If displaySeason is null then only show the name! + if (seasonData?.name != null && seasonData.displaySeason == null) { + txt(seasonData.name) + } else { + val suffix = seasonData?.name?.let { " $it" } ?: "" + txt( + R.string.season_format, + txt(R.string.season), + seasonData?.displaySeason ?: indexer.season, + suffix + ) } } - ) + } + ) _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 - } - ) + + 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) - ) + + 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> = 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" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt index 2e7ec529..bcf401ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt @@ -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 @@ -17,7 +16,9 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter Unit) : RecyclerView.Adapter?) { - setTextHtml(if (text is Some.Success) text.value else null) -} - -fun TextView?.setText(text: Some?) { - setText(if (text is Some.Success) text.value else null) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 649641c8..7fdd6e1d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -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 = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index b4a38216..e0f67d4a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -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? = activity?.let { - SearchAdapter( - ArrayList(), - search_autofit_results, - ) { callback -> - SearchHelper.handleSearchClickCallback(activity, callback) - } + binding?.apply { + val adapter: RecyclerView.Adapter? = + SearchAdapter( + ArrayList(), + 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(androidx.appcompat.R.id.search_close_btn) + binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) // val searchMagIcon = // main_search.findViewById(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() @@ -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,20 +516,23 @@ 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) - // Clear the query as to not make it request the same query every time the page is opened - arguments?.putString(SEARCH_QUERY, null) + // 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 + 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") //(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt index 8132301b..0a2ecb81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt @@ -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,22 +65,24 @@ 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 { - clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE)) - } - openButton.setOnClickListener { - clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN)) + homeHistoryRemove.setOnClickListener { + clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE)) + } + homeHistoryTab.setOnClickListener { + clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN)) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 3447ee32..69812f22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -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 = mutableMapOf() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt index e879f0df..1dc79dc0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt @@ -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, - val layout: Int = R.layout.account_single, private val clickCallback: (AccountClickCallback) -> Unit ) : RecyclerView.Adapter() { 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)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 1ef3cb55..acf715b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 40c996cc..453f93be 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 045ed92d..75ff8305 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -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,29 +139,26 @@ 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 - plugin_storage_appbar?.isVisible = true - if (value.total == 0) { - plugin_download?.setLayoutWidth(1) - plugin_disabled?.setLayoutWidth(0) - plugin_not_downloaded?.setLayoutWidth(0) - } else { - plugin_download?.setLayoutWidth(value.downloaded) - plugin_disabled?.setLayoutWidth(value.disabled) - plugin_not_downloaded?.setLayoutWidth(value.notDownloaded) - } - plugin_not_downloaded_txt.setText(value.notDownloadedText) - plugin_disabled_txt.setText(value.disabledText) - plugin_download_txt.setText(value.downloadedText) - } - is Some.None -> { - plugin_storage_appbar?.isVisible = false - } + return@observeNullable } + + plugin_storage_appbar?.isVisible = true + if (value.total == 0) { + plugin_download?.setLayoutWidth(1) + plugin_disabled?.setLayoutWidth(0) + plugin_not_downloaded?.setLayoutWidth(0) + } else { + plugin_download?.setLayoutWidth(value.downloaded) + plugin_disabled?.setLayoutWidth(value.disabled) + plugin_not_downloaded?.setLayoutWidth(value.notDownloaded) + } + plugin_not_downloaded_txt.setText(value.notDownloadedText) + plugin_disabled_txt.setText(value.disabledText) + plugin_download_txt.setText(value.downloadedText) } plugin_storage_appbar?.setOnClickListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index 63ed5357..866d167c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -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>() val repositories: LiveData> = _repositories - private val _pluginStats: MutableLiveData> = MutableLiveData(Some.None) - val pluginStats: LiveData> = _pluginStats + private val _pluginStats: MutableLiveData = MutableLiveData(null) + val pluginStats: LiveData = _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>(REPOSITORIES_KEY) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index d328d226..1a6215db 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -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,67 +105,69 @@ 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?.setOnQueryTextFocusChangeListener { _, hasFocus -> + if (!hasFocus) pluginViewModel.search(null) + } + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + pluginViewModel.search(query) + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + pluginViewModel.search(newText) + return true + } + }) + } // searchView?.onActionViewCollapsed = { // pluginViewModel.search(null) // } // Because onActionViewCollapsed doesn't wanna work we need this workaround :( - searchView?.setOnQueryTextFocusChangeListener { _, hasFocus -> - if (!hasFocus) pluginViewModel.search(null) - } - - searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - pluginViewModel.search(query) - return true - } - - override fun onQueryTextChange(newText: String?): Boolean { - pluginViewModel.search(newText) - return true - } - }) - 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() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt index b7d2fff6..138a31a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt @@ -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,39 +86,40 @@ 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) { + nextBtt.setText(R.string.setup_done) + } + prevBtt.isVisible = isSetup - if (!isSetup) { - next_btt.setText(R.string.setup_done) - } - prev_btt?.isVisible = isSetup + nextBtt.setOnClickListener { + // Continue setup + if (isSetup) + if ( + // If any available languages + apis.distinctBy { it.lang }.size > 1 + ) { + findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages) + } else { + findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media) + } + else + findNavController().navigate(R.id.navigation_home) + } - next_btt?.setOnClickListener { - // Continue setup - if (isSetup) - if ( - // If any available languages - apis.distinctBy { it.lang }.size > 1 - ) { - findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages) - } else { - findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media) - } - else - findNavController().navigate(R.id.navigation_home) - } - - prev_btt?.setOnClickListener { - findNavController().navigate(R.id.navigation_setup_language) + prevBtt.setOnClickListener { + findNavController().navigate(R.id.navigation_setup_language) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index 80db59ee..5c473b73 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -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 arrayAdapter = - ArrayAdapter(this, R.layout.sort_bottom_single_choice) + val ctx = context ?: return@normalSafeApiCall + val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) + val arrayAdapter = + ArrayAdapter(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) } } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 50fb37d6..98803818 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -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(this, R.layout.sort_bottom_single_choice) + ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) arrayAdapter.addAll(prefNames.toList()) - listview1?.adapter = arrayAdapter - listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE - listview1?.setItemChecked( - prefValues.indexOf(currentLayout), true - ) - - listview1?.setOnItemClickListener { _, _, position, _ -> - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[position]) - .apply() - activity?.recreate() - } - - acra_switch?.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) - } - - val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true) - acra_switch.isChecked = enableCrashReporting - crash_reporting_text.text = - getText( - if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on + binding?.apply { + listview1.adapter = arrayAdapter + listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE + listview1.setItemChecked( + prefValues.indexOf(currentLayout), true ) + listview1.setOnItemClickListener { _, _, position, _ -> + settingsManager.edit() + .putInt(getString(R.string.app_layout_key), prefValues[position]) + .apply() + activity?.recreate() + } + 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 + crashReportingText.text = getText(text) + } - next_btt?.setOnClickListener { - findNavController().navigate(R.id.navigation_home) - } + val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true) - prev_btt?.setOnClickListener { - findNavController().popBackStack() + acraSwitch.isChecked = enableCrashReporting + crashReportingText.text = + getText( + if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on + ) + + + nextBtt.setOnClickListener { + findNavController().navigate(R.id.navigation_home) + } + + prevBtt.setOnClickListener { + findNavController().popBackStack() + } } } } - - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt index 257ce5c1..6916cafe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt @@ -10,72 +10,85 @@ 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(this, R.layout.sort_bottom_single_choice) + ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) val names = enumValues().sorted().map { it.name } val selected = mutableListOf() arrayAdapter.addAll(names) - listview1?.let { - it.adapter = arrayAdapter - it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE + binding?.apply { + listview1.let { + it.adapter = arrayAdapter + it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE - it.setOnItemClickListener { _, _, _, _ -> - it.checkedItemPositions?.forEach { key, value -> - if (value) { - selected.add(key) - } else { - selected.remove(key) + it.setOnItemClickListener { _, _, _, _ -> + it.checkedItemPositions?.forEach { key, value -> + if (value) { + selected.add(key) + } else { + selected.remove(key) + } } + val prefValues = selected.mapNotNull { pos -> + val item = + it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null + val itemVal = TvType.valueOf(item) + itemVal.ordinal.toString() + }.toSet() + settingsManager.edit() + .putStringSet(getString(R.string.prefer_media_type_key), prefValues) + .apply() + + // Regenerate set homepage + removeKey(USER_SELECTED_HOMEPAGE_API) } - val prefValues = selected.mapNotNull { pos -> - val item = it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null - val itemVal = TvType.valueOf(item) - itemVal.ordinal.toString() - }.toSet() - settingsManager.edit() - .putStringSet(getString(R.string.prefer_media_type_key), prefValues) - .apply() - - // Regenerate set homepage - removeKey(USER_SELECTED_HOMEPAGE_API) } - } - next_btt?.setOnClickListener { - findNavController().navigate(R.id.navigation_setup_media_to_navigation_setup_layout) - } + nextBtt.setOnClickListener { + findNavController().navigate(R.id.navigation_setup_media_to_navigation_setup_layout) + } - prev_btt?.setOnClickListener { - findNavController().popBackStack() + prevBtt.setOnClickListener { + findNavController().popBackStack() + } } } } - - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt index 51abee90..8637fc99 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt @@ -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(this, R.layout.sort_bottom_single_choice) + ArrayAdapter(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() - 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() - } + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index 83d134cb..40bf8417 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -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( - 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)) - .build() + binding?.subtitleText?.apply { + setCues( + listOf( + Cue.Builder() + .setTextSize( + getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(), + Cue.TEXT_SIZE_TYPE_ABSOLUTE + ) + .setText(context.getString(R.string.subtitles_example_text)) + .build() + ) ) - ) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index ff0e0e82..8db205ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -238,7 +238,7 @@ class SubtitlesFragment : Fragment() { context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts" ) - context?.fixPaddingStatusbar(subs_root) + fixPaddingStatusbar(subs_root) state = getCurrentSavedStyle() context?.updateState() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 7d798204..4ed1aee6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -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 } diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml index ebcd3e9f..ac7c4abd 100644 --- a/app/src/main/res/layout/fragment_home_tv.xml +++ b/app/src/main/res/layout/fragment_home_tv.xml @@ -172,4 +172,15 @@ app:icon="@drawable/ic_baseline_filter_list_24" tools:ignore="ContentDescription" tools:visibility="visible" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_plugins.xml b/app/src/main/res/layout/fragment_plugins.xml index 40a0299c..c207b2c3 100644 --- a/app/src/main/res/layout/fragment_plugins.xml +++ b/app/src/main/res/layout/fragment_plugins.xml @@ -25,7 +25,7 @@ app:titleTextColor="?attr/textColor" tools:title="Overlord" /> - + - + diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 0a85a471..bb59d503 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -89,7 +89,7 @@ app:tint="?attr/textColor" /> - + diff --git a/app/src/main/res/layout/home_select_mainpage.xml b/app/src/main/res/layout/home_select_mainpage.xml index a4bb686c..1d2d1780 100644 --- a/app/src/main/res/layout/home_select_mainpage.xml +++ b/app/src/main/res/layout/home_select_mainpage.xml @@ -26,7 +26,7 @@ android:layout_gravity="bottom" android:layout_width="match_parent" android:layout_height="60dp"> - + - - + app:cardCornerRadius="@dimen/rounded_image_radius"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:orientation="vertical" + android:padding="10dp"> + android:id="@+id/episode_lin_holder" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + android:layout_width="126dp" + android:layout_height="72dp" + android:foreground="@drawable/outline_drawable"> + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:nextFocusRight="@id/result_episode_download" + android:scaleType="centerCrop" + tools:src="@drawable/example_poster" /> + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="center" + android:contentDescription="@string/play_episode" + android:src="@drawable/play_button" /> + android:id="@+id/episode_progress" + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="5dp" + android:layout_gravity="bottom" + android:layout_marginBottom="-1.5dp" + android:progressBackgroundTint="?attr/colorPrimary" + android:progressTint="?attr/colorPrimary" + tools:progress="50" /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginStart="15dp" + android:layout_marginEnd="50dp" + android:orientation="vertical"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + android:id="@+id/episode_filler" + style="@style/SmallBlackButton" + android:layout_gravity="start" + android:layout_marginEnd="10dp" + android:text="@string/filler" /> + android:id="@+id/episode_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textColor="?attr/textColor" + android:textStyle="bold" + tools:text="1. Jobless" /> + android:id="@+id/episode_rating" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?attr/grayTextColor" + tools:text="Rated: 8.8" /> + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="end" + android:layout_marginStart="-50dp"> + android:id="@+id/result_episode_progress_downloaded" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="40dp" + android:layout_height="40dp" + 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" /> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:background="?selectableItemBackgroundBorderless" + 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" /> + 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: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." /> \ No newline at end of file diff --git a/app/src/main/res/layout/tvtypes_chips_scroll.xml b/app/src/main/res/layout/tvtypes_chips_scroll.xml index 45b27dbc..66c7efda 100644 --- a/app/src/main/res/layout/tvtypes_chips_scroll.xml +++ b/app/src/main/res/layout/tvtypes_chips_scroll.xml @@ -6,5 +6,5 @@ android:requiresFadingEdge="horizontal" xmlns:android="http://schemas.android.com/apk/res/android"> - + \ No newline at end of file From 166a21f74eacd239be86aa1f4e60380edc571b12 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:28:49 +0200 Subject: [PATCH 02/31] more views -> viewbinding --- .../lagradost/cloudstream3/MainActivity.kt | 1 + .../cloudstream3/ui/EasterEggMonke.kt | 21 +-- .../cloudstream3/ui/WebviewFragment.kt | 33 ++-- .../ui/download/DownloadChildAdapter.kt | 57 +++---- .../ui/download/DownloadHeaderAdapter.kt | 60 +++---- .../ui/home/HomeParentItemAdapter.kt | 25 --- .../ui/library/LoadingPosterAdapter.kt | 8 - .../ui/player/FullScreenPlayer.kt | 6 - .../cloudstream3/ui/player/GeneratorPlayer.kt | 2 +- .../ui/player/PlayerEpisodeAdapter.kt | 158 ------------------ .../cloudstream3/ui/search/SearchAdaptor.kt | 31 ++-- .../cloudstream3/ui/search/SearchFragment.kt | 1 + .../ui/settings/SettingsAccount.kt | 104 ++++++------ .../ui/settings/SettingsFragment.kt | 77 +++++---- .../ui/settings/SettingsGeneral.kt | 27 +-- .../ui/settings/SettingsUpdates.kt | 16 +- .../settings/extensions/ExtensionsFragment.kt | 95 ++++++----- .../ui/settings/extensions/RepoAdapter.kt | 72 ++++++-- .../ui/settings/testing/TestFragment.kt | 120 ++++++------- 19 files changed, 412 insertions(+), 502 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerEpisodeAdapter.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d054f504..f409c10f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -743,6 +743,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) updateTv() + if (isTvSettings()) { setContentView(R.layout.activity_main_tv) } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt index 556ebd34..c7041776 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt @@ -16,14 +16,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatImageView import androidx.core.view.isVisible import com.lagradost.cloudstream3.R -import kotlinx.android.synthetic.main.activity_easter_egg_monke.* -import java.util.* +import com.lagradost.cloudstream3.databinding.ActivityEasterEggMonkeBinding class EasterEggMonke : AppCompatActivity() { + lateinit var binding : ActivityEasterEggMonkeBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_easter_egg_monke) + + binding = ActivityEasterEggMonkeBinding.inflate(layoutInflater) + setContentView(binding.root) val handler = Handler(mainLooper) lateinit var runnable: Runnable @@ -32,15 +34,14 @@ class EasterEggMonke : AppCompatActivity() { handler.postDelayed(runnable, 300) } handler.postDelayed(runnable, 1000) - } private fun shower() { - val containerW = frame.width - val containerH = frame.height - var starW: Float = monke.width.toFloat() - var starH: Float = monke.height.toFloat() + val containerW = binding.frame.width + val containerH = binding.frame.height + var starW: Float = binding.monke.width.toFloat() + var starH: Float = binding.monke.height.toFloat() val newStar = AppCompatImageView(this) val idx = (monkeys.size * Math.random()).toInt() @@ -48,7 +49,7 @@ class EasterEggMonke : AppCompatActivity() { newStar.isVisible = true newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT) - frame.addView(newStar) + binding.frame.addView(newStar) newStar.scaleX = Math.random().toFloat() * 1.5f + newStar.scaleX newStar.scaleY = newStar.scaleX @@ -70,7 +71,7 @@ class EasterEggMonke : AppCompatActivity() { set.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - frame.removeView(newStar) + binding.frame.removeView(newStar) } }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index 19e24f74..9ed58e2c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -12,20 +12,23 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.findNavController import com.lagradost.cloudstream3.MainActivity -import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.databinding.FragmentWebviewBinding import com.lagradost.cloudstream3.network.WebViewResolver import com.lagradost.cloudstream3.utils.AppUtils.loadRepository -import kotlinx.android.synthetic.main.fragment_webview.* + class WebviewFragment : Fragment() { + + var binding: FragmentWebviewBinding? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val url = arguments?.getString(WEBVIEW_URL) ?: "".also { findNavController().popBackStack() } - web_view.webViewClient = object : WebViewClient() { + binding?.webView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? @@ -40,24 +43,28 @@ class WebviewFragment : Fragment() { return super.shouldOverrideUrlLoading(view, request) } } + binding?.webView?.apply { + WebViewResolver.webViewUserAgent = settings.userAgentString - WebViewResolver.webViewUserAgent = web_view.settings.userAgentString - - web_view.addJavascriptInterface(RepoApi(activity), "RepoApi") - web_view.settings.javaScriptEnabled = true - web_view.settings.userAgentString = USER_AGENT - web_view.settings.domStorageEnabled = true + addJavascriptInterface(RepoApi(activity), "RepoApi") + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT + settings.domStorageEnabled = true // WebView.setWebContentsDebuggingEnabled(true) - web_view.loadUrl(url) + loadUrl(url) + } + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { + val localBinding = FragmentWebviewBinding.inflate(inflater, container, false) + binding = localBinding // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_webview, container, false) + return localBinding.root//inflater.inflate(R.layout.fragment_webview, container, false) } companion object { @@ -70,7 +77,7 @@ class WebviewFragment : Fragment() { private class RepoApi(val activity: FragmentActivity?) { @JavascriptInterface - fun installRepo(repoUrl: String) { + fun installRepo(repoUrl: String) { activity?.loadRepository(repoUrl) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt index a541171b..1a3e2db3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt @@ -3,18 +3,13 @@ package com.lagradost.cloudstream3.ui.download import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.cardview.widget.CardView -import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.VideoDownloadHelper -import kotlinx.android.synthetic.main.download_child_episode.view.* -import java.util.* +import java.util.Collections const val DOWNLOAD_ACTION_PLAY_FILE = 0 const val DOWNLOAD_ACTION_DELETE_FILE = 1 @@ -68,7 +63,7 @@ class DownloadChildAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return DownloadChildViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.download_child_episode, parent, false), + DownloadChildEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false), clickCallback ) } @@ -88,17 +83,17 @@ class DownloadChildAdapter( class DownloadChildViewHolder constructor( - itemView: View, + val binding: DownloadChildEpisodeBinding, private val clickCallback: (DownloadClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { + ) : RecyclerView.ViewHolder(binding.root), DownloadButtonViewHolder { override var downloadButton = EasyDownloadButton() - private val title: TextView = itemView.download_child_episode_text + /*private val title: TextView = itemView.download_child_episode_text private val extraInfo: TextView = itemView.download_child_episode_text_extra private val holder: CardView = itemView.download_child_episode_holder private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded - private val downloadImage: ImageView = itemView.download_child_episode_download + private val downloadImage: ImageView = itemView.download_child_episode_download*/ private var localCard: VisualDownloadChildCached? = null @@ -107,29 +102,35 @@ class DownloadChildAdapter( val d = card.data val posDur = getViewPos(d.id) - if (posDur != null) { - val visualPos = posDur.fixVisual() - progressBar.max = (visualPos.duration / 1000).toInt() - progressBar.progress = (visualPos.position / 1000).toInt() - progressBar.visibility = View.VISIBLE - } else { - progressBar.visibility = View.GONE + binding.downloadChildEpisodeProgress.apply { + if (posDur != null) { + val visualPos = posDur.fixVisual() + max = (visualPos.duration / 1000).toInt() + progress = (visualPos.position / 1000).toInt() + visibility = View.VISIBLE + } else { + visibility = View.GONE + } + } + + + binding.downloadChildEpisodeText.apply { + text = context.getNameFull(d.name, d.episode, d.season) + isSelected = true // is needed for text repeating } - title.text = title.context.getNameFull(d.name, d.episode, d.season) - title.isSelected = true // is needed for text repeating downloadButton.setUpButton( card.currentBytes, card.totalBytes, - progressBarDownload, - downloadImage, - extraInfo, + binding.downloadChildEpisodeProgressDownloaded, + binding.downloadChildEpisodeDownload, + binding.downloadChildEpisodeTextExtra, card.data, clickCallback ) - holder.setOnClickListener { + binding.downloadChildEpisodeHolder.setOnClickListener { clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d)) } } @@ -141,9 +142,9 @@ class DownloadChildAdapter( downloadButton.setUpButton( card.currentBytes, card.totalBytes, - progressBarDownload, - downloadImage, - extraInfo, + binding.downloadChildEpisodeProgressDownloaded, + binding.downloadChildEpisodeDownload, + binding.downloadChildEpisodeTextExtra, card.data, clickCallback ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt index 29bb303a..1634009b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt @@ -5,16 +5,12 @@ import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.cardview.widget.CardView -import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper -import kotlinx.android.synthetic.main.download_header_episode.view.* import java.util.* data class VisualDownloadHeaderCached( @@ -66,7 +62,7 @@ class DownloadHeaderAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return DownloadHeaderViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.download_header_episode, parent, false), + DownloadHeaderEpisodeBinding.inflate(LayoutInflater.from(parent.context),parent,false), clickCallback, movieClickCallback ) @@ -87,20 +83,20 @@ class DownloadHeaderAdapter( class DownloadHeaderViewHolder constructor( - itemView: View, + val binding: DownloadHeaderEpisodeBinding, private val clickCallback: (DownloadHeaderClickEvent) -> Unit, private val movieClickCallback: (DownloadClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { + ) : RecyclerView.ViewHolder(binding.root), DownloadButtonViewHolder { override var downloadButton = EasyDownloadButton() - private val poster: ImageView? = itemView.download_header_poster + /*private val poster: ImageView? = itemView.download_header_poster private val title: TextView = itemView.download_header_title private val extraInfo: TextView = itemView.download_header_info private val holder: CardView = itemView.episode_holder private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded private val downloadImage: ImageView = itemView.download_header_episode_download - private val normalImage: ImageView = itemView.download_header_goto_child + private val normalImage: ImageView = itemView.download_header_goto_child*/ private var localCard: VisualDownloadHeaderCached? = null @SuppressLint("SetTextI18n") @@ -108,19 +104,24 @@ class DownloadHeaderAdapter( localCard = card val d = card.data - poster?.setImage(d.poster) - poster?.setOnClickListener { - clickCallback.invoke(DownloadHeaderClickEvent(1, d)) + binding.downloadHeaderPoster.apply { + setImage(d.poster) + setOnClickListener { + clickCallback.invoke(DownloadHeaderClickEvent(1, d)) + } } - title.text = d.name + binding.apply { + + binding.downloadHeaderTitle.text = d.name val mbString = formatShortFileSize(itemView.context, card.totalBytes) //val isMovie = d.type.isMovieType() if (card.child != null) { - downloadBar.visibility = View.VISIBLE - downloadImage.visibility = View.VISIBLE - normalImage.visibility = View.GONE + downloadHeaderProgressDownloaded.visibility = View.VISIBLE + + downloadHeaderEpisodeDownload.visibility = View.VISIBLE + binding.downloadHeaderGotoChild.visibility = View.GONE /*setUpButton( card.currentBytes, card.totalBytes, @@ -131,34 +132,35 @@ class DownloadHeaderAdapter( movieClickCallback )*/ - holder.setOnClickListener { + episodeHolder.setOnClickListener { movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child)) } } else { - downloadBar.visibility = View.GONE - downloadImage.visibility = View.GONE - normalImage.visibility = View.VISIBLE + downloadHeaderProgressDownloaded.visibility = View.GONE + downloadHeaderEpisodeDownload.visibility = View.GONE + binding.downloadHeaderGotoChild.visibility = View.VISIBLE try { - extraInfo.text = - extraInfo.context.getString(R.string.extra_info_format).format( + downloadHeaderInfo.text = + downloadHeaderInfo.context.getString(R.string.extra_info_format).format( card.totalDownloads, - if (card.totalDownloads == 1) extraInfo.context.getString(R.string.episode) else extraInfo.context.getString( + if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(R.string.episode) else downloadHeaderInfo.context.getString( R.string.episodes ), mbString ) } catch (t : Throwable) { // you probably formatted incorrectly - extraInfo.text = "Error" + downloadHeaderInfo.text = "Error" logError(t) } - holder.setOnClickListener { + episodeHolder.setOnClickListener { clickCallback.invoke(DownloadHeaderClickEvent(0, d)) } } + } } override fun reattachDownloadButton() { @@ -168,9 +170,9 @@ class DownloadHeaderAdapter( downloadButton.setUpButton( card.currentBytes, card.totalBytes, - downloadBar, - downloadImage, - extraInfo, + binding.downloadHeaderProgressDownloaded, + binding.downloadHeaderEpisodeDownload, + binding.downloadHeaderInfo, card.child, movieClickCallback ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 58c6dbe0..7ce9e67d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -3,49 +3,24 @@ package com.lagradost.cloudstream3.ui.home import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.LinearLayout import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView -import androidx.transition.ChangeBounds -import androidx.transition.TransitionManager -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.chip.Chip -import com.google.android.material.chip.ChipDrawable -import com.lagradost.cloudstream3.APIHolder.getId -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.result.LinearListLayout -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.setLinearListLayout -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable -import com.lagradost.cloudstream3.utils.AppUtils.loadResult -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog -import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView import kotlinx.android.synthetic.main.activity_main_tv.* import kotlinx.android.synthetic.main.activity_main_tv.view.* import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.view.* import kotlinx.android.synthetic.main.fragment_home_head_tv.* import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager import kotlinx.android.synthetic.main.homepage_parent.view.* class LoadClickCallback( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt index a637133b..160fbe2b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt @@ -5,15 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter -import android.widget.FrameLayout -import android.widget.LinearLayout -import android.widget.ListPopupWindow.MATCH_PARENT -import android.widget.RelativeLayout import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.loading_poster_dynamic.view.* -import kotlin.math.roundToInt -import kotlin.math.sqrt class LoadingPosterAdapter(context: Context, private val itemCount: Int) : BaseAdapter() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9ff1c52d..37fb0373 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -97,12 +97,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { protected var isShowing = false protected var isLocked = false - //private var episodes: List = listOf() - protected fun setEpisodes(ep: List) { - //hasEpisodes = ep.size > 1 // if has 2 episodes or more because you dont want to switch to your current episode - //(player_episode_list?.adapter as? PlayerEpisodeAdapter?)?.updateList(ep) - } - protected var hasEpisodes = false private set //protected val hasEpisodes diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index fd29d998..4f29468c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -163,7 +163,7 @@ class GeneratorPlayer : FullScreenPlayer() { currentSelectedLink = link currentMeta = viewModel.getMeta() nextMeta = viewModel.getNextMeta() - setEpisodes(viewModel.getAllMeta() ?: emptyList()) + // setEpisodes(viewModel.getAllMeta() ?: emptyList()) isActive = true setPlayerDimen(null) setTitle() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerEpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerEpisodeAdapter.kt deleted file mode 100644 index cfe27a30..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerEpisodeAdapter.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.lagradost.cloudstream3.ui.player - -import android.annotation.SuppressLint -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.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.button.MaterialButton -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.getDisplayPosition -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.utils.AppUtils.html -import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.player_episodes_large.view.episode_holder_large -import kotlinx.android.synthetic.main.player_episodes_large.view.episode_progress -import kotlinx.android.synthetic.main.player_episodes_small.view.episode_holder -import kotlinx.android.synthetic.main.result_episode_large.view.* - - -data class PlayerEpisodeClickEvent(val action: Int, val data: Any) - -class PlayerEpisodeAdapter( - private val items: MutableList = mutableListOf(), - private val clickCallback: (PlayerEpisodeClickEvent) -> Unit, -) : RecyclerView.Adapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return PlayerEpisodeCardViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.player_episodes, parent, false), - clickCallback, - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - println("HOLDER $holder $position") - - when (holder) { - is PlayerEpisodeCardViewHolder -> { - holder.bind(items[position]) - } - } - } - - override fun getItemCount(): Int { - return items.size - } - - fun updateList(newList: List) { - println("Updated list $newList") - val diffResult = DiffUtil.calculateDiff(EpisodeDiffCallback(this.items, newList)) - items.clear() - items.addAll(newList) - - diffResult.dispatchUpdatesTo(this) - } - - class PlayerEpisodeCardViewHolder - constructor( - itemView: View, - private val clickCallback: (PlayerEpisodeClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView) { - @SuppressLint("SetTextI18n") - fun bind(card: Any) { - if (card is ResultEpisode) { - val (parentView, otherView) = if (card.poster == null) { - itemView.episode_holder to itemView.episode_holder_large - } else { - itemView.episode_holder_large to itemView.episode_holder - } - - 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 - - parentView.isVisible = true - otherView.isVisible = false - - - episodeText?.apply { - val name = - if (card.name == null) "${context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}" - - text = name - isSelected = true - } - - episodeFiller?.isVisible = card.isFiller == true - - val displayPos = card.getDisplayPosition() - episodeProgress?.max = (card.duration / 1000).toInt() - episodeProgress?.progress = (displayPos / 1000).toInt() - episodeProgress?.isVisible = displayPos > 0L - episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true - - if (card.rating != null) { - episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format) - ?.format(card.rating.toFloat() / 10f) - } else { - episodeRating?.text = "" - } - - episodeRating?.isGone = episodeRating?.text.isNullOrBlank() - - episodeDescript?.apply { - text = card.description.html() - isGone = text.isNullOrBlank() - //setOnClickListener { - // clickCallback.invoke(PlayerEpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) - //} - } - - parentView.setOnClickListener { - clickCallback.invoke(PlayerEpisodeClickEvent(0, card)) - } - - if (isTrueTvSettings()) { - parentView.isFocusable = true - parentView.isFocusableInTouchMode = true - parentView.touchscreenBlocksFocus = false - } - } - } - } -} - -class EpisodeDiffCallback( - private val oldList: List, - private val newList: List -) : - DiffUtil.Callback() { - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - val a = oldList[oldItemPosition] - val b = newList[newItemPosition] - return if (a is ResultEpisode && b is ResultEpisode) { - a.id == b.id - } else { - a == b - } - } - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition] == newList[newItemPosition] -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 7fdd6e1d..233614dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -4,16 +4,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.ImageView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.R +import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.databinding.SearchResultGridBinding +import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding import com.lagradost.cloudstream3.ui.AutofitRecyclerView -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.search_result_compact.view.* import kotlin.math.roundToInt /** Click */ @@ -39,10 +38,23 @@ class SearchAdapter( var hasNext: Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + + val layout = - if (parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid + if (parent.context.IsBottomLayout()) SearchResultGridExpandedBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) else SearchResultGridBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) //R.layout.search_result_grid_expanded else R.layout.search_result_grid + + + return CardViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false), + layout, clickCallback, resView ) @@ -73,12 +85,11 @@ class SearchAdapter( class CardViewHolder constructor( - itemView: View, + val binding: ViewBinding, private val clickCallback: (SearchClickCallback) -> Unit, resView: AutofitRecyclerView ) : - RecyclerView.ViewHolder(itemView) { - private val cardView: ImageView = itemView.imageView + RecyclerView.ViewHolder(binding.root) { private val compactView = false//itemView.context.getGridIsCompact() private val coverHeight: Int = @@ -86,7 +97,7 @@ class SearchAdapter( fun bind(card: SearchResponse, position: Int) { if (!compactView) { - cardView.apply { + binding.root.apply { layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, coverHeight diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index e0f67d4a..a11dab25 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -104,6 +104,7 @@ class SearchFragment : Fragment() { val layout = if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search val root = inflater.inflate(layout, container, false) + // TODO TRYCATCH binding = FragmentSearchBinding.bind(root) return root diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index acf715b3..a0166409 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -15,6 +15,9 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.AccountManagmentBinding +import com.lagradost.cloudstream3.databinding.AccountSwitchBinding +import com.lagradost.cloudstream3.databinding.AddAccountInputBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi @@ -31,9 +34,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.account_managment.* -import kotlinx.android.synthetic.main.account_switch.* -import kotlinx.android.synthetic.main.add_account_input.* class SettingsAccount : PreferenceFragmentCompat() { companion object { @@ -43,15 +43,18 @@ class SettingsAccount : PreferenceFragmentCompat() { api: AccountManager, info: AuthAPI.LoginInfo ) { + if (activity == null) return + val binding: AccountManagmentBinding = + AccountManagmentBinding.inflate(activity.layoutInflater, null, false) val builder = - AlertDialog.Builder(activity ?: return, R.style.AlertDialogCustom) - .setView(R.layout.account_managment) + AlertDialog.Builder(activity, R.style.AlertDialogCustom) + .setView(binding.root) val dialog = builder.show() - dialog.account_main_profile_picture_holder?.isVisible = - dialog.account_main_profile_picture?.setImage(info.profilePicture) == true + binding.accountMainProfilePictureHolder.isVisible = + binding.accountMainProfilePicture.setImage(info.profilePicture) - dialog.account_logout?.setOnClickListener { + binding.accountLogout.setOnClickListener { api.logOut() dialog.dismissSafe(activity) } @@ -60,26 +63,28 @@ class SettingsAccount : PreferenceFragmentCompat() { dialog.findViewById(R.id.account_name)?.text = it } - dialog.account_site?.text = api.name - dialog.account_switch_account?.setOnClickListener { + binding.accountSite.text = api.name + binding.accountSwitchAccount.setOnClickListener { dialog.dismissSafe(activity) showAccountSwitch(activity, api) } if (isTvSettings()) { - dialog.account_switch_account?.requestFocus() + binding.accountSwitchAccount.requestFocus() } } - fun showAccountSwitch(activity: FragmentActivity, api: AccountManager) { + private fun showAccountSwitch(activity: FragmentActivity, api: AccountManager) { val accounts = api.getAccounts() ?: return + val binding: AccountSwitchBinding = + AccountSwitchBinding.inflate(activity.layoutInflater, null, false) val builder = AlertDialog.Builder(activity, R.style.AlertDialogCustom) - .setView(R.layout.account_switch) + .setView(binding.root) val dialog = builder.show() - dialog.account_add?.setOnClickListener { + binding.accountAdd.setOnClickListener { addAccount(activity, api) dialog?.dismissSafe(activity) } @@ -111,17 +116,21 @@ class SettingsAccount : PreferenceFragmentCompat() { is OAuth2API -> { api.authenticate(activity) } + is InAppAuthAPI -> { + if (activity == null) return + val binding: AddAccountInputBinding = + AddAccountInputBinding.inflate(activity.layoutInflater, null, false) val builder = - AlertDialog.Builder(activity ?: return, R.style.AlertDialogCustom) - .setView(R.layout.add_account_input) + AlertDialog.Builder(activity, R.style.AlertDialogCustom) + .setView(binding.root) val dialog = builder.show() - val visibilityMap = mapOf( - dialog.login_email_input to api.requiresEmail, - dialog.login_password_input to api.requiresPassword, - dialog.login_server_input to api.requiresServer, - dialog.login_username_input to api.requiresUsername + val visibilityMap = listOf( + binding.loginEmailInput to api.requiresEmail, + binding.loginPasswordInput to api.requiresPassword, + binding.loginServerInput to api.requiresServer, + binding.loginUsernameInput to api.requiresUsername ) if (isTvSettings()) { @@ -145,12 +154,12 @@ class SettingsAccount : PreferenceFragmentCompat() { } } - dialog.login_email_input?.isVisible = api.requiresEmail - dialog.login_password_input?.isVisible = api.requiresPassword - dialog.login_server_input?.isVisible = api.requiresServer - dialog.login_username_input?.isVisible = api.requiresUsername - dialog.create_account?.isGone = api.createAccountUrl.isNullOrBlank() - dialog.create_account?.setOnClickListener { + binding.loginEmailInput.isVisible = api.requiresEmail + binding.loginPasswordInput.isVisible = api.requiresPassword + binding.loginServerInput.isVisible = api.requiresServer + binding.loginUsernameInput.isVisible = api.requiresUsername + binding.createAccount.isGone = api.createAccountUrl.isNullOrBlank() + binding.createAccount.setOnClickListener { openBrowser( api.createAccountUrl ?: return@setOnClickListener, activity @@ -159,43 +168,43 @@ class SettingsAccount : PreferenceFragmentCompat() { } val displayedItems = listOf( - dialog.login_username_input, - dialog.login_email_input, - dialog.login_server_input, - dialog.login_password_input + binding.loginUsernameInput, + binding.loginEmailInput, + binding.loginServerInput, + binding.loginPasswordInput ).filter { it.isVisible } displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous -> - item?.id?.let { previous?.nextFocusDownId = it } - previous?.id?.let { item?.nextFocusUpId = it } + item.id.let { previous?.nextFocusDownId = it } + previous?.id?.let { item.nextFocusUpId = it } item } displayedItems.firstOrNull()?.let { - dialog.create_account?.nextFocusDownId = it.id - it.nextFocusUpId = dialog.create_account.id + binding.createAccount.nextFocusDownId = it.id + it.nextFocusUpId = binding.createAccount.id } - dialog.apply_btt?.id?.let { + binding.applyBtt.id.let { displayedItems.lastOrNull()?.nextFocusDownId = it } - dialog.text1?.text = api.name + binding.text1.text = api.name if (api.storesPasswordInPlainText) { api.getLatestLoginData()?.let { data -> - dialog.login_email_input?.setText(data.email ?: "") - dialog.login_server_input?.setText(data.server ?: "") - dialog.login_username_input?.setText(data.username ?: "") - dialog.login_password_input?.setText(data.password ?: "") + binding.loginEmailInput.setText(data.email ?: "") + binding.loginServerInput.setText(data.server ?: "") + binding.loginUsernameInput.setText(data.username ?: "") + binding.loginPasswordInput.setText(data.password ?: "") } } - dialog.apply_btt?.setOnClickListener { + binding.applyBtt.setOnClickListener { val loginData = InAppAuthAPI.LoginData( - username = if (api.requiresUsername) dialog.login_username_input?.text?.toString() else null, - password = if (api.requiresPassword) dialog.login_password_input?.text?.toString() else null, - email = if (api.requiresEmail) dialog.login_email_input?.text?.toString() else null, - server = if (api.requiresServer) dialog.login_server_input?.text?.toString() else null, + username = if (api.requiresUsername) binding.loginUsernameInput.text?.toString() else null, + password = if (api.requiresPassword) binding.loginPasswordInput.text?.toString() else null, + email = if (api.requiresEmail) binding.loginEmailInput.text?.toString() else null, + server = if (api.requiresServer) binding.loginServerInput.text?.toString() else null, ) ioSafe { val isSuccessful = try { @@ -220,10 +229,11 @@ class SettingsAccount : PreferenceFragmentCompat() { } dialog.dismissSafe(activity) } - dialog.cancel_btt?.setOnClickListener { + binding.cancelBtt.setOnClickListener { dialog.dismissSafe(activity) } } + else -> { throw NotImplementedError("You are trying to add an account that has an unknown login method") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 453f93be..85afc048 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -14,7 +14,9 @@ import androidx.fragment.app.Fragment import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager +import com.google.android.material.appbar.MaterialToolbar import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.MainSettingsBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.ui.home.HomeFragment @@ -22,16 +24,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.main_settings.* -import kotlinx.android.synthetic.main.standard_toolbar.* import java.io.File class SettingsFragment : Fragment() { companion object { var beneneCount = 0 - private var isTv : Boolean = false - private var isTrueTv : Boolean = false + private var isTv: Boolean = false + private var isTrueTv: Boolean = false fun PreferenceFragmentCompat?.getPref(id: Int): Preference? { if (this == null) return null @@ -55,26 +55,30 @@ class SettingsFragment : Fragment() { fun Fragment?.setUpToolbar(title: String) { if (this == null) return - settings_toolbar?.apply { + val settingsToolbar = view?.findViewById(R.id.settings_toolbar) ?: return + + settingsToolbar.apply { setTitle(title) setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { activity?.onBackPressed() } } - fixPaddingStatusbar(settings_toolbar) + fixPaddingStatusbar(settingsToolbar) } fun Fragment?.setUpToolbar(@StringRes title: Int) { if (this == null) return - settings_toolbar?.apply { + val settingsToolbar = view?.findViewById(R.id.settings_toolbar) ?: return + + settingsToolbar.apply { setTitle(title) setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { activity?.onBackPressed() } } - fixPaddingStatusbar(settings_toolbar) + fixPaddingStatusbar(settingsToolbar) } fun getFolderSize(dir: File): Long { @@ -139,12 +143,21 @@ class SettingsFragment : Fragment() { } } + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + + var binding: MainSettingsBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.main_settings, container, false) + ): View { + val localBinding = MainSettingsBinding.inflate(inflater, container, false) + binding = localBinding + return localBinding.root + //return inflater.inflate(R.layout.main_settings, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -157,36 +170,34 @@ class SettingsFragment : Fragment() { for (syncApi in accountManagers) { val login = syncApi.loginInfo() val pic = login?.profilePicture ?: continue - if (settings_profile_pic?.setImage( + if (binding?.settingsProfilePic?.setImage( pic, errorImageDrawable = HomeFragment.errorProfilePic ) == true ) { - settings_profile_text?.text = login.name - settings_profile?.isVisible = true + binding?.settingsProfileText?.text = login.name + binding?.settingsProfile?.isVisible = true break } } - - listOf( - Pair(settings_general, R.id.action_navigation_settings_to_navigation_settings_general), - Pair(settings_player, R.id.action_navigation_settings_to_navigation_settings_player), - Pair(settings_credits, R.id.action_navigation_settings_to_navigation_settings_account), - Pair(settings_ui, R.id.action_navigation_settings_to_navigation_settings_ui), - Pair(settings_providers, R.id.action_navigation_settings_to_navigation_settings_providers), - Pair(settings_updates, R.id.action_navigation_settings_to_navigation_settings_updates), - Pair( - settings_extensions, - R.id.action_navigation_settings_to_navigation_settings_extensions - ), - ).forEach { (view, navigationId) -> - view?.apply { - setOnClickListener { - navigate(navigationId) - } - if (isTrueTv) { - isFocusable = true - isFocusableInTouchMode = true + binding?.apply { + listOf( + settingsGeneral to R.id.action_navigation_settings_to_navigation_settings_general, + settingsPlayer to R.id.action_navigation_settings_to_navigation_settings_player, + settingsCredits to R.id.action_navigation_settings_to_navigation_settings_account, + settingsUi to R.id.action_navigation_settings_to_navigation_settings_ui, + settingsProviders to R.id.action_navigation_settings_to_navigation_settings_providers, + settingsUpdates to R.id.action_navigation_settings_to_navigation_settings_updates, + settingsExtensions to R.id.action_navigation_settings_to_navigation_settings_extensions, + ).forEach { (view, navigationId) -> + view.apply { + setOnClickListener { + navigate(navigationId) + } + if (isTrueTv) { + isFocusable = true + isFocusableInTouchMode = true + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index ee262eec..ef194b57 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -22,6 +22,8 @@ import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding +import com.lagradost.cloudstream3.databinding.AddSiteInputBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.network.initClient @@ -38,8 +40,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath -import kotlinx.android.synthetic.main.add_remove_sites.* -import kotlinx.android.synthetic.main.add_site_input.* import java.io.File fun getCurrentLocale(context: Context): String { @@ -197,18 +197,20 @@ class SettingsGeneral : PreferenceFragmentCompat() { {}) { selection -> val provider = providers.getOrNull(selection) ?: return@showDialog + val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(layoutInflater,null,false) + val builder = AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom) - .setView(R.layout.add_site_input) + .setView(binding.root) val dialog = builder.create() dialog.show() - dialog.text2?.text = provider.name - dialog.apply_btt?.setOnClickListener { - val name = dialog.site_name_input?.text?.toString() - val url = dialog.site_url_input?.text?.toString() - val lang = dialog.site_lang_input?.text?.toString() + binding.text2.text = provider.name + binding.applyBtt.setOnClickListener { + val name = binding.siteNameInput.text?.toString() + val url = binding.siteUrlInput.text?.toString() + val lang = binding.siteLangInput.text?.toString() val realLang = if (lang.isNullOrBlank()) provider.lang else lang if (url.isNullOrBlank() || name.isNullOrBlank() || realLang.length != 2) { showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) @@ -222,7 +224,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { dialog.dismissSafe(activity) } - dialog.cancel_btt?.setOnClickListener { + binding.cancelBtt.setOnClickListener { dialog.dismissSafe(activity) } } @@ -242,18 +244,19 @@ class SettingsGeneral : PreferenceFragmentCompat() { } fun showAddOrDelete() { + val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(layoutInflater,null,false) val builder = AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) - .setView(R.layout.add_remove_sites) + .setView(binding.root) val dialog = builder.create() dialog.show() - dialog.add_site?.setOnClickListener { + binding.addSite.setOnClickListener { showAdd() dialog.dismissSafe(activity) } - dialog.remove_site?.setOnClickListener { + binding.removeSite.setOnClickListener { showDelete() dialog.dismissSafe(activity) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index f9ac3fee..4208f965 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -13,6 +13,7 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.LogcatBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom @@ -25,7 +26,6 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.VideoDownloadManager -import kotlinx.android.synthetic.main.logcat.* import okhttp3.internal.closeQuietly import java.io.BufferedReader import java.io.InputStreamReader @@ -60,7 +60,9 @@ class SettingsUpdates : PreferenceFragmentCompat() { getPref(R.string.show_logcat_key)?.setOnPreferenceClickListener { pref -> val builder = AlertDialog.Builder(pref.context, R.style.AlertDialogCustom) - .setView(R.layout.logcat) + + val binding = LogcatBinding.inflate(layoutInflater,null,false ) + builder.setView(binding.root) val dialog = builder.create() dialog.show() @@ -81,9 +83,9 @@ class SettingsUpdates : PreferenceFragmentCompat() { } val text = log.toString() - dialog.text1?.text = text + binding.text1.text = text - dialog.copy_btt?.setOnClickListener { + binding.copyBtt.setOnClickListener { // Can crash on too much text try { val serviceClipboard = @@ -96,11 +98,11 @@ class SettingsUpdates : PreferenceFragmentCompat() { showToast(activity, R.string.clipboard_too_large) } } - dialog.clear_btt?.setOnClickListener { + binding.clearBtt.setOnClickListener { Runtime.getRuntime().exec("logcat -c") dialog.dismissSafe(activity) } - dialog.save_btt?.setOnClickListener { + binding.saveBtt.setOnClickListener { var fileStream: OutputStream? = null try { fileStream = @@ -119,7 +121,7 @@ class SettingsUpdates : PreferenceFragmentCompat() { dialog.dismissSafe(activity) } } - dialog.close_btt?.setOnClickListener { + binding.closeBtt.setOnClickListener { dialog.dismissSafe(activity) } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 75ff8305..711026c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -18,6 +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.databinding.AddRepoInputBinding +import com.lagradost.cloudstream3.databinding.FragmentExtensionsBinding import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.plugins.RepositoryManager @@ -30,16 +32,22 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.widget.LinearRecycleViewLayoutManager -import kotlinx.android.synthetic.main.add_repo_input.* -import kotlinx.android.synthetic.main.fragment_extensions.* class ExtensionsFragment : Fragment() { + var binding: FragmentExtensionsBinding? = null + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_extensions, container, false) + ): View { + val localBinding = FragmentExtensionsBinding.inflate(inflater, container, false) + binding = localBinding + return localBinding.root//inflater.inflate(R.layout.fragment_extensions, container, false) } private fun View.setLayoutWidth(weight: Int) { @@ -74,7 +82,7 @@ class ExtensionsFragment : Fragment() { setUpToolbar(R.string.extensions) - repo_recycler_view?.adapter = RepoAdapter(false, { + binding?.repoRecyclerView?.adapter = RepoAdapter(false, { findNavController().navigate( R.id.navigation_settings_extensions_to_navigation_settings_plugins, PluginsFragment.newInstance( @@ -113,12 +121,12 @@ class ExtensionsFragment : Fragment() { }) observe(extensionViewModel.repositories) { - repo_recycler_view?.isVisible = it.isNotEmpty() - blank_repo_screen?.isVisible = it.isEmpty() - (repo_recycler_view?.adapter as? RepoAdapter)?.updateList(it) + binding?.repoRecyclerView?.isVisible = it.isNotEmpty() + binding?.blankRepoScreen?.isVisible = it.isEmpty() + (binding?.repoRecyclerView?.adapter as? RepoAdapter)?.updateList(it) } - repo_recycler_view?.apply { + binding?.repoRecyclerView?.apply { context?.let { ctx -> layoutManager = LinearRecycleViewLayoutManager(ctx, nextFocusUpId, nextFocusDownId) } @@ -140,28 +148,30 @@ class ExtensionsFragment : Fragment() { // } observeNullable(extensionViewModel.pluginStats) { value -> - if (value == null) { - plugin_storage_appbar?.isVisible = false + binding?.apply { + if (value == null) { + pluginStorageAppbar.isVisible = false - return@observeNullable - } + return@observeNullable + } - plugin_storage_appbar?.isVisible = true - if (value.total == 0) { - plugin_download?.setLayoutWidth(1) - plugin_disabled?.setLayoutWidth(0) - plugin_not_downloaded?.setLayoutWidth(0) - } else { - plugin_download?.setLayoutWidth(value.downloaded) - plugin_disabled?.setLayoutWidth(value.disabled) - plugin_not_downloaded?.setLayoutWidth(value.notDownloaded) + pluginStorageAppbar.isVisible = true + if (value.total == 0) { + pluginDownload.setLayoutWidth(1) + pluginDisabled.setLayoutWidth(0) + pluginNotDownloaded.setLayoutWidth(0) + } else { + pluginDownload.setLayoutWidth(value.downloaded) + pluginDisabled.setLayoutWidth(value.disabled) + pluginNotDownloaded.setLayoutWidth(value.notDownloaded) + } + pluginNotDownloadedTxt.setText(value.notDownloadedText) + pluginDisabledTxt.setText(value.disabledText) + pluginDownloadTxt.setText(value.downloadedText) } - plugin_not_downloaded_txt.setText(value.notDownloadedText) - plugin_disabled_txt.setText(value.disabledText) - plugin_download_txt.setText(value.downloadedText) } - plugin_storage_appbar?.setOnClickListener { + binding?.pluginStorageAppbar?.setOnClickListener { findNavController().navigate( R.id.navigation_settings_extensions_to_navigation_settings_plugins, PluginsFragment.newInstance( @@ -173,16 +183,18 @@ class ExtensionsFragment : Fragment() { } val addRepositoryClick = View.OnClickListener { + val ctx = context ?: return@OnClickListener + val binding = AddRepoInputBinding.inflate(LayoutInflater.from(ctx), null, false) val builder = - AlertDialog.Builder(context ?: return@OnClickListener, R.style.AlertDialogCustom) - .setView(R.layout.add_repo_input) + AlertDialog.Builder(ctx, R.style.AlertDialogCustom) + .setView(binding.root) val dialog = builder.create() dialog.show() (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt( 0 )?.text?.toString()?.let { copy -> - dialog.repo_url_input?.setText(copy) + binding.repoUrlInput.setText(copy) } // dialog.list_repositories?.setOnClickListener { @@ -192,10 +204,10 @@ class ExtensionsFragment : Fragment() { // } // dialog.text2?.text = provider.name - dialog.apply_btt?.setOnClickListener secondListener@{ - val name = dialog.repo_name_input?.text?.toString() + binding.applyBtt.setOnClickListener secondListener@{ + val name = binding.repoNameInput.text?.toString() ioSafe { - val url = dialog.repo_url_input?.text?.toString() + val url = binding.repoUrlInput.text?.toString() ?.let { it1 -> RepositoryManager.parseRepoUrl(it1) } if (url.isNullOrBlank()) { main { @@ -214,22 +226,23 @@ class ExtensionsFragment : Fragment() { } dialog.dismissSafe(activity) } - dialog.cancel_btt?.setOnClickListener { + binding.cancelBtt.setOnClickListener { dialog.dismissSafe(activity) } } val isTv = isTrueTvSettings() - add_repo_button?.isGone = isTv - add_repo_button_imageview_holder?.isVisible = isTv + binding?.apply { + addRepoButton.isGone = isTv + addRepoButtonImageviewHolder.isVisible = isTv - // Band-aid for Fire TV - plugin_storage_appbar?.isFocusableInTouchMode = isTv - add_repo_button_imageview?.isFocusableInTouchMode = isTv - - add_repo_button?.setOnClickListener(addRepositoryClick) - add_repo_button_imageview?.setOnClickListener(addRepositoryClick) + // Band-aid for Fire TV + pluginStorageAppbar.isFocusableInTouchMode = isTv + addRepoButtonImageview.isFocusableInTouchMode = isTv + addRepoButton.setOnClickListener(addRepositoryClick) + addRepoButtonImageview.setOnClickListener(addRepositoryClick) + } reloadRepositories() } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt index e90166a8..602b45e4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt @@ -1,14 +1,15 @@ package com.lagradost.cloudstream3.ui.settings.extensions import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.RepositoryItemBinding +import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import kotlinx.android.synthetic.main.repository_item.view.* class RepoAdapter( val isSetup: Boolean, @@ -20,9 +21,17 @@ class RepoAdapter( private val repositories: MutableList = mutableListOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item + val layout = if (isTrueTvSettings()) RepositoryItemTvBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) else RepositoryItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) //R.layout.repository_item_tv else R.layout.repository_item return RepoViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false) + layout ) } @@ -57,30 +66,57 @@ class RepoAdapter( diffResult.dispatchUpdatesTo(this) } - inner class RepoViewHolder(itemView: View) : - RecyclerView.ViewHolder(itemView) { + inner class RepoViewHolder( + val binding: ViewBinding + ) : + RecyclerView.ViewHolder(binding.root) { fun bind( repositoryData: RepositoryData ) { val isPrebuilt = PREBUILT_REPOSITORIES.contains(repositoryData) val drawable = if (isSetup) R.drawable.netflix_download else R.drawable.ic_baseline_delete_outline_24 + when (binding) { + is RepositoryItemTvBinding -> { + binding.apply { + // Only shows icon if on setup or if it isn't a prebuilt repo. + // No delete buttons on prebuilt repos. + if (!isPrebuilt || isSetup) { + actionButton.setImageResource(drawable) + } - // Only shows icon if on setup or if it isn't a prebuilt repo. - // No delete buttons on prebuilt repos. - if (!isPrebuilt || isSetup) { - itemView.action_button?.setImageResource(drawable) - } + actionButton.setOnClickListener { + imageClickCallback(repositoryData) + } - itemView.action_button?.setOnClickListener { - imageClickCallback(repositoryData) - } + repositoryItemRoot.setOnClickListener { + clickCallback(repositoryData) + } + mainText.text = repositoryData.name + subText.text = repositoryData.url + } + } - itemView.repository_item_root?.setOnClickListener { - clickCallback(repositoryData) + is RepositoryItemBinding -> { + binding.apply { + // Only shows icon if on setup or if it isn't a prebuilt repo. + // No delete buttons on prebuilt repos. + if (!isPrebuilt || isSetup) { + actionButton.setImageResource(drawable) + } + + actionButton.setOnClickListener { + imageClickCallback(repositoryData) + } + + repositoryItemRoot.setOnClickListener { + clickCallback(repositoryData) + } + mainText.text = repositoryData.name + subText.text = repositoryData.url + } + } } - itemView.main_text?.text = repositoryData.name - itemView.sub_text?.text = repositoryData.url } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt index 34cd67cd..59b1b856 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt @@ -1,97 +1,105 @@ package com.lagradost.cloudstream3.ui.settings.testing import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.FragmentTestingBinding import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar -import kotlinx.android.synthetic.main.fragment_testing.* -import kotlinx.android.synthetic.main.view_test.* class TestFragment : Fragment() { private val testViewModel: TestViewModel by activityViewModels() + var binding: FragmentTestingBinding? = null + + override fun onDestroyView() { + binding = null + super.onDestroyView() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setUpToolbar(R.string.category_provider_test) super.onViewCreated(view, savedInstanceState) - provider_test_recycler_view?.adapter = TestResultAdapter( - mutableListOf() - ) + binding?.apply { + providerTestRecyclerView.adapter = TestResultAdapter( + mutableListOf() + ) - testViewModel.init() - if (testViewModel.isRunningTest) { - provider_test?.setState(TestView.TestState.Running) - } - - observe(testViewModel.providerProgress) { (passed, failed, total) -> - provider_test?.setProgress(passed, failed, total) - } - - observeNullable(testViewModel.providerResults) { - normalSafeApiCall { - val newItems = it.sortedBy { api -> api.first.name } - (provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList( - newItems - ) + testViewModel.init() + if (testViewModel.isRunningTest) { + providerTest.setState(TestView.TestState.Running) } - } - provider_test?.setOnPlayButtonListener { state -> - when (state) { - TestView.TestState.Stopped -> testViewModel.stopTest() - TestView.TestState.Running -> testViewModel.startTest() - TestView.TestState.None -> testViewModel.startTest() + observe(testViewModel.providerProgress) { (passed, failed, total) -> + providerTest.setProgress(passed, failed, total) } - } - if (isTrueTvSettings()) { - tests_play_pause?.isFocusableInTouchMode = true - tests_play_pause?.requestFocus() - } - - provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - provider_test_appbar?.setExpanded(true, true) + observeNullable(testViewModel.providerResults) { + normalSafeApiCall { + val newItems = it.sortedBy { api -> api.first.name } + (providerTestRecyclerView.adapter as? TestResultAdapter)?.updateList( + newItems + ) + } + } + + providerTest.setOnPlayButtonListener { state -> + when (state) { + TestView.TestState.Stopped -> testViewModel.stopTest() + TestView.TestState.Running -> testViewModel.startTest() + TestView.TestState.None -> testViewModel.startTest() + } } - } - fun focusRecyclerView() { - // Hack to make it possible to focus the recyclerview. if (isTrueTvSettings()) { - provider_test_recycler_view?.requestFocus() - provider_test_appbar?.setExpanded(false, true) + providerTest.playPauseButton?.isFocusableInTouchMode = true + providerTest.playPauseButton?.requestFocus() } - } - provider_test?.setOnMainClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All) - focusRecyclerView() - } - provider_test?.setOnFailedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed) - focusRecyclerView() - } - provider_test?.setOnPassedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed) - focusRecyclerView() + providerTest.playPauseButton?.setOnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + providerTestAppbar.setExpanded(true, true) + } + } + + fun focusRecyclerView() { + // Hack to make it possible to focus the recyclerview. + if (isTrueTvSettings()) { + providerTestRecyclerView.requestFocus() + providerTestAppbar.setExpanded(false, true) + } + } + + providerTest.setOnMainClick { + testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All) + focusRecyclerView() + } + providerTest.setOnFailedClick { + testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed) + focusRecyclerView() + } + providerTest.setOnPassedClick { + testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed) + focusRecyclerView() + } } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_testing, container, false) + ): View { + val localBinding = FragmentTestingBinding.inflate(inflater, container, false) + binding = localBinding + return localBinding.root//inflater.inflate(R.layout.fragment_testing, container, false) } } \ No newline at end of file From 647e91bc4b850bcae227e28e4a638b7162d8fc7b Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 14 Jul 2023 21:43:46 +0200 Subject: [PATCH 03/31] more views + MainActivity viewbindings --- app/build.gradle.kts | 4 +- .../lagradost/cloudstream3/AcraApplication.kt | 4 +- .../lagradost/cloudstream3/MainActivity.kt | 140 +++++++++++------- .../cloudstream3/ui/home/HomeFragment.kt | 22 +-- .../ui/library/LibraryScrollTransformer.kt | 4 +- .../source_priority/SourcePriorityDialog.kt | 27 ++-- .../cloudstream3/ui/search/SearchAdaptor.kt | 14 +- .../ui/settings/extensions/PluginAdapter.kt | 82 +++++----- .../lagradost/cloudstream3/utils/UIHelper.kt | 51 ++++++- app/src/main/res/layout/activity_main_tv.xml | 16 ++ app/src/main/res/values/strings.xml | 1 + 11 files changed, 237 insertions(+), 128 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 86d91147..d5364045 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -208,7 +208,7 @@ dependencies { implementation("com.github.discord:OverlappingPanels:0.1.3") // debugImplementation because LeakCanary should only run in debug builds. - // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' + // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") // for shimmer when loading implementation("com.facebook.shimmer:shimmer:0.5.0") @@ -228,7 +228,7 @@ dependencies { // Library/extensions searching with Levenshtein distance implementation("me.xdrop:fuzzywuzzy:1.4.0") - // color pallette for images -> colors + // color palette for images -> colors implementation("androidx.palette:palette-ktx:1.0.0") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 0351b1ff..76b2321f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -51,8 +51,8 @@ class CustomReportSender : ReportSender { thread { // to not run it on main thread runBlocking { suspendSafeApiCall { - val post = app.post(url, data = data) - println("Report response: $post") + app.post(url, data = data) + //println("Report response: $post") } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index f409c10f..4a7a28ad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.gms.cast.framework.* +import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.snackbar.Snackbar @@ -49,6 +50,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.updateLocale +import com.lagradost.cloudstream3.databinding.ActivityMainBinding +import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding +import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.plugins.PluginManager @@ -74,6 +78,7 @@ import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.result.setText +import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings @@ -110,9 +115,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ResponseParser -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.bottom_resultview_preview.* -import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.io.File @@ -334,8 +336,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // Use both navigation views to support both layouts. // It might be better to use the QuickSearch. - nav_view?.selectedItemId = R.id.navigation_search - nav_rail_view?.selectedItemId = R.id.navigation_search + activity?.findViewById(R.id.nav_view)?.selectedItemId = + R.id.navigation_search + activity?.findViewById(R.id.nav_rail_view)?.selectedItemId = + R.id.navigation_search } else if (safeURI(str)?.scheme == appStringPlayer) { val uri = Uri.parse(str) val name = uri.getQueryParameter("name") @@ -412,7 +416,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { this.hideKeyboard() // Fucks up anime info layout since that has its own layout - cast_mini_controller_holder?.isVisible = + binding?.castMiniControllerHolder?.isVisible = !listOf( R.id.navigation_results_phone, R.id.navigation_results_tv, @@ -448,7 +452,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { R.id.navigation_player, ).contains(destination.id) - nav_host_fragment?.apply { + binding?.navHostFragment?.apply { val params = layoutParams as ConstraintLayout.LayoutParams params.setMargins( @@ -464,21 +468,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { Configuration.ORIENTATION_LANDSCAPE -> { true } + Configuration.ORIENTATION_PORTRAIT -> { - false + isTvSettings() } + else -> { false } } + binding?.apply { + navView.isVisible = isNavVisible && !landscape + navRailView.isVisible = isNavVisible && landscape - nav_view?.isVisible = isNavVisible && !landscape - nav_rail_view?.isVisible = isNavVisible && landscape - - // Hide library on TV since it is not supported yet :( - val isTrueTv = isTrueTvSettings() - nav_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv - nav_rail_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv + // Hide library on TV since it is not supported yet :( + val isTrueTv = isTrueTvSettings() + navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv + navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv + } } //private var mCastSession: CastSession? = null @@ -691,28 +698,37 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } private fun hidePreviewPopupDialog() { - viewModel.clear() bottomPreviewPopup.dismissSafe(this) + bottomPreviewPopup = null + bottomPreviewBinding = null } - var bottomPreviewPopup: BottomSheetDialog? = null - private fun showPreviewPopupDialog(): BottomSheetDialog { - val ret = (bottomPreviewPopup ?: run { + private var bottomPreviewPopup: BottomSheetDialog? = null + private var bottomPreviewBinding: BottomResultviewPreviewBinding? = null + private fun showPreviewPopupDialog(): BottomResultviewPreviewBinding { + val ret = (bottomPreviewBinding ?: run { val builder = BottomSheetDialog(this) - builder.setContentView(R.layout.bottom_resultview_preview) + val binding: BottomResultviewPreviewBinding = + BottomResultviewPreviewBinding.inflate(builder.layoutInflater, null, false) + bottomPreviewBinding = binding + builder.setContentView(binding.root) builder.setOnDismissListener { bottomPreviewPopup = null + bottomPreviewBinding = null viewModel.clear() } builder.setCanceledOnTouchOutside(true) builder.show() - builder + bottomPreviewPopup = builder + binding }) - bottomPreviewPopup = ret + return ret } + var binding: ActivityMainBinding? = null + override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -744,10 +760,20 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) updateTv() - if (isTvSettings()) { - setContentView(R.layout.activity_main_tv) - } else { - setContentView(R.layout.activity_main) + // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH + binding = try { + if (isTvSettings()) { + val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false) + setContentView(newLocalBinding.root) + ActivityMainBinding.bind(newLocalBinding.root) // this may crash + } else { + val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false) + setContentView(newLocalBinding.root) + newLocalBinding + } + } catch (t: Throwable) { + showToast(this, txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG) + null } changeStatusBarState(isEmulatorSettings()) @@ -832,41 +858,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { observeNullable(viewModel.page) { resource -> if (resource == null) { - bottomPreviewPopup.dismissSafe(this) + hidePreviewPopupDialog() return@observeNullable } when (resource) { is Resource.Failure -> { showToast(this, R.string.error) + viewModel.clear() hidePreviewPopupDialog() } + is Resource.Loading -> { showPreviewPopupDialog().apply { - resultview_preview_loading?.isVisible = true - resultview_preview_result?.isVisible = false - resultview_preview_loading_shimmer?.startShimmer() + resultviewPreviewLoading.isVisible = true + resultviewPreviewResult.isVisible = false + resultviewPreviewLoadingShimmer.startShimmer() } } + is Resource.Success -> { val d = resource.value showPreviewPopupDialog().apply { - resultview_preview_loading?.isVisible = false - resultview_preview_result?.isVisible = true - resultview_preview_loading_shimmer?.stopShimmer() + resultviewPreviewLoading.isVisible = false + resultviewPreviewResult.isVisible = true + resultviewPreviewLoadingShimmer.stopShimmer() - resultview_preview_title?.text = d.title + resultviewPreviewTitle.text = d.title - resultview_preview_meta_type.setText(d.typeText) - resultview_preview_meta_year.setText(d.yearText) - resultview_preview_meta_duration.setText(d.durationText) - resultview_preview_meta_rating.setText(d.ratingText) + resultviewPreviewMetaType.setText(d.typeText) + resultviewPreviewMetaYear.setText(d.yearText) + resultviewPreviewMetaDuration.setText(d.durationText) + resultviewPreviewMetaRating.setText(d.ratingText) - resultview_preview_description?.setText(d.plotText) - resultview_preview_poster?.setImage( + resultviewPreviewDescription.setText(d.plotText) + resultviewPreviewPoster.setImage( d.posterImage ?: d.posterBackgroundImage ) - resultview_preview_poster?.setOnClickListener { + resultviewPreviewPoster.setOnClickListener { //viewModel.updateWatchStatus(WatchType.PLANTOWATCH) val value = viewModel.watchStatus.value ?: WatchType.NONE @@ -882,7 +911,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } if (!isTvSettings()) // dont want this clickable on tv layout - resultview_preview_description?.setOnClickListener { view -> + resultviewPreviewDescription.setOnClickListener { view -> view.context?.let { ctx -> val builder: AlertDialog.Builder = AlertDialog.Builder(ctx, R.style.AlertDialogCustom) @@ -892,7 +921,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } - resultview_preview_more_info?.setOnClickListener { + resultviewPreviewMoreInfo.setOnClickListener { + viewModel.clear() hidePreviewPopupDialog() lastPopup?.let { loadSearchResult(it) @@ -964,22 +994,22 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { .setPopExitAnim(R.anim.nav_pop_exit) .setPopUpTo(navController.graph.startDestination, false) .build()*/ - nav_view?.setupWithNavController(navController) - val nav_rail = findViewById(R.id.nav_rail_view) - nav_rail?.setupWithNavController(navController) + binding?.navView?.setupWithNavController(navController) + val navRail = findViewById(R.id.nav_rail_view) + navRail?.setupWithNavController(navController) if (isTvSettings()) { - nav_rail?.background?.alpha = 200 + navRail?.background?.alpha = 200 } else { - nav_rail?.background?.alpha = 255 + navRail?.background?.alpha = 255 } - nav_rail?.setOnItemSelectedListener { item -> + navRail?.setOnItemSelectedListener { item -> onNavDestinationSelected( item, navController ) } - nav_view?.setOnItemSelectedListener { item -> + binding?.navView?.setOnItemSelectedListener { item -> onNavDestinationSelected( item, navController @@ -1010,16 +1040,16 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { }*/ val rippleColor = ColorStateList.valueOf(getResourceColor(R.attr.colorPrimary, 0.1f)) - nav_view?.itemRippleColor = rippleColor - nav_rail?.itemRippleColor = rippleColor - nav_rail?.itemActiveIndicatorColor = rippleColor - nav_view?.itemActiveIndicatorColor = rippleColor + binding?.navView?.itemRippleColor = rippleColor + navRail?.itemRippleColor = rippleColor + navRail?.itemActiveIndicatorColor = rippleColor + binding?.navView?.itemActiveIndicatorColor = rippleColor if (!checkWrite()) { requestRW() if (checkWrite()) return } - CastButtonFactory.setUpMediaRouteButton(this, media_route_button) + //CastButtonFactory.setUpMediaRouteButton(this, media_route_button) // THIS IS CURRENTLY REMOVED BECAUSE HIGHER VERS OF ANDROID NEEDS A NOTIFICATION //if (!VideoDownloadManager.isMyServiceRunning(this, VideoDownloadKeepAliveService::class.java)) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 99ce7c3b..f47432dc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -31,6 +31,7 @@ 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 @@ -45,6 +46,7 @@ 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 @@ -431,22 +433,20 @@ class HomeFragment : Fragment() { ): 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 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 + binding = try { + FragmentHomeBinding.bind(root) + } catch (t : Throwable) { + showToast(activity, txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG) + logError(t) + null + } - //return inflater.inflate(layout, container, false) + return root } override fun onDestroyView() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt index 8aafbdd6..c3cee183 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt @@ -2,13 +2,13 @@ package com.lagradost.cloudstream3.ui.library import android.view.View import androidx.viewpager2.widget.ViewPager2 -import kotlinx.android.synthetic.main.library_viewpager_page.view.* +import com.lagradost.cloudstream3.R import kotlin.math.roundToInt class LibraryScrollTransformer : ViewPager2.PageTransformer { override fun transformPage(page: View, position: Float) { val padding = (-position * page.width).roundToInt() - page.page_recyclerview.setPadding( + page.findViewById(R.id.page_recyclerview).setPadding( padding, 0, -padding, 0 ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt index efc1f1b8..1b59882e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt @@ -2,24 +2,18 @@ package com.lagradost.cloudstream3.ui.player.source_priority import android.app.Dialog import android.content.Context -import android.view.View -import android.widget.EditText -import android.widget.TextView +import android.view.LayoutInflater import androidx.annotation.StyleRes import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.FragmentActivity -import androidx.recyclerview.widget.RecyclerView -import androidx.work.impl.constraints.controllers.ConstraintController import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.PlayerSelectSourcePriorityBinding import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe -import kotlinx.android.synthetic.main.player_select_source_priority.* class SourcePriorityDialog( - ctx: Context, + val ctx: Context, @StyleRes themeRes: Int, val links: List, private val profile: QualityDataHelper.QualityProfile, @@ -30,13 +24,14 @@ class SourcePriorityDialog( private val updatedCallback: () -> Unit ) : Dialog(ctx, themeRes) { override fun show() { - setContentView(R.layout.player_select_source_priority) - val sourcesRecyclerView: RecyclerView = sort_sources - val qualitiesRecyclerView: RecyclerView = sort_qualities - val profileText: EditText = profile_text_editable - val saveBtt: View = save_btt - val exitBtt: View = close_btt - val helpBtt: View = help_btt + val binding = PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false) + setContentView(binding.root) + val sourcesRecyclerView = binding.sortSources + val qualitiesRecyclerView = binding.sortQualities + val profileText = binding.profileTextEditable + val saveBtt = binding.saveBtt + val exitBtt = binding.closeBtt + val helpBtt = binding.helpBtt profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context)) profileText.hint = txt(R.string.profile_number, profile.id).asString(context) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 233614dd..b516348d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -38,15 +38,15 @@ class SearchAdapter( var hasNext: Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - + val inflater = LayoutInflater.from(parent.context) val layout = if (parent.context.IsBottomLayout()) SearchResultGridExpandedBinding.inflate( - LayoutInflater.from(parent.context), + inflater, parent, false ) else SearchResultGridBinding.inflate( - LayoutInflater.from(parent.context), + inflater, parent, false ) //R.layout.search_result_grid_expanded else R.layout.search_result_grid @@ -95,9 +95,15 @@ class SearchAdapter( private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt() + private val cardView = when(binding) { + is SearchResultGridExpandedBinding -> binding.imageView + is SearchResultGridBinding -> binding.imageView + else -> null + } + fun bind(card: SearchResponse, position: Int) { if (!compactView) { - binding.root.apply { + cardView?.apply { layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, coverHeight diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index 0c3d481b..eb0082b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.settings.extensions import android.text.format.Formatter.formatShortFileSize import android.util.Log import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isGone @@ -13,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi.getVotes import com.lagradost.cloudstream3.ui.result.setText @@ -26,10 +26,11 @@ import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.repository_item.view.* import org.junit.Assert import org.junit.Test import java.text.DecimalFormat +import kotlin.math.floor +import kotlin.math.log10 data class PluginViewData( @@ -45,8 +46,10 @@ class PluginAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item + val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false) + return PluginViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false) + RepositoryItemBinding.bind(inflated) // may crash ) } @@ -82,8 +85,10 @@ class PluginAdapter( // Clear glide image because setImageResource doesn't override override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - holder.itemView.entry_icon?.let { pluginIcon -> - GlideApp.with(pluginIcon).clear(pluginIcon) + if (holder is PluginViewHolder) { + holder.binding.entryIcon.let { pluginIcon -> + GlideApp.with(pluginIcon).clear(pluginIcon) + } } super.onViewRecycled(holder) } @@ -112,7 +117,7 @@ class PluginAdapter( fun prettyCount(number: Number): String? { val suffix = charArrayOf(' ', 'k', 'M', 'B', 'T', 'P', 'E') val numValue = number.toLong() - val value = Math.floor(Math.log10(numValue.toDouble())).toInt() + val value = floor(log10(numValue.toDouble())).toInt() val base = value / 3 return if (value >= 3 && base < suffix.size) { DecimalFormat("#0.00").format( @@ -127,8 +132,8 @@ class PluginAdapter( } } - inner class PluginViewHolder(itemView: View) : - RecyclerView.ViewHolder(itemView) { + inner class PluginViewHolder(val binding: RepositoryItemBinding) : + RecyclerView.ViewHolder(binding.root) { fun bind( data: PluginViewData, @@ -138,17 +143,17 @@ class PluginAdapter( val name = metadata.name.removeSuffix("Provider") val alpha = if (disabled) 0.6f else 1f val isLocal = !data.plugin.second.url.startsWith("http") - itemView.main_text?.alpha = alpha - itemView.sub_text?.alpha = alpha + binding.mainText.alpha = alpha + binding.subText.alpha = alpha val drawableInt = if (data.isDownloaded) R.drawable.ic_baseline_delete_outline_24 else R.drawable.netflix_download - itemView.nsfw_marker?.isVisible = metadata.tvTypes?.contains("NSFW") ?: false - itemView.action_button?.setImageResource(drawableInt) + binding.nsfwMarker.isVisible = metadata.tvTypes?.contains("NSFW") ?: false + binding.actionButton.setImageResource(drawableInt) - itemView.action_button?.setOnClickListener { + binding.actionButton.setOnClickListener { iconClickCallback.invoke(data.plugin) } itemView.setOnClickListener { @@ -169,10 +174,11 @@ class PluginAdapter( if (data.isDownloaded) { // On local plugins page the filepath is provided instead of url. - val plugin = PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] + val plugin = + PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] if (plugin?.openSettings != null) { - itemView.action_settings?.isVisible = true - itemView.action_settings.setOnClickListener { + binding.actionSettings.isVisible = true + binding.actionSettings.setOnClickListener { try { plugin.openSettings!!.invoke(itemView.context) } catch (e: Throwable) { @@ -185,13 +191,13 @@ class PluginAdapter( } } } else { - itemView.action_settings?.isVisible = false + binding.actionSettings.isVisible = false } } else { - itemView.action_settings?.isVisible = false + binding.actionSettings.isVisible = false } - if (itemView.entry_icon?.setImage(//itemView.entry_icon?.height ?: + if (!binding.entryIcon.setImage(//itemView.entry_icon?.height ?: metadata.iconUrl?.replace( "%size%", "$iconSize" @@ -201,41 +207,47 @@ class PluginAdapter( ), null, errorImageDrawable = R.drawable.ic_baseline_extension_24 - ) != true + ) ) { - itemView.entry_icon?.setImageResource(R.drawable.ic_baseline_extension_24) + binding.entryIcon.setImageResource(R.drawable.ic_baseline_extension_24) } - itemView.ext_version?.isVisible = true - itemView.ext_version?.text = "v${metadata.version}" + binding.extVersion.isVisible = true + binding.extVersion.text = "v${metadata.version}" if (metadata.language.isNullOrBlank()) { - itemView.lang_icon?.isVisible = false + binding.langIcon.isVisible = false } else { - itemView.lang_icon?.isVisible = true - itemView.lang_icon.text = "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" + binding.langIcon.isVisible = true + binding.langIcon.text = + "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" } - itemView.ext_votes?.isVisible = false + binding.extVotes.isVisible = false if (!isLocal) { ioSafe { metadata.getVotes().main { - itemView.ext_votes?.setText(txt(R.string.extension_rating, prettyCount(it))) - itemView.ext_votes?.isVisible = true + binding.extVotes.setText(txt(R.string.extension_rating, prettyCount(it))) + binding.extVotes.isVisible = true } } } if (metadata.fileSize != null) { - itemView.ext_filesize?.isVisible = true - itemView.ext_filesize?.text = formatShortFileSize(itemView.context, metadata.fileSize) + binding.extFilesize.isVisible = true + binding.extFilesize.text = formatShortFileSize(itemView.context, metadata.fileSize) } else { - itemView.ext_filesize?.isVisible = false + binding.extFilesize.isVisible = false } - itemView.main_text.setText(if(disabled) txt(R.string.single_plugin_disabled, name) else txt(name)) - itemView.sub_text?.isGone = metadata.description.isNullOrBlank() - itemView.sub_text?.text = metadata.description.html() + binding.mainText.setText( + if (disabled) txt( + R.string.single_plugin_disabled, + name + ) else txt(name) + ) + binding.subText.isGone = metadata.description.isNullOrBlank() + binding.subText.text = metadata.description.html() } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 4ed1aee6..3be4b190 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -181,6 +181,50 @@ object UIHelper { } } + /*inline fun bindViewBinding( + inflater: LayoutInflater?, + container: ViewGroup?, + layout: Int + ): Pair { + return try { + val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) } + ?: return null to txt( + R.string.unable_to_inflate, + "Requires inflater OR container" + )//throw IllegalArgumentException("Requires inflater OR container")) + + //println("methods: ${T::class.java.methods.map { it.name }}") + val bind = T::class.java.methods.first { it.name == "bind" } + //val inflate = T::class.java.methods.first { it.name == "inflate" } + val root = localInflater.inflate(layout, container, false) + bind.invoke(null, root) as T to null + } catch (t: Throwable) { + logError(t) + val message = txt(R.string.unable_to_inflate, t.message ?: "Primary constructor") + // if the desired layout is not found then we inflate the casted layout + /*try { + val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) } + ?: return null to txt( + R.string.unable_to_inflate, + "Requires inflater OR container" + )//throw IllegalArgumentException("Requires inflater OR container")) + + // we don't know what method to use as there are 2, but first *should* always be true + return try { + val inflate = T::class.java.methods.first { it.name == "inflate" } + inflate.invoke(null, localInflater, container, false) as T + } catch (_: Throwable) { + val inflate = T::class.java.methods.last { it.name == "inflate" } + inflate.invoke(null, localInflater, container, false) as T + } to message + } catch (t: Throwable) { + logError(t) + }*/ + + null to message + } + }*/ + fun ImageView?.setImage( url: String?, headers: Map? = null, @@ -190,7 +234,12 @@ object UIHelper { colorCallback: ((Palette) -> Unit)? = null ): Boolean { if (url.isNullOrBlank()) return false - this.setImage(UiImage.Image(url, headers, errorImageDrawable), errorImageDrawable, fadeIn, colorCallback) + this.setImage( + UiImage.Image(url, headers, errorImageDrawable), + errorImageDrawable, + fadeIn, + colorCallback + ) return true } diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index dc29dec9..4e50f464 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -41,6 +41,22 @@ + + Qualities Profile background + UI was unable to be created correctly, this is a MAJOR BUG and should be reported immediately %s From 273a947f8ef46c294ff14b4be0aeae1081868e92 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:05:13 +0200 Subject: [PATCH 04/31] more views --- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 6 +- .../player/source_priority/PriorityAdapter.kt | 25 +- .../player/source_priority/ProfilesAdapter.kt | 29 +- .../source_priority/QualityProfileDialog.kt | 129 ++-- .../extensions/PluginDetailsFragment.kt | 130 ++-- .../ui/settings/testing/TestResultAdapter.kt | 19 +- .../ui/subtitles/SubtitlesFragment.kt | 607 +++++++++--------- 7 files changed, 491 insertions(+), 454 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index f296e53d..5902132e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -11,9 +11,9 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.fragment_home_head_tv.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* -import kotlinx.android.synthetic.main.home_scroll_view.view.* +import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview +import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview_tags +import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview_title class HomeScrollAdapter( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt index 8e0ce67c..fb60ccce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt @@ -1,14 +1,10 @@ package com.lagradost.cloudstream3.ui.player.source_priority import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.PlayerPrioritizeItemBinding import com.lagradost.cloudstream3.utils.AppUtils -import kotlinx.android.synthetic.main.player_prioritize_item.view.* data class SourcePriority( val data: T, @@ -20,7 +16,8 @@ class PriorityAdapter(override val items: MutableList>) : AppUtils.DiffAdapter>(items) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return PriorityViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false) + PlayerPrioritizeItemBinding.inflate(LayoutInflater.from(parent.context),parent,false), + //LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false) ) } @@ -31,27 +28,27 @@ class PriorityAdapter(override val items: MutableList>) : } class PriorityViewHolder( - itemView: View, - ) : RecyclerView.ViewHolder(itemView) { + val binding: PlayerPrioritizeItemBinding, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: SourcePriority) { - val plusButton: ImageView = itemView.add_button + /* val plusButton: ImageView = itemView.add_button val subtractButton: ImageView = itemView.subtract_button val priorityText: TextView = itemView.priority_text - val priorityNumber: TextView = itemView.priority_number - priorityText.text = item.name + val priorityNumber: TextView = itemView.priority_number*/ + binding.priorityText.text = item.name fun updatePriority() { - priorityNumber.text = item.priority.toString() + binding.priorityNumber.text = item.priority.toString() } updatePriority() - plusButton.setOnClickListener { + binding.addButton.setOnClickListener { // If someone clicks til the integer limit then they deserve to crash. item.priority++ updatePriority() } - subtractButton.setOnClickListener { + binding.subtractButton.setOnClickListener { item.priority-- updatePriority() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt index ff84c1f5..8153d7a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt @@ -8,19 +8,13 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.PlayerQualityProfileItemBinding import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.player_quality_profile_item.view.card_view -import kotlinx.android.synthetic.main.player_quality_profile_item.view.outline -import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_image_background -import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_text -import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_mobile_data -import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_wifi class ProfilesAdapter( override val items: MutableList, @@ -34,8 +28,9 @@ class ProfilesAdapter( }) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return ProfilesViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.player_quality_profile_item, parent, false) + PlayerQualityProfileItemBinding.inflate(LayoutInflater.from(parent.context),parent,false) + //LayoutInflater.from(parent.context) + // .inflate(R.layout.player_quality_profile_item, parent, false) ) } @@ -52,8 +47,8 @@ class ProfilesAdapter( } inner class ProfilesViewHolder( - itemView: View, - ) : RecyclerView.ViewHolder(itemView) { + val binding: PlayerQualityProfileItemBinding, + ) : RecyclerView.ViewHolder(binding.root) { private val art = listOf( R.drawable.profile_bg_teal, R.drawable.profile_bg_blue, @@ -65,12 +60,12 @@ class ProfilesAdapter( ) fun bind(item: QualityDataHelper.QualityProfile, index: Int) { - val priorityText: TextView = itemView.profile_text - val profileBg: ImageView = itemView.profile_image_background - val wifiText: TextView = itemView.text_is_wifi - val dataText: TextView = itemView.text_is_mobile_data - val outline: View = itemView.outline - val cardView: View = itemView.card_view + val priorityText: TextView = binding.profileText + val profileBg: ImageView = binding.profileImageBackground + val wifiText: TextView = binding.textIsWifi + val dataText: TextView = binding.textIsMobileData + val outline: View = binding.outline + val cardView: View = binding.cardView priorityText.text = item.name.asString(itemView.context) dataText.isVisible = item.type == QualityDataHelper.QualityProfileType.Data diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt index 28a6365f..e3629158 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt @@ -1,20 +1,16 @@ package com.lagradost.cloudstream3.ui.player.source_priority import android.app.Dialog -import android.view.View -import android.widget.TextView import androidx.annotation.StyleRes -import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity -import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.PlayerQualityProfileDialogBinding import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe -import kotlinx.android.synthetic.main.player_quality_profile_dialog.* class QualityProfileDialog( val activity: FragmentActivity, @@ -24,83 +20,86 @@ class QualityProfileDialog( private val profileSelectionCallback: (QualityDataHelper.QualityProfile) -> Unit ) : Dialog(activity, themeRes) { override fun show() { - setContentView(R.layout.player_quality_profile_dialog) - val profilesRecyclerView: RecyclerView = profiles_recyclerview + + val binding = PlayerQualityProfileDialogBinding.inflate(this.layoutInflater, null, false) + + setContentView(binding.root)//R.layout.player_quality_profile_dialog) + /*val profilesRecyclerView: RecyclerView = profiles_recyclerview val useBtt: View = use_btt val editBtt: View = edit_btt val cancelBtt: View = cancel_btt val defaultBtt: View = set_default_btt val currentProfileText: TextView = currently_selected_profile_text - val selectedItemActionsHolder: View = selected_item_holder - - fun getCurrentProfile(): QualityDataHelper.QualityProfile? { - return (profilesRecyclerView.adapter as? ProfilesAdapter)?.getCurrentProfile() - } - - fun refreshProfiles() { - currentProfileText.text = getProfileName(usedProfile).asString(context) - (profilesRecyclerView.adapter as? ProfilesAdapter)?.updateList(getProfiles()) - } - - profilesRecyclerView.adapter = ProfilesAdapter( - mutableListOf(), - usedProfile, - ) { oldIndex: Int?, newIndex: Int -> - profilesRecyclerView.adapter?.notifyItemChanged(newIndex) - selectedItemActionsHolder.alpha = 1f - if (oldIndex != null) { - profilesRecyclerView.adapter?.notifyItemChanged(oldIndex) + val selectedItemActionsHolder: View = selected_item_holder*/ + binding.apply { + fun getCurrentProfile(): QualityDataHelper.QualityProfile? { + return (profilesRecyclerview.adapter as? ProfilesAdapter)?.getCurrentProfile() } - } - refreshProfiles() - - editBtt.setOnClickListener { - getCurrentProfile()?.let { profile -> - SourcePriorityDialog(context, themeRes, links, profile) { - refreshProfiles() - }.show() + fun refreshProfiles() { + currentlySelectedProfileText.text = getProfileName(usedProfile).asString(context) + (profilesRecyclerview.adapter as? ProfilesAdapter)?.updateList(getProfiles()) + } + + profilesRecyclerview.adapter = ProfilesAdapter( + mutableListOf(), + usedProfile, + ) { oldIndex: Int?, newIndex: Int -> + profilesRecyclerview.adapter?.notifyItemChanged(newIndex) + selectedItemHolder.alpha = 1f + if (oldIndex != null) { + profilesRecyclerview.adapter?.notifyItemChanged(oldIndex) + } + } + + refreshProfiles() + + editBtt.setOnClickListener { + getCurrentProfile()?.let { profile -> + SourcePriorityDialog(context, themeRes, links, profile) { + refreshProfiles() + }.show() + } } - } - defaultBtt.setOnClickListener { - val currentProfile = getCurrentProfile() ?: return@setOnClickListener - val choices = QualityDataHelper.QualityProfileType.values() - .filter { it != QualityDataHelper.QualityProfileType.None } - val choiceNames = choices.map { txt(it.stringRes).asString(context) } + setDefaultBtt.setOnClickListener { + val currentProfile = getCurrentProfile() ?: return@setOnClickListener + val choices = QualityDataHelper.QualityProfileType.values() + .filter { it != QualityDataHelper.QualityProfileType.None } + val choiceNames = choices.map { txt(it.stringRes).asString(context) } - activity.showBottomDialog( - choiceNames, - choices.indexOf(currentProfile.type), - txt(R.string.set_default).asString(context), - false, - {}, - { index -> - val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog - // Remove previous picks - if (pickedChoice.unique) { - getProfiles().filter { it.type == pickedChoice }.forEach { - QualityDataHelper.setQualityProfileType(it.id, null) + activity.showBottomDialog( + choiceNames, + choices.indexOf(currentProfile.type), + txt(R.string.set_default).asString(context), + false, + {}, + { index -> + val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog + // Remove previous picks + if (pickedChoice.unique) { + getProfiles().filter { it.type == pickedChoice }.forEach { + QualityDataHelper.setQualityProfileType(it.id, null) + } } - } - QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice) - refreshProfiles() - }) - } + QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice) + refreshProfiles() + }) + } - cancelBtt.setOnClickListener { - this.dismissSafe() - } + cancelBtt.setOnClickListener { + this@QualityProfileDialog.dismissSafe() + } - useBtt.setOnClickListener { - getCurrentProfile()?.let { - profileSelectionCallback.invoke(it) - this.dismissSafe() + useBtt.setOnClickListener { + getCurrentProfile()?.let { + profileSelectionCallback.invoke(it) + this@QualityProfileDialog.dismissSafe() + } } } - super.show() } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 9729b4de..00e1806d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -2,30 +2,29 @@ package com.lagradost.cloudstream3.ui.settings.extensions import android.content.res.ColorStateList import android.os.Bundle -import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import android.text.format.Formatter.formatFileSize +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.UIHelper.setImage -import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.fragment_plugin_details.* -import android.text.format.Formatter.formatFileSize -import android.util.Log import androidx.core.view.isVisible +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.FragmentPluginDetailsBinding +import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi +import com.lagradost.cloudstream3.plugins.VotingApi.canVote import com.lagradost.cloudstream3.plugins.VotingApi.getVoteType import com.lagradost.cloudstream3.plugins.VotingApi.getVotes import com.lagradost.cloudstream3.plugins.VotingApi.vote import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.plugins.VotingApi.canVote import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso -import kotlinx.android.synthetic.main.repository_item.view.* +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute +import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.UIHelper.toPx class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragment() { @@ -43,18 +42,27 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen } } + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + + var binding: FragmentPluginDetailsBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_plugin_details, container, false) - + ): View { + val localBinding = FragmentPluginDetailsBinding.inflate(inflater, container, false) + binding = localBinding + return localBinding.root + //return inflater.inflate(R.layout.fragment_plugin_details, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val metadata = data.plugin.second - if (plugin_icon?.setImage(//plugin_icon?.height ?: + binding?.apply { + if (!pluginIcon.setImage(//plugin_icon?.height ?: metadata.iconUrl?.replace( "%size%", "$iconSize" @@ -64,23 +72,33 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen ), null, errorImageDrawable = R.drawable.ic_baseline_extension_24 - ) != true + ) ) { - plugin_icon?.setImageResource(R.drawable.ic_baseline_extension_24) + pluginIcon.setImageResource(R.drawable.ic_baseline_extension_24) } - plugin_name?.text = metadata.name.removeSuffix("Provider") - plugin_version?.text = metadata.version.toString() - plugin_description?.text = metadata.description ?: getString(R.string.no_data) - plugin_size?.text = if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize(context, metadata.fileSize) - plugin_author?.text = if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString(", ") - plugin_status?.text = resources.getStringArray(R.array.extension_statuses)[metadata.status] - plugin_types?.text = if ((metadata.tvTypes == null) || metadata.tvTypes.isEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString(", ") - plugin_lang?.text = if (metadata.language == null) - getString(R.string.no_data) + pluginName.text = metadata.name.removeSuffix("Provider") + pluginVersion.text = metadata.version.toString() + pluginDescription.text = metadata.description ?: getString(R.string.no_data) + pluginSize.text = + if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize( + context, + metadata.fileSize + ) + pluginAuthor.text = + if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString( + ", " + ) + pluginStatus.text = resources.getStringArray(R.array.extension_statuses)[metadata.status] + pluginTypes.text = + if (metadata.tvTypes.isNullOrEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString( + ", " + ) + pluginLang.text = if (metadata.language == null) + getString(R.string.no_data) else - "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" + "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" - github_btn.setOnClickListener { + githubBtn.setOnClickListener { if (metadata.repositoryUrl != null) { openBrowser(metadata.repositoryUrl) } @@ -93,10 +111,11 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen if (data.isDownloaded) { // On local plugins page the filepath is provided instead of url. - val plugin = PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] + val plugin = + PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] if (plugin?.openSettings != null && context != null) { - action_settings?.isVisible = true - action_settings.setOnClickListener { + actionSettings.isVisible = true + actionSettings.setOnClickListener { try { plugin.openSettings!!.invoke(requireContext()) } catch (e: Throwable) { @@ -109,10 +128,10 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen } } } else { - action_settings?.isVisible = false + actionSettings.isVisible = false } } else { - action_settings?.isVisible = false + actionSettings.isVisible = false } upvote.setOnClickListener { @@ -136,23 +155,40 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen updateVoting(it) } } + } } private fun updateVoting(value: Int) { val metadata = data.plugin.second - plugin_votes.text = value.toString() - when (metadata.getVoteType()) { - VotingApi.VoteType.UPVOTE -> { - upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary) - downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white) - } - VotingApi.VoteType.DOWNVOTE -> { - downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary) - upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white) - } - VotingApi.VoteType.NONE -> { - upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white) - downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white) + binding?.apply { + pluginVotes.text = value.toString() + when (metadata.getVoteType()) { + VotingApi.VoteType.UPVOTE -> { + upvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary + ) + downvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.white) ?: R.color.white + ) + } + + VotingApi.VoteType.DOWNVOTE -> { + downvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary + ) + upvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.white) ?: R.color.white + ) + } + + VotingApi.VoteType.NONE -> { + upvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.white) ?: R.color.white + ) + downvote.imageTintList = ColorStateList.valueOf( + context?.colorFromAttribute(R.attr.white) ?: R.color.white + ) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt index d04e2379..83480542 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt @@ -10,19 +10,20 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.ProviderTestItemBinding import com.lagradost.cloudstream3.mvvm.getAllMessages import com.lagradost.cloudstream3.mvvm.getStackTracePretty import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.TestingUtils -import kotlinx.android.synthetic.main.provider_test_item.view.* class TestResultAdapter(override val items: MutableList>) : AppUtils.DiffAdapter>(items) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return ProviderTestViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.provider_test_item, parent, false), + ProviderTestItemBinding.inflate(LayoutInflater.from(parent.context), parent,false) + //LayoutInflater.from(parent.context) + // .inflate(R.layout.provider_test_item, parent, false), ) } @@ -35,12 +36,12 @@ class TestResultAdapter(override val items: MutableList - val suffix = "dp" - val elevationTypes = listOf( - Pair(0, textView.context.getString(R.string.none)), - Pair(10, "10$suffix"), - Pair(20, "20$suffix"), - Pair(30, "30$suffix"), - Pair(40, "40$suffix"), - Pair(50, "50$suffix"), - Pair(60, "60$suffix"), - Pair(70, "70$suffix"), - Pair(80, "80$suffix"), - Pair(90, "90$suffix"), - Pair(100, "100$suffix"), - ) - - //showBottomDialog - activity?.showDialog( - elevationTypes.map { it.second }, - elevationTypes.map { it.first }.indexOf(state.elevation), - (textView as TextView).text.toString(), - false, - dismissCallback - ) { index -> - state.elevation = elevationTypes.map { it.first }[index] - textView.context.updateState() + val dismissCallback = { if (hide) activity?.hideSystemUI() } - } - subs_subtitle_elevation.setOnLongClickListener { - state.elevation = DEF_SUBS_ELEVATION - it.context.updateState() - showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) - return@setOnLongClickListener true - } + subsSubtitleElevation.setFocusableInTv() + subsSubtitleElevation.setOnClickListener { textView -> + val suffix = "dp" + val elevationTypes = listOf( + Pair(0, textView.context.getString(R.string.none)), + Pair(10, "10$suffix"), + Pair(20, "20$suffix"), + Pair(30, "30$suffix"), + Pair(40, "40$suffix"), + Pair(50, "50$suffix"), + Pair(60, "60$suffix"), + Pair(70, "70$suffix"), + Pair(80, "80$suffix"), + Pair(90, "90$suffix"), + Pair(100, "100$suffix"), + ) - subs_edge_type.setFocusableInTv() - subs_edge_type.setOnClickListener { textView -> - val edgeTypes = listOf( - Pair( - CaptionStyleCompat.EDGE_TYPE_NONE, - textView.context.getString(R.string.subtitles_none) - ), - Pair( - CaptionStyleCompat.EDGE_TYPE_OUTLINE, - textView.context.getString(R.string.subtitles_outline) - ), - Pair( - CaptionStyleCompat.EDGE_TYPE_DEPRESSED, - textView.context.getString(R.string.subtitles_depressed) - ), - Pair( - CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW, - textView.context.getString(R.string.subtitles_shadow) - ), - Pair( - CaptionStyleCompat.EDGE_TYPE_RAISED, - textView.context.getString(R.string.subtitles_raised) - ), - ) - - //showBottomDialog - activity?.showDialog( - edgeTypes.map { it.second }, - edgeTypes.map { it.first }.indexOf(state.edgeType), - (textView as TextView).text.toString(), - false, - dismissCallback - ) { index -> - state.edgeType = edgeTypes.map { it.first }[index] - textView.context.updateState() - } - } - - subs_edge_type.setOnLongClickListener { - state.edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE - 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 -> - val suffix = "sp" - val fontSizes = listOf( - Pair(null, textView.context.getString(R.string.normal)), - Pair(6f, "6$suffix"), - Pair(7f, "7$suffix"), - Pair(8f, "8$suffix"), - Pair(9f, "9$suffix"), - Pair(10f, "10$suffix"), - Pair(11f, "11$suffix"), - Pair(12f, "12$suffix"), - Pair(13f, "13$suffix"), - Pair(14f, "14$suffix"), - Pair(15f, "15$suffix"), - Pair(16f, "16$suffix"), - Pair(17f, "17$suffix"), - Pair(18f, "18$suffix"), - Pair(19f, "19$suffix"), - Pair(20f, "20$suffix"), - Pair(21f, "21$suffix"), - Pair(22f, "22$suffix"), - Pair(23f, "23$suffix"), - Pair(24f, "24$suffix"), - Pair(25f, "25$suffix"), - Pair(26f, "26$suffix"), - Pair(28f, "28$suffix"), - Pair(30f, "30$suffix"), - Pair(32f, "32$suffix"), - Pair(34f, "34$suffix"), - Pair(36f, "36$suffix"), - Pair(38f, "38$suffix"), - Pair(40f, "40$suffix"), - Pair(42f, "42$suffix"), - Pair(44f, "44$suffix"), - Pair(48f, "48$suffix"), - Pair(60f, "60$suffix"), - ) - - //showBottomDialog - activity?.showDialog( - fontSizes.map { it.second }, - fontSizes.map { it.first }.indexOf(state.fixedTextSize), - (textView as TextView).text.toString(), - false, - dismissCallback - ) { index -> - state.fixedTextSize = fontSizes.map { it.first }[index] - //textView.context.updateState() // font size not changed - } - } - - subtitles_remove_bloat?.isChecked = state.removeBloat - subtitles_remove_bloat?.setOnCheckedChangeListener { _, b -> - state.removeBloat = b - } - subtitles_uppercase?.isChecked = state.upperCase - subtitles_uppercase?.setOnCheckedChangeListener { _, b -> - state.upperCase = b - context?.updateState() - } - - subtitles_remove_captions?.isChecked = state.removeCaptions - subtitles_remove_captions?.setOnCheckedChangeListener { _, b -> - state.removeCaptions = b - } - - subs_font_size.setOnLongClickListener { _ -> - state.fixedTextSize = null - //textView.context.updateState() // font size not changed - showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) - return@setOnLongClickListener true - } - - //Fetch current value from preference - context?.let { ctx -> - subtitles_filter_sub_lang?.isChecked = - PreferenceManager.getDefaultSharedPreferences(ctx) - .getBoolean(getString(R.string.filter_sub_lang_key), false) - } - - subtitles_filter_sub_lang?.setOnCheckedChangeListener { _, b -> - context?.let { ctx -> - PreferenceManager.getDefaultSharedPreferences(ctx) - .edit() - .putBoolean(getString(R.string.filter_sub_lang_key), b) - .apply() - } - } - - subs_font.setFocusableInTv() - subs_font.setOnClickListener { textView -> - val fontTypes = listOf( - Pair(null, textView.context.getString(R.string.normal)), - Pair(R.font.trebuchet_ms, "Trebuchet MS"), - Pair(R.font.netflix_sans, "Netflix Sans"), - Pair(R.font.google_sans, "Google Sans"), - Pair(R.font.open_sans, "Open Sans"), - Pair(R.font.futura, "Futura"), - Pair(R.font.consola, "Consola"), - Pair(R.font.gotham, "Gotham"), - Pair(R.font.lucida_grande, "Lucida Grande"), - Pair(R.font.stix_general, "STIX General"), - Pair(R.font.times_new_roman, "Times New Roman"), - Pair(R.font.verdana, "Verdana"), - Pair(R.font.ubuntu_regular, "Ubuntu"), - Pair(R.font.comic_sans, "Comic Sans"), - Pair(R.font.poppins_regular, "Poppins"), - ) - val savedFontTypes = textView.context.getSavedFonts() - - val currentIndex = - savedFontTypes.indexOfFirst { it.absolutePath == state.typefaceFilePath } - .let { index -> - if (index == -1) - fontTypes.indexOfFirst { it.first == state.typeface } - else index + fontTypes.size - } - - //showBottomDialog - activity?.showDialog( - fontTypes.map { it.second } + savedFontTypes.map { it.name }, - currentIndex, - (textView as TextView).text.toString(), - false, - dismissCallback - ) { index -> - if (index < fontTypes.size) { - state.typeface = fontTypes[index].first - state.typefaceFilePath = null - } else { - state.typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath - state.typeface = null + //showBottomDialog + activity?.showDialog( + elevationTypes.map { it.second }, + elevationTypes.map { it.first }.indexOf(state.elevation), + (textView as TextView).text.toString(), + false, + dismissCallback + ) { index -> + state.elevation = elevationTypes.map { it.first }[index] + textView.context.updateState() + if (hide) + activity?.hideSystemUI() } + } + + subsSubtitleElevation.setOnLongClickListener { + state.elevation = DEF_SUBS_ELEVATION + it.context.updateState() + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true + } + + subsEdgeType.setFocusableInTv() + subsEdgeType.setOnClickListener { textView -> + val edgeTypes = listOf( + Pair( + CaptionStyleCompat.EDGE_TYPE_NONE, + textView.context.getString(R.string.subtitles_none) + ), + Pair( + CaptionStyleCompat.EDGE_TYPE_OUTLINE, + textView.context.getString(R.string.subtitles_outline) + ), + Pair( + CaptionStyleCompat.EDGE_TYPE_DEPRESSED, + textView.context.getString(R.string.subtitles_depressed) + ), + Pair( + CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW, + textView.context.getString(R.string.subtitles_shadow) + ), + Pair( + CaptionStyleCompat.EDGE_TYPE_RAISED, + textView.context.getString(R.string.subtitles_raised) + ), + ) + + //showBottomDialog + activity?.showDialog( + edgeTypes.map { it.second }, + edgeTypes.map { it.first }.indexOf(state.edgeType), + (textView as TextView).text.toString(), + false, + dismissCallback + ) { index -> + state.edgeType = edgeTypes.map { it.first }[index] + textView.context.updateState() + } + } + + subsEdgeType.setOnLongClickListener { + state.edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE + it.context.updateState() + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true + } + + subsFontSize.setFocusableInTv() + subsFontSize.setOnClickListener { textView -> + val suffix = "sp" + val fontSizes = listOf( + Pair(null, textView.context.getString(R.string.normal)), + Pair(6f, "6$suffix"), + Pair(7f, "7$suffix"), + Pair(8f, "8$suffix"), + Pair(9f, "9$suffix"), + Pair(10f, "10$suffix"), + Pair(11f, "11$suffix"), + Pair(12f, "12$suffix"), + Pair(13f, "13$suffix"), + Pair(14f, "14$suffix"), + Pair(15f, "15$suffix"), + Pair(16f, "16$suffix"), + Pair(17f, "17$suffix"), + Pair(18f, "18$suffix"), + Pair(19f, "19$suffix"), + Pair(20f, "20$suffix"), + Pair(21f, "21$suffix"), + Pair(22f, "22$suffix"), + Pair(23f, "23$suffix"), + Pair(24f, "24$suffix"), + Pair(25f, "25$suffix"), + Pair(26f, "26$suffix"), + Pair(28f, "28$suffix"), + Pair(30f, "30$suffix"), + Pair(32f, "32$suffix"), + Pair(34f, "34$suffix"), + Pair(36f, "36$suffix"), + Pair(38f, "38$suffix"), + Pair(40f, "40$suffix"), + Pair(42f, "42$suffix"), + Pair(44f, "44$suffix"), + Pair(48f, "48$suffix"), + Pair(60f, "60$suffix"), + ) + + //showBottomDialog + activity?.showDialog( + fontSizes.map { it.second }, + fontSizes.map { it.first }.indexOf(state.fixedTextSize), + (textView as TextView).text.toString(), + false, + dismissCallback + ) { index -> + state.fixedTextSize = fontSizes.map { it.first }[index] + //textView.context.updateState() // font size not changed + } + } + + subtitlesRemoveBloat.isChecked = state.removeBloat + subtitlesRemoveBloat.setOnCheckedChangeListener { _, b -> + state.removeBloat = b + } + subtitlesUppercase.isChecked = state.upperCase + subtitlesUppercase.setOnCheckedChangeListener { _, b -> + state.upperCase = b + context?.updateState() + } + + subtitlesRemoveCaptions.isChecked = state.removeCaptions + subtitlesRemoveCaptions.setOnCheckedChangeListener { _, b -> + state.removeCaptions = b + } + + subsFontSize.setOnLongClickListener { _ -> + state.fixedTextSize = null + //textView.context.updateState() // font size not changed + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true + } + + //Fetch current value from preference + context?.let { ctx -> + subtitlesFilterSubLang.isChecked = + PreferenceManager.getDefaultSharedPreferences(ctx) + .getBoolean(getString(R.string.filter_sub_lang_key), false) + } + + subtitlesFilterSubLang.setOnCheckedChangeListener { _, b -> + context?.let { ctx -> + PreferenceManager.getDefaultSharedPreferences(ctx) + .edit() + .putBoolean(getString(R.string.filter_sub_lang_key), b) + .apply() + } + } + + subsFont.setFocusableInTv() + subsFont.setOnClickListener { textView -> + val fontTypes = listOf( + Pair(null, textView.context.getString(R.string.normal)), + Pair(R.font.trebuchet_ms, "Trebuchet MS"), + Pair(R.font.netflix_sans, "Netflix Sans"), + Pair(R.font.google_sans, "Google Sans"), + Pair(R.font.open_sans, "Open Sans"), + Pair(R.font.futura, "Futura"), + Pair(R.font.consola, "Consola"), + Pair(R.font.gotham, "Gotham"), + Pair(R.font.lucida_grande, "Lucida Grande"), + Pair(R.font.stix_general, "STIX General"), + Pair(R.font.times_new_roman, "Times New Roman"), + Pair(R.font.verdana, "Verdana"), + Pair(R.font.ubuntu_regular, "Ubuntu"), + Pair(R.font.comic_sans, "Comic Sans"), + Pair(R.font.poppins_regular, "Poppins"), + ) + val savedFontTypes = textView.context.getSavedFonts() + + val currentIndex = + savedFontTypes.indexOfFirst { it.absolutePath == state.typefaceFilePath } + .let { index -> + if (index == -1) + fontTypes.indexOfFirst { it.first == state.typeface } + else index + fontTypes.size + } + + //showBottomDialog + activity?.showDialog( + fontTypes.map { it.second } + savedFontTypes.map { it.name }, + currentIndex, + (textView as TextView).text.toString(), + false, + dismissCallback + ) { index -> + if (index < fontTypes.size) { + state.typeface = fontTypes[index].first + state.typefaceFilePath = null + } else { + state.typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath + state.typeface = null + } + textView.context.updateState() + } + } + + subsFont.setOnLongClickListener { textView -> + state.typeface = null + state.typefaceFilePath = null textView.context.updateState() + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true } - } - subs_font.setOnLongClickListener { textView -> - state.typeface = null - state.typefaceFilePath = null - textView.context.updateState() - showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) - return@setOnLongClickListener true - } + subsAutoSelectLanguage.setFocusableInTv() + subsAutoSelectLanguage.setOnClickListener { textView -> + val langMap = arrayListOf( + SubtitleHelper.Language639( + textView.context.getString(R.string.none), + textView.context.getString(R.string.none), + "", + "", + "", + "", + "" + ), + ) + langMap.addAll(SubtitleHelper.languages) - subs_auto_select_language.setFocusableInTv() - subs_auto_select_language.setOnClickListener { textView -> - val langMap = arrayListOf( - SubtitleHelper.Language639( - textView.context.getString(R.string.none), - textView.context.getString(R.string.none), - "", - "", - "", - "", - "" - ), - ) - langMap.addAll(SubtitleHelper.languages) - - val lang639_1 = langMap.map { it.ISO_639_1 } - activity?.showDialog( - langMap.map { it.languageName }, - lang639_1.indexOf(getAutoSelectLanguageISO639_1()), - (textView as TextView).text.toString(), - true, - dismissCallback - ) { index -> - setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index]) + val lang639_1 = langMap.map { it.ISO_639_1 } + activity?.showDialog( + langMap.map { it.languageName }, + lang639_1.indexOf(getAutoSelectLanguageISO639_1()), + (textView as TextView).text.toString(), + true, + dismissCallback + ) { index -> + setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index]) + } } - } - subs_auto_select_language.setOnLongClickListener { - setKey(SUBTITLE_AUTO_SELECT_KEY, "en") - showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) - return@setOnLongClickListener true - } - - subs_download_languages.setFocusableInTv() - subs_download_languages.setOnClickListener { textView -> - val langMap = SubtitleHelper.languages - val lang639_1 = langMap.map { it.ISO_639_1 } - val keys = getDownloadSubsLanguageISO639_1() - val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 } - - activity?.showMultiDialog( - langMap.map { it.languageName }, - keyMap, - (textView as TextView).text.toString(), - dismissCallback - ) { indexList -> - setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList()) + subsAutoSelectLanguage.setOnLongClickListener { + setKey(SUBTITLE_AUTO_SELECT_KEY, "en") + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true } - } - subs_download_languages.setOnLongClickListener { - setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en")) + subsDownloadLanguages.setFocusableInTv() + subsDownloadLanguages.setOnClickListener { textView -> + val langMap = SubtitleHelper.languages + val lang639_1 = langMap.map { it.ISO_639_1 } + val keys = getDownloadSubsLanguageISO639_1() + val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 } - showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) - return@setOnLongClickListener true - } + activity?.showMultiDialog( + langMap.map { it.languageName }, + keyMap, + (textView as TextView).text.toString(), + dismissCallback + ) { indexList -> + setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList()) + } + } - cancel_btt.setOnClickListener { - activity?.popCurrentPage() - } + subsDownloadLanguages.setOnLongClickListener { + setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en")) - apply_btt.setOnClickListener { - it.context.saveStyle(state) - applyStyleEvent.invoke(state) - it.context.fromSaveToStyle(state) - activity?.popCurrentPage() + showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) + return@setOnLongClickListener true + } + + cancelBtt.setOnClickListener { + activity?.popCurrentPage() + } + + applyBtt.setOnClickListener { + it.context.saveStyle(state) + applyStyleEvent.invoke(state) + it.context.fromSaveToStyle(state) + activity?.popCurrentPage() + } } } } From 04f52f4a6d3eab60b714479e3bdc647d54439209 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sat, 15 Jul 2023 03:25:32 +0200 Subject: [PATCH 05/31] added tests for layout --- app/build.gradle.kts | 1 + .../cloudstream3/ExampleInstrumentedTest.kt | 63 +++++++++++++++++++ .../lagradost/cloudstream3/MainActivity.kt | 17 ++++- .../ui/home/HomeChildItemAdapter.kt | 2 +- .../ui/home/HomeParentItemAdapterPreview.kt | 19 +++++- .../res/layout/home_result_grid_expanded.xml | 13 +++- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d5364045..288add26 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -142,6 +142,7 @@ dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.3") androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test:core") //implementation("io.karn:khttp-android:0.1.2") //okhttp instead // implementation("org.jsoup:jsoup:1.13.1") diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 92042d60..f28018d1 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -1,6 +1,19 @@ package com.lagradost.cloudstream3 +import android.app.Activity +import android.os.Bundle +import android.os.PersistableBundle +import android.view.LayoutInflater +import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.viewbinding.ViewBinding +import com.lagradost.cloudstream3.databinding.FragmentHomeBinding +import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding +import com.lagradost.cloudstream3.databinding.FragmentSearchBinding +import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding +import com.lagradost.cloudstream3.databinding.HomeResultGridBinding +import com.lagradost.cloudstream3.databinding.RepositoryItemBinding +import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.runBlocking @@ -8,11 +21,18 @@ import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith + /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ +class TestApplication : Activity() { + override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + super.onCreate(savedInstanceState, persistentState) + } +} + @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { private fun getAllProviders(): List { @@ -26,6 +46,49 @@ class ExampleInstrumentedTest { println("Done providersExist") } + @Throws + private inline fun testAllLayouts( + activity: Activity, + vararg layouts: Int + ) { + + val bind = T::class.java.methods.first { it.name == "bind" } + val inflater = LayoutInflater.from(activity) + for (layout in layouts) { + val root = inflater.inflate(layout, null, false) + bind.invoke(null, root) + } + } + + @Test + @Throws + fun layoutTest() { + ActivityScenario.launch(MainActivity::class.java).use { scenario -> + scenario.onActivity { activity: MainActivity -> + // FragmentHomeHeadBinding and FragmentHomeHeadTvBinding CANT be the same + //testAllLayouts(activity, R.layout.fragment_home_head, R.layout.fragment_home_head_tv) + //testAllLayouts(activity, R.layout.fragment_home_head, R.layout.fragment_home_head_tv) + + // main cant be tested + // testAllLayouts(activity,R.layout.activity_main, R.layout.activity_main_tv) + // testAllLayouts(activity,R.layout.activity_main, R.layout.activity_main_tv) + //testAllLayouts(activity, R.layout.activity_main_tv) + + testAllLayouts(activity, R.layout.repository_item_tv, R.layout.repository_item) + testAllLayouts(activity, R.layout.repository_item_tv, R.layout.repository_item) + + testAllLayouts(activity, R.layout.fragment_home_tv, R.layout.fragment_home) + testAllLayouts(activity, R.layout.fragment_home_tv, R.layout.fragment_home) + + testAllLayouts(activity, R.layout.fragment_search_tv, R.layout.fragment_search) + testAllLayouts(activity, R.layout.fragment_search_tv, R.layout.fragment_search) + + testAllLayouts(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid) + //testAllLayouts(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid) ??? fails ??? + } + } + } + @Test @Throws(AssertionError::class) fun providerCorrectData() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 4a7a28ad..c1223415 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -97,6 +97,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus +import com.lagradost.cloudstream3.utils.BackupUtils.backup import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main @@ -753,13 +754,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { if (isCastApiAvailable()) { mSessionManager = CastContext.getSharedInstance(this).sessionManager } - } catch (e: Exception) { - logError(e) + } catch (t: Throwable) { + logError(t) } window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) updateTv() + // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? + try { + val appVer = BuildConfig.VERSION_NAME + val lastAppAutoBackup = getKey("VERSION_NAME") ?: 0 + if (appVer != lastAppAutoBackup) { + setKey("VERSION_NAME", BuildConfig.VERSION_NAME) + backup() + } + } catch (t : Throwable) { + logError(t) + } + // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH binding = try { if (isTvSettings()) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index b90a4e43..1e04acf0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -87,7 +87,7 @@ class HomeChildItemAdapter( else -> null } - (itemView.image_holder ?: itemView.background_card)?.apply { + (itemView.background_card)?.apply { val min = 114.toPx val max = 180.toPx diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 715f1867..9bbfbb37 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -32,15 +32,28 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectSt import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.activity_main.view.* -import kotlinx.android.synthetic.main.fragment_home_head.view.* +import kotlinx.android.synthetic.main.activity_main.view.nav_rail_view +import kotlinx.android.synthetic.main.fragment_home_head.view.home_bookmark_parent_item_title import kotlinx.android.synthetic.main.fragment_home_head.view.home_bookmarked_child_recyclerview +import kotlinx.android.synthetic.main.fragment_home_head.view.home_preview_bookmark +import kotlinx.android.synthetic.main.fragment_home_head.view.home_preview_image +import kotlinx.android.synthetic.main.fragment_home_head.view.home_preview_info +import kotlinx.android.synthetic.main.fragment_home_head.view.home_preview_play +import kotlinx.android.synthetic.main.fragment_home_head.view.home_search import kotlinx.android.synthetic.main.fragment_home_head.view.home_watch_parent_item_title -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_bookmarked_holder import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_none_padding import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_plan_to_watch_btt import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_change_api +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_change_api2 +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_description +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_hidden_next_focus +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_hidden_prev_focus +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_info_btt +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_play_btt +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_tags +import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_text import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_completed_btt import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_dropped_btt diff --git a/app/src/main/res/layout/home_result_grid_expanded.xml b/app/src/main/res/layout/home_result_grid_expanded.xml index b697c1de..3c3804a5 100644 --- a/app/src/main/res/layout/home_result_grid_expanded.xml +++ b/app/src/main/res/layout/home_result_grid_expanded.xml @@ -7,7 +7,7 @@ + Date: Sat, 15 Jul 2023 03:27:25 +0200 Subject: [PATCH 06/31] mini fix --- app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c1223415..a2a24243 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -764,7 +764,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? try { val appVer = BuildConfig.VERSION_NAME - val lastAppAutoBackup = getKey("VERSION_NAME") ?: 0 + val lastAppAutoBackup : String = getKey("VERSION_NAME") ?: "" if (appVer != lastAppAutoBackup) { setKey("VERSION_NAME", BuildConfig.VERSION_NAME) backup() From f209c7286e34e2640238c5d240170c8569b506da Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sat, 15 Jul 2023 20:00:09 +0200 Subject: [PATCH 07/31] more viewbindings + result fix + more tests --- .../cloudstream3/ExampleInstrumentedTest.kt | 17 ++++ .../ui/home/HomeChildItemAdapter.kt | 22 ++--- .../ui/home/HomeParentItemAdapter.kt | 31 +++--- .../ui/home/HomeParentItemAdapterPreview.kt | 5 +- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 59 +++++++----- .../cloudstream3/ui/library/PageAdapter.kt | 12 +-- .../ui/library/ViewpagerAdapter.kt | 2 +- .../ui/search/SearchResultBuilder.kt | 46 +++++---- .../utils/SingleSelectionHelper.kt | 95 +++++++++++++------ .../main/res/layout/search_result_grid.xml | 22 ++++- .../layout/search_result_grid_expanded.xml | 36 +++---- app/src/main/res/values/styles.xml | 7 +- 12 files changed, 214 insertions(+), 140 deletions(-) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index f28018d1..68418704 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -12,8 +12,14 @@ import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding import com.lagradost.cloudstream3.databinding.FragmentSearchBinding import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding +import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding +import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding +import com.lagradost.cloudstream3.databinding.HomepageParentBinding +import com.lagradost.cloudstream3.databinding.HomepageParentTvBinding import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding +import com.lagradost.cloudstream3.databinding.SearchResultGridBinding +import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.runBlocking @@ -85,6 +91,17 @@ class ExampleInstrumentedTest { testAllLayouts(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid) //testAllLayouts(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid) ??? fails ??? + + testAllLayouts(activity, R.layout.search_result_grid, R.layout.search_result_grid_expanded) + testAllLayouts(activity, R.layout.search_result_grid, R.layout.search_result_grid_expanded) + + + // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) + // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) + + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index 1e04acf0..92bc242d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -1,22 +1,20 @@ package com.lagradost.cloudstream3.ui.home import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.home_result_grid.view.background_card -import kotlinx.android.synthetic.main.home_result_grid_expanded.view.* class HomeChildItemAdapter( val cardList: MutableList, - private val overrideLayout: Int? = null, + private val nextFocusUp: Int? = null, private val nextFocusDown: Int? = null, private val clickCallback: (SearchClickCallback) -> Unit, @@ -26,11 +24,13 @@ class HomeChildItemAdapter( var hasNext: Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val layout = overrideLayout - ?: if (parent.context.IsBottomLayout()) R.layout.home_result_grid_expanded else R.layout.home_result_grid + val layout = if (parent.context.IsBottomLayout()) R.layout.home_result_grid_expanded else R.layout.home_result_grid + + val root = LayoutInflater.from(parent.context).inflate(layout, parent, false) + val binding = HomeResultGridBinding.bind(root) return CardViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false), + binding, clickCallback, itemCount, nextFocusUp, @@ -69,14 +69,14 @@ class HomeChildItemAdapter( class CardViewHolder constructor( - itemView: View, + val binding: HomeResultGridBinding, private val clickCallback: (SearchClickCallback) -> Unit, var itemCount: Int, private val nextFocusUp: Int? = null, private val nextFocusDown: Int? = null, private val isHorizontal: Boolean = false ) : - RecyclerView.ViewHolder(itemView) { + RecyclerView.ViewHolder(binding.root) { fun bind(card: SearchResponse, position: Int) { @@ -87,7 +87,7 @@ class HomeChildItemAdapter( else -> null } - (itemView.background_card)?.apply { + binding.backgroundCard.apply { val min = 114.toPx val max = 180.toPx @@ -119,7 +119,7 @@ class HomeChildItemAdapter( itemView.tag = position if (position == 0) { // to fix tv - itemView.background_card?.nextFocusLeftId = R.id.nav_rail_view + binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view } //val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f) //ani.fillAfter = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 7ce9e67d..d05b4cab 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -10,18 +10,12 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.HomepageParentBinding import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable -import kotlinx.android.synthetic.main.activity_main_tv.* -import kotlinx.android.synthetic.main.activity_main_tv.view.* -import kotlinx.android.synthetic.main.fragment_home.* -import kotlinx.android.synthetic.main.fragment_home.view.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* -import kotlinx.android.synthetic.main.homepage_parent.view.* class LoadClickCallback( val action: Int = 0, @@ -37,12 +31,17 @@ open class ParentItemAdapter( private val expandCallback: ((String) -> Unit)? = null, ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + + val root = LayoutInflater.from(parent.context).inflate( + if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent, + parent, + false + ) + + val binding = HomepageParentBinding.bind(root) + return ParentViewHolder( - LayoutInflater.from(parent.context).inflate( - if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent, - parent, - false - ), + binding, clickCallback, moreInfoClickCallback, expandCallback @@ -153,14 +152,14 @@ open class ParentItemAdapter( class ParentViewHolder constructor( - itemView: View, + val binding: HomepageParentBinding, private val clickCallback: (SearchClickCallback) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, ) : - RecyclerView.ViewHolder(itemView) { - val title: TextView = itemView.home_child_more_info - private val recyclerView: RecyclerView = itemView.home_child_recyclerview + RecyclerView.ViewHolder(binding.root) { + val title: TextView = binding.homeChildMoreInfo + private val recyclerView: RecyclerView = binding.homeChildRecyclerview fun update(expand: HomeViewModel.ExpandableHomepageList) { val info = expand.list diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 9bbfbb37..fffe590e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -409,10 +409,7 @@ class HomeParentItemAdapterPreview( // setPageTransformer(null) if (adapter == null) - adapter = HomeScrollAdapter( - if (isTvSettings()) R.layout.home_scroll_view_tv else R.layout.home_scroll_view, - if (isTvSettings()) true else null - ) + adapter = HomeScrollAdapter() } previewAdapter = previewViewpager?.adapter as? HomeScrollAdapter? // previewViewpager?.registerOnPageChangeCallback(previewCallback) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 5902132e..c54996c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -2,24 +2,18 @@ package com.lagradost.cloudstream3.ui.home import android.content.res.Configuration import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.annotation.LayoutRes import androidx.core.view.isGone import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.LoadResponse -import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding +import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview -import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview_tags -import kotlinx.android.synthetic.main.home_scroll_view.view.home_scroll_preview_title - -class HomeScrollAdapter( - @LayoutRes val layout: Int = R.layout.home_scroll_view, - private val forceHorizontalPosters: Boolean? = null -) : RecyclerView.Adapter() { +class HomeScrollAdapter : RecyclerView.Adapter() { private var items: MutableList = mutableListOf() var hasMoreItems: Boolean = false @@ -45,9 +39,16 @@ class HomeScrollAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = if(isTvSettings()) { + HomeScrollViewBinding.inflate(inflater,parent,false) + } else { + HomeScrollViewTvBinding.inflate(inflater,parent,false) + } + return CardViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false), - forceHorizontalPosters + binding, + //forceHorizontalPosters ) } @@ -61,22 +62,30 @@ class HomeScrollAdapter( class CardViewHolder constructor( - itemView: View, - private val forceHorizontalPosters: Boolean? = null + val binding: ViewBinding, + //private val forceHorizontalPosters: Boolean? = null ) : - RecyclerView.ViewHolder(itemView) { + RecyclerView.ViewHolder(binding.root) { fun bind(card: LoadResponse) { - card.apply { - val isHorizontal = - (forceHorizontalPosters == true) || ((forceHorizontalPosters != false) && itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) + val isHorizontal = + binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE - val posterUrl = if (isHorizontal) backgroundPosterUrl ?: posterUrl else posterUrl - ?: backgroundPosterUrl - itemView.home_scroll_preview_tags?.text = tags?.joinToString(" • ") ?: "" - itemView.home_scroll_preview_tags?.isGone = tags.isNullOrEmpty() - itemView.home_scroll_preview?.setImage(posterUrl, posterHeaders) - itemView.home_scroll_preview_title?.text = name + val posterUrl = if (isHorizontal) card.backgroundPosterUrl ?: card.posterUrl else card.posterUrl + ?: card.backgroundPosterUrl + + when(binding) { + is HomeScrollViewBinding -> { + binding.homeScrollPreview.setImage(posterUrl) + binding.homeScrollPreviewTags.apply { + text = card.tags?.joinToString(" • ") ?: "" + isGone = card.tags.isNullOrEmpty() + } + binding.homeScrollPreviewTitle.text = card.name + } + is HomeScrollViewTvBinding -> { + binding.homeScrollPreview.setImage(posterUrl) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt index 05b05f44..d558d6a5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt @@ -84,7 +84,7 @@ class PageAdapter( binding.textRating.apply { setTextColor(ColorStateList.valueOf(fg)) } - binding.textRatingHolder.backgroundTintList = ColorStateList.valueOf(bg) + binding.textRating.backgroundTintList = ColorStateList.valueOf(bg) binding.watchProgress.apply { progressTintList = ColorStateList.valueOf(fg) progressBackgroundTintList = ColorStateList.valueOf(bg) @@ -111,16 +111,6 @@ class PageAdapter( } binding.imageText.text = item.name - - val showRating = (item.personalRating ?: 0) != 0 - 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", "") - - binding.textRating.text = "★ $rating" - } } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 441d6adc..95fefcbe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -64,7 +64,7 @@ class ViewpagerAdapter( } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> + setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> val diff = scrollY - oldScrollY if (diff == 0) return@setOnScrollChangeListener diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 69812f22..2b2269ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -16,22 +16,13 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.isMovieType +import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.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 = mutableMapOf() @@ -59,19 +50,21 @@ object SearchResultBuilder { nextFocusDown: Int? = null, colorCallback : ((Palette) -> Unit)? = null ) { - val cardView: ImageView = itemView.imageView - val cardText: TextView? = itemView.imageText + val cardView: ImageView = itemView.findViewById(R.id.imageView) + val cardText: TextView? = itemView.findViewById(R.id.imageText) - val textIsDub: TextView? = itemView.text_is_dub - val textIsSub: TextView? = itemView.text_is_sub - val textFlag: TextView? = itemView.text_flag - val textQuality: TextView? = itemView.text_quality - val shadow: View? = itemView.title_shadow + val textIsDub: TextView? = itemView.findViewById(R.id.text_is_dub) + val textIsSub: TextView? = itemView.findViewById(R.id.text_is_sub) + val textFlag: TextView? = itemView.findViewById(R.id.text_flag) + val rating: TextView? = itemView.findViewById(R.id.text_rating) - val bg: CardView = itemView.background_card + val textQuality: TextView? = itemView.findViewById(R.id.text_quality) + val shadow: View? = itemView.findViewById(R.id.title_shadow) - val bar: ProgressBar? = itemView.watchProgress - val playImg: ImageView? = itemView.search_item_download_play + val bg: CardView = itemView.findViewById(R.id.background_card) + + val bar: ProgressBar? = itemView.findViewById(R.id.watchProgress) + val playImg: ImageView? = itemView.findViewById(R.id.search_item_download_play) // Do logic @@ -80,12 +73,25 @@ object SearchResultBuilder { textIsDub?.isVisible = false textIsSub?.isVisible = false textFlag?.isVisible = false + rating?.isVisible = false val showSub = showCache[textIsDub?.context?.getString(R.string.show_sub_key)] ?: false val showDub = showCache[textIsDub?.context?.getString(R.string.show_dub_key)] ?: false val showTitle = showCache[cardText?.context?.getString(R.string.show_title_key)] ?: false val showHd = showCache[textQuality?.context?.getString(R.string.show_hd_key)] ?: false + if(card is SyncAPI.LibraryItem) { + val showRating = (card.personalRating ?: 0) != 0 + rating?.isVisible = showRating + if (showRating) { + // We want to show 8.5 but not 8.0 hence the replace + val ratingText = ((card.personalRating ?: 0).toDouble() / 10).toString() + .replace(".0", "") + + rating?.text = ratingText + } + } + shadow?.isVisible = showTitle when (card.quality) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 1f6d726d..8285b8ab 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -2,19 +2,28 @@ package com.lagradost.cloudstream3.utils import android.app.Activity import android.app.Dialog +import android.view.LayoutInflater import android.view.View -import android.widget.* +import android.widget.AbsListView +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ListView +import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.core.view.* +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.view.marginLeft +import androidx.core.view.marginRight +import androidx.core.view.marginTop import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.add_account_input.* -import kotlinx.android.synthetic.main.add_account_input.text1 -import kotlinx.android.synthetic.main.bottom_selection_dialog_direct.* object SingleSelectionHelper { fun Activity?.showOptionSelectStringRes( @@ -82,6 +91,7 @@ object SingleSelectionHelper { } fun Activity?.showDialog( + binding: BottomSelectionDialogBinding, dialog: Dialog, items: List, selectedIndex: List, @@ -95,39 +105,39 @@ object SingleSelectionHelper { if (this == null) return val realShowApply = showApply || isMultiSelect - val listView = dialog.listview1//.findViewById(R.id.listview1)!! - val textView = dialog.text1//.findViewById(R.id.text1)!! - val applyButton = dialog.apply_btt//.findViewById(R.id.apply_btt) - val cancelButton = dialog.cancel_btt//findViewById(R.id.cancel_btt) + val listView = binding.listview1//.findViewById(R.id.listview1)!! + val textView = binding.text1//.findViewById(R.id.text1)!! + val applyButton = binding.applyBtt//.findViewById(R.id.apply_btt) + val cancelButton = binding.cancelBtt//findViewById(R.id.cancel_btt) val applyHolder = - dialog.apply_btt_holder//.findViewById(R.id.apply_btt_holder) + binding.applyBttHolder//.findViewById(R.id.apply_btt_holder) - applyHolder?.isVisible = realShowApply + applyHolder.isVisible = realShowApply if (!realShowApply) { val params = listView.layoutParams as LinearLayout.LayoutParams params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0) listView.layoutParams = params } - textView?.text = name - textView?.isGone = name.isBlank() + textView.text = name + textView.isGone = name.isBlank() val arrayAdapter = ArrayAdapter(this, itemLayout) arrayAdapter.addAll(items) - listView?.adapter = arrayAdapter + listView.adapter = arrayAdapter if (isMultiSelect) { - listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE + listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE } else { - listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE + listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE } for (select in selectedIndex) { - listView?.setItemChecked(select, true) + listView.setItemChecked(select, true) } selectedIndex.minOrNull()?.let { - listView?.setSelection(it) + listView.setSelection(it) } // var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1 @@ -136,7 +146,7 @@ object SingleSelectionHelper { dismissCallback.invoke() } - listView?.setOnItemClickListener { _, _, which, _ -> + listView.setOnItemClickListener { _, _, which, _ -> // lastSelectedIndex = which if (realShowApply) { if (!isMultiSelect) { @@ -148,7 +158,7 @@ object SingleSelectionHelper { } } if (realShowApply) { - applyButton?.setOnClickListener { + applyButton.setOnClickListener { val list = ArrayList() for (index in 0 until listView.count) { if (listView.checkedItemPositions[index]) @@ -157,7 +167,7 @@ object SingleSelectionHelper { callback.invoke(list) dialog.dismissSafe(this) } - cancelButton?.setOnClickListener { + cancelButton.setOnClickListener { dialog.dismissSafe(this) } } @@ -213,13 +223,26 @@ object SingleSelectionHelper { ) { if (this == null) return + val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate( + LayoutInflater.from(this) + ) val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) - .setView(R.layout.bottom_selection_dialog) + .setView(binding.root) val dialog = builder.create() dialog.show() - showDialog(dialog, items, selectedIndex, name, true, true, callback, dismissCallback) + showDialog( + binding, + dialog, + items, + selectedIndex, + name, + showApply = true, + isMultiSelect = true, + callback, + dismissCallback + ) } fun Activity?.showDialog( @@ -232,13 +255,19 @@ object SingleSelectionHelper { ) { if (this == null) return + val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate( + LayoutInflater.from(this) + ) val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) - .setView(R.layout.bottom_selection_dialog) + .setView(binding.root) val dialog = builder.create() dialog.show() + + showDialog( + binding, dialog, items, listOf(selectedIndex), @@ -260,12 +289,18 @@ object SingleSelectionHelper { callback: (Int) -> Unit, ) { if (this == null) return + + val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate( + LayoutInflater.from(this) + ) + val builder = BottomSheetDialog(this) - builder.setContentView(R.layout.bottom_selection_dialog) + builder.setContentView(binding.root) builder.show() showDialog( + binding, builder, items, listOf(selectedIndex), @@ -285,13 +320,19 @@ object SingleSelectionHelper { ): BottomSheetDialog { val builder = BottomSheetDialog(this) - builder.setContentView(R.layout.bottom_selection_dialog_direct) + val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate( + LayoutInflater.from(this) + ) + + //builder.setContentView(R.layout.bottom_selection_dialog_direct) + builder.setContentView(binding.root) builder.show() showDialog( + binding, builder, items, - listOf(), + emptyList(), name, showApply = false, isMultiSelect = false, diff --git a/app/src/main/res/layout/search_result_grid.xml b/app/src/main/res/layout/search_result_grid.xml index f3c35ca4..cec7d4ce 100644 --- a/app/src/main/res/layout/search_result_grid.xml +++ b/app/src/main/res/layout/search_result_grid.xml @@ -1,5 +1,5 @@ - + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/search_result_grid_expanded.xml b/app/src/main/res/layout/search_result_grid_expanded.xml index cf6ab3b2..c64486b4 100644 --- a/app/src/main/res/layout/search_result_grid_expanded.xml +++ b/app/src/main/res/layout/search_result_grid_expanded.xml @@ -58,29 +58,12 @@ style="@style/SubButton" android:layout_gravity="end" /> - - - - - + tools:text="7.7" /> + @color/subColorText + + + + + + + + @@ -547,6 +548,7 @@ 0dp 0dp @drawable/outline_drawable_less + @string/tv_no_focus_tag From c987f7581e590a8f69f525fd8246f5a15224577a Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 28 Jul 2023 04:18:28 +0200 Subject: [PATCH 30/31] major focus fixes --- .../lagradost/cloudstream3/CommonActivity.kt | 79 ++++++++++++------- .../cloudstream3/ui/AutofitRecyclerView.kt | 25 ++++-- .../ui/home/HomeChildItemAdapter.kt | 42 +++++++--- .../ui/home/HomeParentItemAdapter.kt | 4 +- .../ui/home/HomeParentItemAdapterPreview.kt | 4 + .../cloudstream3/ui/result/ActorAdaptor.kt | 12 ++- .../ui/result/LinearListLayout.kt | 24 +++--- .../ui/result/ResultFragmentTv.kt | 34 ++++---- .../ui/search/SearchResultBuilder.kt | 57 ++++++++----- .../drawable/ic_baseline_arrow_back_24.xml | 1 + .../ic_baseline_arrow_back_ios_24.xml | 14 +++- .../drawable/ic_baseline_arrow_forward_24.xml | 1 + .../ic_baseline_keyboard_arrow_left_24.xml | 14 +++- .../res/drawable/ic_baseline_language_24.xml | 13 ++- .../res/drawable/ic_baseline_more_vert_24.xml | 13 ++- .../ic_baseline_notifications_active_24.xml | 13 ++- .../main/res/drawable/netflix_skip_back.xml | 32 ++++---- .../res/drawable/outline_drawable_forced.xml | 5 ++ app/src/main/res/layout/activity_main_tv.xml | 1 - .../main/res/layout/fragment_home_head_tv.xml | 4 +- .../main/res/layout/fragment_result_tv.xml | 3 +- ...sort_bottom_single_choice_no_checkmark.xml | 12 +-- 22 files changed, 268 insertions(+), 139 deletions(-) create mode 100644 app/src/main/res/drawable/outline_drawable_forced.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 7eb1bf6d..684e2269 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.ui.player.PlayerEventType import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv +import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.UIHelper @@ -299,22 +300,29 @@ object CommonActivity { view: View?, direction: FocusDirection, depth: Int = 0 - ): Int? { + ): View? { + // if input is invalid let android decide + depth test to not crash if loop is found if (view == null || depth >= 10 || act == null) { return null } val nextId = when (direction) { - FocusDirection.Left -> { - view.nextFocusLeftId + FocusDirection.Start -> { + if (view.isRtl()) + view.nextFocusRightId + else + view.nextFocusLeftId } FocusDirection.Up -> { view.nextFocusUpId } - FocusDirection.Right -> { - view.nextFocusRightId + FocusDirection.End -> { + if (view.isRtl()) + view.nextFocusLeftId + else + view.nextFocusRightId } FocusDirection.Down -> { @@ -322,27 +330,41 @@ object CommonActivity { } } - return if (nextId != -1) { - val next = act.findViewById(nextId) - //println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" ) + // if view not found then return + if (nextId == -1) return null + var next = act.findViewById(nextId) ?: return null - if (next?.isShown == false) { - getNextFocus(act, next, direction, depth + 1) - } else { - if (depth == 0) { - null - } else { - nextId - } + // because we want closes find, aka when multiple have the same id, we go to parent + // until the correct one is found + /*var currentLook: View = view + while (true) { + val tmpNext = currentLook.findViewById(nextId) + if (tmpNext != null) { + next = tmpNext + break } - } else { - null + + currentLook = currentLook.parent as? View ?: break + }*/ + + var currentLook: View = view + while (currentLook.findViewById(nextId)?.also { next = it } == null) { + currentLook = (currentLook.parent as? View) ?: break } + + // if cant focus but visible then break and let android decide + if (!next.isFocusable && next.isShown) return null + + // if not shown then continue because we will "skip" over views to get to a replacement + if (!next.isShown) return getNextFocus(act, next, direction, depth + 1) + + // nothing wrong with the view found, return it + return next } enum class FocusDirection { - Left, - Right, + Start, + End, Up, Down, } @@ -447,17 +469,17 @@ object CommonActivity { event?.keyCode?.let { keyCode -> if (currentFocus == null || event.action != KeyEvent.ACTION_DOWN) return@let - val next = when (keyCode) { + val nextView = when (keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus( act, currentFocus, - FocusDirection.Left + FocusDirection.Start ) KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus( act, currentFocus, - FocusDirection.Right + FocusDirection.End ) KeyEvent.KEYCODE_DPAD_UP -> getNextFocus( @@ -475,13 +497,10 @@ object CommonActivity { else -> null } - if (next != null && next != -1) { - val nextView = act.findViewById(next) - if (nextView != null) { - nextView.requestFocus() - keyEventListener?.invoke(Pair(event, true)) - return true - } + if (nextView != null) { + nextView.requestFocus() + keyEventListener?.invoke(Pair(event, true)) + return true } if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete)) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt index b4c07792..28ced48c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt @@ -24,7 +24,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) : } } - override fun onRequestChildFocus( + /*override fun onRequestChildFocus( parent: RecyclerView, state: RecyclerView.State, child: View, @@ -32,13 +32,17 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) : ): Boolean { // android.widget.FrameLayout$LayoutParams cannot be cast to androidx.recyclerview.widget.RecyclerView$LayoutParams return try { - val pos = maxOf(0, getPosition(focused!!) - 2) - parent.scrollToPosition(pos) + if(focused != null) { + // val pos = maxOf(0, getPosition(focused) - 2) // IDK WHY + val pos = getPosition(focused) + if(pos >= 0) parent.scrollToPosition(pos) + } + super.onRequestChildFocus(parent, state, child, focused) } catch (e: Exception) { false } - } + }*/ // Allows moving right and left with focus https://gist.github.com/vganin/8930b41f55820ec49e4d override fun onInterceptFocusSearch(focused: View, direction: Int): View? { @@ -65,8 +69,17 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) : val spanCount = this.spanCount val orientation = this.orientation + // fixes arabic by inverting left and right layout focus + val correctDirection = if(this.isLayoutRTL) { + when(direction) { + View.FOCUS_RIGHT -> View.FOCUS_LEFT + View.FOCUS_LEFT -> View.FOCUS_RIGHT + else -> direction + } + } else direction + if (orientation == VERTICAL) { - when (direction) { + when (correctDirection) { View.FOCUS_DOWN -> { return spanCount } @@ -81,7 +94,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) : } } } else if (orientation == HORIZONTAL) { - when (direction) { + when (correctDirection) { View.FOCUS_DOWN -> { return 1 } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index 8b0f9003..607cda01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchResultBuilder +import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout import com.lagradost.cloudstream3.utils.UIHelper.toPx @@ -27,13 +28,17 @@ class HomeChildItemAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val expanded = parent.context.IsBottomLayout() - /* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid + /* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid - val root = LayoutInflater.from(parent.context).inflate(layout, parent, false) - val binding = HomeResultGridBinding.bind(root)*/ + val root = LayoutInflater.from(parent.context).inflate(layout, parent, false) + val binding = HomeResultGridBinding.bind(root)*/ val inflater = LayoutInflater.from(parent.context) - val binding = if(expanded) HomeResultGridExpandedBinding.inflate(inflater,parent,false) else HomeResultGridBinding.inflate(inflater,parent,false) + val binding = if (expanded) HomeResultGridExpandedBinding.inflate( + inflater, + parent, + false + ) else HomeResultGridBinding.inflate(inflater, parent, false) return CardViewHolder( @@ -42,7 +47,8 @@ class HomeChildItemAdapter( itemCount, nextFocusUp, nextFocusDown, - isHorizontal + isHorizontal, + parent.isRtl() ) } @@ -81,7 +87,8 @@ class HomeChildItemAdapter( var itemCount: Int, private val nextFocusUp: Int? = null, private val nextFocusDown: Int? = null, - private val isHorizontal: Boolean = false + private val isHorizontal: Boolean = false, + private val isRtl : Boolean ) : RecyclerView.ViewHolder(binding.root) { @@ -93,7 +100,23 @@ class HomeChildItemAdapter( itemCount - 1 -> false else -> null } - when(binding) { + + if (position == 0) { // to fix tv + if (isRtl) { + itemView.nextFocusRightId = R.id.nav_rail_view + itemView.nextFocusLeftId = -1 + } + else { + itemView.nextFocusLeftId = R.id.nav_rail_view + itemView.nextFocusRightId = -1 + } + } else { + itemView.nextFocusRightId = -1 + itemView.nextFocusLeftId = -1 + } + + + when (binding) { is HomeResultGridBinding -> { binding.backgroundCard.apply { val min = 114.toPx @@ -114,10 +137,9 @@ class HomeChildItemAdapter( } } - if (position == 0) { // to fix tv - binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view - } + } + is HomeResultGridExpandedBinding -> { binding.backgroundCard.apply { val min = 114.toPx diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index d4c0bd62..f6c3fead 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -177,7 +177,7 @@ open class ParentItemAdapter( ).apply { isHorizontal = info.isHorizontalImages } - //recyclerView.setLinearListLayout() + recyclerView.setLinearListLayout() } } @@ -192,7 +192,7 @@ open class ParentItemAdapter( isHorizontal = info.isHorizontalImages hasNext = expand.hasNext } - // recyclerView.setLinearListLayout() + recyclerView.setLinearListLayout() title.text = info.name recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index a304b43f..ce7b8447 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -427,6 +428,9 @@ class HomeParentItemAdapterPreview( resumeRecyclerView.adapter = resumeAdapter bookmarkRecyclerView.adapter = bookmarkAdapter + resumeRecyclerView.setLinearListLayout() + bookmarkRecyclerView.setLinearListLayout() + for ((chip, watch) in toggleList) { chip.isChecked = false chip.setOnCheckedChangeListener { _, isChecked -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt index 7b415d78..531cb5d2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.result import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil @@ -11,7 +12,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.CastItemBinding import com.lagradost.cloudstream3.utils.UIHelper.setImage -class ActorAdaptor : RecyclerView.Adapter() { +class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerView.Adapter() { data class ActorMetaData( var isInverted: Boolean, val actor: ActorData, @@ -21,7 +22,7 @@ class ActorAdaptor : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return CardViewHolder( - CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), focusCallback ) } @@ -66,6 +67,7 @@ class ActorAdaptor : RecyclerView.Adapter() { private class CardViewHolder constructor( val binding: CastItemBinding, + private val focusCallback : (View?) -> Unit = {} ) : RecyclerView.ViewHolder(binding.root) { @@ -76,6 +78,12 @@ class ActorAdaptor : RecyclerView.Adapter() { Pair(actor.voiceActor?.image, actor.actor.image) } + itemView.setOnFocusChangeListener { v, hasFocus -> + if(hasFocus) { + focusCallback(v) + } + } + itemView.setOnClickListener { callback(position) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index 434355a2..26cb7900 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -2,19 +2,16 @@ package com.lagradost.cloudstream3.ui.result import android.content.Context import android.view.View -import android.view.View.LAYOUT_DIRECTION_LTR import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.utils.AppUtils.isLtr -import com.lagradost.cloudstream3.utils.AppUtils.isRtl fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { if (this == null) return this.layoutManager = this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } - // ?: this.layoutManager + // ?: this.layoutManager } open class LinearListLayout(context: Context?) : @@ -60,7 +57,7 @@ open class LinearListLayout(context: Context?) : startSmoothScroll(linearSmoothScroller) }*/ override fun onInterceptFocusSearch(focused: View, direction: Int): View? { - var dir = if (orientation == HORIZONTAL) { + val dir = if (orientation == HORIZONTAL) { if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { // This scrolls the recyclerview before doing focus search, which // allows the focus search to work better. @@ -70,19 +67,28 @@ open class LinearListLayout(context: Context?) : (focused.parent as? RecyclerView)?.focusSearch(direction) return null } - if (direction == View.FOCUS_RIGHT) 1 else -1 + var ret = if (direction == View.FOCUS_RIGHT) 1 else -1 + // only flip on horizontal layout + if (this.isLayoutRTL) { + ret = -ret + } + ret } else { if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null if (direction == View.FOCUS_DOWN) 1 else -1 } - if(this.isLayoutRTL) { - dir = -dir - } return try { getPosition(getCorrectParent(focused))?.let { position -> val lookfor = dir + position //clamp(dir + position, 0, recyclerView.adapter?.itemCount ?: return null) + + // refocus on the same view if going out of bounds, note that we only do it + // for out of bounds one way as we may override the start where item == -1 + if (lookfor >= itemCount) { + return getViewFromPos(itemCount - 1) ?: focused + } + getViewFromPos(lookfor) ?: run { scrollToPosition(lookfor) null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 69127e86..f62d7e73 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -1,15 +1,12 @@ package com.lagradost.cloudstream3.ui.result import android.animation.Animator -import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.AlphaAnimation -import android.view.animation.Animation import android.view.animation.DecelerateInterpolator import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone @@ -31,20 +28,17 @@ import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup -import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.html -import com.lagradost.cloudstream3.utils.AppUtils.isLtr import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.loadCache -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper @@ -52,10 +46,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.setImage -import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur -import kotlinx.coroutines.delay class ResultFragmentTv : Fragment() { protected lateinit var viewModel: ResultViewModel2 @@ -204,7 +195,7 @@ class ResultFragmentTv : Fragment() { } }) } - this.animate().translationX(if (turnVisible) 0f else 100f).apply { + this.animate().translationX(if (turnVisible) 0f else if(isRtl()) -100.0f else 100f).apply { duration = 200 interpolator = DecelerateInterpolator() } @@ -214,6 +205,13 @@ class ResultFragmentTv : Fragment() { binding?.apply { episodesShadow.fade(show) episodeHolderTv.fade(show) + if(episodesShadow.isRtl()) { + episodesShadow.scaleX = -1.0f + episodesShadow.scaleY = -1.0f + } else { + episodesShadow.scaleX = 1.0f + episodesShadow.scaleY = 1.0f + } } } @@ -238,6 +236,7 @@ class ResultFragmentTv : Fragment() { // ===== ===== ===== binding?.apply { + //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f val leftListener: View.OnFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> @@ -264,6 +263,8 @@ class ResultFragmentTv : Fragment() { toggleEpisodes(!episodeHolderTv.isVisible) } + // resultEpisodes.onFocusChangeListener = leftListener + redirectToPlay.setOnFocusChangeListener { _, hasFocus -> if (!hasFocus) return@setOnFocusChangeListener toggleEpisodes(false) @@ -306,7 +307,7 @@ class ResultFragmentTv : Fragment() { } } - resultEpisodes.setLinearListLayout(false)/*.layoutManager = + resultEpisodes.setLinearListLayout(isHorizontal = false)/*.layoutManager = LinearListLayout(resultEpisodes.context, resultEpisodes.isRtl()).apply { setVertical() }*/ @@ -348,7 +349,10 @@ class ResultFragmentTv : Fragment() { ArrayList(), resultRecommendationsList, ) { callback -> - SearchHelper.handleSearchClickCallback(callback) + if(callback.action == SEARCH_ACTION_FOCUSED) + toggleEpisodes(false) + else + SearchHelper.handleSearchClickCallback(callback) } resultEpisodes.adapter = @@ -381,7 +385,9 @@ class ResultFragmentTv : Fragment() { }.apply { this.orientation = RecyclerView.HORIZONTAL } - resultCastItems.adapter = ActorAdaptor() + resultCastItems.adapter = ActorAdaptor { + toggleEpisodes(false) + } } observeNullable(viewModel.resumeWatching) { resume -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 2b2269ff..e1b72b30 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -162,15 +162,42 @@ object SearchResultBuilder { } } - bg.setOnClickListener { - click(it) + bg.isFocusable = false + bg.isFocusableInTouchMode = false + if(!isTrueTvSettings()) { + bg.setOnClickListener { + click(it) + } + bg.setOnLongClickListener { + longClick(it) + return@setOnLongClickListener true + } } + // + // + // itemView.setOnClickListener { click(it) } - if (nextFocusUp != null) { + itemView.nextFocusUpId = nextFocusUp + } + + if (nextFocusDown != null) { + itemView.nextFocusDownId = nextFocusDown + } + + /*when (nextFocusBehavior) { + true -> itemView.nextFocusLeftId = bg.id + false -> itemView.nextFocusRightId = bg.id + null -> { + bg.nextFocusRightId = -1 + bg.nextFocusLeftId = -1 + } + }*/ + + /*if (nextFocusUp != null) { bg.nextFocusUpId = nextFocusUp } @@ -178,36 +205,26 @@ object SearchResultBuilder { bg.nextFocusDownId = nextFocusDown } - when (nextFocusBehavior) { - true -> bg.nextFocusLeftId = bg.id - false -> bg.nextFocusRightId = bg.id - null -> { - bg.nextFocusRightId = -1 - bg.nextFocusLeftId = -1 - } - } + */ if (isTrueTvSettings()) { - bg.isFocusable = true - bg.isFocusableInTouchMode = true - bg.touchscreenBlocksFocus = false + // bg.isFocusable = true + // bg.isFocusableInTouchMode = true + // bg.touchscreenBlocksFocus = false itemView.isFocusableInTouchMode = true itemView.isFocusable = true } - bg.setOnLongClickListener { - longClick(it) - return@setOnLongClickListener true - } + /**/ itemView.setOnLongClickListener { longClick(it) return@setOnLongClickListener true } - bg.setOnFocusChangeListener { view, b -> + /*bg.setOnFocusChangeListener { view, b -> focus(view, b) - } + }*/ itemView.setOnFocusChangeListener { view, b -> focus(view, b) diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml index ebe459b2..dbda1cc0 100644 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml +++ b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml @@ -3,6 +3,7 @@ android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/white" + android:autoMirrored="true" xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml index 6c3197a6..516df382 100644 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml +++ b/app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml @@ -1,5 +1,11 @@ - - + + diff --git a/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml index 2ec8c110..48ac45e7 100644 --- a/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml +++ b/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml @@ -3,6 +3,7 @@ android:viewportWidth="48" android:viewportHeight="48" android:tint="?attr/white" + android:autoMirrored="true" xmlns:android="http://schemas.android.com/apk/res/android"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml index 916c761c..b67188db 100644 --- a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml @@ -1,5 +1,11 @@ - - + + diff --git a/app/src/main/res/drawable/ic_baseline_language_24.xml b/app/src/main/res/drawable/ic_baseline_language_24.xml index 1749952e..89b47937 100644 --- a/app/src/main/res/drawable/ic_baseline_language_24.xml +++ b/app/src/main/res/drawable/ic_baseline_language_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_baseline_more_vert_24.xml b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml index 249fe2a2..b6908e96 100644 --- a/app/src/main/res/drawable/ic_baseline_more_vert_24.xml +++ b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml b/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml index 2003bfe7..5d6045e7 100644 --- a/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml +++ b/app/src/main/res/drawable/ic_baseline_notifications_active_24.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/netflix_skip_back.xml b/app/src/main/res/drawable/netflix_skip_back.xml index bb63e948..5ad9c1a1 100644 --- a/app/src/main/res/drawable/netflix_skip_back.xml +++ b/app/src/main/res/drawable/netflix_skip_back.xml @@ -1,23 +1,23 @@ + android:width="850.39dp" + android:height="850.39dp" + android:viewportWidth="850.39" + android:viewportHeight="850.39"> + android:fillColor="#00000000" + android:pathData="M143.05,279.28A317.41,317.41 0,0 0,106.3 428c0,176.13 142.77,318.9 318.9,318.9S744.09,604.16 744.09,428 601.32,109.14 425.2,109.14q-14.15,0 -28,1.2" + android:strokeWidth="45" + android:strokeColor="#fff" /> + android:fillColor="#fff" + android:pathData="M483.083,223.108l-111.666,-111.666l25.442,-25.442l111.666,111.666z" /> + android:fillColor="#fff" + android:pathData="M371.421,111.662l111.666,-111.666l25.442,25.442l-111.666,111.666z" /> + android:fillColor="#fff" + android:pathData="M398.087,223.272l-111.666,-111.666l25.442,-25.442l111.666,111.666z" /> + android:fillColor="#fff" + android:pathData="M286.427,111.826l111.666,-111.666l25.442,25.442l-111.666,111.666z" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/outline_drawable_forced.xml b/app/src/main/res/drawable/outline_drawable_forced.xml new file mode 100644 index 00000000..16eba83c --- /dev/null +++ b/app/src/main/res/drawable/outline_drawable_forced.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index 77baf1d3..a70a40cd 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -83,7 +83,6 @@ android:layout_height="match_parent"> @@ -111,7 +111,7 @@ android:nextFocusLeft="@id/home_preview_play_btt" android:nextFocusRight="@id/home_preview_hidden_next_focus" android:nextFocusUp="@id/home_preview_change_api" - android:nextFocusDown="@id/home_watch_parent_item_title" + android:nextFocusDown="@id/home_watch_child_recyclerview" android:text="@string/home_info" app:icon="@drawable/ic_outline_info_24" /> diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 949ef2ef..324935e5 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -537,6 +537,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit + xmlns:tools="http://schemas.android.com/tools" + android:id="@android:id/text1" + style="@style/NoCheckLabel" + android:textColor="?attr/textColor" + android:textStyle="normal" + tools:text="hello" /> From 3bdbb35754a45472aad6383df51511f45c4c0dd5 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sun, 30 Jul 2023 05:05:13 +0200 Subject: [PATCH 31/31] alert fix + synchronized + bump + homepage load fix + small focus change --- app/build.gradle.kts | 2 +- .../cloudstream3/ExampleInstrumentedTest.kt | 6 +- .../lagradost/cloudstream3/CommonActivity.kt | 58 +++++++++----- .../com/lagradost/cloudstream3/MainAPI.kt | 36 ++++++--- .../lagradost/cloudstream3/MainActivity.kt | 76 ++++++++++--------- .../metaproviders/CrossTmdbProvider.kt | 13 ++-- .../metaproviders/MultiAnimeProvider.kt | 17 +++-- .../lagradost/cloudstream3/plugins/Plugin.kt | 16 ++-- .../cloudstream3/plugins/PluginManager.kt | 13 +++- .../cloudstream3/ui/home/HomeFragment.kt | 3 +- .../ui/home/HomeParentItemAdapterPreview.kt | 4 +- .../cloudstream3/ui/home/HomeViewModel.kt | 76 +++++++++++++------ .../ui/library/LibraryFragment.kt | 14 ++-- .../ui/result/ResultViewModel2.kt | 22 +++--- .../cloudstream3/ui/search/SearchViewModel.kt | 4 +- .../ui/settings/SettingsFragment.kt | 3 + .../ui/settings/SettingsGeneral.kt | 5 +- .../ui/settings/SettingsProviders.kt | 6 +- .../ui/settings/testing/TestViewModel.kt | 6 +- .../ui/setup/SetupFragmentExtensions.kt | 2 +- .../ui/setup/SetupFragmentProviderLanguage.kt | 4 +- .../lagradost/cloudstream3/utils/DataStore.kt | 2 + .../lagradost/cloudstream3/utils/SyncUtil.kt | 6 +- .../cloudstream3/utils/TestingUtils.kt | 2 +- .../main/res/layout/fragment_home_head_tv.xml | 22 +++--- app/src/main/res/values/styles.xml | 3 +- 26 files changed, 265 insertions(+), 156 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f4886258..27bd1e48 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,7 +52,7 @@ android { targetSdk = 33 versionCode = 59 - versionName = "4.0.1" + versionName = "4.1.1" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 509ea4b9..df41ef91 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -46,9 +46,9 @@ class TestApplication : Activity() { @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - private fun getAllProviders(): List { + private fun getAllProviders(): Array { println("Providers: ${APIHolder.allProviders.size}") - return APIHolder.allProviders //.filter { !it.usesWebView } + return APIHolder.allProviders.toTypedArray() //.filter { !it.usesWebView } } @Test @@ -147,7 +147,7 @@ class ExampleInstrumentedTest { @Test fun providerCorrectHomepage() { runBlocking { - getAllProviders().amap { api -> + getAllProviders().toList().amap { api -> TestingUtils.testHomepage(api, ::println) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 684e2269..9c7c319e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -9,6 +9,7 @@ import android.content.res.Resources import android.os.Build import android.util.Log import android.view.* +import android.view.View.NO_ID import android.widget.TextView import android.widget.Toast import androidx.activity.ComponentActivity @@ -295,6 +296,30 @@ object CommonActivity { ) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW } + /** because we want closes find, aka when multiple have the same id, we go to parent + until the correct one is found */ + private fun localLook(from: View, id: Int): View? { + if (id == NO_ID) return null + var currentLook: View = from + while (true) { + currentLook.findViewById(id)?.let { return it } + currentLook = (currentLook.parent as? View) ?: break + } + return null + } + /*var currentLook: View = view + while (true) { + val tmpNext = currentLook.findViewById(nextId) + if (tmpNext != null) { + next = tmpNext + break + } + currentLook = currentLook.parent as? View ?: break + }*/ + + /** recursively looks for a next focus up to a depth of 10, + * this is used to override the normal shit focus system + * because this application has a lot of invisible views that messes with some tv devices*/ private fun getNextFocus( act: Activity?, view: View?, @@ -306,7 +331,7 @@ object CommonActivity { return null } - val nextId = when (direction) { + var nextId = when (direction) { FocusDirection.Start -> { if (view.isRtl()) view.nextFocusRightId @@ -330,22 +355,16 @@ object CommonActivity { } } - // if view not found then return - if (nextId == -1) return null + if (nextId == NO_ID) { + // if not specified then use forward id + nextId = view.nextFocusForwardId + // if view is still not found to next focus then return and let android decide + if (nextId == NO_ID) return null + } + var next = act.findViewById(nextId) ?: return null - // because we want closes find, aka when multiple have the same id, we go to parent - // until the correct one is found - /*var currentLook: View = view - while (true) { - val tmpNext = currentLook.findViewById(nextId) - if (tmpNext != null) { - next = tmpNext - break - } - - currentLook = currentLook.parent as? View ?: break - }*/ + next = localLook(view, nextId) ?: next var currentLook: View = view while (currentLook.findViewById(nextId)?.also { next = it } == null) { @@ -362,7 +381,7 @@ object CommonActivity { return next } - enum class FocusDirection { + private enum class FocusDirection { Start, End, Up, @@ -463,6 +482,7 @@ object CommonActivity { //} } + /** overrides focus and custom key events */ fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? { if (act == null) return null val currentFocus = act.currentFocus @@ -503,7 +523,9 @@ object CommonActivity { return true } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete)) { + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && + (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) + ) { UIHelper.showInputMethod(act.currentFocus?.findFocus()) } @@ -516,6 +538,8 @@ object CommonActivity { } + // if someone else want to override the focus then don't handle the event as it is already + // consumed. used in video player if (keyEventListener?.invoke(Pair(event, false)) == true) { return true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 86252b40..51d218bf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -50,8 +50,10 @@ object APIHolder { val allProviders = threadSafeListOf() fun initAll() { - for (api in allProviders) { - api.init() + synchronized(allProviders) { + for (api in allProviders) { + api.init() + } } apiMap = null } @@ -64,27 +66,35 @@ object APIHolder { var apiMap: Map? = null fun addPluginMapping(plugin: MainAPI) { - apis = apis + plugin + synchronized(apis) { + apis = apis + plugin + } initMap(true) } fun removePluginMapping(plugin: MainAPI) { - apis = apis.filter { it != plugin } + synchronized(apis) { + apis = apis.filter { it != plugin } + } initMap(true) } private fun initMap(forcedUpdate: Boolean = false) { - if (apiMap == null || forcedUpdate) - apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap() + synchronized(apis) { + if (apiMap == null || forcedUpdate) + apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap() + } } fun getApiFromNameNull(apiName: String?): MainAPI? { if (apiName == null) return null synchronized(allProviders) { initMap() - return apiMap?.get(apiName)?.let { apis.getOrNull(it) } - // Leave the ?. null check, it can crash regardless - ?: allProviders.firstOrNull { it.name == apiName } + synchronized(apis) { + return apiMap?.get(apiName)?.let { apis.getOrNull(it) } + // Leave the ?. null check, it can crash regardless + ?: allProviders.firstOrNull { it.name == apiName } + } } } @@ -215,7 +225,7 @@ object APIHolder { val hashSet = HashSet() val activeLangs = getApiProviderLangSettings() val hasUniversal = activeLangs.contains(AllLanguagesName) - hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) } + hashSet.addAll(synchronized(apis) { apis.filter { hasUniversal || activeLangs.contains(it.lang) } } .map { it.name }) /*val set = settingsManager.getStringSet( @@ -314,8 +324,9 @@ object APIHolder { } ?: default val langs = this.getApiProviderLangSettings() val hasUniversal = langs.contains(AllLanguagesName) - val allApis = apis.filter { hasUniversal || langs.contains(it.lang) } - .filter { api -> api.hasMainPage || !hasHomePageIsRequired } + val allApis = synchronized(apis) { + apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) } + } return if (currentPrefMedia.isEmpty()) { allApis } else { @@ -736,6 +747,7 @@ fun fixTitle(str: String): String { .replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it } } } + /** * Get rhino context in a safe way as it needs to be initialized on the main thread. * Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects() diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 3dd5a495..1083ad49 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -382,10 +382,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { this.navigate(R.id.navigation_downloads) return true } else { - for (api in apis) { - if (str.startsWith(api.mainUrl)) { - loadResult(str, api.name) - return true + synchronized(apis) { + for (api in apis) { + if (str.startsWith(api.mainUrl)) { + loadResult(str, api.name) + return true + } } } } @@ -464,9 +466,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { binding?.navHostFragment?.apply { val params = layoutParams as ConstraintLayout.LayoutParams - val push = if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0 + val push = + if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0 - if(!this.isLtr()) { + if (!this.isLtr()) { params.setMargins( params.leftMargin, params.topMargin, @@ -695,27 +698,29 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { private fun onAllPluginsLoaded(success: Boolean = false) { ioSafe { pluginsLock.withLock { - // Load cloned sites after plugins have been loaded since clones depend on plugins. - try { - getKey>(USER_PROVIDER_API)?.let { list -> - list.forEach { custom -> - allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } - ?.let { - allProviders.add(it.javaClass.newInstance().apply { - name = custom.name - lang = custom.lang - mainUrl = custom.url.trimEnd('/') - canBeOverridden = false - }) - } + synchronized(allProviders) { + // Load cloned sites after plugins have been loaded since clones depend on plugins. + try { + getKey>(USER_PROVIDER_API)?.let { list -> + list.forEach { custom -> + allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } + ?.let { + allProviders.add(it.javaClass.newInstance().apply { + name = custom.name + lang = custom.lang + mainUrl = custom.url.trimEnd('/') + canBeOverridden = false + }) + } + } } + // it.hashCode() is not enough to make sure they are distinct + apis = + allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name } + APIHolder.apiMap = null + } catch (e: Exception) { + logError(e) } - // it.hashCode() is not enough to make sure they are distinct - apis = - allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name } - APIHolder.apiMap = null - } catch (e: Exception) { - logError(e) } } } @@ -814,6 +819,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { translationX = target.x translationY = target.y + bringToFront() } } @@ -839,10 +845,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val out = IntArray(2) newFocus.getLocationInWindow(out) val (screenX, screenY) = out - var (x,y) = screenX.toFloat() to screenY.toFloat() + var (x, y) = screenX.toFloat() to screenY.toFloat() val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY - // println(">><<< $x $y $currentX $currentY") - if(!newFocus.isLtr()) { + // println(">><<< $x $y $currentX $currentY") + if (!newFocus.isLtr()) { x = x - focusOutline.rootView.width + newFocus.measuredWidth } @@ -1195,7 +1201,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { ioSafe { initAll() // No duplicates (which can happen by registerMainAPI) - apis = allProviders.distinctBy { it } + apis = synchronized(allProviders) { + allProviders.distinctBy { it } + } } // val navView: BottomNavigationView = findViewById(R.id.nav_view) @@ -1347,8 +1355,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { }*/ if (BuildConfig.DEBUG) { - try { - var providersAndroidManifestString = "Current androidmanifest should be:\n" + var providersAndroidManifestString = "Current androidmanifest should be:\n" + synchronized(allProviders) { for (api in allProviders) { providersAndroidManifestString += "\n" } - println(providersAndroidManifestString) - - } catch (t: Throwable) { - logError(t) } - + println(providersAndroidManifestString) } handleAppIntent(intent) diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt index 07aa904e..5bbb4538 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt @@ -21,10 +21,11 @@ class CrossTmdbProvider : TmdbProvider() { return Regex("""[^a-zA-Z0-9-]""").replace(name, "") } - private val validApis by lazy { - apis.filter { it.lang == this.lang && it::class.java != this::class.java } - //.distinctBy { it.uniqueId } - } + private val validApis + get() = + synchronized(apis) { apis.filter { it.lang == this.lang && it::class.java != this::class.java } } + //.distinctBy { it.uniqueId } + data class CrossMetaData( @JsonProperty("isSuccess") val isSuccess: Boolean, @@ -60,7 +61,8 @@ class CrossTmdbProvider : TmdbProvider() { override suspend fun load(url: String): LoadResponse? { val base = super.load(url)?.apply { - this.recommendations = this.recommendations?.filterIsInstance() // TODO REMOVE + this.recommendations = + this.recommendations?.filterIsInstance() // TODO REMOVE val matchName = filterName(this.name) when (this) { is MovieLoadResponse -> { @@ -98,6 +100,7 @@ class CrossTmdbProvider : TmdbProvider() { this.dataUrl = CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson() } + else -> { throw ErrorLoadingException("Nothing besides movies are implemented for this provider") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt index e8ac1876..8cfe1e9a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt @@ -25,13 +25,16 @@ class MultiAnimeProvider : MainAPI() { } } - private val validApis by lazy { - APIHolder.apis.filter { - it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains( - TvType.Anime - ) - } - } + private val validApis + get() = + synchronized(APIHolder.apis) { + APIHolder.apis.filter { + it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains( + TvType.Anime + ) + } + } + private fun filterName(name: String): String { return Regex("""[^a-zA-Z0-9-]""").replace(name, "") diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt index 242baf59..6b7dc90b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt @@ -36,7 +36,9 @@ abstract class Plugin { Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI") element.sourcePlugin = this.__filename // Race condition causing which would case duplicates if not for distinctBy - APIHolder.allProviders.add(element) + synchronized(APIHolder.allProviders) { + APIHolder.allProviders.add(element) + } APIHolder.addPluginMapping(element) } @@ -51,10 +53,14 @@ abstract class Plugin { } class Manifest { - @JsonProperty("name") var name: String? = null - @JsonProperty("pluginClassName") var pluginClassName: String? = null - @JsonProperty("version") var version: Int? = null - @JsonProperty("requiresResources") var requiresResources: Boolean = false + @JsonProperty("name") + var name: String? = null + @JsonProperty("pluginClassName") + var pluginClassName: String? = null + @JsonProperty("version") + var version: Int? = null + @JsonProperty("requiresResources") + var requiresResources: Boolean = false } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 0dee57eb..49b5a752 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -163,7 +163,8 @@ object PluginManager { private val classLoaders: MutableMap = HashMap() - private var loadedLocalPlugins = false + var loadedLocalPlugins = false + private set private val gson = Gson() private suspend fun maybeLoadPlugin(context: Context, file: File) { @@ -531,10 +532,14 @@ object PluginManager { } // remove all registered apis - APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach { - removePluginMapping(it) + synchronized(APIHolder.apis) { + APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach { + removePluginMapping(it) + } + } + synchronized(APIHolder.allProviders) { + APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename } } - APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename } extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename } classLoaders.values.removeIf { v -> v == plugin } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index eb12c411..a6e1b5e6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -462,7 +462,7 @@ class HomeFragment : Fragment() { private val apiChangeClickListener = View.OnClickListener { view -> view.context.selectHomepage(currentApiName) { api -> - homeViewModel.loadAndCancel(api) + homeViewModel.loadAndCancel(api, forceReload = true,fromUI = true) } /*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf() @@ -652,6 +652,7 @@ class HomeFragment : Fragment() { } homeViewModel.reloadStored() + homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false) //loadHomePage(false) // nice profile pic on homepage diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index ce7b8447..fd2412da 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -447,12 +447,12 @@ class HomeParentItemAdapterPreview( (binding as? FragmentHomeHeadTvBinding)?.apply { homePreviewChangeApi.setOnClickListener { view -> view.context.selectHomepage(viewModel.repo?.name) { api -> - viewModel.loadAndCancel(api) + viewModel.loadAndCancel(api, forceReload = true, fromUI = true) } } homePreviewChangeApi2.setOnClickListener { view -> view.context.selectHomepage(viewModel.repo?.name) { api -> - viewModel.loadAndCancel(api) + viewModel.loadAndCancel(api, forceReload = true, fromUI = true) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 563a326e..a2dc9821 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia @@ -15,12 +14,22 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity -import com.lagradost.cloudstream3.mvvm.* +import com.lagradost.cloudstream3.HomePageList +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.MainActivity +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.amap +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.mvvm.debugAssert +import com.lagradost.cloudstream3.mvvm.debugWarning +import com.lagradost.cloudstream3.mvvm.launchSafe +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.ui.APIRepository 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.home.HomeFragment.Companion.loadHomepageList import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -30,8 +39,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE -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.DataStoreHelper.getAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds @@ -44,7 +51,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.withContext -import java.util.* +import java.util.EnumSet import kotlin.collections.set class HomeViewModel : ViewModel() { @@ -95,7 +102,7 @@ class HomeViewModel : ViewModel() { private var currentShuffledList: List = listOf() private fun autoloadRepo(): APIRepository { - return APIRepository(apis.first { it.hasMainPage }) + return APIRepository(synchronized(apis) { apis.first { it.hasMainPage }}) } private val _availableWatchStatusTypes = @@ -177,8 +184,10 @@ class HomeViewModel : ViewModel() { } private var onGoingLoad: Job? = null - private fun loadAndCancel(api: MainAPI?) { + private var isCurrentlyLoadingName : String? = null + private fun loadAndCancel(api: MainAPI) { onGoingLoad?.cancel() + isCurrentlyLoadingName = api.name onGoingLoad = load(api) } @@ -280,12 +289,12 @@ class HomeViewModel : ViewModel() { } } - private fun load(api: MainAPI?) = ioSafe { - repo = if (api != null) { + private fun load(api: MainAPI) : Job = ioSafe { + repo = //if (api != null) { APIRepository(api) - } else { - autoloadRepo() - } + //} else { + // autoloadRepo() + //} _apiName.postValue(repo?.name) _randomItems.postValue(listOf()) @@ -299,6 +308,7 @@ class HomeViewModel : ViewModel() { _page.postValue(Resource.Loading()) _preview.postValue(Resource.Loading()) + // cancel the current preview expand as that is no longer relevant addJob?.cancel() when (val data = repo?.getMainPage(1, null)) { @@ -370,7 +380,7 @@ class HomeViewModel : ViewModel() { else -> Unit } - onGoingLoad = null + isCurrentlyLoadingName = null } fun click(callback: SearchClickCallback) { @@ -437,33 +447,51 @@ class HomeViewModel : ViewModel() { loadResult(load.response.url, load.response.apiName, load.action) } - fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) = - viewModelScope.launchSafe { + // only save the key if it is from UI, as we don't want internal functions changing the setting + fun loadAndCancel( + preferredApiName: String?, + forceReload: Boolean = true, + fromUI: Boolean = false + ) = + ioSafe { // Since plugins are loaded in stages this function can get called multiple times. // The issue with this is that the homepage may be fetched multiple times while the first request is loading val api = getApiFromNameNull(preferredApiName) - if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) { - return@launchSafe + // api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true + val currentPage = page.value + + // if we don't need to reload and we have a valid homepage or currently loading the same thing then return + val currentLoading = isCurrentlyLoadingName + if (!forceReload && (currentPage is Resource.Success && currentPage.value.isNotEmpty() || (currentLoading != null && currentLoading == preferredApiName))) { + return@ioSafe } if (preferredApiName == noneApi.name) { - setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) + // just set to random + if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) loadAndCancel(noneApi) } else if (preferredApiName == randomApi.name) { + // randomize the api, if none exist like if not loaded or not installed + // then use nothing val validAPIs = context?.filterProviderByPreferredMedia() if (validAPIs.isNullOrEmpty()) { - // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded loadAndCancel(noneApi) } else { val apiRandom = validAPIs.random() loadAndCancel(apiRandom) - setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) + if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) } - // If the plugin isn't loaded yet. (Does not set the key) } else if (api == null) { - loadAndCancel(noneApi) + // API is not found aka not loaded or removed, post the loading + // progress if waiting for plugins, otherwise nothing + if(PluginManager.loadedLocalPlugins) { + loadAndCancel(noneApi) + } else { + _page.postValue(Resource.Loading()) + } } else { - setKey(USER_SELECTED_HOMEPAGE_API, api.name) + // if the api is found, then set it to it and save key + if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, api.name) loadAndCancel(api) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index a20cd5c6..04ef3d96 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -163,12 +163,14 @@ class LibraryFragment : Fragment() { syncId: SyncIdName, apiName: String? = null, ) { - val availableProviders = allProviders.filter { - it.supportedSyncNames.contains(syncId) - }.map { it.name } + - // Add the api if it exists - (APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } ?: emptyList()) - + val availableProviders = synchronized(allProviders) { + allProviders.filter { + it.supportedSyncNames.contains(syncId) + }.map { it.name } + + // Add the api if it exists + (APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } + ?: emptyList()) + } val baseOptions = listOf( LibraryOpenerType.Default, LibraryOpenerType.None, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 88f55444..011d133d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -321,7 +321,7 @@ data class ExtractedTrailerData( class ResultViewModel2 : ViewModel() { private var currentResponse: LoadResponse? = null - var EPISODE_RANGE_SIZE : Int = 20 + var EPISODE_RANGE_SIZE: Int = 20 fun clear() { currentResponse = null _page.postValue(null) @@ -456,7 +456,7 @@ class ResultViewModel2 : ViewModel() { currentResponse.year ) ) - if(currentWatchType != status) { + if (currentWatchType != status) { MainActivity.bookmarksUpdatedEvent(true) } } @@ -477,7 +477,10 @@ class ResultViewModel2 : ViewModel() { ) ) - private fun getRanges(allEpisodes: Map>, EPISODE_RANGE_SIZE : Int): Map> { + private fun getRanges( + allEpisodes: Map>, + EPISODE_RANGE_SIZE: Int + ): Map> { return allEpisodes.keys.mapNotNull { index -> val episodes = allEpisodes[index] ?: return@mapNotNull null // this should never happened @@ -1505,13 +1508,14 @@ class ResultViewModel2 : ViewModel() { } val realRecommendations = ArrayList() - val apiNames = apis.filter { - it.name.contains("gogoanime", true) || - it.name.contains("9anime", true) - }.map { - it.name + val apiNames = synchronized(apis) { + apis.filter { + it.name.contains("gogoanime", true) || + it.name.contains("9anime", true) + }.map { + it.name + } } - meta.recommendations?.forEach { rec -> apiNames.forEach { name -> realRecommendations.add(rec.copy(apiName = name)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index aceda644..320687f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -37,7 +37,7 @@ class SearchViewModel : ViewModel() { private val _currentHistory: MutableLiveData> = MutableLiveData() val currentHistory: LiveData> get() = _currentHistory - private var repos = apis.map { APIRepository(it) } + private var repos = synchronized(apis) { apis.map { APIRepository(it) } } fun clearSearch() { _searchResponse.postValue(Resource.Success(ArrayList())) @@ -48,7 +48,7 @@ class SearchViewModel : ViewModel() { private var onGoingSearch: Job? = null fun reloadRepos() { - repos = apis.map { APIRepository(it) } + repos = synchronized(apis) { apis.map { APIRepository(it) } } } fun searchAndCancel( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 070389b0..e53fa91a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -8,7 +8,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import androidx.annotation.StringRes +import androidx.core.view.children import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.preference.Preference @@ -74,6 +76,7 @@ class SettingsFragment : Fragment() { settingsToolbar.apply { setTitle(title) setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) + children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag) setNavigationOnClickListener { activity?.onBackPressed() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index b308efc7..85dd9540 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding @@ -188,7 +189,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { fun showAdd() { - val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name } + val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } } activity?.showDialog( providers.map { "${it.name} (${it.mainUrl})" }, -1, @@ -221,6 +222,8 @@ class SettingsGeneral : PreferenceFragmentCompat() { val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang) current.add(newSite) setKey(USER_PROVIDER_API, current.toTypedArray()) + // reload apis + MainActivity.afterPluginsLoadedEvent.invoke(false) dialog.dismissSafe(activity) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index 42a864a6..0bef5e9a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -105,8 +105,10 @@ class SettingsProviders : PreferenceFragmentCompat() { getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener { activity?.getApiProviderLangSettings()?.let { current -> - val languages = APIHolder.apis.map { it.lang }.toSet() - .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName + val languages = synchronized(APIHolder.apis) { + APIHolder.apis.map { it.lang }.toSet() + .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName + } val currentList = current.map { languages.indexOf(it) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt index 2e05baff..4fd24afe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt @@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel +import okhttp3.internal.toImmutableList class TestViewModel : ViewModel() { data class TestProgress( @@ -81,15 +82,14 @@ class TestViewModel : ViewModel() { } fun init() { - val apis = APIHolder.allProviders - total = apis.size + total = synchronized(APIHolder.allProviders) { APIHolder.allProviders.size } updateProgress() } fun startTest() { scope = CoroutineScope(Dispatchers.Default) - val apis = APIHolder.allProviders + val apis = synchronized(APIHolder.allProviders) { APIHolder.allProviders.toTypedArray() } total = apis.size failed = 0 passed = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt index 138a31a3..4369b22f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt @@ -107,7 +107,7 @@ class SetupFragmentExtensions : Fragment() { if (isSetup) if ( // If any available languages - apis.distinctBy { it.lang }.size > 1 + synchronized(apis) { apis.distinctBy { it.lang }.size > 1 } ) { findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages) } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt index 8637fc99..59dcc402 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt @@ -51,8 +51,8 @@ class SetupFragmentProviderLanguage : Fragment() { ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) val current = ctx.getApiProviderLangSettings() - val langs = APIHolder.apis.map { it.lang }.toSet() - .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName + val langs = synchronized(APIHolder.apis) { APIHolder.apis.map { it.lang }.toSet() + .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName} val currentList = current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index e1cedd39..dd2b40a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -18,6 +18,8 @@ const val USER_PROVIDER_API = "user_custom_sites" const val PREFERENCES_NAME = "rebuild_preference" +// TODO degelgate by value for get & set + object DataStore { val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule()) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index 817e9235..71d3a1ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -96,8 +96,10 @@ object SyncUtil { .mapNotNull { it.url }.toMutableList() if (type == "anilist") { // TODO MAKE BETTER - apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach { - current.add("${it.mainUrl}/anime/$id") + synchronized(apis) { + apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach { + current.add("${it.mainUrl}/anime/$id") + } } } return current diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt index 66e1e504..dd973538 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt @@ -211,7 +211,7 @@ object TestingUtils { fun getDeferredProviderTests( scope: CoroutineScope, - providers: List, + providers: Array, logger: (String) -> Unit, callback: (MainAPI, TestResultProvider) -> Unit ) { diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index 0a2c52b2..8592daea 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -1,13 +1,13 @@ - + android:orientation="vertical"> @@ -39,7 +39,9 @@ android:layout_width="wrap_content" android:layout_gravity="top|start" android:layout_marginStart="@dimen/navbar_width" - android:minWidth="150dp" /> + android:minWidth="150dp" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusDown="@id/home_preview_play_btt" /> + android:minWidth="150dp" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusDown="@id/home_watch_child_recyclerview" /> @null - @drawable/outline_drawable_less + @drawable/outline_drawable_forced false @@ -572,6 +572,7 @@ @drawable/ic_baseline_check_24_listview +