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)
|
||||
|
||||
// Extras
|
||||
Extras.Android.apply {
|
||||
with(Extras.Android) {
|
||||
implementation(Acra.notification)
|
||||
implementation(Acra.http)
|
||||
implementation(appUpdator)
|
||||
implementation(matomo)
|
||||
}
|
||||
|
||||
implementation(Extras.kermit)
|
||||
//implementation("com.jakewharton.timber:timber:4.7.1")
|
||||
implementation("dev.icerock.moko:parcelize:0.7.0")
|
||||
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
|
||||
|
@ -72,6 +72,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name="com.shabinder.common.di.worker.ForegroundService"/>
|
||||
<service android:name=".service.ForegroundService"/>
|
||||
</application>
|
||||
</manifest>
|
@ -17,15 +17,16 @@
|
||||
package com.shabinder.spotiflyer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
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.statusBarsPadding
|
||||
import com.shabinder.common.di.*
|
||||
import com.shabinder.common.di.worker.ForegroundService
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.PlatformActions
|
||||
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.methods
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
|
||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import com.shabinder.common.uikit.*
|
||||
import com.shabinder.spotiflyer.service.ForegroundService
|
||||
import com.shabinder.spotiflyer.ui.AnalyticsDialog
|
||||
import com.shabinder.spotiflyer.ui.NetworkDialog
|
||||
import com.shabinder.spotiflyer.ui.PermissionDialog
|
||||
@ -82,12 +82,16 @@ class MainActivity : ComponentActivity() {
|
||||
private val callBacks: SpotiFlyerRootCallBacks get() = root.callBacks
|
||||
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
|
||||
private var permissionGranted = mutableStateOf(true)
|
||||
private lateinit var updateUIReceiver: BroadcastReceiver
|
||||
private lateinit var queryReceiver: BroadcastReceiver
|
||||
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
|
||||
private val tracker get() = (application as App).tracker
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// 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)
|
||||
}
|
||||
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
|
||||
private fun isInternetAvailableState(): State<Boolean?> {
|
||||
return internetAvailability.observeAsState()
|
||||
@ -206,12 +264,9 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
override fun sendTracksToService(array: ArrayList<TrackDetails>) {
|
||||
for (list in array.chunked(50)) {
|
||||
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
|
||||
serviceIntent.putParcelableArrayListExtra("object", list as ArrayList)
|
||||
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
|
||||
}
|
||||
override fun sendTracksToService(array: List<TrackDetails>) {
|
||||
if (foregroundService == null) initForegroundService()
|
||||
foregroundService?.downloadAllTracks(array)
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,10 +351,16 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
|
||||
private fun queryActiveTracks() {
|
||||
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java).apply {
|
||||
action = "query"
|
||||
lifecycleScope.launch {
|
||||
foregroundService?.trackStatusFlowMap?.let { tracksStatus ->
|
||||
trackStatusFlow.emit(tracksStatus)
|
||||
}
|
||||
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
queryActiveTracks()
|
||||
}
|
||||
|
||||
@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?) {
|
||||
super.onNewIntent(intent)
|
||||
handleIntentFromExternalActivity(intent)
|
||||
@ -455,6 +442,11 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unbindService()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val disableDozeCode = 1223
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* 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.app.DownloadManager
|
||||
@ -26,6 +26,7 @@ import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
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.models.DownloadResult
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.Status
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
@ -68,7 +70,8 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = serviceJob + Dispatchers.IO
|
||||
|
||||
private val allTracksStatus = hashMapOf<String, DownloadStatus>()
|
||||
val trackStatusFlowMap = TrackStatusFlowMap(MutableSharedFlow(replay = 1),this)
|
||||
|
||||
private var messageList = mutableListOf("", "", "", "", "")
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private var isServiceStarted = false
|
||||
@ -80,7 +83,16 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
private val logger: Kermit 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")
|
||||
override fun onCreate() {
|
||||
@ -110,31 +122,13 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
"query" -> {
|
||||
val response = Intent().apply {
|
||||
action = "query_result"
|
||||
synchronized(allTracksStatus) {
|
||||
putExtra("tracks", allTracksStatus)
|
||||
synchronized(trackStatusFlowMap) {
|
||||
putExtra("tracks", trackStatusFlowMap)
|
||||
}
|
||||
}
|
||||
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 :
|
||||
return if (isServiceStarted) {
|
||||
@ -156,8 +150,16 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
/**
|
||||
* 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 {
|
||||
trackStatusFlowMap[it.title] = DownloadStatus.Queued
|
||||
launch(Dispatchers.IO) {
|
||||
downloadService.execute {
|
||||
fetcher.findMp3DownloadLink(it).fold(
|
||||
@ -165,10 +167,9 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
enqueueDownload(url, it)
|
||||
},
|
||||
failure = { _ ->
|
||||
sendTrackBroadcast(Status.FAILED.name, it)
|
||||
failed++
|
||||
updateNotification()
|
||||
allTracksStatus[it.title] = DownloadStatus.Failed
|
||||
trackStatusFlowMap[it.title] = DownloadStatus.Failed
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -180,24 +181,20 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
// Initiating Download
|
||||
addToNotification("Downloading ${track.title}")
|
||||
logger.d(tag) { "${track.title} Download Started" }
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloading()
|
||||
sendTrackBroadcast(Status.DOWNLOADING.name, track)
|
||||
trackStatusFlowMap[track.title] = DownloadStatus.Downloading()
|
||||
|
||||
// Enqueueing Download
|
||||
downloadFile(url).collect {
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
launch {
|
||||
logger.d(tag) { it.message }
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
failed++
|
||||
updateNotification()
|
||||
sendTrackBroadcast(Status.FAILED.name, track)
|
||||
}
|
||||
}
|
||||
|
||||
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} %" }
|
||||
|
||||
val intent = Intent().apply {
|
||||
@ -209,21 +206,25 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
}
|
||||
|
||||
is DownloadResult.Success -> {
|
||||
coroutineScope {
|
||||
try {
|
||||
// Save File and Embed Metadata
|
||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) {} }
|
||||
allTracksStatus[track.title] = DownloadStatus.Converting
|
||||
sendTrackBroadcast("Converting", track)
|
||||
|
||||
// Send Converting Status
|
||||
trackStatusFlowMap[track.title] = DownloadStatus.Converting
|
||||
addToNotification("Processing ${track.title}")
|
||||
|
||||
// All Processing Completed for this Track
|
||||
job.invokeOnCompletion {
|
||||
converted++
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloaded
|
||||
sendTrackBroadcast(Status.COMPLETED.name, track)
|
||||
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++
|
||||
@ -233,6 +234,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseWakeLock() {
|
||||
logger.d(tag) { "Releasing Wake Lock" }
|
||||
@ -270,7 +272,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||
downloadService.close()
|
||||
updateNotification()
|
||||
cleanFiles(File(dir.defaultDir()), logger)
|
||||
cleanFiles(File(dir.defaultDir()))
|
||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
||||
messageList = mutableListOf("", "", "", "", "")
|
||||
releaseWakeLock()
|
||||
@ -336,12 +338,4 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
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
|
||||
|
||||
/**
|
||||
* Cleaning All Residual Files except Mp3 Files
|
||||
**/
|
||||
fun cleanFiles(dir: File, logger: Kermit) {
|
||||
fun cleanFiles(dir: File) {
|
||||
try {
|
||||
logger.d("File Cleaning") { "Starting Cleaning in ${dir.path} " }
|
||||
Log.d("File Cleaning","Starting Cleaning in ${dir.path} ")
|
||||
val fList = dir.listFiles()
|
||||
fList?.let {
|
||||
for (file in fList) {
|
||||
if (file.isDirectory) {
|
||||
cleanFiles(file, logger)
|
||||
cleanFiles(file)
|
||||
} else if (file.isFile) {
|
||||
if (file.path.toString().substringAfterLast(".") != "mp3") {
|
||||
logger.d("Files Cleaning") { "Cleaning ${file.path}" }
|
||||
Log.d("Files Cleaning","Cleaning ${file.path}")
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
@ -24,3 +24,4 @@ fun cleanFiles(dir: File, logger: Kermit) {
|
||||
}
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
|
@ -17,12 +17,32 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
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.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
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.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -53,6 +73,7 @@ fun SpotiFlyerListContent(
|
||||
component.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
val result = model.queryResult
|
||||
if (result == null) {
|
||||
|
@ -14,7 +14,7 @@ actual interface PlatformActions {
|
||||
|
||||
fun addToLibrary(path: String)
|
||||
|
||||
fun sendTracksToService(array: ArrayList<TrackDetails>)
|
||||
fun sendTracksToService(array: List<TrackDetails>)
|
||||
}
|
||||
|
||||
actual val StubPlatformActions = object : PlatformActions {
|
||||
@ -24,5 +24,5 @@ actual val StubPlatformActions = object : PlatformActions {
|
||||
|
||||
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.TrackDetails
|
||||
import com.shabinder.database.Database
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.HttpStatement
|
||||
import io.ktor.http.contentLength
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlin.math.roundToInt
|
||||
@ -105,7 +103,7 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
||||
var offset = 0
|
||||
do {
|
||||
// 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
|
||||
val progress = (offset * 100f / data.size).roundToInt()
|
||||
emit(DownloadResult.Progress(progress))
|
||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.list.integration
|
||||
|
||||
import co.touchlab.stately.ensureNeverFrozen
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.lifecycle.doOnResume
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.shabinder.common.caching.Cache
|
||||
import com.shabinder.common.di.Picture
|
||||
@ -38,6 +39,9 @@ internal class SpotiFlyerListImpl(
|
||||
|
||||
init {
|
||||
instanceKeeper.ensureNeverFrozen()
|
||||
lifecycle.doOnResume {
|
||||
onRefreshTracksStatuses()
|
||||
}
|
||||
}
|
||||
|
||||
private val store =
|
||||
|
@ -60,7 +60,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
data class UpdateTrackList(val list: List<TrackDetails>) : Result()
|
||||
data class UpdateTrackItem(val item: TrackDetails) : 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>() {
|
||||
@ -73,7 +73,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
logger.d(message = "Database List Last ID: $it", tag = "Database Last ID")
|
||||
val offset = dir.getDonationOffset
|
||||
dispatch(
|
||||
Result.AskForDonation(
|
||||
Result.AskForSupport(
|
||||
// Every 3rd Interval or After some offset
|
||||
isAllowed = offset < 4 && (it % offset == 0L)
|
||||
)
|
||||
@ -81,7 +81,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
}
|
||||
|
||||
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)
|
||||
if (updatedTrackList.isNotEmpty()) dispatch(Result.UpdateTrackList(updatedTrackList))
|
||||
}
|
||||
@ -131,7 +131,7 @@ internal class SpotiFlyerListStoreProvider(
|
||||
is Result.UpdateTrackList -> copy(trackList = result.list)
|
||||
is Result.UpdateTrackItem -> updateTrackItem(result.item)
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user