mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
- Removed BroadcastReceivers and Bound to Service instead,
- Code Improv and Cleanup
This commit is contained in:
parent
9b447c3a9d
commit
979fcc342b
@ -121,13 +121,14 @@ dependencies {
|
|||||||
implementation(MVIKotlin.mvikotlinTimeTravel)
|
implementation(MVIKotlin.mvikotlinTimeTravel)
|
||||||
|
|
||||||
// Extras
|
// Extras
|
||||||
Extras.Android.apply {
|
with(Extras.Android) {
|
||||||
implementation(Acra.notification)
|
implementation(Acra.notification)
|
||||||
implementation(Acra.http)
|
implementation(Acra.http)
|
||||||
implementation(appUpdator)
|
implementation(appUpdator)
|
||||||
implementation(matomo)
|
implementation(matomo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementation(Extras.kermit)
|
||||||
//implementation("com.jakewharton.timber:timber:4.7.1")
|
//implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
implementation("dev.icerock.moko:parcelize:0.7.0")
|
implementation("dev.icerock.moko:parcelize:0.7.0")
|
||||||
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
||||||
|
@ -72,6 +72,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service android:name="com.shabinder.common.di.worker.ForegroundService"/>
|
<service android:name=".service.ForegroundService"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -17,15 +17,16 @@
|
|||||||
package com.shabinder.spotiflyer
|
package com.shabinder.spotiflyer
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.BroadcastReceiver
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
@ -51,18 +52,17 @@ import com.google.accompanist.insets.navigationBarsPadding
|
|||||||
import com.google.accompanist.insets.statusBarsHeight
|
import com.google.accompanist.insets.statusBarsHeight
|
||||||
import com.google.accompanist.insets.statusBarsPadding
|
import com.google.accompanist.insets.statusBarsPadding
|
||||||
import com.shabinder.common.di.*
|
import com.shabinder.common.di.*
|
||||||
import com.shabinder.common.di.worker.ForegroundService
|
|
||||||
import com.shabinder.common.models.Actions
|
import com.shabinder.common.models.Actions
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.PlatformActions
|
import com.shabinder.common.models.PlatformActions
|
||||||
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
|
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
|
||||||
import com.shabinder.common.models.Status
|
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.methods
|
import com.shabinder.common.models.methods
|
||||||
import com.shabinder.common.root.SpotiFlyerRoot
|
import com.shabinder.common.root.SpotiFlyerRoot
|
||||||
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
|
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
|
||||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||||
import com.shabinder.common.uikit.*
|
import com.shabinder.common.uikit.*
|
||||||
|
import com.shabinder.spotiflyer.service.ForegroundService
|
||||||
import com.shabinder.spotiflyer.ui.AnalyticsDialog
|
import com.shabinder.spotiflyer.ui.AnalyticsDialog
|
||||||
import com.shabinder.spotiflyer.ui.NetworkDialog
|
import com.shabinder.spotiflyer.ui.NetworkDialog
|
||||||
import com.shabinder.spotiflyer.ui.PermissionDialog
|
import com.shabinder.spotiflyer.ui.PermissionDialog
|
||||||
@ -82,12 +82,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
private val callBacks: SpotiFlyerRootCallBacks get() = root.callBacks
|
private val callBacks: SpotiFlyerRootCallBacks get() = root.callBacks
|
||||||
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
|
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
|
||||||
private var permissionGranted = mutableStateOf(true)
|
private var permissionGranted = mutableStateOf(true)
|
||||||
private lateinit var updateUIReceiver: BroadcastReceiver
|
|
||||||
private lateinit var queryReceiver: BroadcastReceiver
|
|
||||||
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
|
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
|
||||||
private val tracker get() = (application as App).tracker
|
private val tracker get() = (application as App).tracker
|
||||||
private val visibleChild get(): SpotiFlyerRoot.Child = root.routerState.value.activeChild.instance
|
private val visibleChild get(): SpotiFlyerRoot.Child = root.routerState.value.activeChild.instance
|
||||||
|
|
||||||
|
// Variable for storing instance of our service class
|
||||||
|
var foregroundService: ForegroundService? = null
|
||||||
|
|
||||||
|
// Boolean to check if our activity is bound to service or not
|
||||||
|
var isServiceBound: Boolean? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// This app draws behind the system bars, so we want to handle fitting system windows
|
// This app draws behind the system bars, so we want to handle fitting system windows
|
||||||
@ -162,8 +166,62 @@ class MainActivity : ComponentActivity() {
|
|||||||
TrackHelper.track().download().with(tracker)
|
TrackHelper.track().download().with(tracker)
|
||||||
}
|
}
|
||||||
handleIntentFromExternalActivity()
|
handleIntentFromExternalActivity()
|
||||||
|
|
||||||
|
initForegroundService()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*START: Foreground Service Handlers*/
|
||||||
|
private fun initForegroundService() {
|
||||||
|
// Start and then Bind to the Service
|
||||||
|
ContextCompat.startForegroundService(
|
||||||
|
this@MainActivity,
|
||||||
|
Intent(this, ForegroundService::class.java)
|
||||||
|
)
|
||||||
|
bindService()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for getting the instance of binder from our service class
|
||||||
|
* So client can get instance of our service class and can directly communicate with it.
|
||||||
|
*/
|
||||||
|
private val serviceConnection = object : ServiceConnection {
|
||||||
|
val tag = "Service Connection"
|
||||||
|
|
||||||
|
override fun onServiceConnected(className: ComponentName, iBinder: IBinder) {
|
||||||
|
Log.d(tag, "connected to service.")
|
||||||
|
// We've bound to MyService, cast the IBinder and get MyBinder instance
|
||||||
|
val binder = iBinder as ForegroundService.DownloadServiceBinder
|
||||||
|
foregroundService = binder.service
|
||||||
|
isServiceBound = true
|
||||||
|
lifecycleScope.launch {
|
||||||
|
foregroundService?.trackStatusFlowMap?.statusFlow?.let {
|
||||||
|
trackStatusFlow.emitAll(it.conflate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
|
Log.d(tag, "disconnected from service.")
|
||||||
|
isServiceBound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Used to bind to our service class*/
|
||||||
|
private fun bindService() {
|
||||||
|
Intent(this, ForegroundService::class.java).also { intent ->
|
||||||
|
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Used to unbind from our service class*/
|
||||||
|
private fun unbindService() {
|
||||||
|
Intent(this, ForegroundService::class.java).also {
|
||||||
|
unbindService(serviceConnection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*END: Foreground Service Handlers*/
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun isInternetAvailableState(): State<Boolean?> {
|
private fun isInternetAvailableState(): State<Boolean?> {
|
||||||
return internetAvailability.observeAsState()
|
return internetAvailability.observeAsState()
|
||||||
@ -206,12 +264,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendTracksToService(array: ArrayList<TrackDetails>) {
|
override fun sendTracksToService(array: List<TrackDetails>) {
|
||||||
for (list in array.chunked(50)) {
|
if (foregroundService == null) initForegroundService()
|
||||||
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
foregroundService?.downloadAllTracks(array)
|
||||||
serviceIntent.putParcelableArrayListExtra("object", list as ArrayList)
|
|
||||||
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,10 +351,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun queryActiveTracks() {
|
private fun queryActiveTracks() {
|
||||||
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java).apply {
|
lifecycleScope.launch {
|
||||||
action = "query"
|
foregroundService?.trackStatusFlowMap?.let { tracksStatus ->
|
||||||
|
trackStatusFlow.emit(tracksStatus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
queryActiveTracks()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@ -357,80 +418,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Broadcast Handlers
|
|
||||||
* */
|
|
||||||
private fun initializeBroadcast(){
|
|
||||||
val intentFilter = IntentFilter().apply {
|
|
||||||
addAction(Status.QUEUED.name)
|
|
||||||
addAction(Status.FAILED.name)
|
|
||||||
addAction(Status.DOWNLOADING.name)
|
|
||||||
addAction(Status.COMPLETED.name)
|
|
||||||
addAction("Progress")
|
|
||||||
addAction("Converting")
|
|
||||||
}
|
|
||||||
updateUIReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
//Update Flow with latest details
|
|
||||||
if (intent != null) {
|
|
||||||
val trackDetails = intent.getParcelableExtra<TrackDetails?>("track")
|
|
||||||
trackDetails?.let { track ->
|
|
||||||
lifecycleScope.launch {
|
|
||||||
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
|
|
||||||
Status.COMPLETED.name -> DownloadStatus.Downloaded
|
|
||||||
else -> DownloadStatus.NotDownloaded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trackStatusFlow.emit(latestMap)
|
|
||||||
Log.i("Track Update",track.title + track.downloaded.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val queryFilter = IntentFilter().apply { addAction("query_result") }
|
|
||||||
queryReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
//UI update here
|
|
||||||
if (intent != null){
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val trackList = intent.getSerializableExtra("tracks") as? HashMap<String, DownloadStatus>?
|
|
||||||
trackList?.let { list ->
|
|
||||||
Log.i("Service Response", "${list.size} Tracks Active")
|
|
||||||
lifecycleScope.launch {
|
|
||||||
trackStatusFlow.emit(list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
registerReceiver(updateUIReceiver, intentFilter)
|
|
||||||
registerReceiver(queryReceiver, queryFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
initializeBroadcast()
|
|
||||||
if(visibleChild is SpotiFlyerRoot.Child.List) {
|
|
||||||
// Update Track List Statuses when Returning to App
|
|
||||||
queryActiveTracks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
unregisterReceiver(updateUIReceiver)
|
|
||||||
unregisterReceiver(queryReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
handleIntentFromExternalActivity(intent)
|
handleIntentFromExternalActivity(intent)
|
||||||
@ -455,6 +442,11 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
unbindService()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val disableDozeCode = 1223
|
const val disableDozeCode = 1223
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.shabinder.common.di.worker
|
package com.shabinder.spotiflyer.service
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
@ -26,6 +26,7 @@ import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
@ -40,12 +41,13 @@ import com.shabinder.common.di.downloadFile
|
|||||||
import com.shabinder.common.di.utils.ParallelExecutor
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.Status
|
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
@ -68,7 +70,8 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = serviceJob + Dispatchers.IO
|
get() = serviceJob + Dispatchers.IO
|
||||||
|
|
||||||
private val allTracksStatus = hashMapOf<String, DownloadStatus>()
|
val trackStatusFlowMap = TrackStatusFlowMap(MutableSharedFlow(replay = 1),this)
|
||||||
|
|
||||||
private var messageList = mutableListOf("", "", "", "", "")
|
private var messageList = mutableListOf("", "", "", "", "")
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var isServiceStarted = false
|
private var isServiceStarted = false
|
||||||
@ -80,7 +83,16 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
private val logger: Kermit by inject()
|
private val logger: Kermit by inject()
|
||||||
private val dir: Dir by inject()
|
private val dir: Dir by inject()
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? = null
|
|
||||||
|
inner class DownloadServiceBinder : Binder() {
|
||||||
|
// Return this instance of MyService so clients can call public methods
|
||||||
|
val service: ForegroundService
|
||||||
|
get() =// Return this instance of Foreground Service so clients can call public methods
|
||||||
|
this@ForegroundService
|
||||||
|
}
|
||||||
|
private val myBinder: IBinder = DownloadServiceBinder()
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder = myBinder
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedImmutableFlag")
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -110,31 +122,13 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
"query" -> {
|
"query" -> {
|
||||||
val response = Intent().apply {
|
val response = Intent().apply {
|
||||||
action = "query_result"
|
action = "query_result"
|
||||||
synchronized(allTracksStatus) {
|
synchronized(trackStatusFlowMap) {
|
||||||
putExtra("tracks", allTracksStatus)
|
putExtra("tracks", trackStatusFlowMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendBroadcast(response)
|
sendBroadcast(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadObjects: ArrayList<TrackDetails>? = (
|
|
||||||
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
|
||||||
"object"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadObjects?.let { list ->
|
|
||||||
downloadObjects.size.let { size ->
|
|
||||||
total += size
|
|
||||||
isSingleDownload = (size == 1)
|
|
||||||
}
|
|
||||||
list.forEach { track ->
|
|
||||||
allTracksStatus[track.title] = DownloadStatus.Queued
|
|
||||||
}
|
|
||||||
updateNotification()
|
|
||||||
downloadAllTracks(list)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Wake locks and misc tasks from here :
|
// Wake locks and misc tasks from here :
|
||||||
return if (isServiceStarted) {
|
return if (isServiceStarted) {
|
||||||
@ -156,8 +150,16 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* Function To Download All Tracks Available in a List
|
* Function To Download All Tracks Available in a List
|
||||||
**/
|
**/
|
||||||
private fun downloadAllTracks(trackList: List<TrackDetails>) {
|
fun downloadAllTracks(trackList: List<TrackDetails>) {
|
||||||
|
|
||||||
|
trackList.size.also { size ->
|
||||||
|
total += size
|
||||||
|
isSingleDownload = (size == 1)
|
||||||
|
updateNotification()
|
||||||
|
}
|
||||||
|
|
||||||
trackList.forEach {
|
trackList.forEach {
|
||||||
|
trackStatusFlowMap[it.title] = DownloadStatus.Queued
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
downloadService.execute {
|
downloadService.execute {
|
||||||
fetcher.findMp3DownloadLink(it).fold(
|
fetcher.findMp3DownloadLink(it).fold(
|
||||||
@ -165,10 +167,9 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
enqueueDownload(url, it)
|
enqueueDownload(url, it)
|
||||||
},
|
},
|
||||||
failure = { _ ->
|
failure = { _ ->
|
||||||
sendTrackBroadcast(Status.FAILED.name, it)
|
|
||||||
failed++
|
failed++
|
||||||
updateNotification()
|
updateNotification()
|
||||||
allTracksStatus[it.title] = DownloadStatus.Failed
|
trackStatusFlowMap[it.title] = DownloadStatus.Failed
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -180,24 +181,20 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
// Initiating Download
|
// Initiating Download
|
||||||
addToNotification("Downloading ${track.title}")
|
addToNotification("Downloading ${track.title}")
|
||||||
logger.d(tag) { "${track.title} Download Started" }
|
logger.d(tag) { "${track.title} Download Started" }
|
||||||
allTracksStatus[track.title] = DownloadStatus.Downloading()
|
trackStatusFlowMap[track.title] = DownloadStatus.Downloading()
|
||||||
sendTrackBroadcast(Status.DOWNLOADING.name, track)
|
|
||||||
|
|
||||||
// Enqueueing Download
|
// Enqueueing Download
|
||||||
downloadFile(url).collect {
|
downloadFile(url).collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
launch {
|
logger.d(tag) { it.message }
|
||||||
logger.d(tag) { it.message }
|
removeFromNotification("Downloading ${track.title}")
|
||||||
removeFromNotification("Downloading ${track.title}")
|
failed++
|
||||||
failed++
|
updateNotification()
|
||||||
updateNotification()
|
|
||||||
sendTrackBroadcast(Status.FAILED.name, track)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is DownloadResult.Progress -> {
|
is DownloadResult.Progress -> {
|
||||||
allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress)
|
trackStatusFlowMap[track.title] = DownloadStatus.Downloading(it.progress)
|
||||||
logger.d(tag) { "${track.title} Progress: ${it.progress} %" }
|
logger.d(tag) { "${track.title} Progress: ${it.progress} %" }
|
||||||
|
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
@ -209,26 +206,31 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
try {
|
coroutineScope {
|
||||||
// Save File and Embed Metadata
|
try {
|
||||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) {} }
|
// Save File and Embed Metadata
|
||||||
allTracksStatus[track.title] = DownloadStatus.Converting
|
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) {} }
|
||||||
sendTrackBroadcast("Converting", track)
|
|
||||||
addToNotification("Processing ${track.title}")
|
// Send Converting Status
|
||||||
job.invokeOnCompletion {
|
trackStatusFlowMap[track.title] = DownloadStatus.Converting
|
||||||
converted++
|
addToNotification("Processing ${track.title}")
|
||||||
allTracksStatus[track.title] = DownloadStatus.Downloaded
|
|
||||||
sendTrackBroadcast(Status.COMPLETED.name, track)
|
// All Processing Completed for this Track
|
||||||
removeFromNotification("Processing ${track.title}")
|
job.invokeOnCompletion {
|
||||||
|
converted++
|
||||||
|
trackStatusFlowMap[track.title] = DownloadStatus.Downloaded
|
||||||
|
removeFromNotification("Processing ${track.title}")
|
||||||
|
}
|
||||||
|
logger.d(tag) { "${track.title} Download Completed" }
|
||||||
|
downloaded++
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
// Download Failed
|
||||||
|
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
||||||
|
failed++
|
||||||
}
|
}
|
||||||
logger.d(tag) { "${track.title} Download Completed" }
|
removeFromNotification("Downloading ${track.title}")
|
||||||
downloaded++
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Download Failed
|
|
||||||
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
|
||||||
failed++
|
|
||||||
}
|
}
|
||||||
removeFromNotification("Downloading ${track.title}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +272,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||||
downloadService.close()
|
downloadService.close()
|
||||||
updateNotification()
|
updateNotification()
|
||||||
cleanFiles(File(dir.defaultDir()), logger)
|
cleanFiles(File(dir.defaultDir()))
|
||||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
// TODO cleanFiles(File(dir.imageCacheDir()))
|
||||||
messageList = mutableListOf("", "", "", "", "")
|
messageList = mutableListOf("", "", "", "", "")
|
||||||
releaseWakeLock()
|
releaseWakeLock()
|
||||||
@ -336,12 +338,4 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
mNotificationManager.notify(notificationId, getNotification())
|
mNotificationManager.notify(notificationId, getNotification())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendTrackBroadcast(action: String, track: TrackDetails) {
|
|
||||||
val intent = Intent().apply {
|
|
||||||
setAction(action)
|
|
||||||
putExtra("track", track)
|
|
||||||
}
|
|
||||||
this@ForegroundService.sendBroadcast(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.shabinder.spotiflyer.service
|
||||||
|
|
||||||
|
import com.shabinder.common.models.DownloadStatus
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class TrackStatusFlowMap(
|
||||||
|
val statusFlow: MutableSharedFlow<HashMap<String,DownloadStatus>>,
|
||||||
|
private val scope: CoroutineScope
|
||||||
|
): HashMap<String,DownloadStatus>() {
|
||||||
|
override fun put(key: String, value: DownloadStatus): DownloadStatus? {
|
||||||
|
val res = super.put(key, value)
|
||||||
|
scope.launch { statusFlow.emit(this@TrackStatusFlowMap) }
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,22 @@
|
|||||||
package com.shabinder.common.di.worker
|
package com.shabinder.spotiflyer.service
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import android.util.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleaning All Residual Files except Mp3 Files
|
* Cleaning All Residual Files except Mp3 Files
|
||||||
**/
|
**/
|
||||||
fun cleanFiles(dir: File, logger: Kermit) {
|
fun cleanFiles(dir: File) {
|
||||||
try {
|
try {
|
||||||
logger.d("File Cleaning") { "Starting Cleaning in ${dir.path} " }
|
Log.d("File Cleaning","Starting Cleaning in ${dir.path} ")
|
||||||
val fList = dir.listFiles()
|
val fList = dir.listFiles()
|
||||||
fList?.let {
|
fList?.let {
|
||||||
for (file in fList) {
|
for (file in fList) {
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
cleanFiles(file, logger)
|
cleanFiles(file)
|
||||||
} else if (file.isFile) {
|
} else if (file.isFile) {
|
||||||
if (file.path.toString().substringAfterLast(".") != "mp3") {
|
if (file.path.toString().substringAfterLast(".") != "mp3") {
|
||||||
logger.d("Files Cleaning") { "Cleaning ${file.path}" }
|
Log.d("Files Cleaning","Cleaning ${file.path}")
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,3 +24,4 @@ fun cleanFiles(dir: File, logger: Kermit) {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) { e.printStackTrace() }
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
}
|
}
|
||||||
|
|
@ -17,12 +17,32 @@
|
|||||||
package com.shabinder.common.uikit
|
package com.shabinder.common.uikit
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.ExtendedFloatingActionButton
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@ -53,6 +73,7 @@ fun SpotiFlyerListContent(
|
|||||||
component.onBackPressed()
|
component.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
val result = model.queryResult
|
val result = model.queryResult
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
|
@ -14,7 +14,7 @@ actual interface PlatformActions {
|
|||||||
|
|
||||||
fun addToLibrary(path: String)
|
fun addToLibrary(path: String)
|
||||||
|
|
||||||
fun sendTracksToService(array: ArrayList<TrackDetails>)
|
fun sendTracksToService(array: List<TrackDetails>)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual val StubPlatformActions = object : PlatformActions {
|
actual val StubPlatformActions = object : PlatformActions {
|
||||||
@ -24,5 +24,5 @@ actual val StubPlatformActions = object : PlatformActions {
|
|||||||
|
|
||||||
override fun addToLibrary(path: String) {}
|
override fun addToLibrary(path: String) {}
|
||||||
|
|
||||||
override fun sendTracksToService(array: ArrayList<TrackDetails>) {}
|
override fun sendTracksToService(array: List<TrackDetails>) {}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,9 @@ import com.shabinder.common.di.utils.removeIllegalChars
|
|||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import io.ktor.client.request.HttpRequestBuilder
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.client.statement.HttpStatement
|
import io.ktor.http.*
|
||||||
import io.ktor.http.contentLength
|
|
||||||
import io.ktor.http.isSuccess
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -105,7 +103,7 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
|||||||
var offset = 0
|
var offset = 0
|
||||||
do {
|
do {
|
||||||
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb
|
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb
|
||||||
val currentRead = response.content.readAvailable(data, offset, 250000)
|
val currentRead = response.content.readAvailable(data, offset, 2_50_000)
|
||||||
offset += currentRead
|
offset += currentRead
|
||||||
val progress = (offset * 100f / data.size).roundToInt()
|
val progress = (offset * 100f / data.size).roundToInt()
|
||||||
emit(DownloadResult.Progress(progress))
|
emit(DownloadResult.Progress(progress))
|
||||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.list.integration
|
|||||||
|
|
||||||
import co.touchlab.stately.ensureNeverFrozen
|
import co.touchlab.stately.ensureNeverFrozen
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
import com.arkivanov.decompose.lifecycle.doOnResume
|
||||||
import com.arkivanov.decompose.value.Value
|
import com.arkivanov.decompose.value.Value
|
||||||
import com.shabinder.common.caching.Cache
|
import com.shabinder.common.caching.Cache
|
||||||
import com.shabinder.common.di.Picture
|
import com.shabinder.common.di.Picture
|
||||||
@ -38,6 +39,9 @@ internal class SpotiFlyerListImpl(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
instanceKeeper.ensureNeverFrozen()
|
instanceKeeper.ensureNeverFrozen()
|
||||||
|
lifecycle.doOnResume {
|
||||||
|
onRefreshTracksStatuses()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val store =
|
private val store =
|
||||||
|
@ -60,7 +60,7 @@ internal class SpotiFlyerListStoreProvider(
|
|||||||
data class UpdateTrackList(val list: List<TrackDetails>) : Result()
|
data class UpdateTrackList(val list: List<TrackDetails>) : Result()
|
||||||
data class UpdateTrackItem(val item: TrackDetails) : Result()
|
data class UpdateTrackItem(val item: TrackDetails) : Result()
|
||||||
data class ErrorOccurred(val error: Throwable) : Result()
|
data class ErrorOccurred(val error: Throwable) : Result()
|
||||||
data class AskForDonation(val isAllowed: Boolean) : Result()
|
data class AskForSupport(val isAllowed: Boolean) : Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
|
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
|
||||||
@ -73,7 +73,7 @@ internal class SpotiFlyerListStoreProvider(
|
|||||||
logger.d(message = "Database List Last ID: $it", tag = "Database Last ID")
|
logger.d(message = "Database List Last ID: $it", tag = "Database Last ID")
|
||||||
val offset = dir.getDonationOffset
|
val offset = dir.getDonationOffset
|
||||||
dispatch(
|
dispatch(
|
||||||
Result.AskForDonation(
|
Result.AskForSupport(
|
||||||
// Every 3rd Interval or After some offset
|
// Every 3rd Interval or After some offset
|
||||||
isAllowed = offset < 4 && (it % offset == 0L)
|
isAllowed = offset < 4 && (it % offset == 0L)
|
||||||
)
|
)
|
||||||
@ -81,7 +81,7 @@ internal class SpotiFlyerListStoreProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadProgressFlow.collect { map ->
|
downloadProgressFlow.collect { map ->
|
||||||
logger.d(map.size.toString(), "ListStore: flow Updated")
|
// logger.d(map.size.toString(), "ListStore: flow Updated")
|
||||||
val updatedTrackList = getState().trackList.updateTracksStatuses(map)
|
val updatedTrackList = getState().trackList.updateTracksStatuses(map)
|
||||||
if (updatedTrackList.isNotEmpty()) dispatch(Result.UpdateTrackList(updatedTrackList))
|
if (updatedTrackList.isNotEmpty()) dispatch(Result.UpdateTrackList(updatedTrackList))
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ internal class SpotiFlyerListStoreProvider(
|
|||||||
is Result.UpdateTrackList -> copy(trackList = result.list)
|
is Result.UpdateTrackList -> copy(trackList = result.list)
|
||||||
is Result.UpdateTrackItem -> updateTrackItem(result.item)
|
is Result.UpdateTrackItem -> updateTrackItem(result.item)
|
||||||
is Result.ErrorOccurred -> copy(errorOccurred = result.error)
|
is Result.ErrorOccurred -> copy(errorOccurred = result.error)
|
||||||
is Result.AskForDonation -> copy(askForDonation = result.isAllowed)
|
is Result.AskForSupport -> copy(askForDonation = result.isAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun State.updateTrackItem(item: TrackDetails): State {
|
private fun State.updateTrackItem(item: TrackDetails): State {
|
||||||
|
Loading…
Reference in New Issue
Block a user