mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
MutableSharedFlow,Active Tracks Query ,etc fixes
This commit is contained in:
parent
1b58bbfcf0
commit
f7e71da827
@ -33,8 +33,7 @@ import com.shabinder.common.ui.colorOffWhite
|
||||
import com.shabinder.database.Database
|
||||
import com.tonyodev.fetch2.Status
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
const val disableDozeCode = 1223
|
||||
@ -47,8 +46,7 @@ class MainActivity : ComponentActivity() {
|
||||
private lateinit var root: SpotiFlyerRoot
|
||||
private val callBacks: SpotiFlyerRootCallBacks
|
||||
get() = root.callBacks
|
||||
//TODO pass updates from Foreground Service
|
||||
private val downloadFlow = MutableStateFlow(hashMapOf<String, DownloadStatus>())
|
||||
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
|
||||
|
||||
private lateinit var updateUIReceiver: BroadcastReceiver
|
||||
private lateinit var queryReceiver: BroadcastReceiver
|
||||
@ -80,7 +78,7 @@ class MainActivity : ComponentActivity() {
|
||||
override val database = this@MainActivity.database
|
||||
override val fetchPlatformQueryResult = this@MainActivity.fetcher
|
||||
override val directories: Dir = this@MainActivity.dir
|
||||
override val downloadProgressReport: StateFlow<HashMap<String, DownloadStatus>> = downloadFlow
|
||||
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
|
||||
}
|
||||
)
|
||||
|
||||
@ -108,9 +106,9 @@ class MainActivity : ComponentActivity() {
|
||||
addAction(Status.QUEUED.name)
|
||||
addAction(Status.FAILED.name)
|
||||
addAction(Status.DOWNLOADING.name)
|
||||
addAction(Status.COMPLETED.name)
|
||||
addAction("Progress")
|
||||
addAction("Converting")
|
||||
addAction("track_download_completed")
|
||||
}
|
||||
updateUIReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
@ -119,18 +117,20 @@ class MainActivity : ComponentActivity() {
|
||||
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
|
||||
trackDetails?.let { track ->
|
||||
lifecycleScope.launch {
|
||||
val latestMap = downloadFlow.value.apply {
|
||||
val latestMap = trackStatusFlow.replayCache.getOrElse(0
|
||||
) { hashMapOf() }.apply {
|
||||
this[track.title] = when (intent.action) {
|
||||
Status.QUEUED.name -> DownloadStatus.Queued
|
||||
Status.FAILED.name -> DownloadStatus.Failed
|
||||
Status.DOWNLOADING.name -> DownloadStatus.Downloading()
|
||||
"Progress" -> DownloadStatus.Downloading(intent.getIntExtra("progress", 0))
|
||||
"Converting" -> DownloadStatus.Converting
|
||||
"track_download_completed" -> DownloadStatus.Downloaded
|
||||
Status.COMPLETED.name -> DownloadStatus.Downloaded
|
||||
else -> DownloadStatus.NotDownloaded
|
||||
}
|
||||
}
|
||||
downloadFlow.emit(latestMap)
|
||||
trackStatusFlow.emit(latestMap)
|
||||
Log.i("Track Update",track.title + track.downloaded.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,7 +146,7 @@ class MainActivity : ComponentActivity() {
|
||||
trackList?.let { list ->
|
||||
Log.i("Service Response", "${list.size} Tracks Active")
|
||||
lifecycleScope.launch {
|
||||
downloadFlow.emit(list)
|
||||
trackStatusFlow.emit(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import com.shabinder.common.utils.Consumer
|
||||
import com.shabinder.common.models.PlatformQueryResult
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SpotiFlyerList {
|
||||
@ -40,13 +41,18 @@ interface SpotiFlyerList {
|
||||
* */
|
||||
suspend fun loadImage(url:String): ImageBitmap?
|
||||
|
||||
/*
|
||||
* Sync Tracks Statuses
|
||||
* */
|
||||
fun onRefreshTracksStatuses()
|
||||
|
||||
interface Dependencies {
|
||||
val storeFactory: StoreFactory
|
||||
val fetchQuery: FetchPlatformQueryResult
|
||||
val dir: Dir
|
||||
val link: String
|
||||
val listOutput: Consumer<Output>
|
||||
val downloadProgressFlow: StateFlow<HashMap<String,DownloadStatus>>
|
||||
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
}
|
||||
sealed class Output {
|
||||
object Finished : Output()
|
||||
|
@ -42,5 +42,9 @@ internal class SpotiFlyerListImpl(
|
||||
listOutput.callback(SpotiFlyerList.Output.Finished)
|
||||
}
|
||||
|
||||
override fun onRefreshTracksStatuses() {
|
||||
store.accept(Intent.RefreshTracksStatuses)
|
||||
}
|
||||
|
||||
override suspend fun loadImage(url: String): ImageBitmap? = dir.loadImage(url)
|
||||
}
|
@ -10,5 +10,6 @@ internal interface SpotiFlyerListStore: Store<Intent, State, Nothing> {
|
||||
data class SearchLink(val link: String): Intent()
|
||||
data class StartDownload(val track:TrackDetails): Intent()
|
||||
data class StartDownloadAll(val trackList: List<TrackDetails>): Intent()
|
||||
object RefreshTracksStatuses: Intent()
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,18 @@ import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import com.arkivanov.mvikotlin.core.store.*
|
||||
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||
import com.shabinder.common.database.getLogger
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.downloadTracks
|
||||
import com.shabinder.common.di.queryActiveTracks
|
||||
import com.shabinder.common.list.SpotiFlyerList.State
|
||||
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.PlatformQueryResult
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.ui.showPopUpMessage
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
@ -21,8 +24,9 @@ internal class SpotiFlyerListStoreProvider(
|
||||
private val storeFactory: StoreFactory,
|
||||
private val fetchQuery: FetchPlatformQueryResult,
|
||||
private val link: String,
|
||||
private val downloadProgressFlow: StateFlow<HashMap<String, DownloadStatus>>
|
||||
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
) {
|
||||
val logger = getLogger()
|
||||
fun provide(): SpotiFlyerListStore =
|
||||
object : SpotiFlyerListStore, Store<Intent, State, Nothing> by storeFactory.create(
|
||||
name = "SpotiFlyerListStore",
|
||||
@ -42,8 +46,10 @@ internal class SpotiFlyerListStoreProvider(
|
||||
|
||||
override suspend fun executeAction(action: Unit, getState: () -> State) {
|
||||
executeIntent(Intent.SearchLink(link),getState)
|
||||
executeIntent(Intent.RefreshTracksStatuses,getState)
|
||||
|
||||
downloadProgressFlow.collectLatest { map ->
|
||||
logger.d(map.size.toString(),"ListStore: flow Updated")
|
||||
val updatedTrackList = getState().trackList.updateTracksStatuses(map)
|
||||
if(updatedTrackList.isNotEmpty()) dispatch(Result.UpdateTrackList(updatedTrackList))
|
||||
}
|
||||
@ -53,7 +59,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
when (intent) {
|
||||
is Intent.SearchLink -> fetchQuery.query(link)?.let{ result ->
|
||||
result.trackList = result.trackList.toMutableList()
|
||||
dispatch((Result.ResultFetched(result,result.trackList.toMutableList().updateTracksStatuses(downloadProgressFlow.value))))
|
||||
dispatch((Result.ResultFetched(result,result.trackList.toMutableList().updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0){ hashMapOf()}))))
|
||||
}
|
||||
|
||||
is Intent.StartDownloadAll -> {
|
||||
@ -68,12 +74,13 @@ internal class SpotiFlyerListStoreProvider(
|
||||
}
|
||||
it
|
||||
}
|
||||
dispatch(Result.UpdateTrackList(list.toMutableList().updateTracksStatuses(downloadProgressFlow.value)))
|
||||
dispatch(Result.UpdateTrackList(list.toMutableList().updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0){ hashMapOf()})))
|
||||
}
|
||||
is Intent.StartDownload -> {
|
||||
downloadTracks(listOf(intent.track),fetchQuery.youtubeMusic::getYTIDBestMatch,dir::saveFileWithMetadata)
|
||||
dispatch(Result.UpdateTrackItem(intent.track.apply { downloaded = DownloadStatus.Queued }))
|
||||
}
|
||||
is Intent.RefreshTracksStatuses -> queryActiveTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,18 +100,18 @@ internal class SpotiFlyerListStoreProvider(
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<TrackDetails>.updateTracksStatuses(map:HashMap<String,DownloadStatus>):SnapshotStateList<TrackDetails>{
|
||||
val titleList = this.map { it.title }
|
||||
val newStateList = mutableStateListOf<TrackDetails>()
|
||||
for(newTrack in map){
|
||||
titleList.indexOf(newTrack.key).let { position ->
|
||||
this.getOrNull(position)?.apply { downloaded = newTrack.value }?.also { updatedTrack ->
|
||||
this[position] = updatedTrack
|
||||
private fun MutableList<TrackDetails>.updateTracksStatuses(map:HashMap<String,DownloadStatus>):SnapshotStateList<TrackDetails>{
|
||||
val titleList = this.map { it.title }
|
||||
val newStateList = mutableStateListOf<TrackDetails>()
|
||||
for(newTrack in map){
|
||||
titleList.indexOf(newTrack.key).let { position ->
|
||||
this.getOrNull(position)?.apply { downloaded = newTrack.value }?.also { updatedTrack ->
|
||||
this[position] = updatedTrack
|
||||
logger.d(updatedTrack.toString(),"List Store Track Update")
|
||||
}
|
||||
}
|
||||
}
|
||||
newStateList.addAll(this)
|
||||
return newStateList
|
||||
}
|
||||
newStateList.addAll(this)
|
||||
return newStateList
|
||||
}
|
@ -53,7 +53,7 @@ fun SpotiFlyerMainContent(component: SpotiFlyerMain){
|
||||
when(model.selectedCategory){
|
||||
HomeCategory.About -> AboutColumn()
|
||||
HomeCategory.History -> HistoryColumn(
|
||||
model.records,
|
||||
model.records.sortedByDescending { it.id },
|
||||
component::loadImage,
|
||||
component::onLinkSearch
|
||||
)
|
||||
|
@ -14,6 +14,7 @@ import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import com.shabinder.database.Database
|
||||
import com.shabinder.common.root.integration.SpotiFlyerRootImpl
|
||||
import com.shabinder.common.utils.Consumer
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SpotiFlyerRoot {
|
||||
@ -32,7 +33,7 @@ interface SpotiFlyerRoot {
|
||||
val database: Database
|
||||
val fetchPlatformQueryResult: FetchPlatformQueryResult
|
||||
val directories: Dir
|
||||
val downloadProgressReport: StateFlow<HashMap<String,DownloadStatus>>
|
||||
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.shabinder.common.root.integration
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.RouterState
|
||||
import com.arkivanov.decompose.pop
|
||||
@ -8,6 +9,7 @@ import com.arkivanov.decompose.router
|
||||
import com.arkivanov.decompose.statekeeper.Parcelable
|
||||
import com.arkivanov.decompose.statekeeper.Parcelize
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.shabinder.common.database.getLogger
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.list.SpotiFlyerList
|
||||
import com.shabinder.common.main.SpotiFlyerMain
|
||||
|
@ -41,6 +41,13 @@ actual fun giveDonation(){
|
||||
//TODO
|
||||
}
|
||||
|
||||
actual fun queryActiveTracks() {
|
||||
val serviceIntent = Intent(appContext, ForegroundService::class.java).apply {
|
||||
action = "query"
|
||||
}
|
||||
ContextCompat.startForegroundService(appContext, serviceIntent)
|
||||
}
|
||||
|
||||
actual suspend fun downloadTracks(
|
||||
list: List<TrackDetails>,
|
||||
getYTIDBestMatch:suspend (String,TrackDetails)->String?,
|
||||
|
@ -251,25 +251,30 @@ class ForegroundService : Service(),CoroutineScope{
|
||||
}
|
||||
|
||||
override fun onCompleted(download: Download) {
|
||||
launch {
|
||||
val track = requestMap[download.request]
|
||||
removeFromNotification("Downloading ${track?.title}")
|
||||
try{
|
||||
track?.let {
|
||||
dir.saveFileWithMetadata(byteArrayOf(),it)
|
||||
allTracksStatus[it.title] = DownloadStatus.Converting
|
||||
val track = requestMap[download.request]
|
||||
try{
|
||||
track?.let {
|
||||
val job = launch { dir.saveFileWithMetadata(byteArrayOf(),it) }
|
||||
allTracksStatus[it.title] = DownloadStatus.Converting
|
||||
sendTrackBroadcast("Converting",it)
|
||||
addToNotification("Processing ${it.title}")
|
||||
job.invokeOnCompletion { _ ->
|
||||
converted++
|
||||
sendTrackBroadcast(Status.COMPLETED.name,it)
|
||||
removeFromNotification("Processing ${it.title}")
|
||||
}
|
||||
logger.d(tag){"${track?.title} Download Completed"}
|
||||
}catch (
|
||||
e: KotlinNullPointerException
|
||||
){
|
||||
logger.d(tag){"${track?.title} Download Failed! Error:Fetch!!!!"}
|
||||
logger.d(tag){"${track?.title} Requesting Download thru Android DM"}
|
||||
downloadUsingDM(download.request.url, download.request.file, track!!)
|
||||
downloaded++
|
||||
requestMap.remove(download.request)
|
||||
}
|
||||
logger.d(tag){"${track?.title} Download Completed"}
|
||||
}catch (
|
||||
e: KotlinNullPointerException
|
||||
){
|
||||
logger.d(tag){"${track?.title} Download Failed! Error:Fetch!!!!"}
|
||||
logger.d(tag){"${track?.title} Requesting Download thru Android DM"}
|
||||
downloadUsingDM(download.request.url, download.request.file, track!!)
|
||||
}
|
||||
downloaded++
|
||||
requestMap.remove(download.request)
|
||||
removeFromNotification("Downloading ${track?.title}")
|
||||
}
|
||||
|
||||
override fun onDeleted(download: Download) {
|
||||
@ -427,7 +432,7 @@ class ForegroundService : Service(),CoroutineScope{
|
||||
fetch.removeAll()
|
||||
updateNotification()
|
||||
cleanFiles(File(dir.defaultDir()))
|
||||
cleanFiles(File(dir.imageCacheDir()))
|
||||
//TODO cleanFiles(File(dir.imageCacheDir()))
|
||||
messageList = mutableListOf("","","","","")
|
||||
releaseWakeLock()
|
||||
serviceJob.cancel()
|
||||
|
@ -12,4 +12,6 @@ expect suspend fun downloadTracks(
|
||||
list: List<TrackDetails>,
|
||||
getYTIDBestMatch:suspend (String,TrackDetails)->String?,
|
||||
saveFileWithMetaData:suspend (mp3ByteArray:ByteArray, trackDetails: TrackDetails) -> Unit
|
||||
)
|
||||
)
|
||||
|
||||
expect fun queryActiveTracks()
|
@ -242,7 +242,7 @@ class YoutubeMusic constructor(
|
||||
val avgMatch = (artistMatch + durationMatch)/2
|
||||
linksWithMatchValue[result.videoId.toString()] = avgMatch.toInt()
|
||||
}
|
||||
logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||
//logger.d("YT Api Result"){"$trackName - $linksWithMatchValue"}
|
||||
return linksWithMatchValue.toList().sortedByDescending { it.second }.toMap()
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,9 @@ actual fun giveDonation(){
|
||||
//TODO
|
||||
}
|
||||
|
||||
val DownloadProgressFlow: MutableStateFlow<HashMap<String,DownloadStatus>> = MutableStateFlow(hashMapOf())
|
||||
actual fun queryActiveTracks(){}
|
||||
|
||||
val DownloadProgressFlow: MutableSharedFlow<HashMap<String,DownloadStatus>> = MutableSharedFlow(1)
|
||||
|
||||
actual suspend fun downloadTracks(
|
||||
list: List<TrackDetails>,
|
||||
@ -36,7 +38,8 @@ actual suspend fun downloadTracks(
|
||||
val searchQuery = "${it.title} - ${it.artists.joinToString(",")}"
|
||||
val videoId = getYTIDBestMatch(searchQuery,it)
|
||||
if (videoId.isNullOrBlank()) {
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.value.apply { set(it.title,DownloadStatus.Failed) })
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
||||
) { hashMapOf() }.apply { set(it.title,DownloadStatus.Failed) })
|
||||
} else {//Found Youtube Video ID
|
||||
downloadTrack(videoId, it,saveFileWithMetaData)
|
||||
}
|
||||
@ -59,14 +62,17 @@ suspend fun downloadTrack(
|
||||
downloadFile(url).collect {
|
||||
when(it){
|
||||
is DownloadResult.Error -> {
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.value.apply { set(trackDetails.title,DownloadStatus.Failed) })
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Failed) })
|
||||
}
|
||||
is DownloadResult.Progress -> {
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.value.apply { set(trackDetails.title,DownloadStatus.Downloading(it.progress)) })
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloading(it.progress)) })
|
||||
}
|
||||
is DownloadResult.Success -> {//Todo clear map
|
||||
saveFileWithMetaData(it.byteArray,trackDetails)
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.value.apply { set(trackDetails.title,DownloadStatus.Downloaded) })
|
||||
DownloadProgressFlow.emit(DownloadProgressFlow.replayCache.getOrElse(0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title,DownloadStatus.Downloaded) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user