mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 01:04:31 +01:00
Multiplatform Settings & Desktop App Fixes. Release Soon!
This commit is contained in:
parent
5118aa10c4
commit
b8c2ee5b47
@ -39,6 +39,7 @@ import com.shabinder.common.database.R
|
||||
import com.shabinder.common.di.Picture
|
||||
import com.shabinder.common.di.dispatcherIO
|
||||
import com.shabinder.common.models.methods
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
@ -138,8 +139,7 @@ actual fun RazorPay() = painterResource(R.drawable.ic_indian_rupee)
|
||||
|
||||
@Composable
|
||||
actual fun Toast(
|
||||
text: String,
|
||||
visibility: MutableState<Boolean>,
|
||||
flow: MutableStateFlow<String>,
|
||||
duration: ToastDuration
|
||||
) {
|
||||
// We Have Android's Implementation of Toast so its just Empty
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.Spring.StiffnessLow
|
||||
import androidx.compose.animation.core.animateDp
|
||||
@ -26,6 +28,7 @@ import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -37,8 +40,12 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.rounded.ArrowBackIosNew
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -48,6 +55,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.crossfadeScale
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.asState
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.root.SpotiFlyerRoot.Child
|
||||
import com.shabinder.common.uikit.splash.Splash
|
||||
@ -93,6 +101,10 @@ fun SpotiFlyerRootContent(component: SpotiFlyerRoot, modifier: Modifier = Modifi
|
||||
contentTopPadding,
|
||||
component
|
||||
)
|
||||
Toast(
|
||||
flow = component.toastState,
|
||||
duration = ToastDuration.Long
|
||||
)
|
||||
}
|
||||
return component
|
||||
}
|
||||
@ -112,9 +124,13 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
|
||||
).then(modifier)
|
||||
) {
|
||||
|
||||
val activeComponent = component.routerState.asState()
|
||||
val callBacks = component.callBacks
|
||||
AppBar(
|
||||
backgroundColor = appBarColor,
|
||||
setDownloadDirectory = component.callBacks::setDownloadDirectory,
|
||||
onBackPressed = callBacks::popBackToHomeScreen ,
|
||||
setDownloadDirectory = callBacks::setDownloadDirectory,
|
||||
isBackButtonVisible = activeComponent.value.activeChild.instance is Child.List,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(Modifier.padding(top = topPadding))
|
||||
@ -130,16 +146,28 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun AppBar(
|
||||
backgroundColor: Color,
|
||||
onBackPressed:()->Unit,
|
||||
setDownloadDirectory:()->Unit,
|
||||
isBackButtonVisible: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TopAppBar(
|
||||
backgroundColor = backgroundColor,
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
AnimatedVisibility(isBackButtonVisible) {
|
||||
Icon(
|
||||
Icons.Rounded.ArrowBackIosNew,
|
||||
contentDescription = "Back Button",
|
||||
modifier = Modifier.clickable { onBackPressed() },
|
||||
tint = Color.LightGray
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
}
|
||||
Image(
|
||||
SpotiFlyerLogo(),
|
||||
"SpotiFlyer Logo",
|
||||
|
@ -19,14 +19,14 @@ package com.shabinder.common.uikit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
enum class ToastDuration(val value: Int) {
|
||||
Short(1000), Long(3000)
|
||||
Short(1000), Long(2500)
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun Toast(
|
||||
text: String,
|
||||
visibility: MutableState<Boolean> = mutableStateOf(false),
|
||||
duration: ToastDuration = ToastDuration.Long
|
||||
flow: MutableStateFlow<String>,
|
||||
duration: ToastDuration
|
||||
)
|
||||
|
@ -16,67 +16,76 @@
|
||||
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private val message: MutableState<String> = mutableStateOf("")
|
||||
private val state: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
fun showToast(text: String) {
|
||||
message.value = text
|
||||
state.value = true
|
||||
}
|
||||
|
||||
private var isShown: Boolean = false
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun Toast(
|
||||
text: String,
|
||||
visibility: MutableState<Boolean>,
|
||||
flow: MutableStateFlow<String>,
|
||||
duration: ToastDuration
|
||||
) {
|
||||
if (isShown) {
|
||||
return
|
||||
}
|
||||
|
||||
if (visibility.value) {
|
||||
isShown = true
|
||||
val state = flow.collectAsState("")
|
||||
val message = state.value
|
||||
|
||||
AnimatedVisibility (
|
||||
visible = message != "",
|
||||
enter = fadeIn() + slideInVertically(initialOffsetY = { it / 4 }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { it / 4 }) + fadeOut()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(bottom = 20.dp),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
modifier = Modifier.fillMaxSize().padding(bottom = 16.dp).padding(end = 16.dp),
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(300.dp, 70.dp),
|
||||
modifier = Modifier.sizeIn(maxWidth = 250.dp,maxHeight = 80.dp),
|
||||
color = Color(23, 23, 23),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
border = BorderStroke(1.dp, colorOffWhite)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()){
|
||||
Text(
|
||||
text = text,
|
||||
color = Color(210, 210, 210)
|
||||
text = message,
|
||||
color = Color(210, 210, 210),
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = SpotiFlyerTypography.body2,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
GlobalScope.launch {
|
||||
delay(duration.value.toLong())
|
||||
isShown = false
|
||||
visibility.value = false
|
||||
flow.value = ""
|
||||
}
|
||||
onDispose { }
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ kotlin {
|
||||
implementation("org.jetbrains.kotlinx:atomicfu:0.16.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0")
|
||||
implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0")
|
||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.7.6")
|
||||
implementation(Extras.youtubeDownloader)
|
||||
implementation(MVIKotlin.rx)
|
||||
}
|
||||
@ -49,7 +50,6 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(compose.materialIconsExtended)
|
||||
implementation(Extras.mp3agic)
|
||||
//implementation(Extras.jaudioTagger)
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
|
@ -16,13 +16,13 @@
|
||||
|
||||
package com.shabinder.common.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.database.SpotiFlyerDatabase
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.methods
|
||||
@ -40,18 +40,14 @@ import java.net.URL
|
||||
|
||||
actual class Dir actual constructor(
|
||||
private val logger: Kermit,
|
||||
private val settings: Settings,
|
||||
private val spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
companion object {
|
||||
const val DirKey = "downloadDir"
|
||||
}
|
||||
|
||||
// This Wont throw `NPE` as We will never pass null
|
||||
private val sharedPreferences:SharedPreferences by lazy { methods.value.platformActions.sharedPreferences!! }
|
||||
|
||||
fun setDownloadDirectory(newBasePath:String){
|
||||
sharedPreferences.edit().putString(DirKey,newBasePath).apply()
|
||||
}
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private val defaultBaseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString()
|
||||
@ -61,8 +57,8 @@ actual class Dir actual constructor(
|
||||
actual fun imageCacheDir(): String = methods.value.platformActions.imageCacheDir
|
||||
|
||||
// fun call in order to always access Updated Value
|
||||
actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator +
|
||||
"SpotiFlyer" + File.separator
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) +
|
||||
File.separator + "SpotiFlyer" + File.separator
|
||||
|
||||
actual fun isPresent(path: String): Boolean = File(path).exists()
|
||||
|
||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import co.touchlab.stately.ensureNeverFrozen
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.database.databaseModule
|
||||
import com.shabinder.common.database.getLogger
|
||||
import com.shabinder.common.di.providers.GaanaProvider
|
||||
@ -51,7 +52,8 @@ fun initKoin() = initKoin(enableNetworkLogs = false) { }
|
||||
|
||||
fun commonModule(enableNetworkLogs: Boolean) = module {
|
||||
single { createHttpClient(enableNetworkLogs = enableNetworkLogs) }
|
||||
single { Dir(get(), get()) }
|
||||
single { Dir(get(), get(), get()) }
|
||||
single { Settings() }
|
||||
single { Kermit(getLogger()) }
|
||||
single { TokenStore(get(), get()) }
|
||||
single { YoutubeMusic(get(), get()) }
|
||||
|
@ -17,6 +17,8 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.SettingsListener
|
||||
import com.shabinder.common.database.SpotiFlyerDatabase
|
||||
import com.shabinder.common.di.utils.removeIllegalChars
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
@ -32,6 +34,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
expect class Dir (
|
||||
logger: Kermit,
|
||||
settings: Settings,
|
||||
spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
val db: Database?
|
||||
@ -40,6 +43,7 @@ expect class Dir (
|
||||
fun defaultDir(): String
|
||||
fun imageCacheDir(): String
|
||||
fun createDirectory(dirPath: String)
|
||||
fun setDownloadDirectory(newBasePath:String)
|
||||
suspend fun cacheImage(image: Any, path: String) // in Android = ImageBitmap, Desktop = BufferedImage
|
||||
suspend fun loadImage(url: String): Picture
|
||||
suspend fun clearCache()
|
||||
|
@ -74,10 +74,15 @@ suspend fun downloadTrack(
|
||||
youtubeMp3: YoutubeMp3
|
||||
) {
|
||||
try {
|
||||
var link = youtubeMp3.getMp3DownloadLink(videoID)
|
||||
val link = youtubeMp3.getMp3DownloadLink(videoID) ?: ytDownloader.getVideo(videoID).getData()?.url
|
||||
|
||||
if (link == null) {
|
||||
link = ytDownloader.getVideo(videoID).getData()?.url ?: return
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) }
|
||||
)
|
||||
return
|
||||
}
|
||||
downloadFile(link).collect {
|
||||
when (it) {
|
||||
|
@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.database.SpotiFlyerDatabase
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.database.Database
|
||||
@ -39,8 +40,12 @@ import javax.imageio.ImageIO
|
||||
|
||||
actual class Dir actual constructor(
|
||||
private val logger: Kermit,
|
||||
private val settings: Settings,
|
||||
private val spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
companion object {
|
||||
const val DirKey = "downloadDir"
|
||||
}
|
||||
|
||||
init {
|
||||
createDirectories()
|
||||
@ -51,9 +56,13 @@ actual class Dir actual constructor(
|
||||
actual fun imageCacheDir(): String = System.getProperty("user.home") +
|
||||
fileSeparator() + "SpotiFlyer/.images" + fileSeparator()
|
||||
|
||||
actual fun defaultDir(): String = System.getProperty("user.home") + fileSeparator() +
|
||||
private val defaultBaseDir = System.getProperty("user.home")!!
|
||||
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) + fileSeparator() +
|
||||
"SpotiFlyer" + fileSeparator()
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
|
||||
actual fun isPresent(path: String): Boolean = File(path).exists()
|
||||
|
||||
actual fun createDirectory(dirPath: String) {
|
||||
@ -88,13 +97,68 @@ actual class Dir actual constructor(
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
) {
|
||||
val file = File(trackDetails.outputFilePath)
|
||||
file.writeBytes(mp3ByteArray)
|
||||
val songFile = File(trackDetails.outputFilePath)
|
||||
try {
|
||||
/*
|
||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||
* */
|
||||
if(!songFile.exists()) {
|
||||
/*Make intermediate Dirs if they don't exist yet*/
|
||||
songFile.parentFile.mkdirs()
|
||||
}
|
||||
|
||||
Mp3File(file)
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)
|
||||
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
Mp3File(File(songFile.absolutePath))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(songFile.absolutePath)
|
||||
}
|
||||
".m4a" -> {
|
||||
/*FFmpeg.executeAsync(
|
||||
"-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}"
|
||||
){ _, returnCode ->
|
||||
when (returnCode) {
|
||||
Config.RETURN_CODE_SUCCESS -> {
|
||||
//FFMPEG task Completed
|
||||
logger.d{ "Async command execution completed successfully." }
|
||||
scope.launch {
|
||||
Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")
|
||||
}
|
||||
}
|
||||
Config.RETURN_CODE_CANCEL -> {
|
||||
logger.d{"Async command execution cancelled by user."}
|
||||
}
|
||||
else -> {
|
||||
logger.d { "Async command execution failed with rc=$returnCode" }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
else -> {
|
||||
try {
|
||||
Mp3File(File(songFile.absolutePath))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(songFile.absolutePath)
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
withContext(Dispatchers.Main){
|
||||
//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" }
|
||||
}
|
||||
}
|
||||
actual fun addToLibrary(path: String) {}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.database.SpotiFlyerDatabase
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.database.Database
|
||||
@ -20,18 +21,28 @@ import platform.Foundation.sendSynchronousRequest
|
||||
import platform.Foundation.writeToFile
|
||||
import platform.UIKit.UIImage
|
||||
import platform.UIKit.UIImageJPEGRepresentation
|
||||
import java.lang.System
|
||||
|
||||
actual class Dir actual constructor(
|
||||
val logger: Kermit,
|
||||
private val settings: Settings,
|
||||
private val spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
companion object {
|
||||
const val DirKey = "downloadDir"
|
||||
}
|
||||
|
||||
actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path)
|
||||
|
||||
actual fun fileSeparator(): String = "/"
|
||||
|
||||
private val defaultBaseDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!.path!!
|
||||
|
||||
// TODO Error Handling
|
||||
actual fun defaultDir(): String = defaultDirURL.path!! + fileSeparator()
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) +
|
||||
fileSeparator() + "SpotiFlyer" + fileSeparator()
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
|
||||
private val defaultDirURL: NSURL by lazy {
|
||||
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.database.SpotiFlyerDatabase
|
||||
import com.shabinder.common.di.gaana.corsApi
|
||||
import com.shabinder.common.di.utils.removeIllegalChars
|
||||
@ -33,8 +34,12 @@ import org.w3c.dom.ImageBitmap
|
||||
|
||||
actual class Dir actual constructor(
|
||||
private val logger: Kermit,
|
||||
private val settings: Settings,
|
||||
private val spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
companion object {
|
||||
const val DirKey = "downloadDir"
|
||||
}
|
||||
|
||||
/*init {
|
||||
createDirectories()
|
||||
|
@ -94,21 +94,22 @@ internal class SpotiFlyerListStoreProvider(
|
||||
}
|
||||
|
||||
is Intent.StartDownloadAll -> {
|
||||
val finalList =
|
||||
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
|
||||
if (finalList.isNullOrEmpty()) methods.value.showPopUpMessage("All Songs are Processed")
|
||||
else downloadTracks(finalList, fetchQuery, dir)
|
||||
|
||||
val list = intent.trackList.map {
|
||||
if (it.downloaded == DownloadStatus.NotDownloaded)
|
||||
return@map it.copy(downloaded = DownloadStatus.Queued)
|
||||
it
|
||||
}
|
||||
dispatch(Result.UpdateTrackList(list.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))
|
||||
|
||||
|
||||
val finalList =
|
||||
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
|
||||
if (finalList.isNullOrEmpty()) methods.value.showPopUpMessage("All Songs are Processed")
|
||||
else downloadTracks(finalList, fetchQuery, dir)
|
||||
}
|
||||
is Intent.StartDownload -> {
|
||||
downloadTracks(listOf(intent.track), fetchQuery, dir)
|
||||
dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
|
||||
downloadTracks(listOf(intent.track), fetchQuery, dir)
|
||||
}
|
||||
is Intent.RefreshTracksStatuses -> methods.value.queryActiveTracks()
|
||||
}
|
||||
|
@ -30,12 +30,16 @@ import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
|
||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import com.shabinder.common.root.integration.SpotiFlyerRootImpl
|
||||
import com.shabinder.database.Database
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
interface SpotiFlyerRoot {
|
||||
|
||||
val routerState: Value<RouterState<*, Child>>
|
||||
|
||||
val toastState: MutableStateFlow<String>
|
||||
|
||||
val callBacks: SpotiFlyerRootCallBacks
|
||||
|
||||
sealed class Child {
|
||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.root.callbacks
|
||||
|
||||
interface SpotiFlyerRootCallBacks {
|
||||
fun searchLink(link: String)
|
||||
fun showToast(text:String)
|
||||
fun popBackToHomeScreen()
|
||||
fun setDownloadDirectory()
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
|
||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class SpotiFlyerRootImpl(
|
||||
@ -91,6 +92,8 @@ internal class SpotiFlyerRootImpl(
|
||||
|
||||
override val routerState: Value<RouterState<*, Child>> = router.state
|
||||
|
||||
override val toastState = MutableStateFlow("")
|
||||
|
||||
override val callBacks = object : SpotiFlyerRootCallBacks {
|
||||
override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link))
|
||||
override fun popBackToHomeScreen() {
|
||||
@ -98,6 +101,7 @@ internal class SpotiFlyerRootImpl(
|
||||
it !is Configuration.Main
|
||||
}
|
||||
}
|
||||
override fun showToast(text:String) { toastState.value = text }
|
||||
override fun setDownloadDirectory() { actions.setDownloadDirectoryAction() }
|
||||
}
|
||||
|
||||
|
@ -40,10 +40,16 @@ kotlin {
|
||||
implementation(project(":common:compose"))
|
||||
implementation(project(":common:data-models"))
|
||||
implementation(project(":common:root"))
|
||||
// Decompose
|
||||
implementation(Decompose.decompose)
|
||||
implementation(Decompose.extensionsCompose)
|
||||
|
||||
// MVI
|
||||
implementation(MVIKotlin.mvikotlin)
|
||||
implementation(MVIKotlin.mvikotlinMain)
|
||||
|
||||
// Koin
|
||||
implementation(Koin.core)
|
||||
}
|
||||
}
|
||||
val jvmTest by getting
|
||||
@ -55,6 +61,7 @@ compose.desktop {
|
||||
mainClass = "MainKt"
|
||||
description = "Music Downloader for Spotify, Gaana, Youtube Music."
|
||||
nativeDistributions {
|
||||
modules("java.sql", "java.security.jgss")
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "SpotiFlyer"
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ import com.shabinder.common.uikit.SpotiFlyerRootContent
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorOffWhite
|
||||
import com.shabinder.common.uikit.showToast
|
||||
import com.shabinder.database.Database
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
private val koin = initKoin(enableNetworkLogs = true).koin
|
||||
private lateinit var showToast: (String)->Unit
|
||||
|
||||
fun main() {
|
||||
|
||||
@ -63,7 +63,8 @@ fun main() {
|
||||
typography = SpotiFlyerTypography,
|
||||
shapes = SpotiFlyerShapes
|
||||
) {
|
||||
SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
|
||||
val root = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
|
||||
showToast = root.callBacks::showToast
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,17 +82,27 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
|
||||
override val actions = object: Actions {
|
||||
override val platformActions = object : PlatformActions {}
|
||||
|
||||
override fun showPopUpMessage(string: String, long: Boolean) = showToast(string)
|
||||
override fun showPopUpMessage(string: String, long: Boolean) {
|
||||
if(::showToast.isInitialized){
|
||||
showToast(string)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDownloadDirectoryAction() {}
|
||||
override fun setDownloadDirectoryAction() {
|
||||
showToast("TODO: Still needs to be Implemented")
|
||||
}
|
||||
|
||||
override fun queryActiveTracks() {}
|
||||
|
||||
override fun giveDonation() {}
|
||||
override fun giveDonation() {
|
||||
|
||||
}
|
||||
|
||||
override fun shareApp() {}
|
||||
|
||||
override fun openPlatform(packageID: String, platformLink: String) {}
|
||||
override fun openPlatform(packageID: String, platformLink: String) {
|
||||
showToast("TODO: Still needs to be Implemented")
|
||||
}
|
||||
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user