mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
Performance, Background Crashes and Notification Cancellation Fixes
This commit is contained in:
parent
758fe62254
commit
8e32d4469b
@ -126,6 +126,7 @@ dependencies {
|
|||||||
with(Versions.androidxLifecycle) {
|
with(Versions.androidxLifecycle) {
|
||||||
implementation("androidx.lifecycle:lifecycle-service:$this")
|
implementation("androidx.lifecycle:lifecycle-service:$this")
|
||||||
implementation("androidx.lifecycle:lifecycle-common-java8:$this")
|
implementation("androidx.lifecycle:lifecycle-common-java8:$this")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$this")
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(Extras.kermit)
|
implementation(Extras.kermit)
|
||||||
|
@ -40,7 +40,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
import com.arkivanov.decompose.defaultComponentContext
|
import com.arkivanov.decompose.defaultComponentContext
|
||||||
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
|
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
|
||||||
@ -71,10 +73,12 @@ 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
|
||||||
import com.shabinder.spotiflyer.utils.*
|
import com.shabinder.spotiflyer.utils.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.conflate
|
import kotlinx.coroutines.flow.conflate
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
@ -204,8 +208,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
foregroundService = binder.service
|
foregroundService = binder.service
|
||||||
isServiceBound = true
|
isServiceBound = true
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
foregroundService?.trackStatusFlowMap?.statusFlow?.let {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
trackStatusFlow.emitAll(it.conflate())
|
foregroundService?.trackStatusFlowMap?.statusFlow?.let {
|
||||||
|
trackStatusFlow.emitAll(it.conflate().flowOn(Dispatchers.Default))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import android.app.Notification
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
@ -44,8 +43,6 @@ import com.shabinder.common.models.event.coroutines.failure
|
|||||||
import com.shabinder.common.providers.FetchPlatformQueryResult
|
import com.shabinder.common.providers.FetchPlatformQueryResult
|
||||||
import com.shabinder.common.translations.Strings
|
import com.shabinder.common.translations.Strings
|
||||||
import com.shabinder.spotiflyer.R
|
import com.shabinder.spotiflyer.R
|
||||||
import com.shabinder.spotiflyer.utils.autoclear.autoClear
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -57,12 +54,11 @@ import java.io.File
|
|||||||
class ForegroundService : LifecycleService() {
|
class ForegroundService : LifecycleService() {
|
||||||
|
|
||||||
private lateinit var downloadService: ParallelExecutor
|
private lateinit var downloadService: ParallelExecutor
|
||||||
val trackStatusFlowMap by autoClear {
|
val trackStatusFlowMap = TrackStatusFlowMap(
|
||||||
TrackStatusFlowMap(
|
MutableSharedFlow(replay = 1),
|
||||||
MutableSharedFlow(replay = 1),
|
lifecycleScope
|
||||||
lifecycleScope
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
private val fetcher: FetchPlatformQueryResult by inject()
|
private val fetcher: FetchPlatformQueryResult by inject()
|
||||||
private val logger: Kermit by inject()
|
private val logger: Kermit by inject()
|
||||||
private val dir: FileManager by inject()
|
private val dir: FileManager by inject()
|
||||||
@ -73,7 +69,12 @@ class ForegroundService : LifecycleService() {
|
|||||||
private var isServiceStarted = false
|
private var isServiceStarted = false
|
||||||
private val cancelIntent: PendingIntent by lazy {
|
private val cancelIntent: PendingIntent by lazy {
|
||||||
val intent = Intent(this, ForegroundService::class.java).apply { action = "kill" }
|
val intent = Intent(this, ForegroundService::class.java).apply { action = "kill" }
|
||||||
PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT)
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
PendingIntent.getService(this, 0, intent, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Variables Holding Download State */
|
/* Variables Holding Download State */
|
||||||
@ -98,6 +99,7 @@ class ForegroundService : LifecycleService() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
downloadService = ParallelExecutor(Dispatchers.IO)
|
downloadService = ParallelExecutor(Dispatchers.IO)
|
||||||
|
trackStatusFlowMap.scope = lifecycleScope
|
||||||
createNotificationChannel(CHANNEL_ID, "Downloader Service")
|
createNotificationChannel(CHANNEL_ID, "Downloader Service")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,12 +273,16 @@ class ForegroundService : LifecycleService() {
|
|||||||
private fun killService() {
|
private fun killService() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
logger.d(TAG) { "Killing Self" }
|
logger.d(TAG) { "Killing Self" }
|
||||||
|
resetVar()
|
||||||
messageList = messageList.getEmpty().apply {
|
messageList = messageList.getEmpty().apply {
|
||||||
set(index = 0, Message(Strings.cleaningAndExiting(), DownloadStatus.NotDownloaded))
|
set(index = 0, Message(Strings.cleaningAndExiting(), DownloadStatus.NotDownloaded))
|
||||||
}
|
}
|
||||||
downloadService.close()
|
downloadService.close()
|
||||||
updateNotification()
|
updateNotification()
|
||||||
trackStatusFlowMap.clear()
|
trackStatusFlowMap.apply {
|
||||||
|
clear()
|
||||||
|
scope = null
|
||||||
|
}
|
||||||
cleanFiles(File(dir.defaultDir()))
|
cleanFiles(File(dir.defaultDir()))
|
||||||
// cleanFiles(File(dir.imageCacheDir()))
|
// cleanFiles(File(dir.imageCacheDir()))
|
||||||
messageList = messageList.getEmpty()
|
messageList = messageList.getEmpty()
|
||||||
@ -290,6 +296,13 @@ class ForegroundService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resetVar() {
|
||||||
|
total = 0
|
||||||
|
downloaded = 0
|
||||||
|
failed = 0
|
||||||
|
converted = 0
|
||||||
|
}
|
||||||
|
|
||||||
private fun createNotification(): Notification =
|
private fun createNotification(): Notification =
|
||||||
NotificationCompat.Builder(this, CHANNEL_ID).run {
|
NotificationCompat.Builder(this, CHANNEL_ID).run {
|
||||||
setSmallIcon(R.drawable.ic_download_arrow)
|
setSmallIcon(R.drawable.ic_download_arrow)
|
||||||
@ -323,6 +336,7 @@ class ForegroundService : LifecycleService() {
|
|||||||
updateNotification()
|
updateNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
private fun updateProgressInNotification(message: Message) {
|
private fun updateProgressInNotification(message: Message) {
|
||||||
synchronized(messageList) {
|
synchronized(messageList) {
|
||||||
val index = messageList.indexOfFirst { it.title == message.title }
|
val index = messageList.indexOfFirst { it.title == message.title }
|
||||||
@ -331,10 +345,16 @@ class ForegroundService : LifecycleService() {
|
|||||||
updateNotification()
|
updateNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Notification only if Service is Still Active
|
||||||
private fun updateNotification() {
|
private fun updateNotification() {
|
||||||
val mNotificationManager: NotificationManager =
|
if (!downloadService.isClosed.value) {
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val mNotificationManager: NotificationManager =
|
||||||
mNotificationManager.notify(NOTIFICATION_ID, createNotification())
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, createNotification())
|
||||||
|
} else {
|
||||||
|
// Service is Inactive so clear status
|
||||||
|
resetVar()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -7,12 +7,12 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class TrackStatusFlowMap(
|
class TrackStatusFlowMap(
|
||||||
val statusFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>,
|
val statusFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>,
|
||||||
private val scope: CoroutineScope
|
var scope: CoroutineScope?
|
||||||
) : HashMap<String, DownloadStatus>() {
|
) : HashMap<String, DownloadStatus>() {
|
||||||
override fun put(key: String, value: DownloadStatus): DownloadStatus? {
|
override fun put(key: String, value: DownloadStatus): DownloadStatus? {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val res = super.put(key, value)
|
val res = super.put(key, value)
|
||||||
emitValue()
|
emitValue(this)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,13 +25,13 @@ class TrackStatusFlowMap(
|
|||||||
super.put(title,DownloadStatus.NotDownloaded)
|
super.put(title,DownloadStatus.NotDownloaded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitValue()
|
emitValue(this)
|
||||||
//super.clear()
|
super.clear()
|
||||||
//emitValue()
|
emitValue(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitValue() {
|
private fun emitValue(map: HashMap<String,DownloadStatus>) {
|
||||||
scope.launch { statusFlow.emit(this@TrackStatusFlowMap) }
|
scope?.launch { statusFlow.emit(map) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ import com.shabinder.common.uikit.screens.splash.Splash
|
|||||||
import com.shabinder.common.uikit.screens.splash.SplashState
|
import com.shabinder.common.uikit.screens.splash.SplashState
|
||||||
import com.shabinder.common.uikit.utils.verticalGradientScrim
|
import com.shabinder.common.uikit.utils.verticalGradientScrim
|
||||||
|
|
||||||
// To Not Show Splash Again After Configuration Change in Android
|
// Splash Status
|
||||||
private var isSplashShown = SplashState.Show
|
private var isSplashShown = SplashState.Show
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -10,7 +10,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":common:data-models"))
|
implementation(project(":common:data-models"))
|
||||||
implementation(project(":common:database"))
|
implementation(project(":common:database"))
|
||||||
implementation("org.jetbrains.kotlinx:atomicfu:0.16.2")
|
api("org.jetbrains.kotlinx:atomicfu:0.16.2")
|
||||||
api(MultiPlatformSettings.dep)
|
api(MultiPlatformSettings.dep)
|
||||||
implementation(MVIKotlin.rx)
|
implementation(MVIKotlin.rx)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,8 @@ class ParallelExecutor(
|
|||||||
|
|
||||||
private var service: Job = SupervisorJob()
|
private var service: Job = SupervisorJob()
|
||||||
override val coroutineContext get() = context + service
|
override val coroutineContext get() = context + service
|
||||||
private var isClosed = atomic(false)
|
var isClosed = atomic(false)
|
||||||
|
private set
|
||||||
private var killQueue = Channel<Unit>(Channel.UNLIMITED)
|
private var killQueue = Channel<Unit>(Channel.UNLIMITED)
|
||||||
private var operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
|
private var operationQueue = Channel<Operation<*>>(Channel.RENDEZVOUS)
|
||||||
private var concurrentOperationLimit = atomic(concurrentOperationLimit)
|
private var concurrentOperationLimit = atomic(concurrentOperationLimit)
|
||||||
@ -132,6 +133,7 @@ class ParallelExecutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
|
// TODO This launches all coroutines in advance even if they're never needed. Find a lazy way to do this.
|
||||||
|
@Suppress("unused")
|
||||||
fun setConcurrentOperationLimit(limit: Int) {
|
fun setConcurrentOperationLimit(limit: Int) {
|
||||||
require(limit >= 1) { "'limit' must be greater than zero: $limit" }
|
require(limit >= 1) { "'limit' must be greater than zero: $limit" }
|
||||||
require(limit < 1_000_000) { "Don't use a very high limit because it will cause a lot of coroutines to be started eagerly: $limit" }
|
require(limit < 1_000_000) { "Don't use a very high limit because it will cause a lot of coroutines to be started eagerly: $limit" }
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package com.shabinder.common.utils
|
package com.shabinder.common.utils
|
||||||
|
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
|
import com.shabinder.common.models.dispatcherIO
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@ -23,3 +27,12 @@ fun StringBuilder.appendPadded(data: Any?) {
|
|||||||
fun StringBuilder.appendPadded(header: Any?, data: Any?) {
|
fun StringBuilder.appendPadded(header: Any?, data: Any?) {
|
||||||
appendLine().append(header).appendLine(data).appendLine()
|
appendLine().append(header).appendLine(data).appendLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <T> runOnMain(block: suspend CoroutineScope.() -> T): T =
|
||||||
|
withContext(Dispatchers.Main, block)
|
||||||
|
|
||||||
|
suspend fun <T> runOnIO(block: suspend CoroutineScope.() -> T): T =
|
||||||
|
withContext(dispatcherIO, block)
|
||||||
|
|
||||||
|
suspend fun <T> runOnDefault(block: suspend CoroutineScope.() -> T): T =
|
||||||
|
withContext(Dispatchers.Default, block)
|
||||||
|
@ -50,7 +50,7 @@ internal class SpotiFlyerListImpl(
|
|||||||
|
|
||||||
private val cache = Cache.Builder
|
private val cache = Cache.Builder
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.maximumCacheSize(75)
|
.maximumCacheSize(30)
|
||||||
.build<String, Picture>()
|
.build<String, Picture>()
|
||||||
|
|
||||||
override val model: Value<State> = store.asValue()
|
override val model: Value<State> = store.asValue()
|
||||||
|
@ -23,9 +23,16 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
|||||||
import com.shabinder.common.list.SpotiFlyerList
|
import com.shabinder.common.list.SpotiFlyerList
|
||||||
import com.shabinder.common.list.SpotiFlyerList.State
|
import com.shabinder.common.list.SpotiFlyerList.State
|
||||||
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
||||||
import com.shabinder.common.models.*
|
import com.shabinder.common.models.Actions
|
||||||
|
import com.shabinder.common.models.DownloadStatus
|
||||||
|
import com.shabinder.common.models.PlatformQueryResult
|
||||||
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.providers.downloadTracks
|
import com.shabinder.common.providers.downloadTracks
|
||||||
|
import com.shabinder.common.utils.runOnDefault
|
||||||
|
import com.shabinder.common.utils.runOnMain
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependencies) :
|
internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependencies) :
|
||||||
SpotiFlyerList.Dependencies by dependencies {
|
SpotiFlyerList.Dependencies by dependencies {
|
||||||
@ -41,7 +48,11 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
private sealed class Result {
|
private sealed class Result {
|
||||||
data class ResultFetched(val result: PlatformQueryResult, val trackList: List<TrackDetails>) : Result()
|
data class ResultFetched(
|
||||||
|
val result: PlatformQueryResult,
|
||||||
|
val trackList: List<TrackDetails>
|
||||||
|
) : Result()
|
||||||
|
|
||||||
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()
|
||||||
@ -52,79 +63,102 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
|
|||||||
|
|
||||||
override suspend fun executeAction(action: Unit, getState: () -> State) {
|
override suspend fun executeAction(action: Unit, getState: () -> State) {
|
||||||
executeIntent(Intent.SearchLink(link), getState)
|
executeIntent(Intent.SearchLink(link), getState)
|
||||||
|
runOnDefault {
|
||||||
|
fileManager.db?.downloadRecordDatabaseQueries?.getLastInsertId()
|
||||||
|
?.executeAsOneOrNull()?.also {
|
||||||
|
// See if It's Time we can request for support for maintaining this project or not
|
||||||
|
fetchQuery.logger.d(
|
||||||
|
message = { "Database List Last ID: $it" },
|
||||||
|
tag = "Database Last ID"
|
||||||
|
)
|
||||||
|
val offset = preferenceManager.getDonationOffset
|
||||||
|
dispatchOnMain(
|
||||||
|
Result.AskForSupport(
|
||||||
|
// Every 3rd Interval or After some offset
|
||||||
|
isAllowed = offset < 4 && (it % offset == 0L)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fileManager.db?.downloadRecordDatabaseQueries?.getLastInsertId()?.executeAsOneOrNull()?.also {
|
downloadProgressFlow.collect { map ->
|
||||||
// See if It's Time we can request for support for maintaining this project or not
|
// logger.d(map.size.toString(), "ListStore: flow Updated")
|
||||||
fetchQuery.logger.d(message = { "Database List Last ID: $it" }, tag = "Database Last ID")
|
getState().trackList.updateTracksStatuses(map).also {
|
||||||
val offset = preferenceManager.getDonationOffset
|
if (it.isNotEmpty())
|
||||||
dispatch(
|
dispatchOnMain(Result.UpdateTrackList(it))
|
||||||
Result.AskForSupport(
|
}
|
||||||
// Every 3rd Interval or After some offset
|
}
|
||||||
isAllowed = offset < 4 && (it % offset == 0L)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProgressFlow.collect { map ->
|
|
||||||
// logger.d(map.size.toString(), "ListStore: flow Updated")
|
|
||||||
val updatedTrackList = getState().trackList.updateTracksStatuses(map)
|
|
||||||
if (updatedTrackList.isNotEmpty()) dispatch(Result.UpdateTrackList(updatedTrackList))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
|
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
|
||||||
when (intent) {
|
withContext(Dispatchers.Default) {
|
||||||
is Intent.SearchLink -> {
|
when (intent) {
|
||||||
val resp = fetchQuery.query(link)
|
is Intent.SearchLink -> {
|
||||||
resp.fold(
|
val resp = fetchQuery.query(link)
|
||||||
success = { result ->
|
resp.fold(
|
||||||
result.trackList = result.trackList.toMutableList()
|
success = { result ->
|
||||||
dispatch(
|
result.trackList =
|
||||||
(Result.ResultFetched(
|
result.trackList.toMutableList()
|
||||||
result,
|
.updateTracksStatuses(
|
||||||
result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })
|
downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() }
|
||||||
))
|
)
|
||||||
)
|
|
||||||
executeIntent(Intent.RefreshTracksStatuses, getState)
|
|
||||||
},
|
|
||||||
failure = {
|
|
||||||
dispatch(Result.ErrorOccurred(it))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Intent.StartDownloadAll -> {
|
dispatchOnMain(
|
||||||
val list = intent.trackList.map {
|
(Result.ResultFetched(
|
||||||
if (it.downloaded is DownloadStatus.NotDownloaded || it.downloaded is DownloadStatus.Failed)
|
result,
|
||||||
return@map it.copy(downloaded = DownloadStatus.Queued)
|
result.trackList
|
||||||
it
|
))
|
||||||
}
|
)
|
||||||
dispatch(
|
executeIntent(Intent.RefreshTracksStatuses, getState)
|
||||||
Result.UpdateTrackList(
|
},
|
||||||
list.updateTracksStatuses(
|
failure = {
|
||||||
downloadProgressFlow.replayCache.getOrElse(
|
dispatchOnMain(Result.ErrorOccurred(it))
|
||||||
0
|
}
|
||||||
) { hashMapOf() })
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
val finalList = intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
|
is Intent.StartDownloadAll -> {
|
||||||
if (finalList.isEmpty()) Actions.instance.showPopUpMessage("All Songs are Processed")
|
val list = intent.trackList.map {
|
||||||
else downloadTracks(finalList, fetchQuery, fileManager)
|
if (it.downloaded is DownloadStatus.NotDownloaded || it.downloaded is DownloadStatus.Failed)
|
||||||
|
return@map it.copy(downloaded = DownloadStatus.Queued)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
dispatchOnMain(
|
||||||
|
Result.UpdateTrackList(
|
||||||
|
list.updateTracksStatuses(
|
||||||
|
downloadProgressFlow.replayCache.getOrElse(
|
||||||
|
0
|
||||||
|
) { hashMapOf() })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val finalList =
|
||||||
|
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
|
||||||
|
if (finalList.isEmpty()) Actions.instance.showPopUpMessage("All Songs are Processed")
|
||||||
|
else downloadTracks(finalList, fetchQuery, fileManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Intent.StartDownload -> {
|
||||||
|
dispatchOnMain(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
|
||||||
|
downloadTracks(listOf(intent.track), fetchQuery, fileManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Intent.RefreshTracksStatuses -> Actions.instance.queryActiveTracks()
|
||||||
}
|
}
|
||||||
is Intent.StartDownload -> {
|
|
||||||
dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
|
|
||||||
downloadTracks(listOf(intent.track), fetchQuery, fileManager)
|
|
||||||
}
|
|
||||||
is Intent.RefreshTracksStatuses -> Actions.instance.queryActiveTracks()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun dispatchOnMain(result: Result) = runOnMain { dispatch(result) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private object ReducerImpl : Reducer<State, Result> {
|
private object ReducerImpl : Reducer<State, Result> {
|
||||||
override fun State.reduce(result: Result): State =
|
override fun State.reduce(result: Result): State =
|
||||||
when (result) {
|
when (result) {
|
||||||
is Result.ResultFetched -> copy(queryResult = result.result, trackList = result.trackList, link = link)
|
is Result.ResultFetched -> copy(
|
||||||
|
queryResult = result.result,
|
||||||
|
trackList = result.trackList,
|
||||||
|
link = link
|
||||||
|
)
|
||||||
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)
|
||||||
@ -158,7 +192,6 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedList
|
return updatedList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ internal class SpotiFlyerMainImpl(
|
|||||||
|
|
||||||
private val cache = Cache.Builder
|
private val cache = Cache.Builder
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.maximumCacheSize(25)
|
.maximumCacheSize(20)
|
||||||
.build<String, Picture>()
|
.build<String, Picture>()
|
||||||
|
|
||||||
override val model: Value<State> = store.asValue()
|
override val model: Value<State> = store.asValue()
|
||||||
|
@ -25,6 +25,7 @@ import com.shabinder.common.main.SpotiFlyerMain.State
|
|||||||
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
|
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
|
||||||
import com.shabinder.common.models.DownloadRecord
|
import com.shabinder.common.models.DownloadRecord
|
||||||
import com.shabinder.common.models.Actions
|
import com.shabinder.common.models.Actions
|
||||||
|
import com.shabinder.common.utils.runOnMain
|
||||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
Loading…
Reference in New Issue
Block a user