diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 079eb49..62262c2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -82,4 +82,7 @@ dependencies {
implementation("io.ktor:ktor-client-serialization:2.1.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
implementation("org.brotli:dec:0.1.2")
+
+ implementation("com.google.android.flexbox:flexbox:3.0.0")
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d5ba17b..b0f3d92 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -56,6 +56,9 @@
+
= Channel(Channel.UNLIMITED)
+
+ enum class Request {
+ TryAgain,
+ }
companion object {
const val PLAY_LIST_PAGE_BROWSE_ID = "play_list_page_browse_id"
}
private lateinit var binding: ActivityDetailsBinding
+ private var browseId: String? = null
override suspend fun main() {
binding = ActivityDetailsBinding.inflate(layoutInflater)
setContentView(binding.root)
initImmersionBar()
- val browseId = intent.getStringExtra(PLAY_LIST_PAGE_BROWSE_ID)
+ browseId = intent.getStringExtra(PLAY_LIST_PAGE_BROWSE_ID)
if (browseId.isNullOrEmpty() || browseId == "null") {
finish()
return
}
initView()
LogD(TAG, "browseId->${browseId}")
- initData(browseId)
+ initData(browseId!!)
+ onReceive()
}
private fun initImmersionBar() {
@@ -39,10 +50,27 @@ class MoListDetailsActivity : MoBaseActivity() {
}
}
+ private suspend fun onReceive() {
+ while (isActive) {
+ select {
+ requests.onReceive {
+ when (it) {
+ Request.TryAgain -> {
+ initData(browseId!!)
+ }
+ }
+ }
+ }
+ }
+ }
+
private fun initView() {
binding.backBtn.setOnClickListener {
finish()
}
+ binding.tryAgainBtn.setOnClickListener {
+ requests.trySend(Request.TryAgain)
+ }
}
private suspend fun initData(browseId: String) {
diff --git a/app/src/main/java/com/player/musicoo/activity/MoSingerDetailsActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoSingerDetailsActivity.kt
new file mode 100644
index 0000000..0402d4a
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/activity/MoSingerDetailsActivity.kt
@@ -0,0 +1,129 @@
+package com.player.musicoo.activity
+
+import android.view.View
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.bumptech.glide.Glide
+import com.gyf.immersionbar.ktx.immersionBar
+import com.player.musicoo.adapter.DetailsListAdapter
+import com.player.musicoo.databinding.ActivityDetailsBinding
+import com.player.musicoo.databinding.ActivitySingerDetailsBinding
+import com.player.musicoo.fragment.MoHomeFragment
+import com.player.musicoo.innertube.Innertube
+import com.player.musicoo.innertube.requests.moPlaylistPage
+import com.player.musicoo.innertube.requests.moSingerListPage
+import com.player.musicoo.util.LogTag.LogD
+import com.player.musicoo.view.SingerDetailsOtherView
+import com.player.musicoo.view.SingerDetailsSongView
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.selects.select
+
+class MoSingerDetailsActivity : MoBaseActivity() {
+ private val requests: Channel = Channel(Channel.UNLIMITED)
+
+ enum class Request {
+ TryAgain,
+ }
+
+ companion object {
+ const val SINGER_DETAILS_PAGE_BROWSE_ID = "singer_details_page_browse_id"
+ }
+
+ private lateinit var binding: ActivitySingerDetailsBinding
+ private var browseId: String? = null
+
+ override suspend fun main() {
+ binding = ActivitySingerDetailsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ initImmersionBar()
+ browseId = intent.getStringExtra(SINGER_DETAILS_PAGE_BROWSE_ID)
+ if (browseId.isNullOrEmpty() || browseId == "null") {
+ finish()
+ return
+ }
+ initView()
+ LogD(TAG, "browseId->${browseId}")
+ initData(browseId!!)
+ onReceive()
+ }
+
+ private fun initImmersionBar() {
+ immersionBar {
+ statusBarDarkFont(false)
+ statusBarView(binding.view)
+ }
+ }
+
+ private suspend fun onReceive() {
+ while (isActive) {
+ select {
+ requests.onReceive {
+ when (it) {
+ Request.TryAgain -> {
+ initData(browseId!!)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun initView() {
+ binding.backBtn.setOnClickListener {
+ finish()
+ }
+ binding.tryAgainBtn.setOnClickListener {
+ requests.trySend(Request.TryAgain)
+ }
+ }
+
+ private suspend fun initData(browseId: String) {
+ showLoadingUi()
+ Innertube.moSingerListPage(browseId)
+ ?.onSuccess {
+ showDataUi()
+ Glide.with(this)
+ .load(it.thumbnail)
+ .into(binding.singerImg)
+ binding.singerName.text = it.title
+ binding.singerDesc.text = it.description
+ if (it.list != null) {
+ for (bean: Innertube.SingerDetailsListPage in it.list) {
+ if (bean.contents?.musicShelfContentList != null && bean.contents.musicShelfContentList.isNotEmpty()) {
+ binding.contentLayout.addView(
+ SingerDetailsSongView(
+ this@MoSingerDetailsActivity,
+ bean
+ )
+ )
+ } else if (bean.contents?.musicCarouselShelfContentList != null && bean.contents.musicCarouselShelfContentList.isNotEmpty()) {
+ binding.contentLayout.addView(
+ SingerDetailsOtherView(
+ this@MoSingerDetailsActivity,
+ bean
+ )
+ )
+ }
+ }
+ }
+ }?.onFailure {
+ showNoContentUi()
+ LogD(TAG, "moSingerListPage onFailure->${it}")
+ }
+ }
+
+ 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/activity/PrimaryActivity.kt b/app/src/main/java/com/player/musicoo/activity/PrimaryActivity.kt
index 8658efc..7f7b010 100644
--- a/app/src/main/java/com/player/musicoo/activity/PrimaryActivity.kt
+++ b/app/src/main/java/com/player/musicoo/activity/PrimaryActivity.kt
@@ -15,12 +15,13 @@ import com.player.musicoo.R
import com.player.musicoo.databinding.ActivityPrimaryBinding
import com.player.musicoo.fragment.ImportFragment
import com.player.musicoo.fragment.MoHomeFragment
+import com.player.musicoo.fragment.SearchFragment
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.util.LogTag.LogD
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
-class PrimaryActivity : MoBaseActivity() {
+class PrimaryActivity : MoBaseActivity() ,SearchFragment.SearchFragmentCancelClickListener{
/**
* musicResponsiveListItemRenderer
* musicTwoRowItemRenderer
@@ -47,12 +48,16 @@ class PrimaryActivity : MoBaseActivity() {
private fun initClick() {
binding.homeBtn.setOnClickListener {
- changeFragment(0)
updateBtnState(0)
+ changeFragment(0)
+ }
+ binding.searchBtn.setOnClickListener {
+ updateBtnState(1)
+ changeFragment(1)
}
binding.importBtn.setOnClickListener {
- changeFragment(1)
- updateBtnState(1)
+ updateBtnState(2)
+ changeFragment(2)
}
binding.playBlackBtn.setOnClickListener {
@@ -81,9 +86,12 @@ class PrimaryActivity : MoBaseActivity() {
private fun initFragment() {
mFragments.clear()
mFragments.add(MoHomeFragment())
+ val searchFragment = SearchFragment()
+ searchFragment.setButtonClickListener(this)
+ mFragments.add(searchFragment)
mFragments.add(ImportFragment())
- changeFragment(0)
updateBtnState(0)
+ changeFragment(0)
}
private fun changeFragment(index: Int) {
@@ -116,9 +124,22 @@ class PrimaryActivity : MoBaseActivity() {
else -> R.drawable.home_unselect_icon
}
)
+ searchImg.setImageResource(
+ when (index) {
+ 1 -> {
+ binding.tabLayout.visibility = View.GONE
+ R.drawable.search_select_icon
+ }
+
+ else -> {
+ binding.tabLayout.visibility = View.VISIBLE
+ R.drawable.search_unselect_icon
+ }
+ }
+ )
importImg.setImageResource(
when (index) {
- 1 -> R.drawable.import_select_icon
+ 2 -> R.drawable.import_select_icon
else -> R.drawable.import_unselect_icon
}
)
@@ -204,14 +225,19 @@ class PrimaryActivity : MoBaseActivity() {
}
override fun onBackPressed() {
- if (backPressedTime + 2000 > System.currentTimeMillis()) {
- super.onBackPressed()
- backToast.cancel()
- return
+ if (currentIndex == 1) {//等于搜索返回响应先退回home
+ changeFragment(0)
+ updateBtnState(0)
} else {
- backToast.show()
+ if (backPressedTime + 2000 > System.currentTimeMillis()) {
+ super.onBackPressed()
+ backToast.cancel()
+ return
+ } else {
+ backToast.show()
+ }
+ backPressedTime = System.currentTimeMillis()
}
- backPressedTime = System.currentTimeMillis()
}
/**
@@ -261,4 +287,8 @@ class PrimaryActivity : MoBaseActivity() {
binding.name.text = mediaItem.mediaMetadata.title
binding.desc.text = mediaItem.mediaMetadata.artist
}
+
+ override fun onFragmentClick() {
+ onBackPressed()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/adapter/SearchHistoryAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/SearchHistoryAdapter.kt
new file mode 100644
index 0000000..578d08c
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/adapter/SearchHistoryAdapter.kt
@@ -0,0 +1,52 @@
+package com.player.musicoo.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.player.musicoo.databinding.SearchHistoryAdapterItemBinding
+
+class SearchHistoryAdapter(
+ private val context: Context,
+ private val list: List,
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding =
+ SearchHistoryAdapterItemBinding.inflate(LayoutInflater.from(context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val bean = list[position]
+ holder.bind(bean)
+ holder.itemView.setOnClickListener {
+ if (itemClickListener != null) {
+ itemClickListener?.onHistoryItemClick(position)
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(private val binding: SearchHistoryAdapterItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(bean: String) {
+ binding.apply {
+ historyName.text = bean
+ }
+ }
+ }
+
+ private var itemClickListener: HistoryOnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: HistoryOnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface HistoryOnItemClickListener {
+ fun onHistoryItemClick(position: Int)
+ }
+}
\ 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
new file mode 100644
index 0000000..150bff6
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/adapter/SearchResultOtherAdapter.kt
@@ -0,0 +1,67 @@
+package com.player.musicoo.adapter
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.media3.common.C
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.player.musicoo.R
+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
+
+class SearchResultOtherAdapter(
+ private val context: Context,
+ private val list: List,
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = SearchResultOtherItemBinding.inflate(LayoutInflater.from(context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val bean = list[position]
+ holder.bind(bean)
+
+ holder.itemView.setOnClickListener {
+
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(private val binding: SearchResultOtherItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ @SuppressLint("SetTextI18n")
+ fun bind(bean: Innertube.SearchDataPage.SearchResult) {
+
+ binding.apply {
+ Glide.with(context)
+ .load(bean.thumbnail)
+ .into(image)
+ title.text = bean.title
+ name.text = bean.desc
+ }
+ }
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: OnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/adapter/SearchSuggestionsAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/SearchSuggestionsAdapter.kt
new file mode 100644
index 0000000..57f30ce
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/adapter/SearchSuggestionsAdapter.kt
@@ -0,0 +1,45 @@
+package com.player.musicoo.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.player.musicoo.databinding.SearchSuggestionsAdapterItemBinding
+
+class SearchSuggestionsAdapter(
+ private val context: Context,
+ private val list: List,
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding =
+ SearchSuggestionsAdapterItemBinding.inflate(LayoutInflater.from(context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val bean = list[position]
+ holder.binding.text.text = bean
+ holder.itemView.setOnClickListener {
+ if (itemClickListener != null) {
+ itemClickListener?.onItemClick(position)
+ }
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(val binding: SearchSuggestionsAdapterItemBinding) :
+ RecyclerView.ViewHolder(binding.root)
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: OnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/adapter/SingerDetailsSongListAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/SingerDetailsSongListAdapter.kt
new file mode 100644
index 0000000..39c509b
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/adapter/SingerDetailsSongListAdapter.kt
@@ -0,0 +1,137 @@
+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
+import androidx.recyclerview.widget.RecyclerView
+import com.bumptech.glide.Glide
+import com.player.musicoo.App
+import com.player.musicoo.R
+import com.player.musicoo.activity.MoPlayDetailsActivity
+import com.player.musicoo.activity.PlayDetailsActivity
+import com.player.musicoo.bean.Audio
+import com.player.musicoo.databinding.DetailsListItemBinding
+import com.player.musicoo.databinding.MusicResponsiveItemBinding
+import com.player.musicoo.databinding.SoundsOfAppliancesLayoutBinding
+import com.player.musicoo.databinding.SoundsOfNatureLayoutBinding
+import com.player.musicoo.innertube.Innertube
+import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
+import com.player.musicoo.innertube.models.MusicShelfRenderer
+import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
+import com.player.musicoo.util.getAudioDurationFromAssets
+
+class SingerDetailsSongListAdapter(
+ private val context: Context,
+ private val list: List,
+) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = DetailsListItemBinding.inflate(LayoutInflater.from(context), parent, false)
+ return ViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val bean = list[position]
+ holder.bind(bean)
+
+ val watchEndpoint = bean.musicResponsiveListItemRenderer
+ ?.flexColumns
+ ?.firstOrNull()
+ ?.musicResponsiveListItemFlexColumnRenderer
+ ?.text
+ ?.runs
+ ?.firstOrNull()
+ ?.navigationEndpoint
+ ?.watchEndpoint
+ val videoId = watchEndpoint?.videoId
+ val playlistId = watchEndpoint?.playlistId
+ val playlistSetVideoId = watchEndpoint?.playlistSetVideoId
+ val params = watchEndpoint?.params
+ val flexColumns = bean.musicResponsiveListItemRenderer
+ ?.flexColumns
+ val name = flexColumns?.get(0)?.musicResponsiveListItemFlexColumnRenderer?.text?.text
+ val desc = flexColumns?.get(1)?.musicResponsiveListItemFlexColumnRenderer?.text?.text
+
+ holder.itemView.setOnClickListener {
+
+ val intent = Intent(context, MoPlayDetailsActivity::class.java)
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID, videoId)
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_ID, playlistId)
+ intent.putExtra(
+ MoPlayDetailsActivity.PLAY_DETAILS_PLAY_LIST_SET_VIDEO_ID,
+ playlistSetVideoId
+ )
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_PARAMS, params)
+
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, name)
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, desc)
+ context.startActivity(intent)
+ }
+ }
+
+ override fun getItemCount(): Int = list.size
+
+ inner class ViewHolder(private val binding: DetailsListItemBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ @SuppressLint("SetTextI18n")
+ fun bind(bean: MusicShelfRenderer.Content) {
+
+ binding.apply {
+
+ val thumbnailUrl = bean.musicResponsiveListItemRenderer
+ ?.thumbnail
+ ?.musicThumbnailRenderer
+ ?.thumbnail
+ ?.thumbnails?.let {
+ it.getOrNull(2) ?: it.getOrNull(1) ?: it.getOrNull(0)
+ }?.url
+
+ if (!thumbnailUrl.isNullOrEmpty()) {
+ image.visibility = View.VISIBLE
+ Glide.with(context)
+ .load(thumbnailUrl)
+ .into(image)
+ sortTv.visibility = View.GONE
+ } else {
+ image.visibility = View.GONE
+ sortTv.visibility = View.VISIBLE
+ sortTv.text = "${bindingAdapterPosition + 1}"
+ }
+
+ title.text = bean.musicResponsiveListItemRenderer
+ ?.flexColumns
+ ?.firstOrNull()
+ ?.musicResponsiveListItemFlexColumnRenderer
+ ?.text
+ ?.text
+ val desc = bean.musicResponsiveListItemRenderer
+ ?.flexColumns
+ ?.drop(1)
+ ?.map { it.musicResponsiveListItemFlexColumnRenderer?.text?.text }
+ ?.joinToString(" ")
+ if (desc.isNullOrEmpty()) {
+ name.visibility = View.GONE
+ } else {
+ name.visibility = View.VISIBLE
+ name.text = desc
+ }
+
+ }
+ }
+ }
+
+ private var itemClickListener: OnItemClickListener? = null
+
+ fun setOnItemClickListener(listener: OnItemClickListener) {
+ itemClickListener = listener
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt b/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt
new file mode 100644
index 0000000..9bd1bb6
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt
@@ -0,0 +1,319 @@
+package com.player.musicoo.fragment
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.TextView
+import android.widget.TextView.OnEditorActionListener
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.flexbox.AlignItems
+import com.google.android.flexbox.FlexWrap
+import com.google.android.flexbox.FlexboxLayoutManager
+import com.google.android.flexbox.JustifyContent
+import com.gyf.immersionbar.ktx.immersionBar
+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.SearchSuggestionsBody
+import com.player.musicoo.innertube.requests.moSearchPage
+import com.player.musicoo.innertube.requests.searchSuggestions
+import com.player.musicoo.util.LogTag
+import com.player.musicoo.view.SearchResultOptimalView
+import com.player.musicoo.view.SearchResultOtherView
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.selects.select
+
+class SearchFragment : MoBaseFragment(), TextWatcher,
+ View.OnFocusChangeListener, SearchSuggestionsAdapter.OnItemClickListener,
+ OnEditorActionListener, SearchHistoryAdapter.HistoryOnItemClickListener {
+
+ interface SearchFragmentCancelClickListener {
+ fun onFragmentClick()
+ }
+
+ fun setButtonClickListener(listener: SearchFragmentCancelClickListener) {
+ this.buttonClickListener = listener
+ }
+
+ private var buttonClickListener: SearchFragmentCancelClickListener? = null
+
+ private val requests: Channel = Channel(Channel.UNLIMITED)
+
+ sealed class Request {
+ data object SearchSuggestions : Request()
+ data class SearchData(val input: String) : Request()
+ }
+
+ private var searchSuggestionsAdapterAdapter: SearchSuggestionsAdapter? = null
+ private var searchSuggestionsList: MutableList = mutableListOf()
+ private var searchHistorySet: MutableSet = mutableSetOf()
+ private var searchHistory: MutableList = mutableListOf()
+ private var searchHistoryAdapter: SearchHistoryAdapter? = null
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentSearchBinding
+ get() = FragmentSearchBinding::inflate
+
+ override suspend fun onViewCreated() {
+ initView()
+ initSearchSuggestionsAdapter()
+ initSearchHistoryAdapter()
+ initImmersionBar()
+ onReceive()
+ }
+
+ private fun initView() {
+ binding.cancelBtn.setOnClickListener {
+ buttonClickListener?.onFragmentClick()
+ }
+ binding.searchEdit.let {
+ it.setOnEditorActionListener(this)
+ it.addTextChangedListener(this)
+ it.onFocusChangeListener = this
+ it.requestFocus()
+ }
+ binding.deleteInputBtn.setOnClickListener {
+ binding.searchEdit.text.clear()
+ }
+ binding.deleteHistoryBtn.setOnClickListener {
+ appStore.searchHistoryStore = emptySet()
+ updateHistoryUi()
+ }
+ }
+
+ private fun initSearchSuggestionsAdapter() {
+ searchSuggestionsAdapterAdapter =
+ SearchSuggestionsAdapter(requireActivity(), searchSuggestionsList)
+ searchSuggestionsAdapterAdapter?.setOnItemClickListener(this)
+ binding.searchSuggestionsRv.layoutManager = LinearLayoutManager(
+ requireActivity(),
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ binding.searchSuggestionsRv.adapter = searchSuggestionsAdapterAdapter
+ }
+
+ private fun initSearchHistoryAdapter() {
+ searchHistoryAdapter =
+ SearchHistoryAdapter(requireActivity(), searchHistory)
+ searchHistoryAdapter?.setOnItemClickListener(this)
+ val layoutManager = FlexboxLayoutManager(requireActivity())
+ layoutManager.flexWrap = FlexWrap.WRAP // 设置换行方式为自动换行
+ layoutManager.alignItems = AlignItems.STRETCH // 设置项目在副轴上的对齐方式为拉伸
+ layoutManager.justifyContent = JustifyContent.FLEX_START // 设置主轴上的对齐方式为起始端对齐
+ binding.historyRv.layoutManager = layoutManager
+
+ binding.historyRv.adapter = searchHistoryAdapter
+
+ updateHistoryUi()
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private fun updateHistoryUi() {
+ searchHistory.clear()
+ searchHistory.addAll(appStore.searchHistoryStore)
+ searchHistoryAdapter?.notifyDataSetChanged()
+ if (appStore.searchHistoryStore.isNotEmpty()) {
+ binding.historyLayout.visibility = View.VISIBLE
+ } else {
+ binding.historyLayout.visibility = View.GONE
+ }
+ }
+
+ private fun initImmersionBar() {
+ immersionBar {
+ statusBarDarkFont(false)
+ statusBarView(binding.view)
+ }
+ }
+
+ @SuppressLint("NotifyDataSetChanged", "SetTextI18n")
+ private suspend fun onReceive() {
+ while (isActive) {
+ select {
+ requests.onReceive {
+ when (it) {
+ is Request.SearchSuggestions -> {
+ val input = binding.searchEdit.text.toString().trim()
+ Innertube.searchSuggestions(SearchSuggestionsBody(input = input))
+ ?.onSuccess { suggestionsList ->
+ LogTag.LogD(TAG, "suggestionsList->${suggestionsList?.size}")
+ if (suggestionsList != null) {
+ showSearchSuggestions()
+ searchSuggestionsList.clear()
+ searchSuggestionsList.addAll(suggestionsList)
+ searchSuggestionsAdapterAdapter?.notifyDataSetChanged()
+ } else {
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ }
+ }?.onFailure { error ->
+ LogTag.LogD(TAG, "searchSuggestions onFailure->${error}")
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ }
+ }
+
+ is Request.SearchData -> {
+ binding.contentLayout.removeAllViews()
+ binding.searchEdit.clearFocus()
+ showLoadingLayout()
+ val input = it.input
+ if (input.isNotEmpty()) {
+ searchHistorySet.clear()
+ searchHistorySet.addAll(appStore.searchHistoryStore)
+ if (!appStore.searchHistoryStore.contains(input)) {//把不存在的记录保存到集合中
+ searchHistorySet.add(input)
+ }
+ appStore.searchHistoryStore = searchHistorySet
+ updateHistoryUi()
+
+ Innertube.moSearchPage(input)?.onSuccess { result ->
+ showResultContent()
+ for (dataPage: Innertube.SearchDataPage in result) {
+ if (dataPage.type == 1) {//type为1的是最佳结果。
+ binding.contentLayout.addView(
+ SearchResultOptimalView(requireActivity(), dataPage)
+ )
+ } else if (dataPage.type == 2) {//type为2的是其他搜索结果。
+ if (dataPage.searchResultList.isNotEmpty()) {//如何数据集合为空就不添加view
+ binding.contentLayout.addView(
+ SearchResultOtherView(
+ requireActivity(),
+ dataPage
+ )
+ )
+ }
+ }
+ }
+ }?.onFailure {
+ showNoContentLayout()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onHiddenChanged(hidden: Boolean) {
+ super.onHiddenChanged(hidden)
+ if (!hidden) {
+ binding.searchEdit.requestFocus()
+ } else {
+ binding.searchEdit.clearFocus()
+ binding.searchEdit.text.clear()
+ binding.contentLayout.removeAllViews()
+ }
+ }
+
+ override fun onHistoryItemClick(position: Int) {
+ binding.searchEdit.setText(searchHistory[position])
+ requests.trySend(Request.SearchData(searchHistory[position]))
+ }
+
+ override fun onItemClick(position: Int) {
+ binding.searchEdit.setText(searchSuggestionsList[position])
+ requests.trySend(Request.SearchData(searchSuggestionsList[position]))
+ }
+
+ override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ requests.trySend(Request.SearchData(binding.searchEdit.text.toString().trim()))
+ return true
+ }
+ return false
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ @SuppressLint("NotifyDataSetChanged")
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ if (s.isNullOrEmpty()) {
+ binding.deleteInputBtn.visibility = View.GONE
+ showSearchHistory()
+ updateHistoryUi()
+ binding.searchEdit.requestFocus()
+ } else {
+ binding.deleteInputBtn.visibility = View.VISIBLE
+ }
+ }
+
+ override fun afterTextChanged(s: Editable?) {
+ requests.trySend(Request.SearchSuggestions)
+ }
+
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ if (hasFocus) {
+ Handler(Looper.getMainLooper()).postDelayed({
+ showImm()
+ }, 200)
+ } else {
+ Handler(Looper.getMainLooper()).postDelayed({
+ hideImm()
+ }, 200)
+ }
+ }
+
+ private fun showImm() {
+ val imm =
+ requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.showSoftInput(binding.searchEdit, InputMethodManager.SHOW_IMPLICIT)
+ }
+
+ private fun hideImm() {
+ val imm =
+ requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(binding.searchEdit.windowToken, 0)
+ }
+
+ private fun showResultContent() {
+ binding.contentScrollView.visibility = View.VISIBLE
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ binding.historyLayout.visibility = View.GONE
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.GONE
+ }
+
+ private fun showSearchSuggestions() {
+ binding.contentScrollView.visibility = View.GONE
+ binding.searchSuggestionsLayout.visibility = View.VISIBLE
+ binding.historyLayout.visibility = View.GONE
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.GONE
+ }
+
+ private fun showSearchHistory() {
+ binding.contentScrollView.visibility = View.GONE
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ binding.historyLayout.visibility = View.VISIBLE
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.GONE
+ }
+
+ private fun showLoadingLayout() {
+ binding.loadingLayout.visibility = View.VISIBLE
+ binding.noContentLayout.visibility = View.GONE
+ binding.contentScrollView.visibility = View.GONE
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ binding.historyLayout.visibility = View.GONE
+ }
+
+ private fun showNoContentLayout() {
+ binding.loadingLayout.visibility = View.GONE
+ binding.noContentLayout.visibility = View.VISIBLE
+ binding.contentScrollView.visibility = View.GONE
+ binding.searchSuggestionsLayout.visibility = View.GONE
+ binding.historyLayout.visibility = View.GONE
+ }
+
+}
\ No newline at end of file
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 a81a353..87a5768 100644
--- a/app/src/main/java/com/player/musicoo/innertube/Innertube.kt
+++ b/app/src/main/java/com/player/musicoo/innertube/Innertube.kt
@@ -1,6 +1,9 @@
package com.player.musicoo.innertube
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer
+import com.player.musicoo.innertube.models.MusicResponsiveListItemRenderer
+import com.player.musicoo.innertube.models.MusicShelfRenderer
+import com.player.musicoo.innertube.models.MusicTwoRowItemRenderer
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.BrowserUserAgent
@@ -165,6 +168,37 @@ object Innertube {
companion object
}
+ data class SingerDetailsPage(
+ val title: String?,
+ val thumbnail: String?,
+ val description: String?,
+ val list: List?
+ )
+
+ data class SingerDetailsListPage(
+ val title: String?,
+ val contents: SingerDetailsContent?
+ ){
+ data class SingerDetailsContent(
+ val musicShelfContentList: List?,
+ val musicCarouselShelfContentList: List?,
+ )
+ }
+
+ data class SearchDataPage(
+ val type: Int?,
+ val title: String?,
+ val searchResultList: List
+ ) {
+ data class SearchResult(
+ val title: String?,
+ val desc: String?,
+ val thumbnail: String?,
+ val videoId: String?,
+ val browseId: String?,
+ )
+ }
+
data class BaseHomePage(
val visitorData: String?,
val cToken: String?,
diff --git a/app/src/main/java/com/player/musicoo/innertube/models/MusicCardShelfRenderer.kt b/app/src/main/java/com/player/musicoo/innertube/models/MusicCardShelfRenderer.kt
new file mode 100644
index 0000000..e2d4b78
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/innertube/models/MusicCardShelfRenderer.kt
@@ -0,0 +1,36 @@
+package com.player.musicoo.innertube.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MusicCardShelfRenderer(
+ val thumbnail: MusicThumbnailRenderer?,
+ val title: Runs?,
+ val subtitle: Runs?,
+ val header: Header?,
+) {
+ @Serializable
+ data class MusicThumbnailRenderer(
+ val musicThumbnailRenderer: MusicThumbnailRenderer?
+ ) {
+ @Serializable
+ data class MusicThumbnailRenderer(
+ val thumbnail: Thumbnail?
+ ) {
+ @Serializable
+ data class Thumbnail(
+ val thumbnails: List?
+ )
+ }
+ }
+
+ @Serializable
+ data class Header(
+ val musicCardShelfHeaderBasicRenderer: MusicCardShelfHeaderBasicRenderer?
+ ) {
+ @Serializable
+ data class MusicCardShelfHeaderBasicRenderer(
+ val title: Runs?,
+ )
+ }
+}
diff --git a/app/src/main/java/com/player/musicoo/innertube/models/SectionListRenderer.kt b/app/src/main/java/com/player/musicoo/innertube/models/SectionListRenderer.kt
index 9050c5e..0cc95b0 100644
--- a/app/src/main/java/com/player/musicoo/innertube/models/SectionListRenderer.kt
+++ b/app/src/main/java/com/player/musicoo/innertube/models/SectionListRenderer.kt
@@ -18,6 +18,8 @@ data class SectionListRenderer(
val musicShelfRenderer: MusicShelfRenderer?,
val gridRenderer: GridRenderer?,
val musicDescriptionShelfRenderer: MusicDescriptionShelfRenderer?,
+ @JsonNames("musicCardShelfRenderer")
+ val musicCardShelfRenderer: MusicCardShelfRenderer?
) {
@Serializable
diff --git a/app/src/main/java/com/player/musicoo/innertube/models/bodies/SearchBody.kt b/app/src/main/java/com/player/musicoo/innertube/models/bodies/SearchBody.kt
index 3d68dff..d9f1588 100644
--- a/app/src/main/java/com/player/musicoo/innertube/models/bodies/SearchBody.kt
+++ b/app/src/main/java/com/player/musicoo/innertube/models/bodies/SearchBody.kt
@@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
data class SearchBody(
val context: Context = Context.DefaultWeb,
val query: String,
- val params: String
+ val params: String? = null
)
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
new file mode 100644
index 0000000..74f761e
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/innertube/requests/MoSearchPage.kt
@@ -0,0 +1,159 @@
+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.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.SearchBody
+import com.player.musicoo.innertube.utils.runCatchingNonCancellable
+import com.player.musicoo.util.LogTag
+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>? =
+ runCatchingNonCancellable {
+
+ val response = client.post(search) {
+ setBody(SearchBody(query = query))
+ }.body()
+
+ val searchDataPageList: MutableList = mutableListOf()
+
+ val contents = response.contents
+ ?.tabbedSearchResultsRenderer
+ ?.tabs
+ ?.firstOrNull()
+ ?.tabRenderer
+ ?.content
+ ?.sectionListRenderer
+ ?.contents
+ var type = -1
+ var title = ""
+ if (contents != null) {
+ for (content: SectionListRenderer.Content in contents) {
+ //保证每次循环都是一个新的集合
+ val searchResultItemList: MutableList =
+ mutableListOf()
+
+ if (content.musicCardShelfRenderer != null) {
+ type = 1
+ title = content.musicCardShelfRenderer
+ .header
+ ?.musicCardShelfHeaderBasicRenderer
+ ?.title
+ ?.text ?: ""
+
+ val searchResultTitle = content.musicCardShelfRenderer
+ .title
+ ?.runs
+ ?.firstOrNull()
+ ?.text ?: ""
+
+ val searchResultDesc = content.musicCardShelfRenderer
+ .subtitle?.text
+
+ val searchResultThumbnail = content.musicCardShelfRenderer
+ .thumbnail
+ ?.musicThumbnailRenderer
+ ?.thumbnail
+ ?.thumbnails
+ ?.let {
+ it.getOrNull(3) ?: it.getOrNull(2) ?: it.getOrNull(1)
+ ?: it.getOrNull(0)
+ }
+ ?.url ?: ""
+
+ val searchResultVideoId = content.musicCardShelfRenderer
+ .title
+ ?.runs
+ ?.firstOrNull()
+ ?.navigationEndpoint
+ ?.watchEndpoint
+ ?.videoId ?: ""
+
+ val searchResultBrowseId = content.musicCardShelfRenderer
+ .title
+ ?.runs
+ ?.firstOrNull()
+ ?.navigationEndpoint
+ ?.browseEndpoint
+ ?.browseId
+
+ val result = Innertube.SearchDataPage.SearchResult(
+ title = searchResultTitle,
+ desc = searchResultDesc,
+ thumbnail = searchResultThumbnail,
+ videoId = searchResultVideoId,
+ browseId = searchResultBrowseId
+ )
+ searchResultItemList.add(result)
+ } else {
+ if (content.musicShelfRenderer != null) {
+ type = 2
+ title = content.musicShelfRenderer.title
+ ?.text ?: ""
+ val itemContents = content.musicShelfRenderer.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 ?: ""
+ )
+ if (!item.videoId.isNullOrBlank() || !item.browseId.isNullOrBlank()) {
+ searchResultItemList.add(item)
+ }
+ }
+ }
+ }
+ }
+ val dataPage =
+ Innertube.SearchDataPage(
+ type,
+ title = title,
+ searchResultList = searchResultItemList
+ )
+ searchDataPageList.add(dataPage)
+ }
+ }
+ searchDataPageList
+ }
\ 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
new file mode 100644
index 0000000..0b7be87
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/innertube/requests/MoSingerlistPage.kt
@@ -0,0 +1,112 @@
+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.MusicCarouselShelfRenderer
+import com.player.musicoo.innertube.models.MusicShelfRenderer
+import com.player.musicoo.innertube.models.SectionListRenderer
+import com.player.musicoo.innertube.models.bodies.BrowseBody
+import com.player.musicoo.innertube.utils.runCatchingNonCancellable
+import com.player.musicoo.util.LogTag
+import io.ktor.client.call.body
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+
+suspend fun Innertube.moSingerListPage(browseId: String): Result? =
+ runCatchingNonCancellable {
+ val response = client.post(browse) {
+ setBody(BrowseBody(browseId = browseId))
+ }.body()
+
+ val list: MutableList = mutableListOf()
+
+ val musicImmersiveHeaderRenderer = response.header
+ ?.musicImmersiveHeaderRenderer
+
+ val title = musicImmersiveHeaderRenderer
+ ?.title
+ ?.text
+
+ val thumbnail = musicImmersiveHeaderRenderer
+ ?.thumbnail
+ ?.musicThumbnailRenderer
+ ?.thumbnail
+ ?.thumbnails
+ ?.let {
+ it.getOrNull(2) ?: it.getOrNull(1) ?: it.getOrNull(0)
+ }?.url
+
+ val description = musicImmersiveHeaderRenderer
+ ?.description
+ ?.text
+
+ val sectionListRendererContents = response
+ .contents
+ ?.singleColumnBrowseResultsRenderer
+ ?.tabs
+ ?.firstOrNull()
+ ?.tabRenderer
+ ?.content
+ ?.sectionListRenderer
+ ?.contents
+
+ if (sectionListRendererContents != null) {
+ for (content: SectionListRenderer.Content in sectionListRendererContents) {
+ var title = ""
+ val musicShelfRendererContents: MutableList =
+ mutableListOf()
+ val musicCarouselShelfContents: MutableList =
+ mutableListOf()
+ if (content.musicShelfRenderer != null) {
+ title = content.musicShelfRenderer
+ .title?.text ?: ""
+ if (content.musicShelfRenderer.contents != null) {
+ musicShelfRendererContents.clear()
+ musicShelfRendererContents.addAll(content.musicShelfRenderer.contents)
+ }
+ }
+ if (content.musicCarouselShelfRenderer != null) {
+ title = content.musicCarouselShelfRenderer.header
+ ?.musicCarouselShelfBasicHeaderRenderer
+ ?.title
+ ?.text ?: ""
+
+ if (content.musicCarouselShelfRenderer.contents != null) {
+ musicCarouselShelfContents.clear()
+ musicCarouselShelfContents.addAll(content.musicCarouselShelfRenderer.contents)
+ }
+ }
+
+ val singerDetailsContent = Innertube.SingerDetailsListPage.SingerDetailsContent(
+ musicShelfRendererContents,
+ musicCarouselShelfContents
+ )
+
+ val singerDetails =
+ Innertube.SingerDetailsListPage(title = title, contents = singerDetailsContent)
+
+ 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)
+ }
+ }
+ }
+
+ Innertube.SingerDetailsPage(
+ title = title,
+ thumbnail = thumbnail,
+ description = description,
+ list
+ )
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/sp/AppStore.kt b/app/src/main/java/com/player/musicoo/sp/AppStore.kt
index 8ce1962..4615abe 100644
--- a/app/src/main/java/com/player/musicoo/sp/AppStore.kt
+++ b/app/src/main/java/com/player/musicoo/sp/AppStore.kt
@@ -11,9 +11,13 @@ class AppStore(context: Context) {
.asStoreProvider()
)
-
+ var searchHistoryStore: Set by store.stringSet(
+ key = SEARCH_HISTORY,
+ defaultValue = emptySet()
+ )
companion object {
private const val FILE_NAME = "music_oo_app"
+ const val SEARCH_HISTORY = "search_history"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt b/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt
new file mode 100644
index 0000000..4b88b12
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/view/SearchResultOptimalView.kt
@@ -0,0 +1,60 @@
+package com.player.musicoo.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.bumptech.glide.Glide
+import com.player.musicoo.R
+import com.player.musicoo.activity.MoPlayDetailsActivity
+import com.player.musicoo.activity.MoSingerDetailsActivity
+import com.player.musicoo.innertube.Innertube
+
+@SuppressLint("ViewConstructor")
+class SearchResultOptimalView(context: Context, data: Innertube.SearchDataPage) :
+ ModuleView(context) {
+
+ init {
+ contentView = inflate(getContext(), R.layout.search_result_optimal_layout, this)
+ val title = contentView?.findViewById(R.id.title)
+ title?.text = data.title
+
+ val name = contentView?.findViewById(R.id.name)
+ name?.text = data.searchResultList.firstOrNull()?.title
+
+ val desc = contentView?.findViewById(R.id.desc)
+ desc?.text = data.searchResultList.firstOrNull()?.desc
+
+ val image = contentView?.findViewById(R.id.image)
+ val optimalBean = data.searchResultList.firstOrNull()
+ val url = optimalBean?.thumbnail
+ Glide.with(context)
+ .load(url)
+ .into(image!!)
+
+ val playBtn = contentView?.findViewById(R.id.playBtn)
+ playBtn?.setOnClickListener {
+ if (optimalBean != null) {
+ if (!optimalBean.videoId.isNullOrEmpty()) {
+ val intent = Intent(context, MoPlayDetailsActivity::class.java)
+ intent.putExtra(
+ MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID,
+ optimalBean.videoId
+ )
+ intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, optimalBean.title)
+ 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)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt b/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt
new file mode 100644
index 0000000..688588e
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/view/SearchResultOtherView.kt
@@ -0,0 +1,26 @@
+package com.player.musicoo.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.widget.TextView
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.player.musicoo.R
+import com.player.musicoo.adapter.SearchResultOtherAdapter
+import com.player.musicoo.innertube.Innertube
+
+@SuppressLint("ViewConstructor")
+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 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/java/com/player/musicoo/view/SingerDetailsOtherView.kt b/app/src/main/java/com/player/musicoo/view/SingerDetailsOtherView.kt
new file mode 100644
index 0000000..ac89771
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/view/SingerDetailsOtherView.kt
@@ -0,0 +1,27 @@
+package com.player.musicoo.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.widget.TextView
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.player.musicoo.R
+import com.player.musicoo.adapter.TowRowListAdapter
+import com.player.musicoo.innertube.Innertube
+
+@SuppressLint("ViewConstructor")
+class SingerDetailsOtherView (context: Context, bean: Innertube.SingerDetailsListPage) :
+ ModuleView(context) {
+ init {
+ contentView = inflate(getContext(), R.layout.singer_music_list_layout, this)
+ val title = contentView?.findViewById(R.id.title)
+ title?.text = bean.title
+
+ val rv = contentView?.findViewById(R.id.rv)
+
+ val adapter = TowRowListAdapter(context, bean.contents?.musicCarouselShelfContentList!!)
+ rv?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ rv?.adapter = adapter
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/player/musicoo/view/SingerDetailsSongView.kt b/app/src/main/java/com/player/musicoo/view/SingerDetailsSongView.kt
new file mode 100644
index 0000000..07899f1
--- /dev/null
+++ b/app/src/main/java/com/player/musicoo/view/SingerDetailsSongView.kt
@@ -0,0 +1,28 @@
+package com.player.musicoo.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.widget.TextView
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.player.musicoo.R
+import com.player.musicoo.adapter.SingerDetailsSongListAdapter
+import com.player.musicoo.innertube.Innertube
+
+@SuppressLint("ViewConstructor")
+class SingerDetailsSongView(context: Context, bean: Innertube.SingerDetailsListPage) :
+ ModuleView(context) {
+ init {
+ contentView = inflate(getContext(), R.layout.singer_music_list_layout, this)
+ val title = contentView?.findViewById(R.id.title)
+ title?.text = bean.title
+
+ val rv = contentView?.findViewById(R.id.rv)
+
+ val adapter = SingerDetailsSongListAdapter(context, bean.contents?.musicShelfContentList!!)
+ rv?.layoutManager = LinearLayoutManager(context, GridLayoutManager.VERTICAL, false)
+ rv?.adapter = adapter
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/arrow_bottom_grey_icon.xml b/app/src/main/res/drawable/arrow_bottom_grey_icon.xml
new file mode 100644
index 0000000..824e787
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_bottom_grey_icon.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/drawable/cha_icon.xml b/app/src/main/res/drawable/cha_icon.xml
new file mode 100644
index 0000000..449ffa7
--- /dev/null
+++ b/app/src/main/res/drawable/cha_icon.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/delete_icon.xml b/app/src/main/res/drawable/delete_icon.xml
new file mode 100644
index 0000000..b2686fc
--- /dev/null
+++ b/app/src/main/res/drawable/delete_icon.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/drw_search_history_tag_bg.xml b/app/src/main/res/drawable/drw_search_history_tag_bg.xml
new file mode 100644
index 0000000..5638a25
--- /dev/null
+++ b/app/src/main/res/drawable/drw_search_history_tag_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/drw_search_layout_bg.xml b/app/src/main/res/drawable/drw_search_layout_bg.xml
new file mode 100644
index 0000000..5567d7c
--- /dev/null
+++ b/app/src/main/res/drawable/drw_search_layout_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/search_select_icon.xml b/app/src/main/res/drawable/search_select_icon.xml
new file mode 100644
index 0000000..10747da
--- /dev/null
+++ b/app/src/main/res/drawable/search_select_icon.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/search_unselect_icon.xml b/app/src/main/res/drawable/search_unselect_icon.xml
new file mode 100644
index 0000000..250c13c
--- /dev/null
+++ b/app/src/main/res/drawable/search_unselect_icon.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml
index cf9733e..62eac86 100644
--- a/app/src/main/res/layout/activity_details.xml
+++ b/app/src/main/res/layout/activity_details.xml
@@ -176,6 +176,8 @@
diff --git a/app/src/main/res/layout/activity_mo_play_details.xml b/app/src/main/res/layout/activity_mo_play_details.xml
index a9b3129..a4317a3 100644
--- a/app/src/main/res/layout/activity_mo_play_details.xml
+++ b/app/src/main/res/layout/activity_mo_play_details.xml
@@ -409,6 +409,8 @@
diff --git a/app/src/main/res/layout/activity_primary.xml b/app/src/main/res/layout/activity_primary.xml
index ab89e07..3dc4f40 100644
--- a/app/src/main/res/layout/activity_primary.xml
+++ b/app/src/main/res/layout/activity_primary.xml
@@ -5,6 +5,13 @@
android:layout_height="match_parent"
android:background="@color/main_bg_color">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index 53bf6f6..fc9692c 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -2,14 +2,7 @@
-
-
+ android:layout_height="match_parent">
+ android:layout_height="match_parent">
-
-
-
-
+ android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/music_list_layout.xml b/app/src/main/res/layout/music_list_layout.xml
index a720bb9..7c3e903 100644
--- a/app/src/main/res/layout/music_list_layout.xml
+++ b/app/src/main/res/layout/music_list_layout.xml
@@ -19,6 +19,8 @@
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_history_adapter_item.xml b/app/src/main/res/layout/search_history_adapter_item.xml
new file mode 100644
index 0000000..f5a0821
--- /dev/null
+++ b/app/src/main/res/layout/search_history_adapter_item.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_result_optimal_layout.xml b/app/src/main/res/layout/search_result_optimal_layout.xml
new file mode 100644
index 0000000..956fea9
--- /dev/null
+++ b/app/src/main/res/layout/search_result_optimal_layout.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_result_other_item.xml b/app/src/main/res/layout/search_result_other_item.xml
new file mode 100644
index 0000000..cc813e7
--- /dev/null
+++ b/app/src/main/res/layout/search_result_other_item.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_result_other_layout.xml b/app/src/main/res/layout/search_result_other_layout.xml
new file mode 100644
index 0000000..695f325
--- /dev/null
+++ b/app/src/main/res/layout/search_result_other_layout.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_suggestions_adapter_item.xml b/app/src/main/res/layout/search_suggestions_adapter_item.xml
new file mode 100644
index 0000000..3f16409
--- /dev/null
+++ b/app/src/main/res/layout/search_suggestions_adapter_item.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/singer_music_list_layout.xml b/app/src/main/res/layout/singer_music_list_layout.xml
new file mode 100644
index 0000000..7c3e903
--- /dev/null
+++ b/app/src/main/res/layout/singer_music_list_layout.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c42ca71..9bfc977 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,6 +9,7 @@
OK
Cancel
Settings
+ Search
About
Feedback
Share
@@ -22,4 +23,8 @@
Press again to exit
Content loading failed, click to try again.
Try Again
+ Enter keywords
+ History
+ No Found
+ More
\ No newline at end of file