This commit is contained in:
ocean 2024-05-14 19:13:18 +08:00
parent 1c741d22ff
commit 8c4629497c
13 changed files with 556 additions and 36 deletions

View File

@ -84,5 +84,6 @@ dependencies {
implementation("org.brotli:dec:0.1.2")
implementation("com.google.android.flexbox:flexbox:3.0.0")
implementation("io.github.scwang90:refresh-layout-kernel:2.1.0")
implementation("io.github.scwang90:refresh-footer-ball:2.1.0")
}

View File

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

View File

@ -140,9 +140,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
binding.totalDurationTv.text =
convertMillisToMinutesAndSecondsString(MediaControllerManager.getDuration())
binding.sbProgress.value = MediaControllerManager.getCurrentPosition().toFloat()
binding.sbProgress.valueTo = MediaControllerManager.getDuration().toFloat()
binding.sbProgress.value = MediaControllerManager.getCurrentPosition().toFloat()
updateProgressState()
binding.progressBar.progress = MediaControllerManager.getBufferedPosition().toInt()

View File

@ -0,0 +1,172 @@
package com.player.musicoo.activity
import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.gyf.immersionbar.ktx.immersionBar
import com.player.musicoo.R
import com.player.musicoo.adapter.DetailsListAdapter
import com.player.musicoo.adapter.SearchResultOtherAdapter
import com.player.musicoo.databinding.ActivityDetailsBinding
import com.player.musicoo.databinding.ActivitySearchMoreBinding
import com.player.musicoo.databinding.ActivitySingerDetailsBinding
import com.player.musicoo.fragment.MoHomeFragment
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.models.bodies.ContinuationBody
import com.player.musicoo.innertube.models.bodies.SearchBody
import com.player.musicoo.innertube.requests.moPlaylistPage
import com.player.musicoo.innertube.requests.moSearchPage
import com.player.musicoo.innertube.requests.moSingerListPage
import com.player.musicoo.util.LogTag
import com.player.musicoo.util.LogTag.LogD
import com.player.musicoo.view.SearchResultOptimalView
import com.player.musicoo.view.SearchResultOtherView
import com.player.musicoo.view.SingerDetailsOtherView
import com.player.musicoo.view.SingerDetailsSongView
import com.scwang.smart.refresh.layout.api.RefreshLayout
import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
class MoSearchMoreActivity : MoBaseActivity() {
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
enum class Request {
TryAgain,
MoreData,
}
companion object {
const val SEARCH_MORE_QUERY = "search_more_query"
const val SEARCH_MORE_PARAMS = "search_more_params"
}
private lateinit var binding: ActivitySearchMoreBinding
private var query: String? = null
private var params: String? = null
private var list: MutableList<Innertube.SearchDataPage.SearchResult> = mutableListOf()
private var currentContinuation: String? = null
private var adapter: SearchResultOtherAdapter? = null
override suspend fun main() {
binding = ActivitySearchMoreBinding.inflate(layoutInflater)
setContentView(binding.root)
query = intent.getStringExtra(SEARCH_MORE_QUERY)
params = intent.getStringExtra(SEARCH_MORE_PARAMS)
if (query.isNullOrEmpty() || params.isNullOrEmpty()) {
return
}
initImmersionBar()
initView()
initAdapter()
initData(query!!, params!!)
onReceive()
}
private fun initImmersionBar() {
immersionBar {
statusBarDarkFont(false)
statusBarView(binding.view)
}
}
private suspend fun onReceive() {
while (isActive) {
select<Unit> {
requests.onReceive {
when (it) {
Request.TryAgain -> {
initData(query!!, params!!)
}
Request.MoreData -> {
LogD(TAG, "Request.MoreData")
currentContinuation?.let { it1 -> initDataMore(it1) }
}
}
}
}
}
}
private fun initView() {
binding.backBtn.setOnClickListener {
finish()
}
binding.tryAgainBtn.setOnClickListener {
requests.trySend(Request.TryAgain)
}
binding.refreshLayout.setEnableRefresh(false)
binding.refreshLayout.setEnableLoadMore(true)
binding.refreshLayout.setOnLoadMoreListener {
requests.trySend(Request.MoreData)
}
}
private fun initAdapter() {
adapter = SearchResultOtherAdapter(this, list)
binding.rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rv.adapter = adapter
}
private suspend fun initData(query: String, params: String) {
showLoadingUi()
Innertube.moSearchPage(SearchBody(query = query, params = params))?.onSuccess { result ->
if (result.isNotEmpty()) {
showDataUi()
val title = result[0].title
if (title.isNullOrEmpty()) {
binding.title.visibility = View.GONE
} else {
binding.title.visibility = View.VISIBLE
binding.title.text = title
}
currentContinuation = result[0].continuation
list.clear()
list.addAll(result[0].searchResultList)
} else {
showNoContentUi()
}
}?.onFailure {
showNoContentUi()
}
}
@SuppressLint("NotifyDataSetChanged")
private suspend fun initDataMore(continuation: String) {
Innertube.moSearchPage(ContinuationBody(continuation = continuation))?.onSuccess { result ->
LogD(TAG, "initDataMore result->$result")
currentContinuation = result.continuation
if (result.searchResultList.isNotEmpty()) {
list.addAll(result.searchResultList)
if (!isFinishing) {
adapter?.notifyDataSetChanged()
}
binding.refreshLayout.finishLoadMore(true)
} else {
binding.refreshLayout.finishLoadMore(2000, true, false)
}
}?.onFailure {
binding.refreshLayout.finishLoadMore(2000, false, false)
}
}
private fun showDataUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.GONE
}
private fun showLoadingUi() {
binding.loadingLayout.visibility = View.VISIBLE
binding.noContentLayout.visibility = View.GONE
}
private fun showNoContentUi() {
binding.loadingLayout.visibility = View.GONE
binding.noContentLayout.visibility = View.VISIBLE
}
}

View File

@ -2,6 +2,7 @@ package com.player.musicoo.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -11,11 +12,15 @@ import androidx.media3.common.Player
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.player.musicoo.R
import com.player.musicoo.activity.MoListDetailsActivity
import com.player.musicoo.activity.MoPlayDetailsActivity
import com.player.musicoo.activity.MoSingerDetailsActivity
import com.player.musicoo.databinding.PlayListItemBinding
import com.player.musicoo.databinding.SearchResultOtherItemBinding
import com.player.musicoo.databinding.SearchResultOtherLayoutBinding
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.media.MediaControllerManager
import com.player.musicoo.util.LogTag
class SearchResultOtherAdapter(
private val context: Context,
@ -24,7 +29,8 @@ class SearchResultOtherAdapter(
RecyclerView.Adapter<SearchResultOtherAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = SearchResultOtherItemBinding.inflate(LayoutInflater.from(context), parent, false)
val binding =
SearchResultOtherItemBinding.inflate(LayoutInflater.from(context), parent, false)
return ViewHolder(binding)
}
@ -33,7 +39,40 @@ class SearchResultOtherAdapter(
holder.bind(bean)
holder.itemView.setOnClickListener {
LogTag.LogD(
LogTag.VO_ACT_LOG,
"SearchResultOtherAdapter bean.pageType->${bean.pageType}"
)
if (!bean.videoId.isNullOrEmpty()) {
val intent = Intent(context, MoPlayDetailsActivity::class.java)
intent.putExtra(
MoPlayDetailsActivity.PLAY_DETAILS_VIDEO_ID,
bean.videoId
)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.title)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.desc)
context.startActivity(intent)
} else if (!bean.browseId.isNullOrEmpty()) {
when (bean.pageType) {
"MUSIC_PAGE_TYPE_ARTIST" -> {
val intent = Intent(context, MoSingerDetailsActivity::class.java)
intent.putExtra(
MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID,
bean.browseId
)
context.startActivity(intent)
}
else -> {
val intent = Intent(context, MoListDetailsActivity::class.java)
intent.putExtra(
MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID,
bean.browseId
)
context.startActivity(intent)
}
}
}
}
}

View File

@ -24,6 +24,7 @@ import com.player.musicoo.adapter.SearchHistoryAdapter
import com.player.musicoo.adapter.SearchSuggestionsAdapter
import com.player.musicoo.databinding.FragmentSearchBinding
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.models.bodies.SearchBody
import com.player.musicoo.innertube.models.bodies.SearchSuggestionsBody
import com.player.musicoo.innertube.requests.moSearchPage
import com.player.musicoo.innertube.requests.searchSuggestions
@ -176,9 +177,10 @@ class SearchFragment : MoBaseFragment<FragmentSearchBinding>(), TextWatcher,
appStore.searchHistoryStore = searchHistorySet
updateHistoryUi()
Innertube.moSearchPage(input)?.onSuccess { result ->
Innertube.moSearchPage(SearchBody(query = input))?.onSuccess { result ->
showResultContent()
for (dataPage: Innertube.SearchDataPage in result) {
LogTag.LogD(TAG,"moSearchPage dataPage->$dataPage")
if (dataPage.type == 1) {//type为1的是最佳结果。
binding.contentLayout.addView(
SearchResultOptimalView(requireActivity(), dataPage)
@ -238,7 +240,7 @@ class SearchFragment : MoBaseFragment<FragmentSearchBinding>(), TextWatcher,
@SuppressLint("NotifyDataSetChanged")
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s.isNullOrEmpty()) {
if (s.isNullOrEmpty()) {//没有输入内容,隐藏删除按钮,展示历史记录,重新获取焦点弹出键盘。
binding.deleteInputBtn.visibility = View.GONE
showSearchHistory()
updateHistoryUi()

View File

@ -178,7 +178,7 @@ object Innertube {
data class SingerDetailsListPage(
val title: String?,
val contents: SingerDetailsContent?
){
) {
data class SingerDetailsContent(
val musicShelfContentList: List<MusicShelfRenderer.Content>?,
val musicCarouselShelfContentList: List<MusicCarouselShelfRenderer.Content>?,
@ -186,9 +186,12 @@ object Innertube {
}
data class SearchDataPage(
val type: Int?,
val title: String?,
val searchResultList: List<SearchResult>
val type: Int? = null,
val title: String? = null,
val searchResultList: List<SearchResult>,
val query: String? = null,
val params: String? = null,
val continuation: String? = null
) {
data class SearchResult(
val title: String?,
@ -196,6 +199,7 @@ object Innertube {
val thumbnail: String?,
val videoId: String?,
val browseId: String?,
val pageType: String?,
)
}

View File

@ -2,10 +2,12 @@ package com.player.musicoo.innertube.requests
import com.player.musicoo.innertube.Innertube
import com.player.musicoo.innertube.models.BrowseResponse
import com.player.musicoo.innertube.models.ContinuationResponse
import com.player.musicoo.innertube.models.MusicShelfRenderer
import com.player.musicoo.innertube.models.SearchResponse
import com.player.musicoo.innertube.models.SectionListRenderer
import com.player.musicoo.innertube.models.bodies.BrowseBody
import com.player.musicoo.innertube.models.bodies.ContinuationBody
import com.player.musicoo.innertube.models.bodies.SearchBody
import com.player.musicoo.innertube.utils.runCatchingNonCancellable
import com.player.musicoo.util.LogTag
@ -13,11 +15,11 @@ import io.ktor.client.call.body
import io.ktor.client.request.post
import io.ktor.client.request.setBody
suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchDataPage>>? =
suspend fun Innertube.moSearchPage(body: SearchBody): Result<List<Innertube.SearchDataPage>>? =
runCatchingNonCancellable {
val response = client.post(search) {
setBody(SearchBody(query = query))
setBody(body)
}.body<SearchResponse>()
val searchDataPageList: MutableList<Innertube.SearchDataPage> = mutableListOf()
@ -32,6 +34,9 @@ suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchD
?.contents
var type = -1
var title = ""
var query = ""
var params = ""
var continuation = ""
if (contents != null) {
for (content: SectionListRenderer.Content in contents) {
//保证每次循环都是一个新的集合
@ -82,12 +87,21 @@ suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchD
?.browseEndpoint
?.browseId
val pageType = content.musicCardShelfRenderer
.title
?.runs
?.firstOrNull()
?.navigationEndpoint
?.browseEndpoint
?.type
val result = Innertube.SearchDataPage.SearchResult(
title = searchResultTitle,
desc = searchResultDesc,
thumbnail = searchResultThumbnail,
videoId = searchResultVideoId,
browseId = searchResultBrowseId
browseId = searchResultBrowseId,
pageType = pageType
)
searchResultItemList.add(result)
} else {
@ -95,10 +109,22 @@ suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchD
type = 2
title = content.musicShelfRenderer.title
?.text ?: ""
query = content.musicShelfRenderer.bottomEndpoint
?.searchEndpoint
?.query ?: ""
params = content.musicShelfRenderer.bottomEndpoint
?.searchEndpoint
?.params ?: ""
continuation = content.musicShelfRenderer
.continuations
?.firstOrNull()
?.nextContinuationData
?.continuation ?: ""
val itemContents = content.musicShelfRenderer.contents
if (itemContents != null) {
for (itemContent: MusicShelfRenderer.Content in itemContents) {
val item = Innertube.SearchDataPage.SearchResult(
title = itemContent
.musicResponsiveListItemRenderer
@ -134,13 +160,33 @@ suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchD
?.navigationEndpoint
?.watchEndpoint
?.videoId,
browseId = itemContent.musicResponsiveListItemRenderer
?.navigationEndpoint
?.browseEndpoint
?.browseId ?: ""
?.browseId ?: "",
pageType = itemContent.musicResponsiveListItemRenderer
?.navigationEndpoint
?.browseEndpoint
?.type,
)
//这两个ID必须有一个id是存在的否则就不进入添加数据
if (!item.videoId.isNullOrBlank() || !item.browseId.isNullOrBlank()) {
searchResultItemList.add(item)
when (item.pageType) {
"MUSIC_PAGE_TYPE_PODCAST_SHOW_DETAIL_PAGE" -> {
//不add博客数据
}
"MUSIC_PAGE_TYPE_USER_CHANNEL" -> {
//不add个人资料数据
}
else -> {
searchResultItemList.add(item)
}
}
}
}
}
@ -150,10 +196,105 @@ suspend fun Innertube.moSearchPage(query: String): Result<List<Innertube.SearchD
Innertube.SearchDataPage(
type,
title = title,
searchResultList = searchResultItemList
searchResultList = searchResultItemList,
query = query,
params = params,
continuation = continuation
)
searchDataPageList.add(dataPage)
}
}
searchDataPageList
}
suspend fun Innertube.moSearchPage(body: ContinuationBody): Result<Innertube.SearchDataPage>? =
runCatchingNonCancellable {
val response = client.post(search) {
setBody(body)
}.body<ContinuationResponse>()
val searchResultItemList: MutableList<Innertube.SearchDataPage.SearchResult> =
mutableListOf()
val musicShelfContinuation = response.continuationContents
?.musicShelfContinuation
val itemContents = musicShelfContinuation
?.contents
if (itemContents != null) {
for (itemContent: MusicShelfRenderer.Content in itemContents) {
val item = Innertube.SearchDataPage.SearchResult(
title = itemContent
.musicResponsiveListItemRenderer
?.flexColumns
?.firstOrNull()
?.musicResponsiveListItemFlexColumnRenderer
?.text
?.runs
?.firstOrNull()
?.text,
desc = itemContent.musicResponsiveListItemRenderer
?.flexColumns
?.get(1)
?.musicResponsiveListItemFlexColumnRenderer
?.text
?.runs
?.map { it.text }
?.joinToString("") ?: "",
thumbnail = itemContent.musicResponsiveListItemRenderer
?.thumbnail
?.musicThumbnailRenderer
?.thumbnail
?.thumbnails
?.let { it.getOrNull(1) ?: it.getOrNull(0) }
?.url,
videoId = itemContent.musicResponsiveListItemRenderer
?.flexColumns
?.firstOrNull()
?.musicResponsiveListItemFlexColumnRenderer
?.text
?.runs
?.firstOrNull()
?.navigationEndpoint
?.watchEndpoint
?.videoId,
browseId = itemContent.musicResponsiveListItemRenderer
?.navigationEndpoint
?.browseEndpoint
?.browseId ?: "",
pageType = itemContent.musicResponsiveListItemRenderer
?.navigationEndpoint
?.browseEndpoint
?.type,
)
//这两个ID必须有一个id是存在的否则就不进入添加数据
if (!item.videoId.isNullOrBlank() || !item.browseId.isNullOrBlank()) {
when (item.pageType) {
"MUSIC_PAGE_TYPE_PODCAST_SHOW_DETAIL_PAGE" -> {
//不add博客数据
}
"MUSIC_PAGE_TYPE_USER_CHANNEL" -> {
//不add个人资料数据
}
else -> {
searchResultItemList.add(item)
}
}
}
}
}
Innertube.SearchDataPage(
searchResultList = searchResultItemList,
continuation = musicShelfContinuation
?.continuations
?.firstOrNull()
?.nextContinuationData
?.continuation
)
}

View File

@ -85,17 +85,17 @@ suspend fun Innertube.moSingerListPage(browseId: String): Result<Innertube.Singe
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, "--------------------------------------------")
// 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)

View File

@ -8,6 +8,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.bumptech.glide.Glide
import com.player.musicoo.R
import com.player.musicoo.activity.MoListDetailsActivity
import com.player.musicoo.activity.MoPlayDetailsActivity
import com.player.musicoo.activity.MoSingerDetailsActivity
import com.player.musicoo.innertube.Innertube
@ -47,12 +48,24 @@ class SearchResultOptimalView(context: Context, data: Innertube.SearchDataPage)
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, optimalBean.desc)
context.startActivity(intent)
} else if (!optimalBean.browseId.isNullOrEmpty()) {
val intent = Intent(context, MoSingerDetailsActivity::class.java)
intent.putExtra(
MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID,
optimalBean.browseId
)
context.startActivity(intent)
when (optimalBean.pageType) {
"MUSIC_PAGE_TYPE_ALBUM" -> {
val intent = Intent(context, MoListDetailsActivity::class.java)
intent.putExtra(
MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID,
optimalBean.browseId
)
context.startActivity(intent)
}
else -> {
val intent = Intent(context, MoSingerDetailsActivity::class.java)
intent.putExtra(
MoSingerDetailsActivity.SINGER_DETAILS_PAGE_BROWSE_ID,
optimalBean.browseId
)
context.startActivity(intent)
}
}
}
}
}

View File

@ -2,25 +2,37 @@ package com.player.musicoo.view
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.player.musicoo.R
import com.player.musicoo.activity.MoSearchMoreActivity
import com.player.musicoo.adapter.SearchResultOtherAdapter
import com.player.musicoo.innertube.Innertube
@SuppressLint("ViewConstructor")
class SearchResultOtherView (context: Context, data: Innertube.SearchDataPage) :
class SearchResultOtherView(context: Context, data: Innertube.SearchDataPage) :
ModuleView(context) {
init {
contentView = inflate(getContext(), R.layout.search_result_other_layout, this)
val title = contentView?.findViewById<TextView>(R.id.title)
title?.text = data.title
val moreBtn = contentView?.findViewById<LinearLayout>(R.id.moreBtn)
moreBtn?.setOnClickListener {
val intent = Intent(context, MoSearchMoreActivity::class.java)
intent.putExtra(MoSearchMoreActivity.SEARCH_MORE_QUERY, data.query)
intent.putExtra(MoSearchMoreActivity.SEARCH_MORE_PARAMS, data.params)
context.startActivity(intent)
}
val rv = contentView?.findViewById<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,133 @@
<RelativeLayout 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="match_parent"
android:background="@color/main_bg_color"
android:orientation="vertical">
<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>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:fontFamily="@font/medium_font"
android:gravity="center_vertical"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="18dp"
android:visibility="gone" />
</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>
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:overScrollMode="never"
android:scrollbars="none"
tools:itemCount="10"
tools:listitem="@layout/music_responsive_item" />
<com.scwang.smart.refresh.footer.BallPulseFooter
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srlAnimatingColor="@color/green" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -21,3 +21,4 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.enableJetifier=true