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