diff --git a/android/src/main/java/com/shabinder/spotiflyer/App.kt b/android/src/main/java/com/shabinder/spotiflyer/App.kt index feb0d934..8d0e63fb 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/App.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/App.kt @@ -33,7 +33,7 @@ class App: Application(), KoinComponent { val loggingEnabled = true initKoin(loggingEnabled) { - androidLogger(Level.NONE) + androidLogger(Level.NONE) // No virtual method elapsedNow androidContext(this@App) modules(appModule(loggingEnabled)) } diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt index b4caef19..670311d0 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/SpotiFlyerListUi.kt @@ -35,6 +35,7 @@ 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.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -49,6 +50,7 @@ import com.shabinder.common.di.Picture import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails +import kotlinx.coroutines.delay @Composable fun SpotiFlyerListContent( @@ -61,11 +63,20 @@ fun SpotiFlyerListContent( // TODO Better Null Handling val result = model.queryResult if (result == null) { + /* Loading Bar */ Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator() Spacer(modifier.padding(8.dp)) Text("Loading..", style = appNameStyle, color = colorPrimary) } + LaunchedEffect(Unit) { + delay(350) + /*Handle if Any Exception Occurred*/ + model.errorOccurred?.let { + showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection") + component.onBackPressed() + } + } } else { LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt index a4b4bc6a..db8c5172 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt @@ -150,8 +150,9 @@ actual class Dir actual constructor( } }catch (e:Exception){ withContext(Dispatchers.Main){ - Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() + //Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show() } + if(songFile.exists()) songFile.delete() logger.e { "${songFile.absolutePath} could not be created" } } } diff --git a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt index bd682a91..f2600a0d 100644 --- a/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt +++ b/common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/worker/ForegroundService.kt @@ -43,7 +43,6 @@ import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.TrackDetails -import com.shabinder.downloader.YoutubeDownloader import com.shabinder.downloader.models.formats.Format import com.shabinder.common.models.Status import kotlinx.coroutines.CoroutineScope @@ -57,6 +56,7 @@ import java.io.File import kotlin.coroutines.CoroutineContext class ForegroundService : Service(), CoroutineScope { + private val tag: String = "Foreground Service" private val channelId = "ForegroundDownloaderService" private val notificationId = 101 @@ -64,26 +64,25 @@ class ForegroundService : Service(), CoroutineScope { private var converted = 0 // Total Files Converted private var downloaded = 0 // Total Files downloaded private var failed = 0 // Total Files failed - private val isFinished: Boolean - get() = converted + failed == total - private var isSingleDownload: Boolean = false + private val isFinished get() = converted + failed == total + private var isSingleDownload = false private lateinit var serviceJob: Job override val coroutineContext: CoroutineContext get() = serviceJob + Dispatchers.IO private val allTracksStatus = hashMapOf() + private var messageList = mutableListOf("", "", "", "", "") private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false - private var messageList = mutableListOf("", "", "", "", "") private lateinit var cancelIntent: PendingIntent + private lateinit var downloadManager: DownloadManager private lateinit var downloadService: ParallelExecutor + private val ytDownloader get() = fetcher.youtubeProvider.ytDownloader private val fetcher: FetchPlatformQueryResult by inject() private val logger: Kermit by inject() private val dir: Dir by inject() - private val ytDownloader: YoutubeDownloader - get() = fetcher.youtubeProvider.ytDownloader override fun onBind(intent: Intent): IBinder? = null diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt index 837a054b..c48b16b1 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt @@ -24,6 +24,7 @@ import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.YoutubeMp3 import com.shabinder.common.di.providers.YoutubeMusic import io.ktor.client.HttpClient +import io.ktor.client.features.HttpTimeout import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.features.logging.DEFAULT @@ -65,6 +66,12 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri install(JsonFeature) { this.serializer = serializer } + // Timeout + install(HttpTimeout) { + requestTimeoutMillis = 15000L + connectTimeoutMillis = 15000L + socketTimeoutMillis = 15000L + } if (enableNetworkLogs) { install(Logging) { logger = Logger.DEFAULT @@ -72,4 +79,5 @@ fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSeri } } } +/*Client Active Throughout App's Lifetime*/ val ktorHttpClient = HttpClient {} diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt index 96d27ad0..321d28f0 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt @@ -53,23 +53,27 @@ expect class Dir ( suspend fun downloadFile(url: String): Flow { return flow { - val client = createHttpClient() - val response = client.get(url).execute() - val data = ByteArray(response.contentLength()!!.toInt()) - 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) - offset += currentRead - val progress = (offset * 100f / data.size).roundToInt() - emit(DownloadResult.Progress(progress)) - } while (currentRead > 0) - if (response.status.isSuccess()) { - emit(DownloadResult.Success(data)) - } else { - emit(DownloadResult.Error("File not downloaded")) + try { + val client = createHttpClient() + val response = client.get(url).execute() + val data = ByteArray(response.contentLength()!!.toInt()) + 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) + offset += currentRead + val progress = (offset * 100f / data.size).roundToInt() + emit(DownloadResult.Progress(progress)) + } while (currentRead > 0) + if (response.status.isSuccess()) { + emit(DownloadResult.Success(data)) + } else { + emit(DownloadResult.Error("File not downloaded")) + } + client.close() + } catch (e:Exception) { + emit(DownloadResult.Error(e.message ?: "File not downloaded")) } - client.close() } } diff --git a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt index 9500391c..44503299 100644 --- a/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt +++ b/common/dependency-injection/src/jsMain/kotlin/com/shabinder/common/di/WebActual.kt @@ -63,10 +63,6 @@ private suspend fun isInternetAvailable(): Boolean { actual val isInternetAvailable: Boolean get() { return true - /*var result = false - val job = GlobalScope.launch { result = isInternetAvailable() } - while(job.isActive){} - return result*/ } val DownloadProgressFlow: MutableSharedFlow> = MutableSharedFlow(1) diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt index 9dc3fb64..54df04e8 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/SpotiFlyerList.kt @@ -73,7 +73,8 @@ interface SpotiFlyerList { data class State( val queryResult: PlatformQueryResult? = null, val link: String = "", - val trackList: List = emptyList() + val trackList: List = emptyList(), + val errorOccurred: Exception? = null ) } diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt index 0fb132f5..3e860ff2 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt @@ -58,6 +58,7 @@ internal class SpotiFlyerListStoreProvider( data class ResultFetched(val result: PlatformQueryResult, val trackList: List) : Result() data class UpdateTrackList(val list: List) : Result() data class UpdateTrackItem(val item: TrackDetails) : Result() + data class ErrorOccurred(val error: Exception) : Result() } private inner class ExecutorImpl : SuspendExecutor() { @@ -74,10 +75,19 @@ internal class SpotiFlyerListStoreProvider( override suspend fun executeIntent(intent: Intent, getState: () -> State) { when (intent) { - is Intent.SearchLink -> fetchQuery.query(link)?.let { result -> - result.trackList = result.trackList.toMutableList() - dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))) - executeIntent(Intent.RefreshTracksStatuses, getState) + is Intent.SearchLink -> { + try { + val result = fetchQuery.query(link) + if( result != null) { + result.trackList = result.trackList.toMutableList() + dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))) + executeIntent(Intent.RefreshTracksStatuses, getState) + } else { + throw Exception("An Error Occurred, Check your Link / Connection") + } + } catch (e:Exception) { + dispatch(Result.ErrorOccurred(e)) + } } is Intent.StartDownloadAll -> { @@ -107,6 +117,7 @@ internal class SpotiFlyerListStoreProvider( is Result.ResultFetched -> copy(queryResult = result.result, trackList = result.trackList, link = link) is Result.UpdateTrackList -> copy(trackList = result.list) is Result.UpdateTrackItem -> updateTrackItem(result.item) + is Result.ErrorOccurred -> copy(errorOccurred = result.error) } private fun State.updateTrackItem(item: TrackDetails): State {