Merge remote-tracking branch 'origin/main' into main

# Conflicts:
#	android/build.gradle.kts
#	android/src/main/java/com/shabinder/spotiflyer/App.kt
#	android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt
#	common/dependency-injection/src/androidMain/kotlin/com/shabinder/common/di/AndroidDir.kt
#	common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt
#	common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Dir.kt
This commit is contained in:
shabinder 2021-05-14 03:44:15 +05:30
commit 6d6a73c713
23 changed files with 157 additions and 196 deletions

View File

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- <color name="colorPrimary">#2d6c55</color>
<color name="colorPrimaryDark">#235644</color>
<color name="colorAccent">#ff9c40</color>
<color name="colorCyanListClick">#d6c8f5f3</color>
<color name="colorListDivider">#89606060</color>
<color name="pathLayoutBgColor">#70ffffff</color>
<color name="chevronBgColor">#50ffffff</color>
<color name="inactiveGradientColor">#53000000</color>
&lt;!&ndash; dialog colors &ndash;&gt;
<color name="memory_status_color">#de6565</color>
<color name="memory_bar_color">#7bde65</color>
<color name="new_folder_color">#c14b84</color>
<color name="select_color">#6b3fa1</color>
<color name="cancel_color">#3fa19f</color>-->
<array name="default_light">
<!-- Overview -->
<item>@color/colorPrimary</item> <!-- Top Header bg -->
<item>@android:color/white</item> <!-- header text -->
<item>@android:color/white</item> <!-- list bg -->
<item>@android:color/black</item> <!-- storage list name text -->
<item>@color/colorPrimary</item> <!-- free space text -->
<item>@color/colorAccent</item> <!-- memory bar -->
<!-- secondary dialog colors -->
<item>@color/colorPrimary</item> <!-- address bar bg -->
<item>@android:color/white</item> <!-- list bg -->
<item>@android:color/black</item> <!-- list text -->
<item>@android:color/white</item> <!-- address bar tint -->
<item>@color/chevronBgColor</item> <!-- new folder hint tint -->
<item>#da6c6c</item> <!-- select button color -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>@color/colorPrimary</item> <!-- new folder layour bg -->
</array>
<array name="default_dark">
<!-- Overview -->
<item>@color/colorPrimary</item> <!-- Top Header bg -->
<item>@android:color/white</item> <!-- header text -->
<item>@android:color/black</item> <!-- list bg -->
<item>@android:color/white</item> <!-- storage list name text -->
<item>#da6c6c</item> <!-- free space text -->
<item>@color/colorPrimary</item> <!-- memory bar -->
<!-- secondary dialog colors -->
<item>@color/colorPrimary</item> <!-- address bar bg -->
<item>@android:color/black</item> <!-- list bg -->
<item>@android:color/white</item> <!-- list text -->
<item>@android:color/white</item> <!-- address bar tint -->
<item>@color/grey</item> <!-- new folder hint tint -->
<item>#da6c6c</item> <!-- select button color -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>#da6c6c</item> <!-- new multi fab -->
<item>#da6c6c</item> <!-- new multi fab -->
</array>
</resources>

View File

@ -45,5 +45,11 @@ kotlin {
implementation("co.touchlab:stately-iso-collections:$statelyIsoVersion")
}
}
androidMain {
dependencies {
api("com.github.K1rakishou:Fuck-Storage-Access-Framework:v1.1")
api("androidx.documentfile:documentfile:1.0.1")
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.shabinder.common.models
import com.github.k1rakishou.fsaf.file.AbstractFile
// Use Storage Access Framework `SAF`
actual data class File(
val documentFile: AbstractFile?
)

View File

@ -8,7 +8,7 @@ actual interface PlatformActions {
const val SharedPreferencesKey = "configurations"
}
val imageCacheDir: String
val imageCacheDir: java.io.File
val sharedPreferences: SharedPreferences?
@ -18,7 +18,7 @@ actual interface PlatformActions {
}
actual val StubPlatformActions = object: PlatformActions {
override val imageCacheDir: String = ""
override val imageCacheDir = java.io.File("/")
override val sharedPreferences: SharedPreferences? = null

View File

@ -0,0 +1,18 @@
package com.shabinder.common.models
import android.net.Uri
import com.github.k1rakishou.fsaf.manager.base_directory.BaseDirectory
import java.io.File
class SpotiFlyerBaseDir(
private val getDirType: ()-> ActiveBaseDirType,
private val getJavaFile: ()-> File?,
private val getSAFUri: ()-> Uri?
): BaseDirectory() {
override fun currentActiveBaseDirType(): ActiveBaseDirType = getDirType()
override fun getDirFile(): File? = getJavaFile()
override fun getDirUri(): Uri? = getSAFUri()
}

View File

@ -32,12 +32,12 @@ data class TrackDetails(
var comment: String? = null,
var lyrics: String? = null,
var trackUrl: String? = null,
var albumArtPath: String,
var albumArtPath: String, // UriString in Android
var albumArtURL: String,
var source: Source,
val progress: Int = 2,
val downloaded: DownloadStatus = DownloadStatus.NotDownloaded,
var outputFilePath: String,
var outputFilePath: String, // UriString in Android
var videoID: String? = null,
) : Parcelable

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
expect class File

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
actual typealias File = java.io.File

View File

@ -0,0 +1,5 @@
package com.shabinder.common.models
actual data class File(
val path: String
)

View File

@ -0,0 +1,5 @@
package com.shabinder.common.models
actual data class File(
val path: String
)

View File

@ -41,7 +41,6 @@ kotlin {
dependencies {
implementation(compose.materialIconsExtended)
implementation(Extras.mp3agic)
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
// implementation(files("$rootDir/libs/mobile-ffmpeg.aar"))
}
}

View File

@ -99,9 +99,8 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
}
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails,tempMp3Path:String) {
val id3v2Tag = ID3v24Tag().apply {
artist = track.artists.joinToString(", ")
title = track.title
album = track.albumName
@ -118,9 +117,9 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
fis.close()
id3v2Tag.setAlbumImage(bytesArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(track.outputFilePath)
saveFile(tempMp3Path)
} catch (e: java.io.FileNotFoundException) {
Log.e("Error", "Couldn't Write Cached Mp3 Album Art, error: ${e.stackTrace}")
Log.e("Error", "Couldn't Write Cached Mp3 Album Art, Downloading And Trying Again, error: ${e.message}")
try {
// Image Still Not Downloaded!
// Lets Download Now and Write it into Album Art
@ -130,7 +129,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
is DownloadResult.Success -> {
id3v2Tag.setAlbumImage(it.byteArray, "image/jpeg")
this.id3v2Tag = id3v2Tag
saveFile(track.outputFilePath)
saveFile(tempMp3Path)
}
is DownloadResult.Progress -> {} // Nothing for Now , no progress bar to show
}
@ -143,6 +142,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
}
fun Mp3File.saveFile(filePath: String) {
Log.d("Mp3 File Save",filePath)
save(filePath.substringBeforeLast('.') + ".new.mp3")
val m4aFile = File(filePath)
m4aFile.delete()

View File

@ -18,33 +18,29 @@ package com.shabinder.common.di.worker
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.*
import com.shabinder.common.di.providers.getData
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 com.shabinder.downloader.models.formats.Format
import com.shabinder.common.models.Status
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -124,8 +120,7 @@ class ForegroundService : Service(), CoroutineScope {
val downloadObjects: ArrayList<TrackDetails>? = (
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
"object"
)
"object")
)
downloadObjects?.let { list ->
@ -212,14 +207,12 @@ class ForegroundService : Service(), CoroutineScope {
is DownloadResult.Error -> {
launch {
logger.d(tag) { it.message }
/*logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
downloadUsingDM(url, track.outputFilePath, track)*/
removeFromNotification("Downloading ${track.title}")
failed++
}
updateNotification()
sendTrackBroadcast(Status.FAILED.name,track)
}
}
is DownloadResult.Progress -> {
allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress)
@ -248,14 +241,10 @@ class ForegroundService : Service(), CoroutineScope {
}
logger.d(tag) { "${track.title} Download Completed" }
downloaded++
} catch (
e: Exception
) {
// Try downloading using android DM
} catch (e: Exception) {
// Download Failed
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
failed++
/*logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
downloadUsingDM(url, track.outputFilePath, track)*/
}
removeFromNotification("Downloading ${track.title}")
}
@ -263,54 +252,6 @@ class ForegroundService : Service(), CoroutineScope {
}
}
/**
* If Custom Downloader Fails , Android Download Manager To RESCUE!!
**/
private fun downloadUsingDM(url: String, outputDir: String, track: TrackDetails) {
launch {
val uri = Uri.parse(url)
val request = DownloadManager.Request(uri).apply {
setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
setAllowedOverRoaming(false)
setTitle(track.title)
setDescription("Spotify Downloader Working Up here...")
setDestinationUri(File(outputDir).toUri())
setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
}
// Start Download
val downloadID = downloadManager.enqueue(request)
logger.d("DownloadManager") { "Download Request Sent" }
val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Fetching the download id received with the broadcast
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
// Checking if the received broadcast is for our enqueued download by matching download id
if (downloadID == id) {
allTracksStatus[track.title] = DownloadStatus.Converting
launch { dir.saveFileWithMetadata(byteArrayOf(), track){}; converted++ }
// Unregister this broadcast Receiver
this@ForegroundService.unregisterReceiver(this)
}
}
}
registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
}
/**
* This is the method that can be called to update the Notification
*/
private fun updateNotification() {
val mNotificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
mNotificationManager.notify(notificationId, getNotification())
}
private fun releaseWakeLock() {
logger.d(tag) { "Releasing Wake Lock" }
try {
@ -337,34 +278,18 @@ class ForegroundService : Service(), CoroutineScope {
service.createNotificationChannel(channel)
}
/**
* Cleaning All Residual Files except Mp3 Files
/*
* Time To Wrap UP
* - `Clean Up` and `Stop this Foreground Service`
* */
private fun cleanFiles(dir: File) {
logger.d(tag) { "Starting Cleaning in ${dir.path} " }
val fList = dir.listFiles()
fList?.let {
for (file in fList) {
if (file.isDirectory) {
cleanFiles(file)
} else if (file.isFile) {
if (file.path.toString().substringAfterLast(".") != "mp3") {
logger.d(tag) { "Cleaning ${file.path}" }
file.delete()
}
}
}
}
}
private fun killService() {
launch {
logger.d(tag) { "Killing Self" }
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
downloadService.close()
updateNotification()
cleanFiles(File(dir.defaultDir()))
// TODO cleanFiles(File(dir.imageCacheDir()))
dir.defaultDir().documentFile?.let { cleanFiles(it,dir.fileManager,logger) }
cleanFiles(File(dir.imageCachePath + "Tracks/"),logger)
messageList = mutableListOf("", "", "", "", "")
releaseWakeLock()
serviceJob.cancel()
@ -391,6 +316,9 @@ class ForegroundService : Service(), CoroutineScope {
}
}
/*
* Create A New Notification with all the updated data
* */
private fun getNotification(): Notification = NotificationCompat.Builder(this, channelId).run {
setSmallIcon(R.drawable.ic_download_arrow)
setContentTitle("Total: $total Completed:$converted Failed:$failed")
@ -418,6 +346,15 @@ class ForegroundService : Service(), CoroutineScope {
updateNotification()
}
/**
* This is the method that can be called to update the Notification
*/
private fun updateNotification() {
val mNotificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
mNotificationManager.notify(notificationId, getNotification())
}
private fun sendTrackBroadcast(action: String, track: TrackDetails) {
val intent = Intent().apply {
setAction(action)

View File

@ -0,0 +1,50 @@
package com.shabinder.common.di.worker
import co.touchlab.kermit.Kermit
import com.github.k1rakishou.fsaf.FileManager
import com.github.k1rakishou.fsaf.file.AbstractFile
import java.io.File
/**
* Cleaning All Residual Files except Mp3 Files
**/
fun cleanFiles(dir: File,logger: Kermit) {
try {
logger.d("File Cleaning") { "Starting Cleaning in ${dir.path} " }
val fList = dir.listFiles()
fList?.let {
for (file in fList) {
if (file.isDirectory) {
cleanFiles(file, logger)
} else if (file.isFile) {
if (file.path.toString().substringAfterLast(".") != "mp3") {
logger.d("Files Cleaning") { "Cleaning ${file.path}" }
file.delete()
}
}
}
}
} catch (e:Exception) { e.printStackTrace() }
}
/**
* Cleaning All Residual Files except Mp3 Files
**/
fun cleanFiles(directory: AbstractFile,fm: FileManager,logger: Kermit) {
try {
logger.d("Files Cleaning") { "Starting Cleaning in ${directory.getFullPath()} " }
val fList = fm.listFiles(directory)
for (file in fList) {
if (fm.isDirectory(file)) {
cleanFiles(file, fm, logger)
} else if (fm.isFile(file)) {
if (file.getFullPath().substringAfterLast(".") != "mp3"
||
fm.getLength(file) == 0L
) {
logger.d("Files Cleaning") { "Cleaning ${file.getFullPath()}" }
fm.delete(file)
}
}
}
} catch (e:Exception) { e.printStackTrace() }
}

View File

@ -21,13 +21,10 @@ import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SpotifyProvider
import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.di.providers.YoutubeProvider
import com.shabinder.common.models.PlatformQueryResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class FetchPlatformQueryResult(
val gaanaProvider: GaanaProvider,

View File

@ -18,8 +18,8 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir
import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.gaana.GaanaRequests
import com.shabinder.common.di.getNameURL
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
@ -136,7 +136,7 @@ class GaanaProvider(
title = it.track_title,
artists = it.artist.map { artist -> artist?.name.toString() },
durationSec = it.duration,
albumArtPath = dir.imageCacheDir() + (it.artworkLink.substringBeforeLast('/').substringAfterLast('/')) + ".jpeg",
albumArtPath = dir.imageCachePath + getNameURL(it.artworkLink),
albumName = it.album_title,
year = it.release_date,
comment = "Genres:${it.genre?.map { genre -> genre?.name }?.reduceOrNull { acc, s -> acc + s }}",
@ -144,16 +144,15 @@ class GaanaProvider(
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana,
albumArtURL = it.artworkLink.replace("http:","https:"),
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
outputFilePath = dir.finalOutputPath(it.track_title, type, subFolder /*,".m4a"*/)
)
}
private fun GaanaTrack.updateStatusIfPresent(folderType: String, subFolder: String) {
if (dir.isPresent(
dir.finalOutputDir(
dir.finalOutputFile(
track_title,
folderType,
subFolder,
dir.defaultDir()
)
)
) { // Download Already Present!!

View File

@ -17,14 +17,10 @@
package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit
import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.freeze
import com.shabinder.common.di.Dir
import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.createHttpClient
import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.di.ktorHttpClient
import com.shabinder.common.di.getNameURL
import com.shabinder.common.di.spotify.SpotifyRequests
import com.shabinder.common.di.spotify.authenticateSpotify
import com.shabinder.common.models.NativeAtomicReference
@ -35,12 +31,8 @@ import com.shabinder.common.models.spotify.Image
import com.shabinder.common.models.spotify.Source
import com.shabinder.common.models.spotify.Track
import io.ktor.client.HttpClient
import io.ktor.client.features.auth.*
import io.ktor.client.features.auth.providers.*
import io.ktor.client.features.defaultRequest
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.header
import kotlin.native.concurrent.SharedImmutable
class SpotifyProvider(
private val tokenStore: TokenStore,
@ -224,28 +216,28 @@ class SpotifyProvider(
}
private fun List<Track>.toTrackDetailsList(type: String, subFolder: String) = this.map {
val albumArtLink = it.album?.images?.firstOrNull()?.url.toString()
TrackDetails(
title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms / 1000).toInt(),
albumArtPath = dir.imageCacheDir() + (it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg",
albumArtPath = dir.imageCachePath + getNameURL(albumArtLink),
albumName = it.album?.name,
year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}",
trackUrl = it.href,
downloaded = it.downloaded,
source = Source.Spotify,
albumArtURL = it.album?.images?.firstOrNull()?.url.toString(),
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
albumArtURL = albumArtLink,
outputFilePath = dir.finalOutputPath(it.name.toString(), type, subFolder/*,".m4a"*/)
)
}
private fun Track.updateStatusIfPresent(folderType: String, subFolder: String) {
if (dir.isPresent(
dir.finalOutputDir(
dir.finalOutputFile(
name.toString(),
folderType,
subFolder,
dir.defaultDir()
)
)
) { // Download Already Present!!

View File

@ -14,9 +14,11 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir
import com.shabinder.common.di.getNameURL
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
@ -106,15 +108,14 @@ class YoutubeProvider(
title = it.title ?: "N/A",
artists = listOf(it.author ?: "N/A"),
durationSec = it.lengthSeconds,
albumArtPath = dir.imageCacheDir() + it.videoId + ".jpeg",
albumArtPath = dir.imageCachePath + getNameURL(coverUrl),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
albumArtURL = coverUrl,
downloaded = if (dir.isPresent(
dir.finalOutputDir(
dir.finalOutputFile(
itemName = it.title ?: "N/A",
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
@ -122,7 +123,7 @@ class YoutubeProvider(
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title ?: "N/A", folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
outputFilePath = dir.finalOutputPath(it.title ?: "N/A", folderType, subFolder/*,".m4a"*/),
videoID = it.videoId
)
}
@ -160,15 +161,14 @@ class YoutubeProvider(
title = name,
artists = listOf(detail.author ?: "N/A"),
durationSec = detail.lengthSeconds,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
albumArtPath = dir.imageCachePath + getNameURL(coverUrl),
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
albumArtURL = coverUrl,
downloaded = if (dir.isPresent(
dir.finalOutputDir(
dir.finalOutputFile(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
@ -176,7 +176,7 @@ class YoutubeProvider(
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
outputFilePath = dir.finalOutputPath(name, folderType, subFolder /*,".m4a"*/),
videoID = searchId
)
)

View File

@ -90,6 +90,7 @@ fun removeIllegalChars(fileName: String): String {
name = fileName.replace(c, '_')
}
name = name.replace("\\s".toRegex(), "_")
name = name.replace("/".toRegex(), "_")
name = name.replace("\\)".toRegex(), "")
name = name.replace("\\(".toRegex(), "")
name = name.replace("\\[".toRegex(), "")

View File

@ -17,6 +17,7 @@
package com.shabinder.common.di
import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.getData
import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult

View File

@ -1,5 +1,6 @@
package com.shabinder.common.di
import com.shabinder.common.di.providers.getData
import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult

View File

@ -25,7 +25,6 @@ import com.shabinder.common.main.integration.SpotiFlyerMainImpl
import com.shabinder.common.models.Consumer
import com.shabinder.common.models.DownloadRecord
import com.shabinder.database.Database
import kotlinx.coroutines.flow.Flow
interface SpotiFlyerMain {

View File

@ -18,7 +18,6 @@ package com.shabinder.common.main.integration
import co.touchlab.stately.ensureNeverFrozen
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.lifecycle.doOnDestroy
import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Picture
import com.shabinder.common.di.utils.asValue