This commit is contained in:
ocean 2024-05-13 18:34:51 +08:00
parent c90b8a6930
commit 1c741d22ff
44 changed files with 2211 additions and 40 deletions

View File

@ -82,4 +82,7 @@ dependencies {
implementation("io.ktor:ktor-client-serialization:2.1.2") implementation("io.ktor:ktor-client-serialization:2.1.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
implementation("org.brotli:dec:0.1.2") implementation("org.brotli:dec:0.1.2")
implementation("com.google.android.flexbox:flexbox:3.0.0")
} }

View File

@ -56,6 +56,9 @@
<activity <activity
android:name=".activity.MoPlayDetailsActivity" android:name=".activity.MoPlayDetailsActivity"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity
android:name=".activity.MoSingerDetailsActivity"
android:screenOrientation="portrait" />
<service <service
android:name=".service.PlaybackService" android:name=".service.PlaybackService"

View File

@ -6,30 +6,41 @@ import com.bumptech.glide.Glide
import com.gyf.immersionbar.ktx.immersionBar import com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.adapter.DetailsListAdapter import com.player.musicoo.adapter.DetailsListAdapter
import com.player.musicoo.databinding.ActivityDetailsBinding import com.player.musicoo.databinding.ActivityDetailsBinding
import com.player.musicoo.fragment.MoHomeFragment
import com.player.musicoo.innertube.Innertube import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.requests.moPlaylistPage import com.player.musicoo.innertube.requests.moPlaylistPage
import com.player.musicoo.util.LogTag.LogD import com.player.musicoo.util.LogTag.LogD
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
class MoListDetailsActivity : MoBaseActivity() { class MoListDetailsActivity : MoBaseActivity() {
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
enum class Request {
TryAgain,
}
companion object { companion object {
const val PLAY_LIST_PAGE_BROWSE_ID = "play_list_page_browse_id" const val PLAY_LIST_PAGE_BROWSE_ID = "play_list_page_browse_id"
} }
private lateinit var binding: ActivityDetailsBinding private lateinit var binding: ActivityDetailsBinding
private var browseId: String? = null
override suspend fun main() { override suspend fun main() {
binding = ActivityDetailsBinding.inflate(layoutInflater) binding = ActivityDetailsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
initImmersionBar() initImmersionBar()
val browseId = intent.getStringExtra(PLAY_LIST_PAGE_BROWSE_ID) browseId = intent.getStringExtra(PLAY_LIST_PAGE_BROWSE_ID)
if (browseId.isNullOrEmpty() || browseId == "null") { if (browseId.isNullOrEmpty() || browseId == "null") {
finish() finish()
return return
} }
initView() initView()
LogD(TAG, "browseId->${browseId}") LogD(TAG, "browseId->${browseId}")
initData(browseId) initData(browseId!!)
onReceive()
} }
private fun initImmersionBar() { private fun initImmersionBar() {
@ -39,10 +50,27 @@ class MoListDetailsActivity : MoBaseActivity() {
} }
} }
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
requests.onReceive {
when (it) {
Request.TryAgain -> {
initData(browseId!!)
}
}
}
}
}
}
private fun initView() { private fun initView() {
binding.backBtn.setOnClickListener { binding.backBtn.setOnClickListener {
finish() finish()
} }
binding.tryAgainBtn.setOnClickListener {
requests.trySend(Request.TryAgain)
}
} }
private suspend fun initData(browseId: String) { private suspend fun initData(browseId: String) {

View File

@ -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<Request> = 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<Unit> {
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
}
}

View File

@ -15,12 +15,13 @@ import com.player.musicoo.R
import com.player.musicoo.databinding.ActivityPrimaryBinding import com.player.musicoo.databinding.ActivityPrimaryBinding
import com.player.musicoo.fragment.ImportFragment import com.player.musicoo.fragment.ImportFragment
import com.player.musicoo.fragment.MoHomeFragment import com.player.musicoo.fragment.MoHomeFragment
import com.player.musicoo.fragment.SearchFragment
import com.player.musicoo.media.MediaControllerManager import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.util.LogTag.LogD import com.player.musicoo.util.LogTag.LogD
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select import kotlinx.coroutines.selects.select
class PrimaryActivity : MoBaseActivity() { class PrimaryActivity : MoBaseActivity() ,SearchFragment.SearchFragmentCancelClickListener{
/** /**
* musicResponsiveListItemRenderer * musicResponsiveListItemRenderer
* musicTwoRowItemRenderer * musicTwoRowItemRenderer
@ -47,12 +48,16 @@ class PrimaryActivity : MoBaseActivity() {
private fun initClick() { private fun initClick() {
binding.homeBtn.setOnClickListener { binding.homeBtn.setOnClickListener {
changeFragment(0)
updateBtnState(0) updateBtnState(0)
changeFragment(0)
}
binding.searchBtn.setOnClickListener {
updateBtnState(1)
changeFragment(1)
} }
binding.importBtn.setOnClickListener { binding.importBtn.setOnClickListener {
changeFragment(1) updateBtnState(2)
updateBtnState(1) changeFragment(2)
} }
binding.playBlackBtn.setOnClickListener { binding.playBlackBtn.setOnClickListener {
@ -81,9 +86,12 @@ class PrimaryActivity : MoBaseActivity() {
private fun initFragment() { private fun initFragment() {
mFragments.clear() mFragments.clear()
mFragments.add(MoHomeFragment()) mFragments.add(MoHomeFragment())
val searchFragment = SearchFragment()
searchFragment.setButtonClickListener(this)
mFragments.add(searchFragment)
mFragments.add(ImportFragment()) mFragments.add(ImportFragment())
changeFragment(0)
updateBtnState(0) updateBtnState(0)
changeFragment(0)
} }
private fun changeFragment(index: Int) { private fun changeFragment(index: Int) {
@ -116,9 +124,22 @@ class PrimaryActivity : MoBaseActivity() {
else -> R.drawable.home_unselect_icon 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( importImg.setImageResource(
when (index) { when (index) {
1 -> R.drawable.import_select_icon 2 -> R.drawable.import_select_icon
else -> R.drawable.import_unselect_icon else -> R.drawable.import_unselect_icon
} }
) )
@ -204,14 +225,19 @@ class PrimaryActivity : MoBaseActivity() {
} }
override fun onBackPressed() { override fun onBackPressed() {
if (backPressedTime + 2000 > System.currentTimeMillis()) { if (currentIndex == 1) {//等于搜索返回响应先退回home
super.onBackPressed() changeFragment(0)
backToast.cancel() updateBtnState(0)
return
} else { } 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.name.text = mediaItem.mediaMetadata.title
binding.desc.text = mediaItem.mediaMetadata.artist binding.desc.text = mediaItem.mediaMetadata.artist
} }
override fun onFragmentClick() {
onBackPressed()
}
} }

View File

@ -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<String>,
) :
RecyclerView.Adapter<SearchHistoryAdapter.ViewHolder>() {
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)
}
}

View File

@ -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<Innertube.SearchDataPage.SearchResult>,
) :
RecyclerView.Adapter<SearchResultOtherAdapter.ViewHolder>() {
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)
}
}

View File

@ -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<String>,
) :
RecyclerView.Adapter<SearchSuggestionsAdapter.ViewHolder>() {
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)
}
}

View File

@ -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<MusicShelfRenderer.Content>,
) :
RecyclerView.Adapter<SingerDetailsSongListAdapter.ViewHolder>() {
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)
}
}

View File

@ -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<FragmentSearchBinding>(), 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<Request> = 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<String> = mutableListOf()
private var searchHistorySet: MutableSet<String> = mutableSetOf()
private var searchHistory: MutableList<String> = 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<Unit> {
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
}
}

View File

@ -1,6 +1,9 @@
package com.player.musicoo.innertube package com.player.musicoo.innertube
import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer 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.HttpClient
import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.BrowserUserAgent import io.ktor.client.plugins.BrowserUserAgent
@ -165,6 +168,37 @@ object Innertube {
companion object companion object
} }
data class SingerDetailsPage(
val title: String?,
val thumbnail: String?,
val description: String?,
val list: List<SingerDetailsListPage>?
)
data class SingerDetailsListPage(
val title: String?,
val contents: SingerDetailsContent?
){
data class SingerDetailsContent(
val musicShelfContentList: List<MusicShelfRenderer.Content>?,
val musicCarouselShelfContentList: List<MusicCarouselShelfRenderer.Content>?,
)
}
data class SearchDataPage(
val type: Int?,
val title: String?,
val searchResultList: List<SearchResult>
) {
data class SearchResult(
val title: String?,
val desc: String?,
val thumbnail: String?,
val videoId: String?,
val browseId: String?,
)
}
data class BaseHomePage( data class BaseHomePage(
val visitorData: String?, val visitorData: String?,
val cToken: String?, val cToken: String?,

View File

@ -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<com.player.musicoo.innertube.models.Thumbnail>?
)
}
}
@Serializable
data class Header(
val musicCardShelfHeaderBasicRenderer: MusicCardShelfHeaderBasicRenderer?
) {
@Serializable
data class MusicCardShelfHeaderBasicRenderer(
val title: Runs?,
)
}
}

View File

@ -18,6 +18,8 @@ data class SectionListRenderer(
val musicShelfRenderer: MusicShelfRenderer?, val musicShelfRenderer: MusicShelfRenderer?,
val gridRenderer: GridRenderer?, val gridRenderer: GridRenderer?,
val musicDescriptionShelfRenderer: MusicDescriptionShelfRenderer?, val musicDescriptionShelfRenderer: MusicDescriptionShelfRenderer?,
@JsonNames("musicCardShelfRenderer")
val musicCardShelfRenderer: MusicCardShelfRenderer?
) { ) {
@Serializable @Serializable

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
data class SearchBody( data class SearchBody(
val context: Context = Context.DefaultWeb, val context: Context = Context.DefaultWeb,
val query: String, val query: String,
val params: String val params: String? = null
) )

View File

@ -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<List<Innertube.SearchDataPage>>? =
runCatchingNonCancellable {
val response = client.post(search) {
setBody(SearchBody(query = query))
}.body<SearchResponse>()
val searchDataPageList: MutableList<Innertube.SearchDataPage> = 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<Innertube.SearchDataPage.SearchResult> =
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
}

View File

@ -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<Innertube.SingerDetailsPage>? =
runCatchingNonCancellable {
val response = client.post(browse) {
setBody(BrowseBody(browseId = browseId))
}.body<BrowseResponse>()
val list: MutableList<Innertube.SingerDetailsListPage> = 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<MusicShelfRenderer.Content> =
mutableListOf()
val musicCarouselShelfContents: MutableList<MusicCarouselShelfRenderer.Content> =
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
)
}

View File

@ -11,9 +11,13 @@ class AppStore(context: Context) {
.asStoreProvider() .asStoreProvider()
) )
var searchHistoryStore: Set<String> by store.stringSet(
key = SEARCH_HISTORY,
defaultValue = emptySet()
)
companion object { companion object {
private const val FILE_NAME = "music_oo_app" private const val FILE_NAME = "music_oo_app"
const val SEARCH_HISTORY = "search_history"
} }
} }

View File

@ -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<TextView>(R.id.title)
title?.text = data.title
val name = contentView?.findViewById<TextView>(R.id.name)
name?.text = data.searchResultList.firstOrNull()?.title
val desc = contentView?.findViewById<TextView>(R.id.desc)
desc?.text = data.searchResultList.firstOrNull()?.desc
val image = contentView?.findViewById<ImageView>(R.id.image)
val optimalBean = data.searchResultList.firstOrNull()
val url = optimalBean?.thumbnail
Glide.with(context)
.load(url)
.into(image!!)
val playBtn = contentView?.findViewById<LinearLayout>(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)
}
}
}
}
}

View File

@ -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<TextView>(R.id.title)
title?.text = data.title
val rv = contentView?.findViewById<RecyclerView>(R.id.rv)
val adapter = SearchResultOtherAdapter(context, data.searchResultList)
rv?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
rv?.adapter = adapter
}
}

View File

@ -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<TextView>(R.id.title)
title?.text = bean.title
val rv = contentView?.findViewById<RecyclerView>(R.id.rv)
val adapter = TowRowListAdapter(context, bean.contents?.musicCarouselShelfContentList!!)
rv?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
rv?.adapter = adapter
}
}

View File

@ -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<TextView>(R.id.title)
title?.text = bean.title
val rv = contentView?.findViewById<RecyclerView>(R.id.rv)
val adapter = SingerDetailsSongListAdapter(context, bean.contents?.musicShelfContentList!!)
rv?.layoutManager = LinearLayoutManager(context, GridLayoutManager.VERTICAL, false)
rv?.adapter = adapter
}
}

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M15.833,7.083L10,12.917L4.167,7.083"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/white_60"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@color/white"
android:pathData="M512,471.7l207.4,-207.4a28.4,28.4 0,1 1,40.3 40.2L552.3,512l207.4,207.4a28.4,28.4 0,1 1,-40.2 40.3L512,552.3l-207.4,207.4a28.4,28.4 0,1 1,-40.3 -40.2L471.7,512l-207.4,-207.4a28.4,28.4 0,0 1,40.2 -40.3L512,471.7z" />
</vector>

View File

@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M7.5,7.033C7.5,6.503 7.616,5.979 7.843,5.489C8.069,5 8.4,4.556 8.818,4.181C9.236,3.807 9.732,3.51 10.278,3.307C10.824,3.104 11.409,3 12,3C12.591,3 13.176,3.104 13.722,3.307C14.268,3.51 14.764,3.807 15.182,4.181C15.6,4.556 15.931,5 16.157,5.489C16.384,5.979 16.5,6.503 16.5,7.033"
android:strokeAlpha="0.6"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
<path
android:pathData="M2.25,7.033C2.25,7.447 2.586,7.783 3,7.783H4.25V12.176C4.25,13.821 4.481,15.458 4.938,17.038C5.618,19.396 7.574,21.159 9.993,21.568L10.151,21.595C11.375,21.802 12.625,21.802 13.849,21.595L14.007,21.568C16.42,21.16 18.384,19.39 19.062,17.038C19.519,15.458 19.75,13.821 19.75,12.176V7.783H21C21.414,7.783 21.75,7.447 21.75,7.033C21.75,6.618 21.414,6.283 21,6.283H3C2.586,6.283 2.25,6.618 2.25,7.033ZM5.75,12.176V7.783H18.25V12.176C18.25,13.68 18.038,15.177 17.621,16.622C17.102,18.42 15.602,19.777 13.757,20.089L13.599,20.116C12.54,20.295 11.46,20.295 10.401,20.116L10.243,20.089C8.392,19.776 6.899,18.426 6.379,16.622C5.962,15.177 5.75,13.68 5.75,12.176ZM5,6.283V7.033H4.25V6.283H5Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"
android:fillType="evenOdd"/>
<path
android:pathData="M10,12V16M14,12V16"
android:strokeAlpha="0.6"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF1F1F1F" />
<corners android:radius="30dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF212121" />
<corners android:radius="20dp" />
</shape>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:strokeWidth="1"
android:pathData="M18,18m-17.5,0a17.5,17.5 0,1 1,35 0a17.5,17.5 0,1 1,-35 0"
android:fillColor="#00000000"
android:strokeColor="#80F988"/>
<group>
<clip-path
android:pathData="M32,4l-28,0l-0,28l28,0z"/>
<path
android:pathData="M17.5,6C11.149,6 6,11.149 6,17.5C6,23.851 11.149,29 17.5,29C23.851,29 29,23.851 29,17.5C29,11.149 23.851,6 17.5,6ZM15.354,10.861C15.819,10.795 16.176,10.395 16.176,9.912C16.176,9.383 15.747,8.954 15.218,8.954C15.16,8.954 15.104,8.959 15.049,8.968C11.923,9.472 9.472,11.923 8.968,15.049C8.959,15.104 8.954,15.16 8.954,15.218C8.954,15.747 9.383,16.176 9.912,16.176C10.395,16.176 10.795,15.819 10.861,15.354C11.233,13.044 13.044,11.233 15.354,10.861Z"
android:fillColor="#80F988"
android:fillType="evenOdd"/>
<path
android:pathData="M29.431,29.434C29.745,29.107 29.745,28.594 29.431,28.279L27.264,26.111C26.938,25.796 26.425,25.796 26.111,26.111C25.796,26.437 25.796,26.95 26.111,27.265L28.278,29.434C28.429,29.585 28.639,29.667 28.848,29.667C29.058,29.667 29.268,29.585 29.431,29.434Z"
android:fillColor="#80F988"/>
</group>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<group>
<clip-path
android:pathData="M28,0l-28,0l-0,28l28,0z"/>
<path
android:pathData="M13.5,2C7.149,2 2,7.149 2,13.5C2,19.851 7.149,25 13.5,25C19.851,25 25,19.851 25,13.5C25,7.149 19.851,2 13.5,2ZM11.354,6.861C11.819,6.795 12.176,6.395 12.176,5.912C12.176,5.383 11.747,4.954 11.218,4.954C11.16,4.954 11.104,4.959 11.049,4.968C7.923,5.472 5.472,7.923 4.968,11.049C4.959,11.104 4.954,11.16 4.954,11.218C4.954,11.747 5.383,12.176 5.912,12.176C6.395,12.176 6.795,11.819 6.861,11.354C7.233,9.044 9.044,7.233 11.354,6.861Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"
android:fillType="evenOdd"/>
<path
android:pathData="M25.431,25.434C25.745,25.107 25.745,24.594 25.431,24.279L23.264,22.111C22.938,21.796 22.425,21.796 22.111,22.111C21.796,22.437 21.796,22.95 22.111,23.265L24.278,25.434C24.429,25.585 24.639,25.667 24.848,25.667C25.058,25.667 25.268,25.585 25.431,25.434Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
</group>
</vector>

View File

@ -176,6 +176,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv" android:id="@+id/rv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" /> android:layout_margin="16dp" />
</LinearLayout> </LinearLayout>

View File

@ -409,6 +409,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/playListRv" android:id="@+id/playListRv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="16dp" /> android:layout_margin="16dp" />

View File

@ -5,6 +5,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/main_bg_color"> android:background="@color/main_bg_color">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/settings_bg_img" />
<View <View
android:id="@+id/view" android:id="@+id/view"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -177,6 +184,21 @@
android:src="@drawable/home_select_icon" /> android:src="@drawable/home_select_icon" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/search_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center">
<ImageView
android:id="@+id/search_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/search_unselect_icon" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/import_btn" android:id="@+id/import_btn"
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -0,0 +1,205 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_bg_color"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/relative"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/singerImg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:scaleType="centerCrop" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="@drawable/drw_details_bg"
android:visibility="visible" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:background="@color/black" />
</LinearLayout>
</RelativeLayout>
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/view"
android:orientation="vertical">
<LinearLayout
android:id="@+id/title_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/back_btn"
android:layout_width="42dp"
android:layout_height="42dp"
android:background="@drawable/drw_back_bg">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/back_icon" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green" />
</LinearLayout>
<LinearLayout
android:id="@+id/no_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/no_content_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_content_img" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/content_loading_failed"
android:textSize="14dp" />
<TextView
android:id="@+id/tryAgainBtn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:background="@drawable/drw_btn_bg"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:text="@string/try_again"
android:textColor="@color/black"
android:textSize="16dp" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/singerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="68dp"
android:fontFamily="@font/medium_font"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="22dp" />
<TextView
android:id="@+id/singerDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:fontFamily="@font/regular_font"
android:maxLines="2"
android:text="@string/app_name"
android:textColor="@color/white_60"
android:textSize="14dp" />
<TextView
android:id="@+id/singerDescExpand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:fontFamily="@font/regular_font"
android:text="@string/expand"
android:textColor="@color/white_60"
android:textSize="12dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</RelativeLayout>

View File

@ -2,14 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="@color/main_bg_color">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@mipmap/main_bg_img" />
<View <View
android:id="@+id/view" android:id="@+id/view"

View File

@ -1,20 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="#FF000000">
<View <View
android:id="@+id/view" android:id="@+id/view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" /> android:layout_height="0dp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="@mipmap/import_fragment_bg" />
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -2,14 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="@color/main_bg_color">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@mipmap/main_bg_img" />
<View <View
android:id="@+id/view" android:id="@+id/view"

View File

@ -0,0 +1,217 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/settings_bg_img"
android:visibility="gone" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/view"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="18dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@drawable/drw_search_layout_bg"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="16dp"
android:src="@drawable/search_unselect_icon" />
<EditText
android:id="@+id/searchEdit"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="@null"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/enter_keywords"
android:imeOptions="actionSearch"
android:singleLine="true"
android:textSize="14dp" />
<RelativeLayout
android:id="@+id/deleteInputBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_centerInParent="true"
android:src="@drawable/cha_icon" />
</RelativeLayout>
</LinearLayout>
<TextView
android:id="@+id/cancelBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:fontFamily="@font/regular_font"
android:gravity="center"
android:text="@string/cancel"
android:textColor="@color/white_80"
android:textSize="14dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateTint="@color/green"
android:progressBackgroundTint="@color/green"
android:progressTint="@color/green" />
</LinearLayout>
<LinearLayout
android:id="@+id/no_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:id="@+id/no_content_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_content_img" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:fontFamily="@font/medium_font"
android:gravity="center"
android:text="@string/no_found"
android:textSize="14dp" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/contentScrollView"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:id="@+id/searchSuggestionsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="18dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="18dp"
android:orientation="vertical"
android:visibility="gone">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchSuggestionsRv"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:itemCount="1"
tools:listitem="@layout/search_suggestions_adapter_item" />
</LinearLayout>
<LinearLayout
android:id="@+id/historyLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="18dp"
android:orientation="vertical"
android:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/bold_font_italic"
android:text="@string/history"
android:textColor="@color/white_60"
android:textSize="14dp" />
<RelativeLayout
android:id="@+id/delete_history_btn"
android:layout_width="40dp"
android:layout_height="40dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/delete_icon" />
</RelativeLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/historyRv"
android:layout_width="match_parent"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -19,6 +19,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv" android:id="@+id/rv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" /> android:layout_marginBottom="12dp" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:layout_height="wrap_content"
android:background="@drawable/drw_search_history_tag_bg">
<TextView
android:id="@+id/historyName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="12dp"
android:textSize="12dp"
android:layout_marginBottom="4dp"
android:text="@string/app_name"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="@font/medium_font"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18dp" />
<LinearLayout
android:id="@+id/playBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="0dp"
android:visibility="visible"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="100dp"
android:layout_height="56dp">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@mipmap/musicoo_logo_img" />
<RelativeLayout
android:id="@+id/currentPlayingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_60"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/playing_small_green_icon" />
</RelativeLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/medium_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="16dp" />
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white_60"
android:textSize="14dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingDefaultResource">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="12dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="0dp"
android:visibility="visible"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<RelativeLayout
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="centerCrop"
android:src="@mipmap/musicoo_logo_img" />
<RelativeLayout
android:id="@+id/currentPlayingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_60"
android:gravity="center"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/playing_small_green_icon" />
</RelativeLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="14dp" />
<com.player.musicoo.view.MarqueeTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="@color/white_60"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/medium_font"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18dp" />
<LinearLayout
android:id="@+id/moreBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/regular_font"
android:text="@string/more"
android:textColor="@color/white_60"
android:textSize="12dp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:rotation="270"
android:src="@drawable/arrow_bottom_grey_icon" />
</LinearLayout>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:scrollbars="none"
android:overScrollMode="never"
tools:itemCount="1"
tools:listitem="@layout/play_list_item" />
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="40dp"
android:fontFamily="@font/regular_font"
android:textSize="14dp"
android:textColor="@color/white_80"
android:gravity="center_vertical"
android:text="@string/app_name" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#33EEEEEE" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<TextView
android:id="@+id/title"
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/medium_font"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp" />
</LinearLayout>

View File

@ -9,6 +9,7 @@
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="search">Search</string>
<string name="about">About</string> <string name="about">About</string>
<string name="feedback">Feedback</string> <string name="feedback">Feedback</string>
<string name="share">Share</string> <string name="share">Share</string>
@ -22,4 +23,8 @@
<string name="exit_main_text">Press again to exit</string> <string name="exit_main_text">Press again to exit</string>
<string name="content_loading_failed">Content loading failed, click to try again.</string> <string name="content_loading_failed">Content loading failed, click to try again.</string>
<string name="try_again">Try Again</string> <string name="try_again">Try Again</string>
<string name="enter_keywords">Enter keywords</string>
<string name="history">History</string>
<string name="no_found">No Found</string>
<string name="more">More</string>
</resources> </resources>