diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6e6f31c..410f502 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,9 @@ + ${browseId}") + initData(browseId) + } + + private fun initImmersionBar() { + immersionBar { + statusBarDarkFont(false) + statusBarView(binding.view) + } + } + + private fun initView() { + binding.backBtn.setOnClickListener { + finish() + } + } + + private suspend fun initData(browseId: String) { + Innertube.moPlaylistPage(browseId) + ?.onSuccess { + Glide.with(this) + .load(it.thumbnail) + .into(binding.imageView) + + binding.title.text = it.title + binding.subtitle.text = it.subtitle + binding.secondSubtitle.text = it.secondSubtitle + + val adapter = DetailsListAdapter(this, it.moPlaylistOrAlbumListBean) + binding.rv.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.rv.adapter = adapter + + }?.onFailure { + Log.d(TAG, "moPlaylistPage onFailure->${it}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt b/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt index 546bccb..855a77d 100644 --- a/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt +++ b/app/src/main/java/com/player/musicoo/activity/MoBaseActivity.kt @@ -1,15 +1,33 @@ package com.player.musicoo.activity +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.drawable.Drawable import android.os.Bundle +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicBlur +import android.view.LayoutInflater +import android.widget.TextView +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.player.musicoo.R import com.player.musicoo.sp.AppStore import com.player.musicoo.util.LogTag import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.IOException +import java.io.InputStream abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope() { @@ -70,4 +88,63 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope } } } + + suspend fun loadBitmapWithGlide(imageUrl: String): Bitmap? { + return withContext(Dispatchers.IO) { + try { + Glide.with(this@MoBaseActivity) + .asBitmap() + .load(imageUrl) + .submit() // 异步加载Bitmap + .get() // 阻塞等待获取Bitmap + } catch (e: Exception) { + e.printStackTrace() + null + } + } + } + + fun applyGaussianBlur(inputBitmap: Bitmap, radius: Float, context: Context): Bitmap { + val rsContext = RenderScript.create(context) + val outputBitmap = + Bitmap.createBitmap(inputBitmap.width, inputBitmap.height, inputBitmap.config) + val blurScript = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext)) + val tmpIn = Allocation.createFromBitmap(rsContext, inputBitmap) + val tmpOut = Allocation.createFromBitmap(rsContext, outputBitmap) + blurScript.setRadius(radius) + blurScript.setInput(tmpIn) + blurScript.forEach(tmpOut) + tmpOut.copyTo(outputBitmap) + rsContext.finish() + return outputBitmap + } + + fun loadBitmapFromAsset(id: Int): Bitmap { + return try { + val inputStream: InputStream = resources.openRawResource(id) + BitmapFactory.decodeStream(inputStream) + } catch (e: IOException) { + e.printStackTrace() + throw RuntimeException("Could not load bitmap from asset") + } + } + + fun showSongDescriptionDialog(description: String) { + val inflater = LayoutInflater.from(this) + val dialogView = inflater.inflate(R.layout.dialog_description, null) + val title = dialogView.findViewById(R.id.dialog_title) + title.text = getString(R.string.description) + val content = dialogView.findViewById(R.id.dialog_content) + content.text = description + val okBtn = dialogView.findViewById(R.id.dialog_ok_btn) + val dialogBuilder = AlertDialog.Builder(this) + .setView(dialogView) + val dialog = dialogBuilder.create() + dialog.show() + okBtn.setOnClickListener { + dialog.dismiss() + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt new file mode 100644 index 0000000..d7b59db --- /dev/null +++ b/app/src/main/java/com/player/musicoo/adapter/DetailsListAdapter.kt @@ -0,0 +1,82 @@ +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.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.util.convertMillisToMinutesAndSecondsString +import com.player.musicoo.util.getAudioDurationFromAssets + +class DetailsListAdapter( + private val context: Context, + private val list: List, +) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = DetailsListItemBinding.inflate(LayoutInflater.from(context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val bean = list[position] + holder.bind(bean) + } + + override fun getItemCount(): Int = list.size + + inner class ViewHolder(private val binding: DetailsListItemBinding) : + RecyclerView.ViewHolder(binding.root) { + + @SuppressLint("SetTextI18n") + fun bind(bean: Innertube.MoPlaylistOrAlbumPage.MoPlaylistOrAlbumListBean) { + + binding.apply { + if (!bean.thumbnailUrl.isNullOrEmpty()) { + image.visibility = View.VISIBLE + Glide.with(context) + .load(bean.thumbnailUrl) + .into(image) + sortTv.visibility = View.GONE + } else { + image.visibility = View.GONE + sortTv.visibility = View.VISIBLE + sortTv.text = "${bindingAdapterPosition + 1}" + } + + title.text = bean.title + if (bean.name.isNullOrEmpty()) { + name.visibility = View.GONE + } else { + name.visibility = View.VISIBLE + name.text = bean.name + } + + } + } + } + + private var itemClickListener: OnItemClickListener? = null + + fun setOnItemClickListener(listener: OnItemClickListener) { + itemClickListener = listener + } + + interface OnItemClickListener { + fun onItemClick(position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/player/musicoo/adapter/TowRowListAdapter.kt b/app/src/main/java/com/player/musicoo/adapter/TowRowListAdapter.kt index a7f9996..cf42bd1 100644 --- a/app/src/main/java/com/player/musicoo/adapter/TowRowListAdapter.kt +++ b/app/src/main/java/com/player/musicoo/adapter/TowRowListAdapter.kt @@ -2,6 +2,7 @@ package com.player.musicoo.adapter import android.content.Context import android.content.Intent +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,6 +10,7 @@ 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.DetailsActivity import com.player.musicoo.activity.PlayDetailsActivity import com.player.musicoo.bean.Audio import com.player.musicoo.databinding.MusicResponsiveItemBinding @@ -16,6 +18,7 @@ import com.player.musicoo.databinding.MusicTowRowItemBinding import com.player.musicoo.databinding.SoundsOfAppliancesLayoutBinding import com.player.musicoo.databinding.SoundsOfNatureLayoutBinding import com.player.musicoo.innertube.models.MusicCarouselShelfRenderer +import com.player.musicoo.util.LogTag import com.player.musicoo.util.convertMillisToMinutesAndSecondsString import com.player.musicoo.util.getAudioDurationFromAssets @@ -33,7 +36,23 @@ class TowRowListAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val bean = list[position] + + val browseEndpoint = bean.musicTwoRowItemRenderer + ?.title + ?.runs + ?.firstOrNull() + ?.navigationEndpoint + ?.browseEndpoint + + val browseId = browseEndpoint?.browseId + holder.bind(bean) + + holder.itemView.setOnClickListener { + val intent = Intent(context, DetailsActivity::class.java) + intent.putExtra(DetailsActivity.PLAY_LIST_PAGE_BROWSE_ID, browseId) + context.startActivity(intent) + } } override fun getItemCount(): Int = list.size diff --git a/app/src/main/java/com/player/musicoo/innertube/Innertube.kt b/app/src/main/java/com/player/musicoo/innertube/Innertube.kt index 3b1725b..58d64f0 100644 --- a/app/src/main/java/com/player/musicoo/innertube/Innertube.kt +++ b/app/src/main/java/com/player/musicoo/innertube/Innertube.kt @@ -177,6 +177,28 @@ object Innertube { val contents: List ) + data class MoPlaylistOrAlbumPage( + val title: String?, + val subtitle: String?, + val thumbnail: String?, + val secondSubtitle: String?, + val description: String?, + val moPlaylistOrAlbumListBean: List + ) { + data class MoPlaylistOrAlbumListBean( + val title: String?, + val name: String?, + val desc: String?, + val timeText: String?, + val browseId: String, + val videoId: String?, + val playlistId: String?, + val musicVideoType: String?, + val pageType: String?, + val thumbnailUrl: String? + ) + } + data class ArtistPage( val name: String?, val description: String?, diff --git a/app/src/main/java/com/player/musicoo/innertube/models/BrowseResponse.kt b/app/src/main/java/com/player/musicoo/innertube/models/BrowseResponse.kt index 7297521..227ebf7 100644 --- a/app/src/main/java/com/player/musicoo/innertube/models/BrowseResponse.kt +++ b/app/src/main/java/com/player/musicoo/innertube/models/BrowseResponse.kt @@ -41,6 +41,7 @@ data class BrowseResponse( val subtitle: Runs?, val secondSubtitle: Runs?, val thumbnail: ThumbnailRenderer?, + val description: Runs? ) @Serializable diff --git a/app/src/main/java/com/player/musicoo/innertube/requests/MoPlaylistPage.kt b/app/src/main/java/com/player/musicoo/innertube/requests/MoPlaylistPage.kt new file mode 100644 index 0000000..e6cb842 --- /dev/null +++ b/app/src/main/java/com/player/musicoo/innertube/requests/MoPlaylistPage.kt @@ -0,0 +1,135 @@ +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.bodies.BrowseBody +import com.player.musicoo.innertube.utils.runCatchingNonCancellable +import io.ktor.client.call.body +import io.ktor.client.request.post +import io.ktor.client.request.setBody + +suspend fun Innertube.moPlaylistPage(browseId: String): Result? = + runCatchingNonCancellable { + val response = client.post(browse) { + setBody(BrowseBody(browseId = browseId)) + }.body() + + + val musicDetailHeaderRenderer = response + .header + ?.musicDetailHeaderRenderer + + val sectionListRendererContents = response + .contents + ?.singleColumnBrowseResultsRenderer + ?.tabs + ?.firstOrNull() + ?.tabRenderer + ?.content + ?.sectionListRenderer + ?.contents + + val musicShelfRenderer = sectionListRendererContents + ?.firstOrNull() + ?.musicShelfRenderer + + val musicCarouselShelfRenderer = sectionListRendererContents + ?.getOrNull(1) + ?.musicCarouselShelfRenderer + + val list: MutableList = + mutableListOf() + list.clear() + val contents = musicShelfRenderer?.contents + if (contents != null) { + for (content: MusicShelfRenderer.Content in contents) { + val runs0 = content.musicResponsiveListItemRenderer + ?.flexColumns + ?.getOrNull(0) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + val watchEndpoint = runs0?.navigationEndpoint?.watchEndpoint + val runs1 = content.musicResponsiveListItemRenderer + ?.flexColumns + ?.getOrNull(1) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + val browseEndpoint = runs1?.navigationEndpoint?.browseEndpoint + + val runs2 = content.musicResponsiveListItemRenderer + ?.flexColumns + ?.getOrNull(2) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + + val thumbnailUrl = content.musicResponsiveListItemRenderer + ?.thumbnail + ?.musicThumbnailRenderer + ?.thumbnail + ?.thumbnails + ?.firstOrNull() + ?.url + + + val bean = Innertube.MoPlaylistOrAlbumPage.MoPlaylistOrAlbumListBean( + title = runs0?.text, + name = runs1?.text, + desc = runs2?.text, + timeText = content.musicResponsiveListItemRenderer + ?.fixedColumns?.firstOrNull() + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + ?.text, + browseId = browseEndpoint?.browseId.toString(), + videoId = watchEndpoint?.videoId, + playlistId = watchEndpoint?.playlistId, + musicVideoType = watchEndpoint?.type, + pageType = browseEndpoint?.type, + thumbnailUrl = thumbnailUrl + ) + + list.add(bean) + } + } + + + Innertube.MoPlaylistOrAlbumPage( + title = musicDetailHeaderRenderer + ?.title + ?.runs + ?.firstOrNull() + ?.text, + subtitle = musicDetailHeaderRenderer + ?.subtitle + ?.runs + ?.map { it.text } + ?.joinToString(""), + thumbnail = musicDetailHeaderRenderer + ?.thumbnail + ?.musicThumbnailRenderer + ?.thumbnail + ?.thumbnails + ?.let { it.getOrNull(3) ?: it.getOrNull(2) ?: it.getOrNull(1) ?: it.getOrNull(0) } + ?.url, + secondSubtitle = musicDetailHeaderRenderer + ?.secondSubtitle + ?.runs + ?.map { it.text } + ?.joinToString(""), + description = musicDetailHeaderRenderer + ?.description + ?.runs + ?.firstOrNull() + ?.text, + moPlaylistOrAlbumListBean = list + ) + } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml new file mode 100644 index 0000000..d018cbe --- /dev/null +++ b/app/src/main/res/layout/activity_details.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/details_list_item.xml b/app/src/main/res/layout/details_list_item.xml new file mode 100644 index 0000000..9358577 --- /dev/null +++ b/app/src/main/res/layout/details_list_item.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_description.xml b/app/src/main/res/layout/dialog_description.xml new file mode 100644 index 0000000..639be3e --- /dev/null +++ b/app/src/main/res/layout/dialog_description.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1ab11ba..905ca71 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,6 +4,7 @@ #99000000 #FFFFFFFF #99FFFFFF + #CCFFFFFF #151718 #FF80F988 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4a54d6..b8fb464 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,4 +16,6 @@ Terms of Service Listen Music Anytime Resource Loading… + EXPAND + Description \ No newline at end of file