diff --git a/app/build.gradle b/app/build.gradle index c432e374..bda1c187 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.lagradost.cloudstream3" minSdkVersion 21 targetSdkVersion 31 - versionCode 29 - versionName "2.1.0" + versionCode 30 + versionName "2.1.1" resValue "string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}" 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 e8bda2d1..89d573be 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,12 +11,12 @@ import com.lagradost.cloudstream3.ui.search.SearchResultBuilder class HomeChildItemAdapter( var cardList: List, + val layout: Int = R.layout.home_result_grid, private val clickCallback: (SearchClickCallback) -> Unit ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val layout = R.layout.home_result_grid return CardViewHolder( LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback ) @@ -25,7 +25,7 @@ class HomeChildItemAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is CardViewHolder -> { - holder.bind(cardList[position]) + holder.bind(cardList[position], position) } } } @@ -34,12 +34,21 @@ class HomeChildItemAdapter( return cardList.size } + override fun getItemId(position: Int): Long { + return (cardList[position].id ?: position).toLong() + } + class CardViewHolder constructor(itemView: View, private val clickCallback: (SearchClickCallback) -> Unit) : RecyclerView.ViewHolder(itemView) { - fun bind(card: SearchResponse) { + fun bind(card: SearchResponse, index: Int) { SearchResultBuilder.bind(clickCallback, card, itemView) + itemView.tag = index + //val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f) + //ani.fillAfter = true + //ani.duration = 200 + //itemView.startAnimation(ani) } } } 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 e1a76d57..6435bbdf 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 @@ -15,10 +15,13 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe @@ -39,10 +42,11 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.HOMEPAGE_API import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar +import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.widget.CenterZoomLayoutManager import kotlinx.android.synthetic.main.fragment_home.* const val HOME_BOOKMARK_VALUE = "home_bookmarked_last" @@ -106,61 +110,32 @@ class HomeFragment : Fragment() { } private var currentHomePage: HomePageResponse? = null - var currentMainIndex = 0 - var currentMainList: ArrayList = ArrayList() private fun toggleMainVisibility(visible: Boolean) { home_main_holder.isVisible = visible } @SuppressLint("SetTextI18n") - private fun chooseRandomMainPage(item: SearchResponse? = null): SearchResponse? { + private fun chooseRandomMainPage() { val home = currentHomePage if (home != null && home.items.isNotEmpty()) { - var random: SearchResponse? = item - - var breakCount = 0 - val MAX_BREAK_COUNT = 10 - - while (random?.posterUrl == null) { - try { - random = home.items.random().list.random() - } catch (e: Exception) { - // probs Collection is empty. + val randomItems = home.items.shuffled().flatMap { it.list }.distinctBy { it.url }.toList().shuffled() + if (randomItems.isNullOrEmpty()) { + toggleMainVisibility(false) + } else { + home_main_poster_recyclerview.adapter = + HomeChildItemAdapter(randomItems, R.layout.home_result_big_grid) { callback -> + handleSearchClickCallback(activity, callback) + } + home_main_poster_recyclerview.post { + (home_main_poster_recyclerview.layoutManager as CenterZoomLayoutManager?)?.updateSize(forceUpdate = true) } - breakCount++ - if (breakCount > MAX_BREAK_COUNT) { - break - } - } - - if (random?.posterUrl != null) { - home_main_poster.setOnClickListener { - activity.loadSearchResult(random) - } - home_main_play.setOnClickListener { - activity.loadSearchResult(random, START_ACTION_RESUME_LATEST) - } - home_main_info.setOnClickListener { - activity.loadSearchResult(random) - } - - home_main_text.text = random.name + if (random is AnimeSearchResponse && !random.dubStatus.isNullOrEmpty()) { - random.dubStatus?.joinToString(prefix = " • ", separator = " | ") { it.name } - } else "" - home_main_poster?.setImage(random.posterUrl) - toggleMainVisibility(true) - return random - } else { - toggleMainVisibility(false) - return null } } else { toggleMainVisibility(false) } - return null } private fun fixGrid() { @@ -214,39 +189,30 @@ class HomeFragment : Fragment() { } }*/ + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) fixGrid() - home_reroll_next.setOnClickListener { - currentMainIndex++ - if (currentMainIndex >= currentMainList.size) { - val newItem = chooseRandomMainPage() - if (newItem != null) { - currentMainList.add(newItem) - } - currentMainIndex = currentMainList.size - 1 - } - chooseRandomMainPage(currentMainList[currentMainIndex]) - } - - home_reroll_prev.setOnClickListener { - currentMainIndex-- - if (currentMainIndex < 0) { - val newItem = chooseRandomMainPage() - if (newItem != null) { - currentMainList.add(0, newItem) - } - currentMainIndex = 0 - } - chooseRandomMainPage(currentMainList[currentMainIndex]) - } - home_change_api.setOnClickListener(apiChangeClickListener) home_change_api_loading.setOnClickListener(apiChangeClickListener) - observe(homeViewModel.apiName) { - context?.setKey(HOMEPAGE_API, it) + observe(homeViewModel.apiName) { apiName -> + context?.setKey(HOMEPAGE_API, apiName) + home_provider_name?.text = apiName + home_provider_meta_info?.isVisible = false + + getApiFromNameNull(apiName)?.let { currentApi -> + val typeChoices = listOf( + Pair(R.string.movies, listOf(TvType.Movie)), + Pair(R.string.tv_series, listOf(TvType.TvSeries)), + Pair(R.string.cartoons, listOf(TvType.Cartoon)), + Pair(R.string.anime, listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), + Pair(R.string.torrent, listOf(TvType.Torrent)), + ).filter { item -> currentApi.supportedTypes.any { type -> item.second.contains(type) } } + home_provider_meta_info?.text = typeChoices.joinToString(separator = " ") { getString(it.first) } + home_provider_meta_info?.isVisible = true + } } observe(homeViewModel.page) { data -> @@ -259,18 +225,14 @@ class HomeFragment : Fragment() { d.items.mapNotNull { try { HomePageList(it.name, it.list.filterSearchResponse()) - } catch (e : Exception) { + } catch (e: Exception) { logError(e) null } } home_master_recycler?.adapter?.notifyDataSetChanged() - currentMainList.clear() - chooseRandomMainPage()?.let { response -> - currentMainList.add(response) - } - currentMainIndex = 0 + chooseRandomMainPage() home_loading.visibility = View.GONE home_loading_error.visibility = View.GONE @@ -407,15 +369,35 @@ class HomeFragment : Fragment() { } } - context?.fixPaddingStatusbar(home_root) + context?.fixPaddingStatusbarView(home_statusbar) + context?.fixPaddingStatusbar(home_loading_statusbar) home_master_recycler.adapter = adapter home_master_recycler.layoutManager = GridLayoutManager(context, 1) + LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap + val centerLayoutManager = CenterZoomLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + centerLayoutManager.setOnSizeListener { index -> + (home_main_poster_recyclerview.adapter as HomeChildItemAdapter?)?.cardList?.get(index)?.let { random -> + home_main_play.setOnClickListener { + activity.loadSearchResult(random, START_ACTION_RESUME_LATEST) + } + home_main_info.setOnClickListener { + activity.loadSearchResult(random) + } + + home_main_text.text = + random.name + if (random is AnimeSearchResponse && !random.dubStatus.isNullOrEmpty()) { + random.dubStatus.joinToString(prefix = " • ", separator = " | ") { it.name } + } else "" + } + } + home_main_poster_recyclerview.layoutManager = centerLayoutManager // scale + reloadStored() val apiName = context?.getKey(HOMEPAGE_API) - if(homeViewModel.apiName.value != apiName || apiName == null) { - println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) + if (homeViewModel.apiName.value != apiName || apiName == null) { + //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) homeViewModel.loadAndCancel(apiName) } } 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 acb17feb..5ea32286 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 @@ -47,7 +47,7 @@ class ParentItemAdapter( private val moreInfo: FrameLayout = itemView.home_child_more_info fun bind(info: HomePageList) { title.text = info.name - recyclerView.adapter = HomeChildItemAdapter(info.list, clickCallback) + recyclerView.adapter = HomeChildItemAdapter(info.list, clickCallback = clickCallback) (recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() moreInfo.setOnClickListener { 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 36318000..8752a17c 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 @@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.MainActivity.Companion.getCastSession import com.lagradost.cloudstream3.MainActivity.Companion.showToast import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD @@ -310,7 +311,8 @@ class ResultFragment : Fragment() { CastButtonFactory.setUpMediaRouteButton(it, media_route_button) val castContext = CastContext.getSharedInstance(it.applicationContext) - if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = VISIBLE + if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = + VISIBLE castContext.addCastStateListener { state -> if (media_route_button != null) { if (state == CastState.NO_DEVICES_AVAILABLE) media_route_button.visibility = GONE else { @@ -318,7 +320,7 @@ class ResultFragment : Fragment() { } } } - } catch (e : Exception) { + } catch (e: Exception) { logError(e) } } @@ -535,16 +537,18 @@ class ResultFragment : Fragment() { val topFolder = "$folder" withContext(Dispatchers.IO) { - VideoDownloadManager.downloadThing( - ctx, - link, - fileName, - topFolder, - "vtt", - false, - null - ) { - // no notification + normalSafeApiCall { + VideoDownloadManager.downloadThing( + ctx, + link, + fileName, + topFolder, + "vtt", + false, + null + ) { + // no notification + } } } } @@ -841,7 +845,7 @@ class ResultFragment : Fragment() { } is Resource.Loading -> { result_episode_loading?.isVisible = true - // result_episodes?.isVisible = false + // result_episodes?.isVisible = false } is Resource.Success -> { //result_episodes?.isVisible = true @@ -877,7 +881,7 @@ class ResultFragment : Fragment() { if (count < 0) { result_episodes_text?.isVisible = false } else { - // result_episodes_text?.isVisible = true + // result_episodes_text?.isVisible = true result_episodes_text?.text = "$count ${if (count == 1) getString(R.string.episode) else getString(R.string.episodes)}" } 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 3a6f4934..2ba9d1df 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 @@ -19,11 +19,10 @@ object SearchResultBuilder { itemView: View ) { val cardView: ImageView = itemView.imageView - val cardText: TextView = itemView.imageText + val cardText: TextView? = itemView.imageText val textIsDub: TextView? = itemView.text_is_dub val textIsSub: TextView? = itemView.text_is_sub - println(card.name) val bg: CardView = itemView.backgroundCard @@ -37,7 +36,7 @@ object SearchResultBuilder { textIsDub?.visibility = View.GONE textIsSub?.visibility = View.GONE - cardText.text = card.name + cardText?.text = card.name //imageTextProvider.text = card.apiName cardView.setImage(card.posterUrl) @@ -69,7 +68,7 @@ object SearchResultBuilder { playImg?.visibility = View.VISIBLE if (!card.type.isMovieType()) { - cardText.text = cardText.context.getNameFull(card.name, card.episode, card.season) + cardText?.text = cardText?.context?.getNameFull(card.name, card.episode, card.season) } } is AnimeSearchResponse -> { 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 4310cd4e..ea6b1bec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -214,6 +214,12 @@ object UIHelper { v.setPadding(v.paddingLeft, v.paddingTop + getStatusBarHeight(), v.paddingRight, v.paddingBottom) } + fun Context.fixPaddingStatusbarView(v: View) { + val params = v.layoutParams + params.height = getStatusBarHeight() + v.layoutParams = params + } + fun Context.getNavigationBarHeight(): Int { var result = 0 val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") diff --git a/app/src/main/java/com/lagradost/cloudstream3/widget/CenterZoomLayoutManager.kt b/app/src/main/java/com/lagradost/cloudstream3/widget/CenterZoomLayoutManager.kt new file mode 100644 index 00000000..23e98a9f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/widget/CenterZoomLayoutManager.kt @@ -0,0 +1,88 @@ +package com.lagradost.cloudstream3.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.abs +import kotlin.math.min + +class CenterZoomLayoutManager : LinearLayoutManager { + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( + context, attrs, defStyleAttr, defStyleRes + ) + + constructor(context: Context?) : super(context) + constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super( + context, + orientation, + reverseLayout + ) + + private var itemListener: ((Int) -> Unit)? = null + + // to not spam updates + private var lastViewIndex: Int? = null + + private val mShrinkAmount = 0.15f + private val mShrinkDistance = 0.9f + + fun updateSize(forceUpdate: Boolean = false) { + val midpoint = width / 2f + val d0 = 0f + val d1 = mShrinkDistance * midpoint + val s0 = 1f + val s1 = 1f - mShrinkAmount + + var largestTag: Int? = null + var largestSize = 0f + for (i in 0 until childCount) { + getChildAt(i)?.let { child -> + val childMidpoint = (getDecoratedRight(child) + getDecoratedLeft(child)) / 2f + val d = min(d1, abs(midpoint - childMidpoint)) + val scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0) + child.scaleX = scale + child.scaleY = scale + + if (scale > largestSize) { + (child.tag as Int?)?.let { tag -> + largestSize = scale + largestTag = tag + } + } + } + } + + largestTag?.let { tag -> + if (lastViewIndex != tag || forceUpdate) { + lastViewIndex = tag + itemListener?.invoke(tag) + } + } + } + + fun setOnSizeListener(listener: (Int) -> Unit) { + lastViewIndex = null + itemListener = listener + } + + fun removeOnSizeListener() { + itemListener = null + } + + override fun onLayoutCompleted(state: RecyclerView.State?) { + super.onLayoutCompleted(state) + updateSize() + } + + override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State): Int { + val orientation = orientation + return if (orientation == HORIZONTAL) { + val scrolled = super.scrollHorizontallyBy(dx, recycler, state) + updateSize() + scrolled + } else { + 0 + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 599c45a0..39caa58c 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -11,7 +11,7 @@ @@ -22,22 +22,27 @@ android:layout_width="50dp" android:layout_height="50dp"> - + - + android:src="@drawable/ic_outline_settings_24" + android:layout_width="25dp" + android:layout_height="25dp" + android:contentDescription="@string/home_change_provider_img_des"> + + - + + + + + + + + + + + + - android:src="@drawable/ic_outline_settings_24" - android:layout_width="25dp" - android:layout_height="25dp" - android:contentDescription="@string/home_change_provider_img_des"> - - - + + android:maxLines="2" + android:ellipsize="end" + android:layout_height="40sp"/> - - - - diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 39bf78c0..5c87f02a 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -21,62 +21,35 @@ android:id="@+id/result_loading_error" android:visibility="gone" - tools:visibility="gone" + tools:visibility="visible" android:orientation="vertical" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content"> - + /> - + /> + + + + + +