diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 62262c2..30f0e80 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -84,5 +84,6 @@ dependencies { implementation("org.brotli:dec:0.1.2") implementation("com.google.android.flexbox:flexbox:3.0.0") - + implementation("io.github.scwang90:refresh-layout-kernel:2.1.0") + implementation("io.github.scwang90:refresh-footer-ball:2.1.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0f3d92..0ac0368 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,6 +59,9 @@ + = Channel(Channel.UNLIMITED) + + enum class Request { + TryAgain, + MoreData, + } + + companion object { + const val SEARCH_MORE_QUERY = "search_more_query" + const val SEARCH_MORE_PARAMS = "search_more_params" + } + + private lateinit var binding: ActivitySearchMoreBinding + private var query: String? = null + private var params: String? = null + private var list: MutableList = mutableListOf() + private var currentContinuation: String? = null + private var adapter: SearchResultOtherAdapter? = null + + override suspend fun main() { + binding = ActivitySearchMoreBinding.inflate(layoutInflater) + setContentView(binding.root) + query = intent.getStringExtra(SEARCH_MORE_QUERY) + params = intent.getStringExtra(SEARCH_MORE_PARAMS) + if (query.isNullOrEmpty() || params.isNullOrEmpty()) { + return + } + initImmersionBar() + initView() + initAdapter() + initData(query!!, params!!) + onReceive() + } + + private fun initImmersionBar() { + immersionBar { + statusBarDarkFont(false) + statusBarView(binding.view) + } + } + + private suspend fun onReceive() { + while (isActive) { + select { + requests.onReceive { + when (it) { + Request.TryAgain -> { + initData(query!!, params!!) + } + + Request.MoreData -> { + LogD(TAG, "Request.MoreData") + currentContinuation?.let { it1 -> initDataMore(it1) } + } + } + } + } + } + } + + private fun initView() { + binding.backBtn.setOnClickListener { + finish() + } + binding.tryAgainBtn.setOnClickListener { + requests.trySend(Request.TryAgain) + } + binding.refreshLayout.setEnableRefresh(false) + binding.refreshLayout.setEnableLoadMore(true) + binding.refreshLayout.setOnLoadMoreListener { + requests.trySend(Request.MoreData) + } + } + + private fun initAdapter() { + adapter = SearchResultOtherAdapter(this, list) + binding.rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.rv.adapter = adapter + } + + private suspend fun initData(query: String, params: String) { + showLoadingUi() + Innertube.moSearchPage(SearchBody(query = query, params = params))?.onSuccess { result -> + if (result.isNotEmpty()) { + showDataUi() + val title = result[0].title + if (title.isNullOrEmpty()) { + binding.title.visibility = View.GONE + } else { + binding.title.visibility = View.VISIBLE + binding.title.text = title + } + currentContinuation = result[0].continuation + list.clear() + list.addAll(result[0].searchResultList) + } else { + showNoContentUi() + } + }?.onFailure { + showNoContentUi() + } + } + + @SuppressLint("NotifyDataSetChanged") + private suspend fun initDataMore(continuation: String) { + Innertube.moSearchPage(ContinuationBody(continuation = continuation))?.onSuccess { result -> + LogD(TAG, "initDataMore result->$result") + currentContinuation = result.continuation + if (result.searchResultList.isNotEmpty()) { + list.addAll(result.searchResultList) + if (!isFinishing) { + adapter?.notifyDataSetChanged() + } + binding.refreshLayout.finishLoadMore(true) + } else { + binding.refreshLayout.finishLoadMore(2000, true, false) + } + }?.onFailure { + binding.refreshLayout.finishLoadMore(2000, false, false) + } + } + + + private fun showDataUi() { + binding.loadingLayout.visibility = View.GONE + binding.noContentLayout.visibility = View.GONE + } + + private fun showLoadingUi() { + binding.loadingLayout.visibility = View.VISIBLE + binding.noContentLayout.visibility = View.GONE + } + + private fun showNoContentUi() { + binding.loadingLayout.visibility = View.GONE + binding.noContentLayout.visibility = View.VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/player/musicoo/adapter/SearchResultOtherAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/SearchResultOtherAdapter.kt index 150bff6..13e1488 100644 --- a/app/src/main/java/com/player/musicoo/adapter/SearchResultOtherAdapter.kt +++ b/app/src/main/java/com/player/musicoo/adapter/SearchResultOtherAdapter.kt @@ -2,6 +2,7 @@ package com.player.musicoo.adapter import android.annotation.SuppressLint import android.content.Context +import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,11 +12,15 @@ import androidx.media3.common.Player import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.player.musicoo.R +import com.player.musicoo.activity.MoListDetailsActivity +import com.player.musicoo.activity.MoPlayDetailsActivity +import com.player.musicoo.activity.MoSingerDetailsActivity import com.player.musicoo.databinding.PlayListItemBinding import com.player.musicoo.databinding.SearchResultOtherItemBinding import com.player.musicoo.databinding.SearchResultOtherLayoutBinding import com.player.musicoo.innertube.Innertube import com.player.musicoo.media.MediaControllerManager +import com.player.musicoo.util.LogTag class SearchResultOtherAdapter( private val context: Context, @@ -24,7 +29,8 @@ class SearchResultOtherAdapter( RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding = SearchResultOtherItemBinding.inflate(LayoutInflater.from(context), parent, false) + val binding = + SearchResultOtherItemBinding.inflate(LayoutInflater.from(context), parent, false) return ViewHolder(binding) } @@ -33,7 +39,40 @@ class SearchResultOtherAdapter( holder.bind(bean) holder.itemView.setOnClickListener { + LogTag.LogD( + LogTag.VO_ACT_LOG, + "SearchResultOtherAdapter bean.pageType->${bean.pageType}" + ) + if (!bean.videoId.isNullOrEmpty()) { + val intent = Intent(context, MoPlayDetailsActivity::class.java) + intent.putExtra( + MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID, + bean.videoId + ) + intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.title) + intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.desc) + context.startActivity(intent) + } else if (!bean.browseId.isNullOrEmpty()) { + when (bean.pageType) { + "MUSIC_PAGE_TYPE_ARTIST" -> { + val intent = Intent(context, MoSingerDetailsActivity::class.java) + intent.putExtra( + MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID, + bean.browseId + ) + context.startActivity(intent) + } + else -> { + val intent = Intent(context, MoListDetailsActivity::class.java) + intent.putExtra( + MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, + bean.browseId + ) + context.startActivity(intent) + } + } + } } } diff --git a/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt b/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt index 9bd1bb6..e77cbcb 100644 --- a/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt +++ b/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt @@ -24,6 +24,7 @@ import com.player.musicoo.adapter.SearchHistoryAdapter import com.player.musicoo.adapter.SearchSuggestionsAdapter import com.player.musicoo.databinding.FragmentSearchBinding import com.player.musicoo.innertube.Innertube +import com.player.musicoo.innertube.models.bodies.SearchBody import com.player.musicoo.innertube.models.bodies.SearchSuggestionsBody import com.player.musicoo.innertube.requests.moSearchPage import com.player.musicoo.innertube.requests.searchSuggestions @@ -176,9 +177,10 @@ class SearchFragment : MoBaseFragment(), TextWatcher, appStore.searchHistoryStore = searchHistorySet updateHistoryUi() - Innertube.moSearchPage(input)?.onSuccess { result -> + Innertube.moSearchPage(SearchBody(query = input))?.onSuccess { result -> showResultContent() for (dataPage: Innertube.SearchDataPage in result) { + LogTag.LogD(TAG,"moSearchPage dataPage->$dataPage") if (dataPage.type == 1) {//type为1的是最佳结果。 binding.contentLayout.addView( SearchResultOptimalView(requireActivity(), dataPage) @@ -238,7 +240,7 @@ class SearchFragment : MoBaseFragment(), TextWatcher, @SuppressLint("NotifyDataSetChanged") override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - if (s.isNullOrEmpty()) { + if (s.isNullOrEmpty()) {//没有输入内容,隐藏删除按钮,展示历史记录,重新获取焦点弹出键盘。 binding.deleteInputBtn.visibility = View.GONE showSearchHistory() updateHistoryUi() diff --git a/app/src/main/java/com/player/musicoo/innertube/Innertube.kt b/app/src/main/java/com/player/musicoo/innertube/Innertube.kt index 87a5768..999e40a 100644 --- a/app/src/main/java/com/player/musicoo/innertube/Innertube.kt +++ b/app/src/main/java/com/player/musicoo/innertube/Innertube.kt @@ -178,7 +178,7 @@ object Innertube { data class SingerDetailsListPage( val title: String?, val contents: SingerDetailsContent? - ){ + ) { data class SingerDetailsContent( val musicShelfContentList: List?, val musicCarouselShelfContentList: List?, @@ -186,9 +186,12 @@ object Innertube { } data class SearchDataPage( - val type: Int?, - val title: String?, - val searchResultList: List + val type: Int? = null, + val title: String? = null, + val searchResultList: List, + val query: String? = null, + val params: String? = null, + val continuation: String? = null ) { data class SearchResult( val title: String?, @@ -196,6 +199,7 @@ object Innertube { val thumbnail: String?, val videoId: String?, val browseId: String?, + val pageType: String?, ) } diff --git a/app/src/main/java/com/player/musicoo/innertube/requests/MoSearchPage.kt b/app/src/main/java/com/player/musicoo/innertube/requests/MoSearchPage.kt index 74f761e..530e1ad 100644 --- a/app/src/main/java/com/player/musicoo/innertube/requests/MoSearchPage.kt +++ b/app/src/main/java/com/player/musicoo/innertube/requests/MoSearchPage.kt @@ -2,10 +2,12 @@ package com.player.musicoo.innertube.requests import com.player.musicoo.innertube.Innertube import com.player.musicoo.innertube.models.BrowseResponse +import com.player.musicoo.innertube.models.ContinuationResponse import com.player.musicoo.innertube.models.MusicShelfRenderer import com.player.musicoo.innertube.models.SearchResponse import com.player.musicoo.innertube.models.SectionListRenderer import com.player.musicoo.innertube.models.bodies.BrowseBody +import com.player.musicoo.innertube.models.bodies.ContinuationBody import com.player.musicoo.innertube.models.bodies.SearchBody import com.player.musicoo.innertube.utils.runCatchingNonCancellable import com.player.musicoo.util.LogTag @@ -13,11 +15,11 @@ import io.ktor.client.call.body import io.ktor.client.request.post import io.ktor.client.request.setBody -suspend fun Innertube.moSearchPage(query: String): Result>? = +suspend fun Innertube.moSearchPage(body: SearchBody): Result>? = runCatchingNonCancellable { val response = client.post(search) { - setBody(SearchBody(query = query)) + setBody(body) }.body() val searchDataPageList: MutableList = mutableListOf() @@ -32,6 +34,9 @@ suspend fun Innertube.moSearchPage(query: String): Result { + //不add博客数据 + } + + "MUSIC_PAGE_TYPE_USER_CHANNEL" -> { + //不add个人资料数据 + } + + else -> { + searchResultItemList.add(item) + } + } + } } } @@ -150,10 +196,105 @@ suspend fun Innertube.moSearchPage(query: String): Result? = + runCatchingNonCancellable { + val response = client.post(search) { + setBody(body) + }.body() + + val searchResultItemList: MutableList = + mutableListOf() + + val musicShelfContinuation = response.continuationContents + ?.musicShelfContinuation + + val itemContents = musicShelfContinuation + ?.contents + if (itemContents != null) { + for (itemContent: MusicShelfRenderer.Content in itemContents) { + val item = Innertube.SearchDataPage.SearchResult( + title = itemContent + .musicResponsiveListItemRenderer + ?.flexColumns + ?.firstOrNull() + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + ?.text, + desc = itemContent.musicResponsiveListItemRenderer + ?.flexColumns + ?.get(1) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.map { it.text } + ?.joinToString("") ?: "", + thumbnail = itemContent.musicResponsiveListItemRenderer + ?.thumbnail + ?.musicThumbnailRenderer + ?.thumbnail + ?.thumbnails + ?.let { it.getOrNull(1) ?: it.getOrNull(0) } + ?.url, + videoId = itemContent.musicResponsiveListItemRenderer + ?.flexColumns + ?.firstOrNull() + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + ?.navigationEndpoint + ?.watchEndpoint + ?.videoId, + + browseId = itemContent.musicResponsiveListItemRenderer + ?.navigationEndpoint + ?.browseEndpoint + ?.browseId ?: "", + + pageType = itemContent.musicResponsiveListItemRenderer + ?.navigationEndpoint + ?.browseEndpoint + ?.type, + ) + //这两个ID必须有一个id是存在的,否则就不进入添加数据 + if (!item.videoId.isNullOrBlank() || !item.browseId.isNullOrBlank()) { + when (item.pageType) { + "MUSIC_PAGE_TYPE_PODCAST_SHOW_DETAIL_PAGE" -> { + //不add博客数据 + } + + "MUSIC_PAGE_TYPE_USER_CHANNEL" -> { + //不add个人资料数据 + } + + else -> { + searchResultItemList.add(item) + } + } + + } + } + } + + Innertube.SearchDataPage( + searchResultList = searchResultItemList, + continuation = musicShelfContinuation + ?.continuations + ?.firstOrNull() + ?.nextContinuationData + ?.continuation + ) } \ No newline at end of file diff --git a/app/src/main/java/com/player/musicoo/innertube/requests/MoSingerlistPage.kt b/app/src/main/java/com/player/musicoo/innertube/requests/MoSingerlistPage.kt index 0b7be87..2586c31 100644 --- a/app/src/main/java/com/player/musicoo/innertube/requests/MoSingerlistPage.kt +++ b/app/src/main/java/com/player/musicoo/innertube/requests/MoSingerlistPage.kt @@ -85,17 +85,17 @@ suspend fun Innertube.moSingerListPage(browseId: String): Result${singerDetails.title}") - LogTag.LogD( - TAG, - "musicShelfContentList size->${singerDetails.contents?.musicShelfContentList?.size}" - ) - LogTag.LogD( - TAG, - "musicCarouselShelfContentList size->${singerDetails.contents?.musicCarouselShelfContentList?.size}" - ) - - LogTag.LogD(TAG, "--------------------------------------------") +// LogTag.LogD(TAG, "title->${singerDetails.title}") +// LogTag.LogD( +// TAG, +// "musicShelfContentList size->${singerDetails.contents?.musicShelfContentList?.size}" +// ) +// LogTag.LogD( +// TAG, +// "musicCarouselShelfContentList size->${singerDetails.contents?.musicCarouselShelfContentList?.size}" +// ) +// +// LogTag.LogD(TAG, "--------------------------------------------") if (title.isNotEmpty()) { list.add(singerDetails) diff --git a/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt b/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt index 4b88b12..61dcea2 100644 --- a/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt +++ b/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt @@ -8,6 +8,7 @@ import android.widget.LinearLayout import android.widget.TextView import com.bumptech.glide.Glide import com.player.musicoo.R +import com.player.musicoo.activity.MoListDetailsActivity import com.player.musicoo.activity.MoPlayDetailsActivity import com.player.musicoo.activity.MoSingerDetailsActivity import com.player.musicoo.innertube.Innertube @@ -47,12 +48,24 @@ class SearchResultOptimalView(context: Context, data: Innertube.SearchDataPage) intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, optimalBean.desc) context.startActivity(intent) } else if (!optimalBean.browseId.isNullOrEmpty()) { - val intent = Intent(context, MoSingerDetailsActivity::class.java) - intent.putExtra( - MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID, - optimalBean.browseId - ) - context.startActivity(intent) + when (optimalBean.pageType) { + "MUSIC_PAGE_TYPE_ALBUM" -> { + val intent = Intent(context, MoListDetailsActivity::class.java) + intent.putExtra( + MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, + optimalBean.browseId + ) + context.startActivity(intent) + } + else -> { + val intent = Intent(context, MoSingerDetailsActivity::class.java) + intent.putExtra( + MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID, + optimalBean.browseId + ) + context.startActivity(intent) + } + } } } } diff --git a/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt b/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt index 688588e..d5fc19d 100644 --- a/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt +++ b/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt @@ -2,25 +2,37 @@ package com.player.musicoo.view import android.annotation.SuppressLint import android.content.Context +import android.content.Intent +import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.player.musicoo.R +import com.player.musicoo.activity.MoSearchMoreActivity import com.player.musicoo.adapter.SearchResultOtherAdapter import com.player.musicoo.innertube.Innertube @SuppressLint("ViewConstructor") -class SearchResultOtherView (context: Context, data: Innertube.SearchDataPage) : +class SearchResultOtherView(context: Context, data: Innertube.SearchDataPage) : ModuleView(context) { init { contentView = inflate(getContext(), R.layout.search_result_other_layout, this) val title = contentView?.findViewById(R.id.title) title?.text = data.title + val moreBtn = contentView?.findViewById(R.id.moreBtn) + moreBtn?.setOnClickListener { + val intent = Intent(context, MoSearchMoreActivity::class.java) + intent.putExtra(MoSearchMoreActivity.SEARCH_MORE_QUERY, data.query) + intent.putExtra(MoSearchMoreActivity.SEARCH_MORE_PARAMS, data.params) + context.startActivity(intent) + } + val rv = contentView?.findViewById(R.id.rv) val adapter = SearchResultOtherAdapter(context, data.searchResultList) rv?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) rv?.adapter = adapter + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search_more.xml b/app/src/main/res/layout/activity_search_more.xml new file mode 100644 index 0000000..87ee8ee --- /dev/null +++ b/app/src/main/res/layout/activity_search_more.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 3c5031e..c22da57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.enableJetifier=true \ No newline at end of file