update
This commit is contained in:
parent
0fa6e9156c
commit
d0a2d1d2cf
@ -3,6 +3,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-kapt")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -58,6 +59,9 @@ dependencies {
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.media3:media3-session:1.3.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
|
||||
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
|
||||
@ -66,6 +66,9 @@
|
||||
<activity
|
||||
android:name=".activity.MoSingerMoreSongActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.MoOfflineSongsActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<service
|
||||
android:name=".service.PlaybackService"
|
||||
@ -83,8 +86,8 @@
|
||||
android:foregroundServiceType="dataSync"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
||||
@ -7,11 +7,13 @@ import com.player.musicoo.bean.Audio
|
||||
import com.player.musicoo.bean.CurrentPlayingAudio
|
||||
import com.player.musicoo.bean.ResourcesList
|
||||
import com.player.musicoo.database.AppDatabase
|
||||
import com.player.musicoo.database.AppOfflineDBManager
|
||||
import com.player.musicoo.database.CurrentAudioDatabase
|
||||
import com.player.musicoo.database.CurrentAudioManager
|
||||
import com.player.musicoo.database.DatabaseManager
|
||||
import com.player.musicoo.media.MediaControllerManager
|
||||
import com.player.musicoo.util.CacheManager
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import com.player.musicoo.util.parseResources
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -24,6 +26,8 @@ class App : Application() {
|
||||
companion object {
|
||||
lateinit var app: App
|
||||
private set
|
||||
lateinit var appOfflineDBManager: AppOfflineDBManager
|
||||
private set
|
||||
lateinit var currentAudioManager: CurrentAudioManager
|
||||
private set
|
||||
lateinit var databaseManager: DatabaseManager
|
||||
@ -102,11 +106,13 @@ class App : Application() {
|
||||
app = this
|
||||
initialize(this)
|
||||
MediaControllerManager.init(this)
|
||||
appOfflineDBManager = AppOfflineDBManager.getInstance(this)
|
||||
currentAudioManager = CurrentAudioManager.getInstance(this)
|
||||
databaseManager = DatabaseManager.getInstance(this)
|
||||
initCurrentPlayingAudio()
|
||||
initImportAudio()
|
||||
CacheManager.initializeCaches(this)
|
||||
DownloadUtil.getDownloadManager(this)
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,12 +2,9 @@ package com.player.musicoo.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.renderscript.Allocation
|
||||
import android.renderscript.Element
|
||||
import android.renderscript.RenderScript
|
||||
@ -19,16 +16,19 @@ import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaController
|
||||
import com.bumptech.glide.Glide
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.Innertube.TAG
|
||||
import com.player.musicoo.media.MediaControllerManager
|
||||
import com.player.musicoo.sp.AppStore
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import com.player.musicoo.util.FileSizeConverter
|
||||
import com.player.musicoo.util.LogTag
|
||||
import com.player.musicoo.view.MusicPlayerView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -38,11 +38,12 @@ 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() {
|
||||
@UnstableApi
|
||||
abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(),
|
||||
LifecycleOwner {
|
||||
private var playerListener: Player.Listener? = null
|
||||
private var downloadManagerListener: DownloadManager.Listener? = null
|
||||
|
||||
enum class Event {
|
||||
ActivityStart,
|
||||
@ -64,6 +65,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
||||
this.defer = operation
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
musicPlayerView = MusicPlayerView(this, meController)
|
||||
@ -131,7 +133,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
LogTag.LogD(TAG, "MoBaseActivity onDestroy")
|
||||
if (meController != null && playerListener != null) {
|
||||
meController.removeListener(playerListener!!)
|
||||
}
|
||||
@ -147,10 +148,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
||||
reason: Int
|
||||
) {
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
LogTag.LogD(
|
||||
Innertube.TAG,
|
||||
"MoBaseActivity DISCONTINUITY_REASON_AUTO_TRANSITION"
|
||||
)
|
||||
if (meController != null) {
|
||||
musicPlayerView.updateInfoUi(meController.currentMediaItem)
|
||||
musicPlayerView.updateSetProgress(meController)
|
||||
@ -162,7 +159,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
LogTag.LogD(Innertube.TAG, "MoBaseActivity playbackState->$playbackState")
|
||||
val meController = MediaControllerManager.getController()
|
||||
if (meController != null) {
|
||||
musicPlayerView.updateProgressState(meController)
|
||||
@ -181,10 +177,6 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
||||
playWhenReady: Boolean,
|
||||
reason: Int
|
||||
) {
|
||||
LogTag.LogD(
|
||||
Innertube.TAG,
|
||||
"MoBaseActivity onPlayWhenReadyChanged->$playWhenReady"
|
||||
)
|
||||
musicPlayerView.updatePlayState(playWhenReady)
|
||||
val meController = MediaControllerManager.getController()
|
||||
if (meController != null) {
|
||||
|
||||
@ -103,6 +103,10 @@ class MoListDetailsActivity : MoBaseActivity() {
|
||||
showLoadingUi()
|
||||
Innertube.moPlaylistPage(browseId)
|
||||
?.onSuccess {
|
||||
if (this.isDestroyed || this.isFinishing) {
|
||||
return
|
||||
}
|
||||
|
||||
showDataUi()
|
||||
Glide.with(this)
|
||||
.load(it.thumbnail)
|
||||
|
||||
@ -0,0 +1,143 @@
|
||||
package com.player.musicoo.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.adapter.DetailsListAdapter
|
||||
import com.player.musicoo.adapter.OfflineSongsAdapter
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.databinding.ActivityDetailsBinding
|
||||
import com.player.musicoo.databinding.ActivityOfflineSongsBinding
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.requests.moPlaylistPage
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import com.player.musicoo.util.LogTag.LogD
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
|
||||
@UnstableApi
|
||||
class MoOfflineSongsActivity : MoBaseActivity() {
|
||||
private val requests: Channel<Request> = Channel(Channel.UNLIMITED)
|
||||
|
||||
enum class Request {
|
||||
TryAgain,
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityOfflineSongsBinding
|
||||
private var adapter: OfflineSongsAdapter? = null
|
||||
private var offlineList: MutableList<OfflineBean> = mutableListOf()
|
||||
|
||||
override suspend fun main() {
|
||||
binding = ActivityOfflineSongsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
initImmersionBar()
|
||||
initView()
|
||||
initAdapter()
|
||||
initData()
|
||||
onReceive()
|
||||
}
|
||||
|
||||
private fun initImmersionBar() {
|
||||
immersionBar {
|
||||
statusBarDarkFont(false)
|
||||
statusBarView(binding.view)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private suspend fun onReceive() {
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
requests.onReceive {
|
||||
when (it) {
|
||||
Request.TryAgain -> {
|
||||
initData()
|
||||
}
|
||||
}
|
||||
}
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.ActivityOnResume -> {
|
||||
activityOnResume()
|
||||
}
|
||||
|
||||
Event.AutomaticallySwitchSongs -> {
|
||||
if (adapter != null) {
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun activityOnResume() {
|
||||
addMusicPlayerViewToLayout(binding.playMusicLayout)
|
||||
|
||||
if (adapter != null) {
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.backBtn.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
binding.tryAgainBtn.setOnClickListener {
|
||||
requests.trySend(Request.TryAgain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
adapter = OfflineSongsAdapter(this, offlineList)
|
||||
binding.rv.layoutManager =
|
||||
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||
binding.rv.adapter = adapter
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private suspend fun initData() {
|
||||
showLoadingUi()
|
||||
|
||||
offlineList.clear()
|
||||
offlineList.addAll(App.appOfflineDBManager.getAllOfflineBeans())
|
||||
|
||||
for (offline in offlineList){
|
||||
LogD(TAG,"offline id->${offline.videoId}")
|
||||
}
|
||||
|
||||
if (offlineList.size > 0) {
|
||||
showDataUi()
|
||||
} else {
|
||||
showNoContentUi()
|
||||
}
|
||||
if (adapter != null) {
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ import android.view.View
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
@ -23,25 +24,29 @@ import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.adapter.PlayListAdapter
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.databinding.ActivityMoPlayDetailsBinding
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.media.MediaControllerManager
|
||||
import com.player.musicoo.media.SongRadio
|
||||
import com.player.musicoo.service.MyDownloadService
|
||||
import com.player.musicoo.service.ViewModelMain
|
||||
import com.player.musicoo.sp.AppStore
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import com.player.musicoo.util.FileSizeConverter
|
||||
import com.player.musicoo.util.LogTag
|
||||
import com.player.musicoo.util.LogTag.LogD
|
||||
import com.player.musicoo.util.PlayMode
|
||||
import com.player.musicoo.util.asMediaItem
|
||||
import com.player.musicoo.util.convertMillisToMinutesAndSecondsString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.lang.Exception
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
@ -77,7 +82,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
initImmersionBar()
|
||||
initClick()
|
||||
initPlayerListener()
|
||||
initDownloadListener()
|
||||
initPlayListAdapter()
|
||||
updatePlayModeUi()
|
||||
val videoId = intent.getStringExtra(PLAY_DETAILS_VIDEO_ID)
|
||||
@ -87,33 +91,41 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
|
||||
comeFrom = intent.getSerializableExtra(PLAY_DETAILS_COME_FROM) as Class<*>?
|
||||
if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
|
||||
LogD(TAG, "从当前播放的悬浮layout进入")
|
||||
// 处理来自 PrimaryActivity 的情况
|
||||
updateCurrentMediaItemInfo()
|
||||
} else {
|
||||
if (meController != null && meController.currentMediaItem != null && videoId == meController.currentMediaItem?.mediaId) {
|
||||
//进入的id与当前的id一样就不重新去获取播放
|
||||
updateCurrentMediaItemInfo()
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
updateInfoUi(meController.currentMediaItem)
|
||||
} else {
|
||||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||||
|
||||
if (videoId.isNullOrEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
//传入进来的ID,就是进入此界面的当前ID
|
||||
currentVideoID = videoId
|
||||
//根据进来界面的当前ID来获取资源。
|
||||
initData(
|
||||
videoId,
|
||||
playlistId,
|
||||
playlistSetVideoId,
|
||||
params
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LogD(TAG, "从点击任意歌曲进入")
|
||||
// if (meController != null && meController.currentMediaItem != null && videoId == meController.currentMediaItem?.mediaId) {
|
||||
// //进入的id与当前的id一样就不重新去获取播放
|
||||
// updateCurrentMediaItemInfo()
|
||||
// updateInfoUi(meController.currentMediaItem)
|
||||
// } else {
|
||||
//
|
||||
// }
|
||||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||||
|
||||
if (videoId.isNullOrEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
//要加载数据的话就隐藏喜欢和下载按钮
|
||||
binding.likeAndDownloadLayout.visibility = View.GONE
|
||||
//传入进来的ID,就是进入此界面的当前ID
|
||||
currentVideoID = videoId
|
||||
//根据进来界面的当前ID来获取资源。
|
||||
initData(
|
||||
videoId,
|
||||
playlistId,
|
||||
playlistSetVideoId,
|
||||
params
|
||||
)
|
||||
}
|
||||
initDownloadFlow()
|
||||
onReceive()
|
||||
}
|
||||
|
||||
@ -134,25 +146,72 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
}
|
||||
|
||||
private fun activityOnResume() {
|
||||
if (comeFrom != null && comeFrom == PrimaryActivity::class.java) {
|
||||
// if (meController != null && meController.currentMediaItem != null) {
|
||||
// updateInfoUi(meController.currentMediaItem)
|
||||
// }
|
||||
}
|
||||
|
||||
private fun initDownloadFlow() {
|
||||
ViewModelMain.modelDownloadsFlow.observe(this) { downloads ->
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
updateInfoUi(meController.currentMediaItem)
|
||||
val id = meController.currentMediaItem?.mediaId
|
||||
LogD(TAG, "initDownloadFlow id ->${id}")
|
||||
val currentScreenDownloads = downloads[id]
|
||||
LogD(TAG, "currentScreenDownloads->${currentScreenDownloads}")
|
||||
if (currentScreenDownloads != null) {
|
||||
updateDownloadUI(currentScreenDownloads)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
LogD(TAG,"meController.currentMediaItem != null->${meController.currentMediaItem?.mediaId!!}")
|
||||
updateDownloadUi(meController.currentMediaItem?.mediaId!!)
|
||||
} else {
|
||||
LogD(TAG,"currentVideoID->${currentVideoID}")
|
||||
updateDownloadUi(currentVideoID)
|
||||
}
|
||||
|
||||
private fun updateDownloadUI(download: Download) {
|
||||
when (download.state) {
|
||||
Download.STATE_DOWNLOADING -> {
|
||||
binding.downloadLoading.visibility = View.VISIBLE
|
||||
binding.downloadImg.setImageResource(R.drawable.download_icon)
|
||||
binding.downloadImg.visibility = View.GONE
|
||||
|
||||
binding.downloadBtn.isClickable = false
|
||||
binding.downloadBtn.isEnabled = false
|
||||
}
|
||||
|
||||
Download.STATE_COMPLETED -> {
|
||||
binding.downloadLoading.visibility = View.GONE
|
||||
binding.downloadImg.setImageResource(R.drawable.download_done_icon)
|
||||
binding.downloadImg.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
Download.STATE_FAILED -> {
|
||||
binding.downloadLoading.visibility = View.GONE
|
||||
binding.downloadImg.setImageResource(R.drawable.error)
|
||||
binding.downloadImg.visibility = View.VISIBLE
|
||||
|
||||
binding.downloadBtn.isClickable = true
|
||||
binding.downloadBtn.isEnabled = true
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.downloadLoading.visibility = View.GONE
|
||||
binding.downloadImg.setImageResource(R.drawable.download_icon)
|
||||
binding.downloadImg.visibility = View.VISIBLE
|
||||
|
||||
binding.downloadBtn.isClickable = true
|
||||
binding.downloadBtn.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDownloadUi(id: String) {
|
||||
if (DownloadUtil.downloadResourceExist(id)) {
|
||||
if (DownloadUtil.downloadResourceExist(id)) {//已经下载,按钮不可点击
|
||||
binding.downloadImg.setImageResource(R.drawable.download_done_icon)
|
||||
binding.downloadBtn.isClickable = false
|
||||
binding.downloadBtn.isEnabled = false
|
||||
} else {
|
||||
binding.downloadImg.setImageResource(R.drawable.download_icon)
|
||||
binding.downloadBtn.isClickable = true
|
||||
binding.downloadBtn.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,40 +220,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
meController?.addListener(playerListener)
|
||||
}
|
||||
|
||||
private fun initDownloadListener() {
|
||||
downloadManager = DownloadUtil.getDownloadManager(this)
|
||||
if (downloadManager != null) {
|
||||
downloadManager?.addListener(object : DownloadManager.Listener {
|
||||
override fun onDownloadChanged(
|
||||
downloadManager: DownloadManager,
|
||||
download: Download,
|
||||
finalException: Exception?
|
||||
) {
|
||||
when (download.state) {
|
||||
Download.STATE_DOWNLOADING -> {
|
||||
binding.downloadLoading.visibility = View.VISIBLE
|
||||
binding.downloadImg.setImageResource(R.drawable.download_icon)
|
||||
binding.downloadImg.visibility = View.GONE
|
||||
}
|
||||
|
||||
Download.STATE_COMPLETED -> {
|
||||
binding.downloadLoading.visibility = View.GONE
|
||||
binding.downloadImg.setImageResource(R.drawable.download_done_icon)
|
||||
binding.downloadImg.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.downloadLoading.visibility = View.GONE
|
||||
binding.downloadImg.setImageResource(R.drawable.error)
|
||||
binding.downloadImg.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCurrentMediaItemInfo() {
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
binding.playbackErrorLayout.visibility = View.GONE
|
||||
@ -289,11 +314,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
|
||||
PlayMode.RANDOM.value -> {
|
||||
meController.repeatMode = Player.REPEAT_MODE_ALL
|
||||
// val availableCommands = meController.availableCommands
|
||||
// //控制器支持设置随机播放模式的命令
|
||||
// if (availableCommands.contains(Player.COMMAND_SET_SHUFFLE_MODE)) {
|
||||
// meController.shuffleModeEnabled = true
|
||||
// }
|
||||
meController.shuffleModeEnabled = true
|
||||
}
|
||||
}
|
||||
@ -363,35 +383,24 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
|
||||
binding.downloadBtn.setOnClickListener {
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
val contentId = meController.currentMediaItem?.mediaId!!
|
||||
val currentMediaItem = meController.currentMediaItem
|
||||
val contentId = currentMediaItem?.mediaId ?: ""
|
||||
//如果已经存在就不进行下载
|
||||
if (DownloadUtil.downloadResourceExist(contentId)) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
val currentDownload = DownloadUtil.currentDownload(contentId)
|
||||
if (currentDownload != null) {
|
||||
if (currentDownload.state == Download.STATE_DOWNLOADING) {
|
||||
DownloadService.sendRemoveDownload(
|
||||
this,
|
||||
MyDownloadService::class.java,
|
||||
contentId,
|
||||
false
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val downloadRequest = DownloadRequest
|
||||
.Builder(contentId, contentId.toUri())
|
||||
.setCustomCacheKey(contentId)
|
||||
.build()
|
||||
DownloadService.sendAddDownload(
|
||||
this,
|
||||
MyDownloadService::class.java,
|
||||
downloadRequest,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
val downloadRequest = DownloadRequest
|
||||
.Builder(contentId, contentId.toUri())
|
||||
.setCustomCacheKey(contentId)
|
||||
.build()
|
||||
DownloadService.sendAddDownload(
|
||||
this,
|
||||
MyDownloadService::class.java,
|
||||
downloadRequest,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,6 +462,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
}
|
||||
|
||||
if (mediaItem != null) {
|
||||
//数据请求完毕mediaItem不等于空就显示喜欢与下载按钮
|
||||
binding.likeAndDownloadLayout.visibility = View.VISIBLE
|
||||
updateInfoUi(mediaItem)
|
||||
|
||||
binding.playbackErrorLayout.visibility = View.GONE
|
||||
@ -573,6 +584,8 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
||||
binding.playbackErrorLayout.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
// currentVideoID = mediaItem.mediaId
|
||||
updateDownloadUi(mediaItem.mediaId)
|
||||
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
|
||||
@ -51,8 +51,8 @@ class DetailsListAdapter(
|
||||
)
|
||||
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_PLAY_PARAMS, bean.params)
|
||||
|
||||
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.name)
|
||||
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.title)
|
||||
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_NAME, bean.title)
|
||||
intent.putExtra(MoPlayDetailsActivity.PLAY_DETAILS_DESC, bean.name)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
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.bean.OfflineBean
|
||||
import com.player.musicoo.databinding.OfflineListItemBinding
|
||||
import com.player.musicoo.databinding.PlayListItemBinding
|
||||
import com.player.musicoo.media.MediaControllerManager
|
||||
|
||||
class OfflineSongsAdapter(
|
||||
private val context: Context,
|
||||
private val list: List<OfflineBean>,
|
||||
) :
|
||||
RecyclerView.Adapter<OfflineSongsAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = OfflineListItemBinding.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 {
|
||||
// val meController = MediaControllerManager.getController()
|
||||
// if (meController != null && meController.currentMediaItem != null) {
|
||||
// var index = holder.bindingAdapterPosition
|
||||
// if (index > meController.mediaItemCount) {
|
||||
// index = 1
|
||||
// }
|
||||
// meController.seekTo(index, C.TIME_UNSET)
|
||||
// if (!meController.isPlaying) {
|
||||
// meController.prepare()
|
||||
// meController.play()
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = list.size
|
||||
|
||||
inner class ViewHolder(private val binding: OfflineListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(bean: OfflineBean) {
|
||||
|
||||
binding.apply {
|
||||
Glide.with(context)
|
||||
.load(bean.thumbnail)
|
||||
.into(image)
|
||||
title.text = bean.title
|
||||
if (bean.name.isEmpty()) {
|
||||
name.visibility = View.GONE
|
||||
} else {
|
||||
name.visibility = View.VISIBLE
|
||||
name.text = bean.name
|
||||
}
|
||||
size.text = bean.size
|
||||
|
||||
val meController = MediaControllerManager.getController()
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
if (meController.currentMediaItem?.mediaId == bean.videoId) {
|
||||
binding.listPlayView.visibility = View.VISIBLE
|
||||
binding.title.setTextColor(context.getColor(R.color.green))
|
||||
binding.name.setTextColor(context.getColor(R.color.green_60))
|
||||
binding.size.setTextColor(context.getColor(R.color.green_60))
|
||||
} else {
|
||||
binding.title.setTextColor(context.getColor(R.color.white))
|
||||
binding.name.setTextColor(context.getColor(R.color.white_60))
|
||||
binding.size.setTextColor(context.getColor(R.color.white_60))
|
||||
binding.listPlayView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var itemClickListener: OnItemClickListener? = null
|
||||
|
||||
fun setOnItemClickListener(listener: OnItemClickListener) {
|
||||
itemClickListener = listener
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(position: Int)
|
||||
}
|
||||
}
|
||||
22
app/src/main/java/com/player/musicoo/bean/OfflineBean.kt
Normal file
22
app/src/main/java/com/player/musicoo/bean/OfflineBean.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.player.musicoo.bean
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
@Entity
|
||||
data class OfflineBean(
|
||||
@ColumnInfo(name = "videoId") var videoId: String,
|
||||
@ColumnInfo(name = "title") var title: String,
|
||||
@ColumnInfo(name = "name") var name: String,
|
||||
@ColumnInfo(name = "thumbnail") var thumbnail: String? = null,
|
||||
@ColumnInfo(name = "size") var size: String? = null,
|
||||
@ColumnInfo(name = "isOffline") var isOffline: Boolean
|
||||
) : Serializable {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package com.player.musicoo.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.util.LogTag
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AppOfflineDBManager private constructor(context: Context) {
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var instance: AppOfflineDBManager? = null
|
||||
|
||||
fun getInstance(context: Context): AppOfflineDBManager {
|
||||
return instance ?: synchronized(this) {
|
||||
instance ?: AppOfflineDBManager(context).also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val database = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
AppOfflineDatabase::class.java, "offline_data_base"
|
||||
).build()
|
||||
|
||||
private val dao = database.localOfflineDao()
|
||||
|
||||
suspend fun insertOfflineBean(bean: OfflineBean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val offlineBean = getOfflineBeanByID(bean.videoId)
|
||||
if (offlineBean == null) {
|
||||
LogTag.LogD(LogTag.VO_TEST_ONLY,"insertOfflineBean")
|
||||
dao.insertOfflineBean(bean)
|
||||
} else {
|
||||
LogTag.LogD(LogTag.VO_TEST_ONLY,"updateOfflineBean")
|
||||
dao.updateOfflineBean(bean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun insertOfflineListBean(list: List<OfflineBean>) {
|
||||
withContext(Dispatchers.IO) {
|
||||
for (bean in list) {
|
||||
val offlineBean = getOfflineBeanByID(bean.videoId)
|
||||
if (offlineBean == null) {
|
||||
dao.insertOfflineBean(bean)
|
||||
} else {
|
||||
dao.updateOfflineBean(bean)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllOfflineBeans(): List<OfflineBean> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
dao.getAllOfflineBeans()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteOfflineBean(bean: OfflineBean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.deleteOfflineBean(bean)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteAllOfflineBean() {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.deleteAllOfflineBean()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateOfflineBean(bean: OfflineBean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.updateOfflineBean(bean)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getOfflineBeanByID(id: String): OfflineBean? {
|
||||
return dao.getOfflineBeanByID(id)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
|
||||
package com.player.musicoo.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
|
||||
@Database(entities = [OfflineBean::class], version = 1, exportSchema = false)
|
||||
abstract class AppOfflineDatabase : RoomDatabase() {
|
||||
abstract fun localOfflineDao(): OfflineDao
|
||||
}
|
||||
33
app/src/main/java/com/player/musicoo/database/OfflineDao.kt
Normal file
33
app/src/main/java/com/player/musicoo/database/OfflineDao.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package com.player.musicoo.database
|
||||
|
||||
import androidx.room.*
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
|
||||
@Dao
|
||||
interface OfflineDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertOfflineBean(bean: OfflineBean)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertOfflineBeans(beans: List<OfflineBean>)
|
||||
|
||||
@Query("SELECT * FROM OfflineBean")
|
||||
suspend fun getAllOfflineBeans(): List<OfflineBean>
|
||||
|
||||
@Delete
|
||||
suspend fun deleteOfflineBean(bean: OfflineBean)
|
||||
|
||||
@Query("DELETE FROM OfflineBean")
|
||||
suspend fun deleteAllOfflineBean()
|
||||
|
||||
@Update
|
||||
suspend fun updateOfflineBean(bean: OfflineBean)
|
||||
|
||||
@Query("SELECT * FROM OfflineBean WHERE videoId = :id LIMIT 1")
|
||||
suspend fun getOfflineBeanByID(id: String): OfflineBean?
|
||||
|
||||
@Query("SELECT * FROM OfflineBean WHERE isOffline = 1")
|
||||
suspend fun getOfflineBeanByIsOffline(): List<OfflineBean>
|
||||
|
||||
}
|
||||
@ -1,10 +1,15 @@
|
||||
package com.player.musicoo.fragment
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
import com.player.musicoo.databinding.FragmentMoHomeBinding
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.activity.MoOfflineSongsActivity
|
||||
import com.player.musicoo.databinding.FragmentMoMeBinding
|
||||
import com.player.musicoo.innertube.utils.BrotliEncoder.decode
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
@ -38,13 +43,39 @@ class MoMeFragment : MoBaseFragment<FragmentMoMeBinding>() {
|
||||
requests.onReceive {
|
||||
|
||||
}
|
||||
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.FragmentOnResume -> {
|
||||
fragmentOnResume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.likedSongsBtn.setOnClickListener {
|
||||
|
||||
}
|
||||
binding.offlineSongsBtn.setOnClickListener {
|
||||
if (DownloadUtil.getDownloadCount() > 0) {
|
||||
val intent = Intent(context, MoOfflineSongsActivity::class.java)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(R.string.offline_songs_no_data_prompt),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fragmentOnResume() {
|
||||
binding.offlineSongsTv.text = "${DownloadUtil.getDownloadCount()}"
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
@ -12,8 +12,13 @@ import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper
|
||||
import androidx.media3.exoplayer.offline.DownloadService
|
||||
import androidx.media3.exoplayer.scheduler.PlatformScheduler
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.util.DownloadUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@ -37,8 +42,9 @@ class MyDownloadService : DownloadService(
|
||||
val downloadManager = DownloadUtil.getDownloadManager(this)
|
||||
downloadManager!!.maxParallelDownloads = 3
|
||||
val downloadNotificationHelper = DownloadUtil.getDownloadNotificationHelper(this)
|
||||
|
||||
downloadManager.addListener(
|
||||
TerminalStateNotificationHelper(
|
||||
DownloadUtil.TerminalStateNotificationHelper(
|
||||
this, downloadNotificationHelper!!, FOREGROUND_NOTIFICATION_ID + 1
|
||||
)
|
||||
)
|
||||
@ -63,43 +69,4 @@ class MyDownloadService : DownloadService(
|
||||
notMetRequirements
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private class TerminalStateNotificationHelper(
|
||||
private val context: Context,
|
||||
private val notificationHelper: DownloadNotificationHelper,
|
||||
private var nextNotificationId: Int
|
||||
) :
|
||||
DownloadManager.Listener {
|
||||
|
||||
override fun onDownloadChanged(
|
||||
downloadManager: DownloadManager,
|
||||
download: Download,
|
||||
finalException: Exception?
|
||||
) {
|
||||
val notification: Notification = when (download.state) {
|
||||
Download.STATE_COMPLETED -> {
|
||||
notificationHelper.buildDownloadCompletedNotification(
|
||||
context,
|
||||
R.drawable.download_done_icon,
|
||||
null,
|
||||
Util.fromUtf8Bytes(download.request.data)
|
||||
)
|
||||
}
|
||||
Download.STATE_FAILED -> {
|
||||
notificationHelper.buildDownloadFailedNotification(
|
||||
context,
|
||||
R.drawable.error,
|
||||
null,
|
||||
Util.fromUtf8Bytes(download.request.data)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
return
|
||||
}
|
||||
}
|
||||
NotificationUtil.setNotification(context, nextNotificationId++, notification)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.player.musicoo.service
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
|
||||
object ViewModelMain : ViewModel() {
|
||||
var modelDownloadsFlow = MutableLiveData<Map<String, Download>>()
|
||||
}
|
||||
@ -1,11 +1,16 @@
|
||||
package com.player.musicoo.util
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.util.NotificationUtil
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.database.DatabaseProvider
|
||||
import androidx.media3.database.StandaloneDatabaseProvider
|
||||
import androidx.media3.datasource.DataSource
|
||||
@ -23,15 +28,28 @@ import androidx.media3.datasource.cronet.CronetUtil
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper
|
||||
import com.player.musicoo.App
|
||||
import com.player.musicoo.R
|
||||
import com.player.musicoo.bean.OfflineBean
|
||||
import com.player.musicoo.innertube.Innertube
|
||||
import com.player.musicoo.innertube.Innertube.TAG
|
||||
import com.player.musicoo.innertube.models.bodies.PlayerBody
|
||||
import com.player.musicoo.innertube.requests.player
|
||||
import com.player.musicoo.media.MediaControllerManager
|
||||
import com.player.musicoo.service.LoginRequiredException
|
||||
import com.player.musicoo.service.PlayableFormatNotFoundException
|
||||
import com.player.musicoo.service.UnplayableException
|
||||
import com.player.musicoo.service.VideoIdMismatchException
|
||||
import com.player.musicoo.service.ViewModelMain
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.net.CookieHandler
|
||||
import java.net.CookieManager
|
||||
@ -84,7 +102,6 @@ object DownloadUtil {
|
||||
context.applicationContext,
|
||||
getHttpDataSourceFactory(context.applicationContext)!!
|
||||
)
|
||||
val chunkLength = 512 * 1024L
|
||||
val ringBuffer = RingBuffer<Pair<String, Uri>?>(2) { null }
|
||||
return ResolvingDataSource.Factory(
|
||||
CacheDataSource.Factory()
|
||||
@ -158,31 +175,43 @@ object DownloadUtil {
|
||||
.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val download = cursor.download
|
||||
LogTag.LogD(TAG, "download.request.id->${download.request.id}")
|
||||
LogTag.LogD(
|
||||
TAG,
|
||||
"download formattedSize->${FileSizeConverter(download.bytesDownloaded).formattedSize()}"
|
||||
)
|
||||
if (download.request.id == id) {
|
||||
isExist = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogTag.LogD(TAG, "isExist->$isExist")
|
||||
return isExist
|
||||
}
|
||||
|
||||
fun currentDownload(id: String): Download? {
|
||||
var download: Download? = null
|
||||
fun getCurrentIdDownloadState(id: String): Int {
|
||||
if (downloadManager != null) {
|
||||
downloadManager?.currentDownloads?.map {
|
||||
if (it.request.id === id) {
|
||||
download = it
|
||||
val downloadIndex = downloadManager!!.downloadIndex
|
||||
downloadIndex.getDownloads()
|
||||
.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val download = cursor.download
|
||||
if (download.request.id == id) {
|
||||
return download.state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return download
|
||||
return -1
|
||||
}
|
||||
|
||||
fun getDownloadCount(): Int {
|
||||
var count = 0
|
||||
if (downloadManager != null) {
|
||||
val downloadIndex = downloadManager!!.downloadIndex
|
||||
downloadIndex.getDownloads()
|
||||
.use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@ -212,13 +241,68 @@ object DownloadUtil {
|
||||
return databaseProvider
|
||||
}
|
||||
|
||||
private fun buildReadOnlyCacheDataSource(
|
||||
upstreamFactory: DataSource.Factory, cache: Cache?
|
||||
): CacheDataSource.Factory {
|
||||
return CacheDataSource.Factory()
|
||||
.setCache(cache!!)
|
||||
.setUpstreamDataSourceFactory(upstreamFactory)
|
||||
.setCacheWriteDataSinkFactory(null)
|
||||
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
|
||||
class TerminalStateNotificationHelper(
|
||||
private val context: Context,
|
||||
private val notificationHelper: DownloadNotificationHelper,
|
||||
private var nextNotificationId: Int
|
||||
) :
|
||||
DownloadManager.Listener {
|
||||
|
||||
override fun onDownloadChanged(
|
||||
downloadManager: DownloadManager,
|
||||
download: Download,
|
||||
finalException: Exception?
|
||||
) {
|
||||
val downloadsMap = mutableMapOf<String, Download>()
|
||||
downloadsMap[download.request.id] = download
|
||||
ViewModelMain.modelDownloadsFlow.postValue(downloadsMap)
|
||||
|
||||
val notification: Notification = when (download.state) {
|
||||
Download.STATE_COMPLETED -> {
|
||||
insertOfflineData(download)
|
||||
|
||||
notificationHelper.buildDownloadCompletedNotification(
|
||||
context,
|
||||
R.drawable.download_done_icon,
|
||||
null,
|
||||
Util.fromUtf8Bytes(download.request.data)
|
||||
)
|
||||
}
|
||||
|
||||
Download.STATE_FAILED -> {
|
||||
notificationHelper.buildDownloadFailedNotification(
|
||||
context,
|
||||
R.drawable.error,
|
||||
null,
|
||||
Util.fromUtf8Bytes(download.request.data)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return
|
||||
}
|
||||
}
|
||||
NotificationUtil.setNotification(context, nextNotificationId++, notification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertOfflineData(download: Download) {
|
||||
val meController = MediaControllerManager.getController()
|
||||
if (meController != null && meController.currentMediaItem != null) {
|
||||
val currentMediaItem = meController.currentMediaItem
|
||||
val mediaMetadata = currentMediaItem?.mediaMetadata
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val bean = OfflineBean(
|
||||
videoId = download.request.id,
|
||||
title = mediaMetadata?.title.toString(),
|
||||
name = mediaMetadata?.artist.toString(),
|
||||
thumbnail = mediaMetadata?.artworkUri.toString(),
|
||||
size = FileSizeConverter(download.bytesDownloaded).formattedSize(),
|
||||
isOffline = true
|
||||
)
|
||||
LogTag.LogD(TAG, "insertOfflineBean bean->${bean}")
|
||||
App.appOfflineDBManager.insertOfflineBean(bean)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,8 @@ object LogTag {
|
||||
const val VO_API_LOG = "vo-api—log"
|
||||
const val VO_SERVICE_LOG = "vo-service—log"
|
||||
|
||||
const val VO_TEST_ONLY = "vo-only—log"
|
||||
|
||||
fun LogD(tag: String, message: String) {
|
||||
Log.d(tag, message)
|
||||
}
|
||||
|
||||
16
app/src/main/res/drawable/download_green_done_icon.xml
Normal file
16
app/src/main/res/drawable/download_green_done_icon.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/green"
|
||||
android:pathData="M2.623,8.779C3.339,5.724 5.724,3.339 8.779,2.623C10.898,2.126 13.102,2.126 15.221,2.623C18.276,3.339 20.661,5.724 21.377,8.779C21.874,10.898 21.874,13.102 21.377,15.221C20.661,18.276 18.276,20.661 15.221,21.377C13.102,21.874 10.898,21.874 8.779,21.377C5.724,20.661 3.339,18.276 2.623,15.221C2.126,13.102 2.126,10.898 2.623,8.779L4.083,9.122C3.639,11.015 3.639,12.985 4.083,14.878C4.669,17.378 6.622,19.33 9.122,19.917C11.015,20.361 12.985,20.361 14.878,19.917C17.378,19.33 19.33,17.378 19.917,14.878C20.361,12.985 20.361,11.015 19.917,9.122C19.33,6.622 17.378,4.669 14.878,4.083C12.985,3.639 11.015,3.639 9.122,4.083C6.622,4.669 4.669,6.622 4.083,9.122L2.623,8.779Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8.5,12L11,14.5L16,9.5"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="@color/green"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/library_liked_icon.xml
Normal file
9
app/src/main/res/drawable/library_liked_icon.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="54dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M15.698,3.065C17.377,2.805 18.953,3.333 20.229,4.577C21.526,5.841 22.147,7.507 21.97,9.254C21.795,10.979 20.879,12.684 19.338,14.186C18.908,14.606 17.752,15.738 16.093,17.365C15.347,18.097 14.553,18.876 13.753,19.662L12.975,20.425L12.666,20.729C12.488,20.903 12.249,21.001 12,21.001C11.75,21.001 11.511,20.903 11.334,20.729L8.648,18.088L8.08,17.532C6.941,16.417 5.801,15.302 4.662,14.186C3.12,12.684 2.205,10.98 2.03,9.254C1.853,7.507 2.474,5.841 3.771,4.577C5.047,3.333 6.623,2.805 8.302,3.065C9.548,3.257 10.812,3.878 12,4.868C13.189,3.878 14.452,3.257 15.698,3.065H15.698Z"
|
||||
android:fillColor="@color/white"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/library_offline_icon.xml
Normal file
9
app/src/main/res/drawable/library_offline_icon.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="54dp"
|
||||
android:height="54dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/white"
|
||||
android:pathData="M12.972,22.844C12.385,22.701 11.834,22.446 11.349,22.094C10.865,21.742 10.458,21.3 10.151,20.794C9.845,20.288 9.645,19.728 9.564,19.146C9.483,18.565 9.522,17.973 9.678,17.407C9.835,16.84 10.106,16.309 10.476,15.845C10.847,15.38 11.308,14.992 11.834,14.703C12.361,14.413 12.941,14.228 13.542,14.158C14.143,14.088 14.752,14.135 15.334,14.296C16.118,14.498 16.835,14.895 17.412,15.448L19.193,8.997L19.435,8.12C19.486,7.935 19.586,7.766 19.725,7.629C19.864,7.492 20.037,7.393 20.227,7.34C20.417,7.288 20.618,7.284 20.811,7.329C21.003,7.375 21.18,7.467 21.324,7.599L23.641,9.714C23.791,9.851 23.9,10.026 23.957,10.218C24.013,10.411 24.014,10.615 23.961,10.809C23.907,11.002 23.801,11.178 23.653,11.318C23.504,11.457 23.32,11.555 23.119,11.602L20.714,12.15L18.477,20.267L18.443,20.258C18.094,21.072 17.503,21.767 16.746,22.256C15.989,22.744 15.099,23.003 14.19,23C13.778,23 13.369,22.947 12.972,22.844ZM8.582,20.195C6.203,19.876 4.024,18.733 2.447,16.977C0.869,15.221 0,12.971 0,10.642C-0,8.735 0.583,6.871 1.675,5.285C2.767,3.7 4.32,2.464 6.136,1.734C7.953,1.004 9.951,0.813 11.88,1.185C13.808,1.557 15.579,2.476 16.969,3.824C17.571,4.408 18.094,5.064 18.525,5.776C18.512,5.809 18.5,5.844 18.491,5.881L18.139,7.152L16.447,13.273C15.977,13.069 15.481,12.927 14.971,12.851C15.297,12.158 15.465,11.405 15.463,10.643C15.463,9.627 15.165,8.632 14.604,7.774C14.043,6.917 13.242,6.231 12.295,5.798C11.348,5.365 10.293,5.203 9.254,5.329C8.215,5.455 7.234,5.866 6.427,6.512C5.619,7.159 5.017,8.014 4.692,8.98C4.367,9.945 4.331,10.981 4.59,11.965C4.848,12.949 5.389,13.842 6.151,14.539C6.913,15.236 7.863,15.709 8.891,15.902C8.489,16.68 8.281,17.539 8.284,18.41C8.283,19.016 8.384,19.619 8.582,20.195H8.582Z" />
|
||||
</vector>
|
||||
@ -212,51 +212,57 @@
|
||||
android:textSize="12dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/favoritesBtn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
<LinearLayout
|
||||
android:id="@+id/likeAndDownloadLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favoritesImg"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/not_favorited_icon" />
|
||||
</RelativeLayout>
|
||||
<RelativeLayout
|
||||
android:id="@+id/favoritesBtn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:gravity="center">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/downloadBtn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center">
|
||||
<ImageView
|
||||
android:id="@+id/favoritesImg"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/not_favorited_icon" />
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/downloadImg"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/download_icon" />
|
||||
<RelativeLayout
|
||||
android:id="@+id/downloadBtn"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/downloadLoading"
|
||||
android:visibility="gone"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:indeterminateTint="@color/green"
|
||||
android:progressBackgroundTint="@color/green"
|
||||
android:progressTint="@color/green" />
|
||||
<ImageView
|
||||
android:id="@+id/downloadImg"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/download_icon" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/downloadLoading"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:indeterminateTint="@color/green"
|
||||
android:progressBackgroundTint="@color/green"
|
||||
android:progressTint="@color/green"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
146
app/src/main/res/layout/activity_offline_songs.xml
Normal file
146
app/src/main/res/layout/activity_offline_songs.xml
Normal file
@ -0,0 +1,146 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/main_bg_color"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@mipmap/settings_bg_img" />
|
||||
|
||||
<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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="58dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:gravity="center"
|
||||
android:text="@string/offline_songs"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18dp" />
|
||||
</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>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/play_music_layout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/play_music_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="vertical" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@ -63,8 +63,9 @@
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
@ -90,6 +91,17 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/downloadBtn"
|
||||
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/download_icon" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -15,7 +16,161 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="@string/library"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32dp" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="14dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/likedSongsBtn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
app:cardBackgroundColor="@color/green_60"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:padding="24dp"
|
||||
android:src="@drawable/library_liked_icon" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="@string/liked_songs"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14dp" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/likedSongsTv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:text="0"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:text="@string/songs"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/offlineSongsBtn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
app:cardBackgroundColor="@color/offline_bg_color_60"
|
||||
app:cardCornerRadius="10dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:padding="24dp"
|
||||
android:src="@drawable/library_offline_icon" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="@string/offline_songs"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/offlineSongsTv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:text="0"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:text="@string/songs"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:fontFamily="@font/medium_font"
|
||||
android:text="@string/new_playlist"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20dp" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
116
app/src/main/res/layout/offline_list_item.xml
Normal file
116
app/src/main/res/layout/offline_list_item.xml
Normal file
@ -0,0 +1,116 @@
|
||||
<?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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@mipmap/musicoo_logo_img" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/listPlayView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_60"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.player.musicoo.view.MusicBarsView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
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/medium_font"
|
||||
android:maxLines="1"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14dp" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/download_green_done_icon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp"/>
|
||||
|
||||
<com.player.musicoo.view.MarqueeTextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:fontFamily="@font/regular_font"
|
||||
android:maxLines="1"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/white_60"
|
||||
android:textSize="12dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@ -9,6 +9,8 @@
|
||||
<color name="main_bg_color">#151718</color>
|
||||
<color name="green">#FF80F988</color>
|
||||
<color name="green_60">#9980F988</color>
|
||||
<color name="offline_bg_color">#FFFC746F</color>
|
||||
<color name="offline_bg_color_60">#99FC746F</color>
|
||||
<color name="bottom_layout_bg_color">#1A1A1A</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
</resources>
|
||||
@ -29,4 +29,11 @@
|
||||
<string name="more">More</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="downloads">Downloads</string>
|
||||
<string name="library">Library</string>
|
||||
<string name="liked_songs">Liked songs</string>
|
||||
<string name="offline_songs">Offline songs</string>
|
||||
<string name="songs">Songs</string>
|
||||
<string name="new_playlist">New playlist</string>
|
||||
<string name="liked_songs_no_data_prompt">You haven\'t liked any songs yet.</string>
|
||||
<string name="offline_songs_no_data_prompt">You haven\'t saved any songs for offline listening yet.</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue
Block a user