Code Cleanup and Formatting and i18n4k dependency

This commit is contained in:
shabinder 2021-07-14 02:15:09 +05:30
parent c591842fb4
commit 116530cc3c
80 changed files with 315 additions and 328 deletions

View File

@ -23,6 +23,7 @@ plugins {
kotlin("android") kotlin("android")
id("kotlin-parcelize") id("kotlin-parcelize")
id("org.jetbrains.compose") id("org.jetbrains.compose")
id("ktlint-setup")
} }
group = "com.shabinder" group = "com.shabinder"
@ -39,7 +40,7 @@ repositories {
android { android {
val props = gradleLocalProperties(rootDir) val props = gradleLocalProperties(rootDir)
if(props.containsKey("storeFileDir")) { if (props.containsKey("storeFileDir")) {
signingConfigs { signingConfigs {
create("release") { create("release") {
storeFile = file(props.getProperty("storeFileDir")) storeFile = file(props.getProperty("storeFileDir"))
@ -65,7 +66,7 @@ android {
getByName("release") { getByName("release") {
isMinifyEnabled = true isMinifyEnabled = true
// isShrinkResources = true // isShrinkResources = true
if(props.containsKey("storeFileDir")){ if (props.containsKey("storeFileDir")) {
signingConfig = signingConfigs.getByName("release") signingConfig = signingConfigs.getByName("release")
} }
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
@ -134,7 +135,7 @@ dependencies {
} }
implementation(Extras.kermit) implementation(Extras.kermit)
//implementation("com.jakewharton.timber:timber:4.7.1") // implementation("com.jakewharton.timber:timber:4.7.1")
implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}") implementation("dev.icerock.moko:parcelize:${Versions.mokoParcelize}")
implementation("com.github.shabinder:storage-chooser:2.0.4.45") implementation("com.github.shabinder:storage-chooser:2.0.4.45")
implementation("com.google.accompanist:accompanist-insets:0.12.0") implementation("com.google.accompanist:accompanist-insets:0.12.0")
@ -146,4 +147,4 @@ dependencies {
// Desugaring // Desugaring
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
} }

View File

@ -34,7 +34,7 @@ import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder import org.matomo.sdk.TrackerBuilder
class App: Application(), KoinComponent { class App : Application(), KoinComponent {
companion object { companion object {
const val SIGNATURE_HEX = "53304f6d75736a2f30484230334c454b714753525763724259444d3d0a" const val SIGNATURE_HEX = "53304f6d75736a2f30484230334c454b714753525763724259444d3d0a"
@ -42,7 +42,8 @@ class App: Application(), KoinComponent {
val tracker: Tracker by lazy { val tracker: Tracker by lazy {
TrackerBuilder.createDefault( TrackerBuilder.createDefault(
"https://matomo.spotiflyer.ml/matomo.php", 1) "https://matomo.spotiflyer.ml/matomo.php", 1
)
.build(Matomo.getInstance(this)).apply { .build(Matomo.getInstance(this)).apply {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
/*Timber.plant(DebugTree()) /*Timber.plant(DebugTree())
@ -96,4 +97,4 @@ class App: Application(), KoinComponent {
} }
} }
} }
} }

View File

@ -98,7 +98,6 @@ import org.koin.android.ext.android.inject
import org.matomo.sdk.extra.TrackHelper import org.matomo.sdk.extra.TrackHelper
import java.io.File import java.io.File
@ExperimentalAnimationApi @ExperimentalAnimationApi
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -167,7 +166,7 @@ class MainActivity : ComponentActivity() {
LaunchedEffect(view) { LaunchedEffect(view) {
permissionGranted.value = checkPermissions() permissionGranted.value = checkPermissions()
if(preferenceManager.isFirstLaunch) { if (preferenceManager.isFirstLaunch) {
delay(2500) delay(2500)
// Ask For Analytics Permission on first Dialog // Ask For Analytics Permission on first Dialog
askForAnalyticsPermission = true askForAnalyticsPermission = true
@ -187,8 +186,8 @@ class MainActivity : ComponentActivity() {
* and Track Downloads for all other releases like F-Droid, * and Track Downloads for all other releases like F-Droid,
* for `Github Downloads` we will track Downloads using : https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer * for `Github Downloads` we will track Downloads using : https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer
* */ * */
if(isGithubRelease) { checkIfLatestVersion() } if (isGithubRelease) { checkIfLatestVersion() }
if(preferenceManager.isAnalyticsEnabled && !isGithubRelease) { if (preferenceManager.isAnalyticsEnabled && !isGithubRelease) {
// Download/App Install Event for F-Droid builds // Download/App Install Event for F-Droid builds
TrackHelper.track().download().with(tracker) TrackHelper.track().download().with(tracker)
} }
@ -248,7 +247,6 @@ class MainActivity : ComponentActivity() {
} }
/*END: Foreground Service Handlers*/ /*END: Foreground Service Handlers*/
@Composable @Composable
private fun isInternetAvailableState(): State<Boolean?> { private fun isInternetAvailableState(): State<Boolean?> {
return internetAvailability.observeAsState() return internetAvailability.observeAsState()
@ -258,7 +256,7 @@ class MainActivity : ComponentActivity() {
android.widget.Toast.makeText( android.widget.Toast.makeText(
applicationContext, applicationContext,
string, string,
if(long) android.widget.Toast.LENGTH_LONG else android.widget.Toast.LENGTH_SHORT if (long) android.widget.Toast.LENGTH_LONG else android.widget.Toast.LENGTH_SHORT
).show() ).show()
} }
@ -270,23 +268,24 @@ class MainActivity : ComponentActivity() {
private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot = private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
SpotiFlyerRoot( SpotiFlyerRoot(
componentContext, componentContext,
dependencies = object : SpotiFlyerRoot.Dependencies{ dependencies = object : SpotiFlyerRoot.Dependencies {
override val storeFactory = LoggingStoreFactory(DefaultStoreFactory) override val storeFactory = LoggingStoreFactory(DefaultStoreFactory)
override val database = this@MainActivity.dir.db override val database = this@MainActivity.dir.db
override val fetchQuery = this@MainActivity.fetcher override val fetchQuery = this@MainActivity.fetcher
override val dir: Dir = this@MainActivity.dir override val dir: Dir = this@MainActivity.dir
override val preferenceManager = this@MainActivity.preferenceManager override val preferenceManager = this@MainActivity.preferenceManager
override val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow override val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
override val actions = object: Actions { override val actions = object : Actions {
override val platformActions = object : PlatformActions { override val platformActions = object : PlatformActions {
override val imageCacheDir: String = applicationContext.cacheDir.absolutePath + File.separator override val imageCacheDir: String = applicationContext.cacheDir.absolutePath + File.separator
override val sharedPreferences = applicationContext.getSharedPreferences(SharedPreferencesKey, override val sharedPreferences = applicationContext.getSharedPreferences(
SharedPreferencesKey,
MODE_PRIVATE MODE_PRIVATE
) )
override fun addToLibrary(path: String) { override fun addToLibrary(path: String) {
MediaScannerConnection.scanFile ( MediaScannerConnection.scanFile(
applicationContext, applicationContext,
listOf(path).toTypedArray(), null, null listOf(path).toTypedArray(), null, null
) )
@ -298,14 +297,14 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun showPopUpMessage(string: String, long: Boolean) = this@MainActivity.showPopUpMessage(string,long) override fun showPopUpMessage(string: String, long: Boolean) = this@MainActivity.showPopUpMessage(string, long)
override fun setDownloadDirectoryAction(callBack: (String) -> Unit) = setUpOnPrefClickListener(callBack) override fun setDownloadDirectoryAction(callBack: (String) -> Unit) = setUpOnPrefClickListener(callBack)
override fun queryActiveTracks() = this@MainActivity.queryActiveTracks() override fun queryActiveTracks() = this@MainActivity.queryActiveTracks()
override fun giveDonation() { override fun giveDonation() {
openPlatform("",platformLink = "https://razorpay.com/payment-button/pl_GnKuuDBdBu0ank/view/?utm_source=payment_button&utm_medium=button&utm_campaign=payment_button") openPlatform("", platformLink = "https://razorpay.com/payment-button/pl_GnKuuDBdBu0ank/view/?utm_source=payment_button&utm_medium=button&utm_campaign=payment_button")
} }
override fun shareApp() { override fun shareApp() {
@ -342,25 +341,25 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/} override fun writeMp3Tags(trackDetails: TrackDetails) { /*IMPLEMENTED*/ }
override val isInternetAvailable get() = internetAvailability.value ?: true override val isInternetAvailable get() = internetAvailability.value ?: true
} }
/* /*
* Analytics Will Only Be Sent if User Granted us the Permission * Analytics Will Only Be Sent if User Granted us the Permission
* */ * */
override val analytics = object: Analytics { override val analytics = object : Analytics {
override fun appLaunchEvent() { override fun appLaunchEvent() {
if(preferenceManager.isAnalyticsEnabled){ if (preferenceManager.isAnalyticsEnabled) {
TrackHelper.track() TrackHelper.track()
.event("events","App_Launch") .event("events", "App_Launch")
.name("App Launch").with(tracker) .name("App Launch").with(tracker)
} }
} }
override fun homeScreenVisit() { override fun homeScreenVisit() {
if(preferenceManager.isAnalyticsEnabled){ if (preferenceManager.isAnalyticsEnabled) {
// HomeScreen Visit Event // HomeScreen Visit Event
TrackHelper.track().screen("/main_activity/home_screen") TrackHelper.track().screen("/main_activity/home_screen")
.title("HomeScreen").with(tracker) .title("HomeScreen").with(tracker)
@ -368,7 +367,7 @@ class MainActivity : ComponentActivity() {
} }
override fun listScreenVisit() { override fun listScreenVisit() {
if(preferenceManager.isAnalyticsEnabled){ if (preferenceManager.isAnalyticsEnabled) {
// ListScreen Visit Event // ListScreen Visit Event
TrackHelper.track().screen("/main_activity/list_screen") TrackHelper.track().screen("/main_activity/list_screen")
.title("ListScreen").with(tracker) .title("ListScreen").with(tracker)
@ -400,15 +399,17 @@ class MainActivity : ComponentActivity() {
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun setUpOnPrefClickListener(callBack : (String) -> Unit) { private fun setUpOnPrefClickListener(callBack: (String) -> Unit) {
// Initialize Builder // Initialize Builder
val chooser = StorageChooser.Builder() val chooser = StorageChooser.Builder()
.withActivity(this) .withActivity(this)
.withFragmentManager(fragmentManager) .withFragmentManager(fragmentManager)
.withMemoryBar(true) .withMemoryBar(true)
.setTheme(StorageChooser.Theme(applicationContext).apply { .setTheme(
scheme = applicationContext.resources.getIntArray(R.array.default_dark) StorageChooser.Theme(applicationContext).apply {
}) scheme = applicationContext.resources.getIntArray(R.array.default_dark)
}
)
.setDialogTitle(Strings.setDownloadDirectory()) .setDialogTitle(Strings.setDownloadDirectory())
.allowCustomPath(true) .allowCustomPath(true)
.setType(StorageChooser.DIRECTORY_CHOOSER) .setType(StorageChooser.DIRECTORY_CHOOSER)
@ -423,7 +424,7 @@ class MainActivity : ComponentActivity() {
preferenceManager.setDownloadDirectory(path) preferenceManager.setDownloadDirectory(path)
callBack(path) callBack(path)
showPopUpMessage(Strings.downloadDirectorySetTo("\n${dir.defaultDir()}")) showPopUpMessage(Strings.downloadDirectorySetTo("\n${dir.defaultDir()}"))
}else{ } else {
showPopUpMessage(Strings.noWriteAccess("\n$path ")) showPopUpMessage(Strings.noWriteAccess("\n$path "))
} }
} }
@ -445,7 +446,7 @@ class MainActivity : ComponentActivity() {
// Ignoring battery optimization // Ignoring battery optimization
permissionGranted.value = true permissionGranted.value = true
} else { } else {
disableDozeMode(disableDozeCode)//Again Ask For Permission!! disableDozeMode(disableDozeCode) // Again Ask For Permission!!
} }
} }
} }
@ -463,12 +464,12 @@ class MainActivity : ComponentActivity() {
val filterLinkRegex = """http.+\w""".toRegex() val filterLinkRegex = """http.+\w""".toRegex()
val string = it.replace("\n".toRegex(), " ") val string = it.replace("\n".toRegex(), " ")
val link = filterLinkRegex.find(string)?.value.toString() val link = filterLinkRegex.find(string)?.value.toString()
Log.i("Intent",link) Log.i("Intent", link)
lifecycleScope.launch { lifecycleScope.launch {
while(!this@MainActivity::root.isInitialized){ while (!this@MainActivity::root.isInitialized) {
delay(100) delay(100)
} }
if(methods.value.isInternetAvailable)callBacks.searchLink(link) if (methods.value.isInternetAvailable)callBacks.searchLink(link)
} }
} }
} }

View File

@ -18,4 +18,4 @@ package com.shabinder.spotiflyer.di
import org.koin.dsl.module import org.koin.dsl.module
fun appModule(enableLogging:Boolean = false) = module {} fun appModule(enableLogging: Boolean = false) = module {}

View File

@ -57,7 +57,7 @@ import java.io.File
class ForegroundService : LifecycleService() { class ForegroundService : LifecycleService() {
private var downloadService: AutoClear<ParallelExecutor> = autoClear { ParallelExecutor(Dispatchers.IO) } private var downloadService: AutoClear<ParallelExecutor> = autoClear { ParallelExecutor(Dispatchers.IO) }
val trackStatusFlowMap by autoClear { TrackStatusFlowMap(MutableSharedFlow(replay = 1),lifecycleScope) } val trackStatusFlowMap by autoClear { TrackStatusFlowMap(MutableSharedFlow(replay = 1), 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: Dir by inject() private val dir: Dir by inject()
@ -133,18 +133,18 @@ class ForegroundService : LifecycleService() {
updateNotification() updateNotification()
} }
trackList.forEach { for (track in trackList) {
trackStatusFlowMap[it.title] = DownloadStatus.Queued trackStatusFlowMap[track.title] = DownloadStatus.Queued
lifecycleScope.launch { lifecycleScope.launch {
downloadService.value.execute { downloadService.value.execute {
fetcher.findMp3DownloadLink(it).fold( fetcher.findMp3DownloadLink(track).fold(
success = { url -> success = { url ->
enqueueDownload(url, it) enqueueDownload(url, track)
}, },
failure = { error -> failure = { error ->
failed++ failed++
updateNotification() updateNotification()
trackStatusFlowMap[it.title] = DownloadStatus.Failed(error) trackStatusFlowMap[track.title] = DownloadStatus.Failed(error)
} }
) )
} }
@ -238,7 +238,7 @@ class ForegroundService : LifecycleService() {
lifecycleScope.launch { lifecycleScope.launch {
logger.d(TAG) { "Killing Self" } logger.d(TAG) { "Killing Self" }
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.getOrNull()?.close() downloadService.getOrNull()?.close()
downloadService.reset() downloadService.reset()
@ -260,7 +260,7 @@ class ForegroundService : LifecycleService() {
setSmallIcon(R.drawable.ic_download_arrow) setSmallIcon(R.drawable.ic_download_arrow)
setContentTitle("${Strings.total()}: $total ${Strings.completed()}:$converted ${Strings.failed()}:$failed") setContentTitle("${Strings.total()}: $total ${Strings.completed()}:$converted ${Strings.failed()}:$failed")
setSilent(true) setSilent(true)
setProgress(total,failed+converted,false) setProgress(total, failed + converted, false)
setStyle( setStyle(
NotificationCompat.InboxStyle().run { NotificationCompat.InboxStyle().run {
addLine(messageList[messageList.size - 1].asString()) addLine(messageList[messageList.size - 1].asString())

View File

@ -7,7 +7,7 @@ typealias Message = Pair<String, DownloadStatus>
val Message.title: String get() = first val Message.title: String get() = first
val Message.downloadStatus: DownloadStatus get() = second val Message.downloadStatus: DownloadStatus get() = second
val Message.progress: String get() = when (downloadStatus) { val Message.progress: String get() = when (downloadStatus) {
is DownloadStatus.Downloading -> "-> ${(downloadStatus as DownloadStatus.Downloading).progress}%" is DownloadStatus.Downloading -> "-> ${(downloadStatus as DownloadStatus.Downloading).progress}%"
@ -18,12 +18,12 @@ val Message.progress: String get() = when (downloadStatus) {
is DownloadStatus.NotDownloaded -> "" is DownloadStatus.NotDownloaded -> ""
} }
val emptyMessage = Message("",DownloadStatus.NotDownloaded) val emptyMessage = Message("", DownloadStatus.NotDownloaded)
// `Progress` is not being shown because we don't get get consistent Updates from Download Fun , // `Progress` is not being shown because we don't get get consistent Updates from Download Fun ,
// all Progress data is emitted all together from fun // all Progress data is emitted all together from fun
fun Message.asString(): String { fun Message.asString(): String {
val statusString = when(downloadStatus){ val statusString = when (downloadStatus) {
is DownloadStatus.Downloading -> Strings.downloading() is DownloadStatus.Downloading -> Strings.downloading()
is DownloadStatus.Converting -> Strings.processing() is DownloadStatus.Converting -> Strings.processing()
else -> "" else -> ""
@ -31,4 +31,4 @@ fun Message.asString(): String {
return "$statusString $title ${""/*progress*/}".trim() return "$statusString $title ${""/*progress*/}".trim()
} }
fun List<Message>.getEmpty(): MutableList<Message> = java.util.Collections.synchronizedList(MutableList(size) { emptyMessage }) fun List<Message>.getEmpty(): MutableList<Message> = java.util.Collections.synchronizedList(MutableList(size) { emptyMessage })

View File

@ -6,12 +6,12 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class TrackStatusFlowMap( class TrackStatusFlowMap(
val statusFlow: MutableSharedFlow<HashMap<String,DownloadStatus>>, val statusFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>,
private val scope: CoroutineScope private val scope: CoroutineScope
): HashMap<String,DownloadStatus>() { ) : HashMap<String, DownloadStatus>() {
override fun put(key: String, value: DownloadStatus): DownloadStatus? { override fun put(key: String, value: DownloadStatus): DownloadStatus? {
val res = super.put(key, value) val res = super.put(key, value)
scope.launch { statusFlow.emit(this@TrackStatusFlowMap) } scope.launch { statusFlow.emit(this@TrackStatusFlowMap) }
return res return res
} }
} }

View File

@ -8,7 +8,7 @@ import java.io.File
**/ **/
fun cleanFiles(dir: File) { fun cleanFiles(dir: File) {
try { try {
Log.d("File Cleaning","Starting Cleaning in ${dir.path} ") Log.d("File Cleaning", "Starting Cleaning in ${dir.path} ")
val fList = dir.listFiles() val fList = dir.listFiles()
fList?.let { fList?.let {
for (file in fList) { for (file in fList) {
@ -16,7 +16,7 @@ fun cleanFiles(dir: File) {
cleanFiles(file) cleanFiles(file)
} else if (file.isFile) { } else if (file.isFile) {
if (file.path.toString().substringAfterLast(".") != "mp3") { if (file.path.toString().substringAfterLast(".") != "mp3") {
Log.d("Files Cleaning","Cleaning ${file.path}") Log.d("Files Cleaning", "Cleaning ${file.path}")
file.delete() file.delete()
} }
} }
@ -24,4 +24,3 @@ fun cleanFiles(dir: File) {
} }
} catch (e: Exception) { e.printStackTrace() } } catch (e: Exception) { e.printStackTrace() }
} }

View File

@ -34,8 +34,8 @@ import com.shabinder.common.uikit.configurations.colorPrimary
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
fun AnalyticsDialog( fun AnalyticsDialog(
isVisible:Boolean, isVisible: Boolean,
enableAnalytics: ()->Unit, enableAnalytics: () -> Unit,
dismissDialog: () -> Unit, dismissDialog: () -> Unit,
) { ) {
// Analytics Permission Dialog // Analytics Permission Dialog
@ -43,10 +43,10 @@ fun AnalyticsDialog(
AlertDialog( AlertDialog(
onDismissRequest = dismissDialog, onDismissRequest = dismissDialog,
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically,horizontalArrangement = Arrangement.SpaceEvenly) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceEvenly) {
Icon(Icons.Rounded.Insights,Strings.analytics(), Modifier.size(42.dp)) Icon(Icons.Rounded.Insights, Strings.analytics(), Modifier.size(42.dp))
Spacer(Modifier.padding(horizontal = 8.dp)) Spacer(Modifier.padding(horizontal = 8.dp))
Text(Strings.grantAnalytics(),style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center) Text(Strings.grantAnalytics(), style = SpotiFlyerTypography.h5, textAlign = TextAlign.Center)
} }
}, },
backgroundColor = Color.DarkGray, backgroundColor = Color.DarkGray,
@ -60,7 +60,7 @@ fun AnalyticsDialog(
shape = SpotiFlyerShapes.medium, shape = SpotiFlyerShapes.medium,
colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF303030)) colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF303030))
) { ) {
Text(Strings.no(),color = colorPrimary,fontSize = 18.sp,textAlign = TextAlign.Center) Text(Strings.no(), color = colorPrimary, fontSize = 18.sp, textAlign = TextAlign.Center)
} }
Spacer(Modifier.padding(vertical = 4.dp)) Spacer(Modifier.padding(vertical = 4.dp))
TextButton( TextButton(
@ -73,14 +73,14 @@ fun AnalyticsDialog(
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
shape = SpotiFlyerShapes.medium shape = SpotiFlyerShapes.medium
) { ) {
Text(Strings.yes(),color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) Text(Strings.yes(), color = Color.Black, fontSize = 18.sp, textAlign = TextAlign.Center)
} }
} }
}, },
text = { text = {
Text(Strings.analyticsDescription(),style = SpotiFlyerTypography.body2,textAlign = TextAlign.Center) Text(Strings.analyticsDescription(), style = SpotiFlyerTypography.body2, textAlign = TextAlign.Center)
}, },
properties = DialogProperties(dismissOnBackPress = false,dismissOnClickOutside = false) properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
) )
} }
} }

View File

@ -53,10 +53,10 @@ import kotlinx.coroutines.delay
@Composable @Composable
fun NetworkDialog( fun NetworkDialog(
networkAvailability: State<Boolean?> networkAvailability: State<Boolean?>
){ ) {
var visible by remember { mutableStateOf(false) } var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit){ LaunchedEffect(Unit) {
delay(2600) delay(2600)
visible = true visible = true
} }
@ -75,21 +75,30 @@ fun NetworkDialog(
Text("Retry",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) Text("Retry",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
Icon(Icons.Rounded.SyncProblem,"Check Network Connection Again") Icon(Icons.Rounded.SyncProblem,"Check Network Connection Again")
} }
*/}, */
title = { Text(Strings.noInternetConnection(), },
style = SpotiFlyerTypography.h5, title = {
textAlign = TextAlign.Center) }, Text(
Strings.noInternetConnection(),
style = SpotiFlyerTypography.h5,
textAlign = TextAlign.Center
)
},
backgroundColor = Color.DarkGray, backgroundColor = Color.DarkGray,
text = { text = {
Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center){ Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Row(verticalAlignment = Alignment.CenterVertically, Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
) { ) {
Image(Icons.Rounded.CloudOff, Image(
Strings.noInternetConnection(),Modifier.size(42.dp),colorFilter = ColorFilter.tint( Icons.Rounded.CloudOff,
colorOffWhite Strings.noInternetConnection(), Modifier.size(42.dp),
)) colorFilter = ColorFilter.tint(
colorOffWhite
)
)
Spacer(modifier = Modifier.padding(start = 16.dp)) Spacer(modifier = Modifier.padding(start = 16.dp))
Text( Text(
text = Strings.checkInternetConnection(), text = Strings.checkInternetConnection(),
@ -97,8 +106,8 @@ fun NetworkDialog(
) )
} }
} }
} },
,shape = SpotiFlyerShapes.medium shape = SpotiFlyerShapes.medium
) )
} }
} }

View File

@ -38,9 +38,9 @@ import kotlinx.coroutines.delay
@Composable @Composable
fun PermissionDialog( fun PermissionDialog(
permissionGranted: Boolean, permissionGranted: Boolean,
requestStoragePermission:() -> Unit, requestStoragePermission: () -> Unit,
disableDozeMode:() -> Unit, disableDozeMode: () -> Unit,
){ ) {
var askForPermission by remember { mutableStateOf(false) } var askForPermission by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
delay(2600) delay(2600)
@ -61,18 +61,20 @@ fun PermissionDialog(
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth() Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium) .background(colorPrimary, shape = SpotiFlyerShapes.medium)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
){ ) {
Text(Strings.grantPermissions(),color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) Text(Strings.grantPermissions(), color = Color.Black, fontSize = 18.sp, textAlign = TextAlign.Center)
} }
},title = { Text(Strings.requiredPermissions(),style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center) }, },
title = { Text(Strings.requiredPermissions(), style = SpotiFlyerTypography.h5, textAlign = TextAlign.Center) },
backgroundColor = Color.DarkGray, backgroundColor = Color.DarkGray,
text = { text = {
Column{ Column {
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Row(verticalAlignment = Alignment.CenterVertically, Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
) { ) {
Icon(Icons.Rounded.SdStorage,Strings.storagePermission()) Icon(Icons.Rounded.SdStorage, Strings.storagePermission())
Spacer(modifier = Modifier.padding(start = 16.dp)) Spacer(modifier = Modifier.padding(start = 16.dp))
Column { Column {
Text( Text(
@ -89,7 +91,7 @@ fun PermissionDialog(
modifier = Modifier.fillMaxWidth().padding(top = 6.dp), modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Rounded.SystemSecurityUpdate,Strings.backgroundRunning()) Icon(Icons.Rounded.SystemSecurityUpdate, Strings.backgroundRunning())
Spacer(modifier = Modifier.padding(start = 16.dp)) Spacer(modifier = Modifier.padding(start = 16.dp))
Column { Column {
Text( Text(
@ -123,4 +125,4 @@ fun PermissionDialog(
} }
) )
} }
} }

View File

@ -5,7 +5,6 @@ import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.util.Base64 import android.util.Base64
import android.util.Log
import com.shabinder.spotiflyer.App import com.shabinder.spotiflyer.App
import java.security.MessageDigest import java.security.MessageDigest
@ -22,14 +21,14 @@ fun checkAppSignature(context: Context): Boolean {
// Log.d("REMOVE_ME", "Include this string as a value for SIGNATURE:$currentSignature") // Log.d("REMOVE_ME", "Include this string as a value for SIGNATURE:$currentSignature")
// Log.d("REMOVE_ME HEX", "Include this string as a value for SIGNATURE Hex:${currentSignature.toByteArray().toHEX()}") // Log.d("REMOVE_ME HEX", "Include this string as a value for SIGNATURE Hex:${currentSignature.toByteArray().toHEX()}")
//compare signatures // compare signatures
if (App.SIGNATURE_HEX == currentSignature.toByteArray().toHEX()) { if (App.SIGNATURE_HEX == currentSignature.toByteArray().toHEX()) {
return true return true
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
//assumes an issue in checking signature., but we let the caller decide on what to do. // assumes an issue in checking signature., but we let the caller decide on what to do.
} }
return false return false
} }
@ -40,4 +39,4 @@ fun ByteArray.toHEX(): String {
builder.append(String.format("%02x", aByte)) builder.append(String.format("%02x", aByte))
} }
return builder.toString() return builder.toString()
} }

View File

@ -32,9 +32,9 @@ import com.github.javiersantos.appupdater.enums.Display
import com.github.javiersantos.appupdater.enums.UpdateFrom import com.github.javiersantos.appupdater.enums.UpdateFrom
fun Activity.checkIfLatestVersion() { fun Activity.checkIfLatestVersion() {
AppUpdater(this,0).run { AppUpdater(this, 0).run {
setDisplay(Display.NOTIFICATION) setDisplay(Display.NOTIFICATION)
showAppUpdated(false)//true:Show App is Updated Dialog showAppUpdated(false) // true:Show App is Updated Dialog
setUpdateFrom(UpdateFrom.XML) setUpdateFrom(UpdateFrom.XML)
setUpdateXML("https://raw.githubusercontent.com/Shabinder/SpotiFlyer/Compose/app/src/main/res/xml/app_update.xml") setUpdateXML("https://raw.githubusercontent.com/Shabinder/SpotiFlyer/Compose/app/src/main/res/xml/app_update.xml")
setCancelable(false) setCancelable(false)
@ -42,24 +42,26 @@ fun Activity.checkIfLatestVersion() {
} }
} }
fun Activity.checkPermissions():Boolean = ContextCompat fun Activity.checkPermissions(): Boolean = ContextCompat
.checkSelfPermission(this, .checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED this,
&& Manifest.permission.WRITE_EXTERNAL_STORAGE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ ) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(
} else true this,
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
) == PackageManager.PERMISSION_GRANTED
} else true
@SuppressLint("BatteryLife", "ObsoleteSdkInt") @SuppressLint("BatteryLife", "ObsoleteSdkInt")
fun Activity.disableDozeMode(requestCode:Int) { fun Activity.disableDozeMode(requestCode: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val pm = val pm =
this.getSystemService(Context.POWER_SERVICE) as PowerManager this.getSystemService(Context.POWER_SERVICE) as PowerManager
val isIgnoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(packageName) val isIgnoringBatteryOptimizations = pm.isIgnoringBatteryOptimizations(packageName)
if (!isIgnoringBatteryOptimizations) { if (!isIgnoringBatteryOptimizations) {
val intent = Intent().apply{ val intent = Intent().apply {
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = Uri.parse("package:$packageName") data = Uri.parse("package:$packageName")
} }

View File

@ -2,7 +2,6 @@ package com.shabinder.spotiflyer.utils.autoclear
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.shabinder.common.requireNotNull
import com.shabinder.spotiflyer.utils.autoclear.AutoClear.Companion.TRIGGER import com.shabinder.spotiflyer.utils.autoclear.AutoClear.Companion.TRIGGER
import com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers.LifecycleCreateAndDestroyObserver import com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers.LifecycleCreateAndDestroyObserver
import com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers.LifecycleResumeAndPauseObserver import com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers.LifecycleResumeAndPauseObserver
@ -34,7 +33,7 @@ class AutoClear<T : Any?>(
fun getOrNull(): T? = _value fun getOrNull(): T? = _value
private val observer: LifecycleAutoInitializer<T?> by lazy { private val observer: LifecycleAutoInitializer<T?> by lazy {
when(trigger) { when (trigger) {
TRIGGER.ON_CREATE -> LifecycleCreateAndDestroyObserver(initializer) TRIGGER.ON_CREATE -> LifecycleCreateAndDestroyObserver(initializer)
TRIGGER.ON_START -> LifecycleStartAndStopObserver(initializer) TRIGGER.ON_START -> LifecycleStartAndStopObserver(initializer)
TRIGGER.ON_RESUME -> LifecycleResumeAndPauseObserver(initializer) TRIGGER.ON_RESUME -> LifecycleResumeAndPauseObserver(initializer)
@ -74,4 +73,4 @@ fun <T : Any> LifecycleOwner.autoClear(
initializer: () -> T initializer: () -> T
): AutoClear<T> { ): AutoClear<T> {
return AutoClear(this.lifecycle, initializer, trigger) return AutoClear(this.lifecycle, initializer, trigger)
} }

View File

@ -59,4 +59,4 @@ class AutoClearFragment<T : Any?>(
fun <T : Any> Fragment.autoClear(initializer: () -> T): AutoClearFragment<T> { fun <T : Any> Fragment.autoClear(initializer: () -> T): AutoClearFragment<T> {
return AutoClearFragment(this, initializer) return AutoClearFragment(this, initializer)
} }

View File

@ -2,6 +2,6 @@ package com.shabinder.spotiflyer.utils.autoclear
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
interface LifecycleAutoInitializer<T>: DefaultLifecycleObserver { interface LifecycleAutoInitializer<T> : DefaultLifecycleObserver {
var value: T? var value: T?
} }

View File

@ -3,7 +3,7 @@ package com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer
class LifecycleCreateAndDestroyObserver<T: Any?>( class LifecycleCreateAndDestroyObserver<T : Any?>(
private val initializer: (() -> T)? private val initializer: (() -> T)?
) : LifecycleAutoInitializer<T> { ) : LifecycleAutoInitializer<T> {
@ -18,4 +18,4 @@ class LifecycleCreateAndDestroyObserver<T: Any?>(
super.onDestroy(owner) super.onDestroy(owner)
value = null value = null
} }
} }

View File

@ -3,7 +3,7 @@ package com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer
class LifecycleResumeAndPauseObserver<T: Any?>( class LifecycleResumeAndPauseObserver<T : Any?>(
private val initializer: (() -> T)? private val initializer: (() -> T)?
) : LifecycleAutoInitializer<T> { ) : LifecycleAutoInitializer<T> {
@ -18,4 +18,4 @@ class LifecycleResumeAndPauseObserver<T: Any?>(
super.onPause(owner) super.onPause(owner)
value = null value = null
} }
} }

View File

@ -3,7 +3,7 @@ package com.shabinder.spotiflyer.utils.autoclear.lifecycleobservers
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer import com.shabinder.spotiflyer.utils.autoclear.LifecycleAutoInitializer
class LifecycleStartAndStopObserver<T: Any?>( class LifecycleStartAndStopObserver<T : Any?>(
private val initializer: (() -> T)? private val initializer: (() -> T)?
) : LifecycleAutoInitializer<T> { ) : LifecycleAutoInitializer<T> {
@ -18,4 +18,4 @@ class LifecycleStartAndStopObserver<T: Any?>(
super.onStop(owner) super.onStop(owner)
value = null value = null
} }
} }

View File

@ -31,9 +31,8 @@ allprojects {
maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions { dependsOn(":common:data-models:generateI18n4kFiles")
jvmTarget = "1.8" kotlinOptions { jvmTarget = "1.8" }
}
} }
afterEvaluate { afterEvaluate {
project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt -> project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt ->

View File

@ -19,26 +19,14 @@ plugins {
id("org.jlleitschuh.gradle.ktlint-idea") id("org.jlleitschuh.gradle.ktlint-idea")
} }
subprojects { ktlint {
apply(plugin = "org.jlleitschuh.gradle.ktlint") outputToConsole.set(true)
apply(plugin = "org.jlleitschuh.gradle.ktlint-idea") ignoreFailures.set(true)
repositories { coloredOutput.set(true)
// Required to download KtLint verbose.set(true)
mavenCentral() disabledRules.set(setOf("filename,no-wildcard-imports"))
filter {
exclude("**/generated/**")
exclude("**/build/**")
} }
ktlint { }
android.set(true)
outputToConsole.set(true)
ignoreFailures.set(true)
coloredOutput.set(true)
verbose.set(true)
filter {
exclude("**/generated/**")
exclude("**/build/**")
}
}
// Optionally configure plugin
/*configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
debug.set(true)
}*/
}

View File

@ -16,4 +16,4 @@ actual fun Dialog(
content() content()
} }
} }
} }

View File

@ -14,4 +14,4 @@ actual fun montserratFont() = FontFamily(
actual fun pristineFont() = FontFamily( actual fun pristineFont() = FontFamily(
Font(R.font.pristine_script, FontWeight.Bold) Font(R.font.pristine_script, FontWeight.Bold)
) )

View File

@ -7,4 +7,4 @@ expect fun Dialog(
isVisible: Boolean, isVisible: Boolean,
onDismiss: () -> Unit, onDismiss: () -> Unit,
content: @Composable () -> Unit content: @Composable () -> Unit
) )

View File

@ -70,4 +70,4 @@ expect fun HeartIcon(): Painter
expect fun DownloadImageError(modifier: Modifier) expect fun DownloadImageError(modifier: Modifier)
@Composable @Composable
expect fun DownloadImageArrow(modifier: Modifier) expect fun DownloadImageArrow(modifier: Modifier)

View File

@ -35,7 +35,7 @@ import com.shabinder.common.uikit.RazorPay
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
import com.shabinder.common.uikit.configurations.colorAccent import com.shabinder.common.uikit.configurations.colorAccent
typealias DonationDialogCallBacks = Triple<openAction,dismissAction,snoozeAction> typealias DonationDialogCallBacks = Triple<openAction, dismissAction, snoozeAction>
internal typealias openAction = () -> Unit internal typealias openAction = () -> Unit
internal typealias dismissAction = () -> Unit internal typealias dismissAction = () -> Unit
private typealias snoozeAction = () -> Unit private typealias snoozeAction = () -> Unit
@ -58,7 +58,7 @@ fun DonationDialogComponent(onDismissExtra: () -> Unit): DonationDialogCallBacks
onDismissExtra() onDismissExtra()
isDonationDialogVisible = false isDonationDialogVisible = false
} }
return DonationDialogCallBacks(openDonationDialog,dismissDonationDialog,snoozeDonationDialog) return DonationDialogCallBacks(openDonationDialog, dismissDonationDialog, snoozeDonationDialog)
} }
@ExperimentalAnimationApi @ExperimentalAnimationApi
@ -68,7 +68,7 @@ fun DonationDialog(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSnooze: () -> Unit onSnooze: () -> Unit
) { ) {
Dialog(isVisible,onDismiss) { Dialog(isVisible, onDismiss) {
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, Color.Gray) // Gray border = BorderStroke(1.dp, Color.Gray) // Gray

View File

@ -31,7 +31,7 @@ import com.shabinder.common.uikit.Dialog
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
import com.shabinder.common.uikit.configurations.colorAccent import com.shabinder.common.uikit.configurations.colorAccent
typealias ErrorInfoDialogCallBacks = Pair<openAction,dismissAction> typealias ErrorInfoDialogCallBacks = Pair<openAction, dismissAction>
@Composable @Composable
fun ErrorInfoDialog(error: Throwable): ErrorInfoDialogCallBacks { fun ErrorInfoDialog(error: Throwable): ErrorInfoDialogCallBacks {
@ -75,5 +75,5 @@ fun ErrorInfoDialog(error: Throwable): ErrorInfoDialogCallBacks {
} }
} }
return ErrorInfoDialogCallBacks(openErrorDialog,onDismissDialog) return ErrorInfoDialogCallBacks(openErrorDialog, onDismissDialog)
} }

View File

@ -121,7 +121,7 @@ fun SpotiFlyerListContent(
) )
// Donation Dialog Visibility // Donation Dialog Visibility
val (openDonationDialog,dismissDonationDialog,snoozeDonationDialog) = DonationDialogComponent { val (openDonationDialog, dismissDonationDialog, snoozeDonationDialog) = DonationDialogComponent {
component.dismissDonationDialogSetOffset() component.dismissDonationDialogSetOffset()
} }
@ -184,11 +184,14 @@ fun TrackCard(
CircularProgressIndicator() CircularProgressIndicator()
} }
is DownloadStatus.Failed -> { is DownloadStatus.Failed -> {
val (openErrorDialog,dismissErrorDialog) = ErrorInfoDialog((track.downloaded as DownloadStatus.Failed).error) val (openErrorDialog, dismissErrorDialog) = ErrorInfoDialog((track.downloaded as DownloadStatus.Failed).error)
Icon(Icons.Rounded.Info,Strings.downloadError(),tint = lightGray,modifier = Modifier.size(42.dp).clickable { Icon(
openErrorDialog() Icons.Rounded.Info, Strings.downloadError(), tint = lightGray,
}.padding(start = 4.dp,end = 12.dp)) modifier = Modifier.size(42.dp).clickable {
openErrorDialog()
}.padding(start = 4.dp, end = 12.dp)
)
DownloadImageError( DownloadImageError(
Modifier.clickable( Modifier.clickable(

View File

@ -106,7 +106,7 @@ import com.shabinder.common.uikit.rememberScrollbarAdapter
fun SpotiFlyerMainContent(component: SpotiFlyerMain) { fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
val model by component.model.subscribeAsState() val model by component.model.subscribeAsState()
val (openDonationDialog,_,_) = DonationDialogComponent { val (openDonationDialog, _, _) = DonationDialogComponent {
component.dismissDonationDialogOffset() component.dismissDonationDialogOffset()
} }
@ -253,7 +253,7 @@ fun SearchPanel(
@Composable @Composable
fun AboutColumn( fun AboutColumn(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
analyticsEnabled:Boolean, analyticsEnabled: Boolean,
openDonationDialog: () -> Unit, openDonationDialog: () -> Unit,
toggleAnalytics: (enabled: Boolean) -> Unit toggleAnalytics: (enabled: Boolean) -> Unit
) { ) {

View File

@ -47,7 +47,7 @@ import com.shabinder.common.uikit.configurations.colorOffWhite
@Composable @Composable
fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) { fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
val model by component.model.subscribeAsState() val model by component.model.subscribeAsState()
val stateVertical = rememberScrollState(0) val stateVertical = rememberScrollState(0)
Column(Modifier.fillMaxSize().padding(8.dp).verticalScroll(stateVertical)) { Column(Modifier.fillMaxSize().padding(8.dp).verticalScroll(stateVertical)) {
@ -82,7 +82,7 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
save() save()
} }
) )
.padding(horizontal = 16.dp,vertical = 2.dp) .padding(horizontal = 16.dp, vertical = 2.dp)
) { ) {
RadioButton( RadioButton(
selected = (quality == model.preferredQuality), selected = (quality == model.preferredQuality),
@ -98,7 +98,6 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
) )
} }
} }
} }
Spacer(Modifier.padding(top = 12.dp)) Spacer(Modifier.padding(top = 12.dp))
@ -155,9 +154,8 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
} }
} }
} }
Spacer(modifier = Modifier.padding(top = 8.dp)) Spacer(modifier = Modifier.padding(top = 8.dp))
} }
} }
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@ -165,7 +163,7 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
fun SettingsRow( fun SettingsRow(
icon: Painter, icon: Painter,
title: String, title: String,
value:String, value: String,
editContent: @Composable ColumnScope.(() -> Unit) -> Unit editContent: @Composable ColumnScope.(() -> Unit) -> Unit
) { ) {
@ -198,4 +196,4 @@ fun SettingsRow(
} }
} }
} }
} }

View File

@ -16,4 +16,4 @@ actual fun Dialog(
content() content()
} }
} }
} }

View File

@ -4,7 +4,6 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.platform.Font import androidx.compose.ui.text.platform.Font
actual fun montserratFont() = FontFamily( actual fun montserratFont() = FontFamily(
Font("font/montserrat_light.ttf", FontWeight.Light), Font("font/montserrat_light.ttf", FontWeight.Light),
Font("font/montserrat_regular.ttf", FontWeight.Normal), Font("font/montserrat_regular.ttf", FontWeight.Normal),
@ -14,4 +13,4 @@ actual fun montserratFont() = FontFamily(
actual fun pristineFont() = FontFamily( actual fun pristineFont() = FontFamily(
Font("font/pristine_script.ttf", FontWeight.Bold) Font("font/pristine_script.ttf", FontWeight.Bold)
) )

View File

@ -29,9 +29,9 @@ val statelyVersion = "1.1.7"
val statelyIsoVersion = "1.1.7-a1" val statelyIsoVersion = "1.1.7-a1"
i18n4k { i18n4k {
inputDirectory = "../../translations" inputDirectory = "../../translations"
packageName = "com.shabinder.common.translations" packageName = "com.shabinder.common.translations"
// sourceCodeLocales = listOf("en", "de", "es", "fr", "id", "pt", "ru", "uk") // sourceCodeLocales = listOf("en", "de", "es", "fr", "id", "pt", "ru", "uk")
} }
kotlin { kotlin {

View File

@ -1,3 +1,3 @@
package com.shabinder.common package com.shabinder.common
fun <T: Any?> T?.requireNotNull() : T = requireNotNull(this) fun <T : Any?> T?.requireNotNull(): T = requireNotNull(this)

View File

@ -49,4 +49,4 @@ sealed class CorsProxy(open val url: String) {
* */ * */
var corsProxy: CorsProxy = CorsProxy.SelfHostedCorsProxy() var corsProxy: CorsProxy = CorsProxy.SelfHostedCorsProxy()
val corsApi get() = if (activePlatform is TargetPlatforms.Js) corsProxy.url else "" val corsApi get() = if (activePlatform is TargetPlatforms.Js) corsProxy.url else ""

View File

@ -2,42 +2,42 @@ package com.shabinder.common.models
import com.shabinder.common.translations.Strings import com.shabinder.common.translations.Strings
sealed class SpotiFlyerException(override val message: String): Exception(message) { sealed class SpotiFlyerException(override val message: String) : Exception(message) {
data class FeatureNotImplementedYet(override val message: String = Strings.featureUnImplemented()): SpotiFlyerException(message) data class FeatureNotImplementedYet(override val message: String = Strings.featureUnImplemented()) : SpotiFlyerException(message)
data class NoInternetException(override val message: String = Strings.checkInternetConnection()): SpotiFlyerException(message) data class NoInternetException(override val message: String = Strings.checkInternetConnection()) : SpotiFlyerException(message)
data class MP3ConversionFailed( data class MP3ConversionFailed(
val extraInfo:String? = null, val extraInfo: String? = null,
override val message: String = "${Strings.mp3ConverterBusy()} \nCAUSE:$extraInfo" override val message: String = "${Strings.mp3ConverterBusy()} \nCAUSE:$extraInfo"
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
data class UnknownReason( data class UnknownReason(
val exception: Throwable? = null, val exception: Throwable? = null,
override val message: String = Strings.unknownError() override val message: String = Strings.unknownError()
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
data class NoMatchFound( data class NoMatchFound(
val trackName: String? = null, val trackName: String? = null,
override val message: String = "$trackName : ${Strings.noMatchFound()}" override val message: String = "$trackName : ${Strings.noMatchFound()}"
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
data class YoutubeLinkNotFound( data class YoutubeLinkNotFound(
val videoID: String? = null, val videoID: String? = null,
override val message: String = "${Strings.noLinkFound()}: $videoID" override val message: String = "${Strings.noLinkFound()}: $videoID"
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
data class DownloadLinkFetchFailed( data class DownloadLinkFetchFailed(
val trackName: String, val trackName: String,
val jioSaavnError: Throwable, val jioSaavnError: Throwable,
val ytMusicError: Throwable, val ytMusicError: Throwable,
override val message: String = "${Strings.noLinkFound()}: $trackName," + override val message: String = "${Strings.noLinkFound()}: $trackName," +
" \n YtMusic Error's StackTrace: ${ytMusicError.stackTraceToString()} \n " + " \n YtMusic Error's StackTrace: ${ytMusicError.stackTraceToString()} \n " +
" \n JioSaavn Error's StackTrace: ${jioSaavnError.stackTraceToString()} \n " " \n JioSaavn Error's StackTrace: ${jioSaavnError.stackTraceToString()} \n "
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
data class LinkInvalid( data class LinkInvalid(
val link: String? = null, val link: String? = null,
override val message: String = "${Strings.linkNotValid()}\n ${link ?: ""}" override val message: String = "${Strings.linkNotValid()}\n ${link ?: ""}"
): SpotiFlyerException(message) ) : SpotiFlyerException(message)
} }

View File

@ -130,8 +130,7 @@ inline fun <V, E : Throwable> Event<V, E>.unwrap(failure: (E) -> Nothing): V =
inline fun <V, E : Throwable> Event<V, E>.unwrapError(success: (V) -> Nothing): E = inline fun <V, E : Throwable> Event<V, E>.unwrapError(success: (V) -> Nothing): E =
apply { component1()?.let(success) }.component2()!! apply { component1()?.let(success) }.component2()!!
sealed class Event<out V : Any?, out E : Throwable> : ReadOnlyProperty<Any?, V> {
sealed class Event<out V : Any?, out E : Throwable>: ReadOnlyProperty<Any?, V> {
open operator fun component1(): V? = null open operator fun component1(): V? = null
open operator fun component2(): E? = null open operator fun component2(): E? = null
@ -204,4 +203,4 @@ sealed class Event<out V : Any?, out E : Throwable>: ReadOnlyProperty<Any?, V> {
error(ex) error(ex)
} }
} }
} }

View File

@ -14,4 +14,4 @@ inline infix fun <T, V> T.runCatching(block: T.() -> V): Event<V, Throwable> {
} catch (e: Throwable) { } catch (e: Throwable) {
Event.error(e) Event.error(e)
} }
} }

View File

@ -5,4 +5,4 @@ class Validation<out E : Throwable>(vararg resultSequence: Event<*, E>) {
val failures: List<E> = resultSequence.filterIsInstance<Event.Failure<E>>().map { it.getThrowable() } val failures: List<E> = resultSequence.filterIsInstance<Event.Failure<E>>().map { it.getThrowable() }
val hasFailure = failures.isNotEmpty() val hasFailure = failures.isNotEmpty()
} }

View File

@ -19,7 +19,7 @@ infix fun <V : Any?, E : Throwable> SuspendableEvent<V, E>.or(fallback: V) = whe
else -> SuspendableEvent.Success(fallback) else -> SuspendableEvent.Success(fallback)
} }
suspend inline infix fun <V : Any?, E : Throwable> SuspendableEvent<V, E>.getOrElse(crossinline fallback:suspend (E) -> V): V { suspend inline infix fun <V : Any?, E : Throwable> SuspendableEvent<V, E>.getOrElse(crossinline fallback: suspend (E) -> V): V {
return when (this) { return when (this) {
is SuspendableEvent.Success -> value is SuspendableEvent.Success -> value
is SuspendableEvent.Failure -> fallback(error) is SuspendableEvent.Failure -> fallback(error)
@ -93,7 +93,6 @@ suspend inline fun <V : Any?, U : Any> SuspendableEvent<V, *>.fanout(
): SuspendableEvent<Pair<V, U>, *> = ): SuspendableEvent<Pair<V, U>, *> =
flatMap { outer -> other().map { outer to it } } flatMap { outer -> other().map { outer to it } }
suspend fun <V : Any?, E : Throwable> List<SuspendableEvent<V, E>>.lift(): SuspendableEvent<List<V>, E> = fold( suspend fun <V : Any?, E : Throwable> List<SuspendableEvent<V, E>>.lift(): SuspendableEvent<List<V>, E> = fold(
SuspendableEvent.Success<MutableList<V>, E>(mutableListOf<V>()) as SuspendableEvent<MutableList<V>, E> SuspendableEvent.Success<MutableList<V>, E>(mutableListOf<V>()) as SuspendableEvent<MutableList<V>, E>
) { acc, result -> ) { acc, result ->
@ -102,7 +101,7 @@ suspend fun <V : Any?, E : Throwable> List<SuspendableEvent<V, E>>.lift(): Suspe
} }
} }
sealed class SuspendableEvent<out V : Any?, out E : Throwable>: ReadOnlyProperty<Any?,V> { sealed class SuspendableEvent<out V : Any?, out E : Throwable> : ReadOnlyProperty<Any?, V> {
abstract operator fun component1(): V? abstract operator fun component1(): V?
abstract operator fun component2(): E? abstract operator fun component2(): E?
@ -156,7 +155,7 @@ sealed class SuspendableEvent<out V : Any?, out E : Throwable>: ReadOnlyProperty
// Factory methods // Factory methods
fun <E : Throwable> error(ex: E) = Failure<Nothing, E>(ex) fun <E : Throwable> error(ex: E) = Failure<Nothing, E>(ex)
inline fun <V : Any?> of(value: V?,crossinline fail: (() -> Throwable) = { Throwable() }): SuspendableEvent<V, Throwable> { inline fun <V : Any?> of(value: V?, crossinline fail: (() -> Throwable) = { Throwable() }): SuspendableEvent<V, Throwable> {
return value?.let { Success<V, Nothing>(it) } ?: error(fail()) return value?.let { Success<V, Nothing>(it) } ?: error(fail())
} }
@ -172,5 +171,4 @@ sealed class SuspendableEvent<out V : Any?, out E : Throwable>: ReadOnlyProperty
crossinline block: suspend () -> V crossinline block: suspend () -> V
): SuspendableEvent<V, Throwable> = of(block) ): SuspendableEvent<V, Throwable> = of(block)
} }
}
}

View File

@ -5,5 +5,4 @@ class SuspendedValidation<out E : Throwable>(vararg resultSequence: SuspendableE
val failures: List<E> = resultSequence.filterIsInstance<SuspendableEvent.Failure<*, E>>().map { it.getThrowable() } val failures: List<E> = resultSequence.filterIsInstance<SuspendableEvent.Failure<*, E>>().map { it.getThrowable() }
val hasFailure = failures.isNotEmpty() val hasFailure = failures.isNotEmpty()
}
}

View File

@ -28,9 +28,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.IOException
import java.lang.Exception import java.lang.Exception
import java.net.InetSocketAddress
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection

View File

@ -40,7 +40,7 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
album = track.albumName album = track.albumName
year = track.year year = track.year
comment = "Genres:${track.comment}" comment = "Genres:${track.comment}"
if(track.trackNumber != null) if (track.trackNumber != null)
this.track = track.trackNumber.toString() this.track = track.trackNumber.toString()
} }
this.id3v1Tag = id3v1Tag this.id3v1Tag = id3v1Tag
@ -60,7 +60,7 @@ suspend fun Mp3File.setId3v2TagsAndSaveFile(track: TrackDetails) {
comment = track.comment comment = track.comment
lyrics = track.lyrics ?: "" lyrics = track.lyrics ?: ""
url = track.trackUrl url = track.trackUrl
if(track.trackNumber != null) if (track.trackNumber != null)
this.track = track.trackNumber.toString() this.track = track.trackNumber.toString()
} }
try { try {

View File

@ -59,7 +59,7 @@ fun Dir.createDirectories() {
createDirectory(defaultDir() + "Albums/") createDirectory(defaultDir() + "Albums/")
createDirectory(defaultDir() + "Playlists/") createDirectory(defaultDir() + "Playlists/")
createDirectory(defaultDir() + "YT_Downloads/") createDirectory(defaultDir() + "YT_Downloads/")
} catch (e:Exception){} } catch (e: Exception) {}
} }
fun Dir.finalOutputDir(itemName: String, type: String, subFolder: String, defaultDir: String, extension: String = ".mp3"): String = fun Dir.finalOutputDir(itemName: String, type: String, subFolder: String, defaultDir: String, extension: String = ".mp3"): String =

View File

@ -58,7 +58,7 @@ class FetchPlatformQueryResult(
suspend fun authenticateSpotifyClient() = spotifyProvider.authenticateSpotifyClient() suspend fun authenticateSpotifyClient() = spotifyProvider.authenticateSpotifyClient()
suspend fun query(link: String): SuspendableEvent<PlatformQueryResult,Throwable> { suspend fun query(link: String): SuspendableEvent<PlatformQueryResult, Throwable> {
val result = when { val result = when {
// SPOTIFY // SPOTIFY
link.contains("spotify", true) -> link.contains("spotify", true) ->
@ -94,17 +94,17 @@ class FetchPlatformQueryResult(
suspend fun findMp3DownloadLink( suspend fun findMp3DownloadLink(
track: TrackDetails, track: TrackDetails,
preferredQuality: AudioQuality = preferenceManager.audioQuality preferredQuality: AudioQuality = preferenceManager.audioQuality
): SuspendableEvent<String,Throwable> = ): SuspendableEvent<String, Throwable> =
if (track.videoID != null) { if (track.videoID != null) {
// We Already have VideoID // We Already have VideoID
when (track.source) { when (track.source) {
Source.JioSaavn -> { Source.JioSaavn -> {
saavnProvider.getSongFromID(track.videoID.requireNotNull()).flatMap { song -> saavnProvider.getSongFromID(track.videoID.requireNotNull()).flatMap { song ->
song.media_url?.let { audioToMp3.convertToMp3(it) } ?: findMp3Link(track,preferredQuality) song.media_url?.let { audioToMp3.convertToMp3(it) } ?: findMp3Link(track, preferredQuality)
} }
} }
Source.YouTube -> { Source.YouTube -> {
youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull(),preferredQuality).flatMapError { youtubeMp3.getMp3DownloadLink(track.videoID.requireNotNull(), preferredQuality).flatMapError {
logger.e("Yt1sMp3 Failed") { it.message ?: "couldn't fetch link for ${track.videoID} ,trying manual extraction" } logger.e("Yt1sMp3 Failed") { it.message ?: "couldn't fetch link for ${track.videoID} ,trying manual extraction" }
youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url?.let { m4aLink -> youtubeProvider.ytDownloader.getVideo(track.videoID!!).get()?.url?.let { m4aLink ->
audioToMp3.convertToMp3(m4aLink) audioToMp3.convertToMp3(m4aLink)
@ -113,17 +113,17 @@ class FetchPlatformQueryResult(
} }
else -> { else -> {
/*We should never reach here for now*/ /*We should never reach here for now*/
findMp3Link(track,preferredQuality) findMp3Link(track, preferredQuality)
} }
} }
} else { } else {
findMp3Link(track,preferredQuality) findMp3Link(track, preferredQuality)
} }
private suspend fun findMp3Link( private suspend fun findMp3Link(
track: TrackDetails, track: TrackDetails,
preferredQuality: AudioQuality preferredQuality: AudioQuality
):SuspendableEvent<String,Throwable> { ): SuspendableEvent<String, Throwable> {
// Try Fetching Track from Jio Saavn // Try Fetching Track from Jio Saavn
return saavnProvider.findMp3SongDownloadURL( return saavnProvider.findMp3SongDownloadURL(
trackName = track.title, trackName = track.title,
@ -132,11 +132,11 @@ class FetchPlatformQueryResult(
).flatMapError { saavnError -> ).flatMapError { saavnError ->
logger.e { "Fetching From Saavn Failed: \n${saavnError.stackTraceToString()}" } logger.e { "Fetching From Saavn Failed: \n${saavnError.stackTraceToString()}" }
// Saavn Failed, Lets Try Fetching Now From Youtube Music // Saavn Failed, Lets Try Fetching Now From Youtube Music
youtubeMusic.findMp3SongDownloadURLYT(track,preferredQuality).flatMapError { ytMusicError -> youtubeMusic.findMp3SongDownloadURLYT(track, preferredQuality).flatMapError { ytMusicError ->
// If Both Failed Bubble the Exception Up with both StackTraces // If Both Failed Bubble the Exception Up with both StackTraces
SuspendableEvent.error( SuspendableEvent.error(
SpotiFlyerException.DownloadLinkFetchFailed( SpotiFlyerException.DownloadLinkFetchFailed(
trackName = track.title, trackName = track.title,
ytMusicError = ytMusicError, ytMusicError = ytMusicError,
jioSaavnError = saavnError jioSaavnError = saavnError
) )

View File

@ -3,7 +3,7 @@ package com.shabinder.common.di.preference
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality
class PreferenceManager(settings: Settings): Settings by settings { class PreferenceManager(settings: Settings) : Settings by settings {
companion object { companion object {
const val DIR_KEY = "downloadDir" const val DIR_KEY = "downloadDir"
@ -17,14 +17,13 @@ class PreferenceManager(settings: Settings): Settings by settings {
val isAnalyticsEnabled get() = getBooleanOrNull(ANALYTICS_KEY) ?: false val isAnalyticsEnabled get() = getBooleanOrNull(ANALYTICS_KEY) ?: false
fun toggleAnalytics(enabled: Boolean) = putBoolean(ANALYTICS_KEY, enabled) fun toggleAnalytics(enabled: Boolean) = putBoolean(ANALYTICS_KEY, enabled)
/* DOWNLOAD DIRECTORY */ /* DOWNLOAD DIRECTORY */
val downloadDir get() = getStringOrNull(DIR_KEY) val downloadDir get() = getStringOrNull(DIR_KEY)
fun setDownloadDirectory(newBasePath: String) = putString(DIR_KEY, newBasePath) fun setDownloadDirectory(newBasePath: String) = putString(DIR_KEY, newBasePath)
/* Preferred Audio Quality */ /* Preferred Audio Quality */
val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320") val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320")
fun setPreferredAudioQuality(quality: AudioQuality) = putString(PREFERRED_AUDIO_QUALITY,quality.kbps) fun setPreferredAudioQuality(quality: AudioQuality) = putString(PREFERRED_AUDIO_QUALITY, quality.kbps)
/* OFFSET FOR WHEN TO ASK FOR SUPPORT */ /* OFFSET FOR WHEN TO ASK FOR SUPPORT */
val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also { val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also {
@ -33,8 +32,7 @@ class PreferenceManager(settings: Settings): Settings by settings {
} }
fun setDonationOffset(offset: Int = 5) = putInt(DONATION_INTERVAL, offset) fun setDonationOffset(offset: Int = 5) = putInt(DONATION_INTERVAL, offset)
/* TO CHECK IF THIS IS APP's FIRST LAUNCH */ /* TO CHECK IF THIS IS APP's FIRST LAUNCH */
val isFirstLaunch get() = getBooleanOrNull(FIRST_LAUNCH) ?: true val isFirstLaunch get() = getBooleanOrNull(FIRST_LAUNCH) ?: true
fun firstLaunchDone() = putBoolean(FIRST_LAUNCH, false) fun firstLaunchDone() = putBoolean(FIRST_LAUNCH, false)
} }

View File

@ -37,7 +37,7 @@ class GaanaProvider(
private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg" private val gaanaPlaceholderImageUrl = "https://a10.gaanacdn.com/images/social/gaana_social.jpg"
suspend fun query(fullLink: String): SuspendableEvent<PlatformQueryResult,Throwable> = SuspendableEvent { suspend fun query(fullLink: String): SuspendableEvent<PlatformQueryResult, Throwable> = SuspendableEvent {
// Link Schema: https://gaana.com/type/link // Link Schema: https://gaana.com/type/link
val gaanaLink = fullLink.substringAfter("gaana.com/") val gaanaLink = fullLink.substringAfter("gaana.com/")

View File

@ -13,4 +13,4 @@ fun providersModule() = module {
single { YoutubeMp3(get(), get()) } single { YoutubeMp3(get(), get()) }
single { YoutubeMusic(get(), get(), get(), get(), get()) } single { YoutubeMusic(get(), get(), get(), get(), get()) }
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
} }

View File

@ -33,7 +33,7 @@ class SaavnProvider(
).apply { ).apply {
val pageLink = fullLink.substringAfter("saavn.com/").substringBefore("?") val pageLink = fullLink.substringAfter("saavn.com/").substringBefore("?")
when { when {
pageLink.contains("/song/",true) -> { pageLink.contains("/song/", true) -> {
getSong(fullLink).value.let { getSong(fullLink).value.let {
folderType = "Tracks" folderType = "Tracks"
subFolder = "" subFolder = ""
@ -42,7 +42,7 @@ class SaavnProvider(
coverUrl = it.image.replace("http:", "https:") coverUrl = it.image.replace("http:", "https:")
} }
} }
pageLink.contains("/album/",true) -> { pageLink.contains("/album/", true) -> {
getAlbum(fullLink).value.let { getAlbum(fullLink).value.let {
folderType = "Albums" folderType = "Albums"
subFolder = removeIllegalChars(it.title) subFolder = removeIllegalChars(it.title)
@ -51,7 +51,7 @@ class SaavnProvider(
coverUrl = it.image.replace("http:", "https:") coverUrl = it.image.replace("http:", "https:")
} }
} }
pageLink.contains("/featured/",true) -> { // Playlist pageLink.contains("/featured/", true) -> { // Playlist
getPlaylist(fullLink).value.let { getPlaylist(fullLink).value.let {
folderType = "Playlists" folderType = "Playlists"
subFolder = removeIllegalChars(it.listname) subFolder = removeIllegalChars(it.listname)

View File

@ -24,7 +24,7 @@ import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map
import io.ktor.client.* import io.ktor.client.*
interface YoutubeMp3: Yt1sMp3 { interface YoutubeMp3 : Yt1sMp3 {
companion object { companion object {
operator fun invoke( operator fun invoke(
@ -38,7 +38,7 @@ interface YoutubeMp3: Yt1sMp3 {
} }
} }
suspend fun getMp3DownloadLink(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = getLinkFromYt1sMp3(videoID,quality).map { suspend fun getMp3DownloadLink(videoID: String, quality: AudioQuality): SuspendableEvent<String, Throwable> = getLinkFromYt1sMp3(videoID, quality).map {
corsApi + it corsApi + it
} }
} }

View File

@ -63,7 +63,7 @@ class YoutubeMusic constructor(
): SuspendableEvent<String, Throwable> { ): SuspendableEvent<String, Throwable> {
return getYTIDBestMatch(trackDetails).flatMap { videoID -> return getYTIDBestMatch(trackDetails).flatMap { videoID ->
// As YT compress Audio hence there is no benefit of quality for more than 192 // As YT compress Audio hence there is no benefit of quality for more than 192
val optimalQuality = if((preferredQuality.kbps.toIntOrNull() ?: 0) > 192) AudioQuality.KBPS192 else preferredQuality val optimalQuality = if ((preferredQuality.kbps.toIntOrNull() ?: 0) > 192) AudioQuality.KBPS192 else preferredQuality
// 1 Try getting Link from Yt1s // 1 Try getting Link from Yt1s
youtubeMp3.getMp3DownloadLink(videoID, optimalQuality).flatMapError { youtubeMp3.getMp3DownloadLink(videoID, optimalQuality).flatMapError {
// 2 if Yt1s failed , Extract Manually // 2 if Yt1s failed , Extract Manually
@ -79,7 +79,7 @@ class YoutubeMusic constructor(
private suspend fun getYTIDBestMatch( private suspend fun getYTIDBestMatch(
trackDetails: TrackDetails trackDetails: TrackDetails
):SuspendableEvent<String,Throwable> = ): SuspendableEvent<String, Throwable> =
getYTTracks("${trackDetails.title} - ${trackDetails.artists.joinToString(",")}").map { matchList -> getYTTracks("${trackDetails.title} - ${trackDetails.artists.joinToString(",")}").map { matchList ->
sortByBestMatch( sortByBestMatch(
matchList, matchList,
@ -89,7 +89,7 @@ class YoutubeMusic constructor(
).keys.firstOrNull() ?: throw SpotiFlyerException.NoMatchFound(trackDetails.title) ).keys.firstOrNull() ?: throw SpotiFlyerException.NoMatchFound(trackDetails.title)
} }
private suspend fun getYTTracks(query: String): SuspendableEvent<List<YoutubeTrack>,Throwable> = private suspend fun getYTTracks(query: String): SuspendableEvent<List<YoutubeTrack>, Throwable> =
getYoutubeMusicResponse(query).map { youtubeResponseData -> getYoutubeMusicResponse(query).map { youtubeResponseData ->
val youtubeTracks = mutableListOf<YoutubeTrack>() val youtubeTracks = mutableListOf<YoutubeTrack>()
val responseObj = Json.parseToJsonElement(youtubeResponseData) val responseObj = Json.parseToJsonElement(youtubeResponseData)
@ -233,9 +233,9 @@ class YoutubeMusic constructor(
} }
} }
} }
// logger.d {youtubeTracks.joinToString("\n")} // logger.d {youtubeTracks.joinToString("\n")}
youtubeTracks youtubeTracks
} }
private fun sortByBestMatch( private fun sortByBestMatch(
ytTracks: List<YoutubeTrack>, ytTracks: List<YoutubeTrack>,
@ -311,7 +311,7 @@ class YoutubeMusic constructor(
} }
} }
private suspend fun getYoutubeMusicResponse(query: String): SuspendableEvent<String,Throwable> = SuspendableEvent { private suspend fun getYoutubeMusicResponse(query: String): SuspendableEvent<String, Throwable> = SuspendableEvent {
httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") { httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
headers { headers {

View File

@ -51,7 +51,7 @@ class YoutubeProvider(
private val sampleDomain2 = "youtube.com" private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be" private val sampleDomain3 = "youtu.be"
suspend fun query(fullLink: String): SuspendableEvent<PlatformQueryResult,Throwable> { suspend fun query(fullLink: String): SuspendableEvent<PlatformQueryResult, Throwable> {
val link = fullLink.removePrefix("https://").removePrefix("http://") val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) { if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist // Given Link is of a Playlist
@ -86,7 +86,7 @@ class YoutubeProvider(
private suspend fun getYTPlaylist( private suspend fun getYTPlaylist(
searchId: String searchId: String
): SuspendableEvent<PlatformQueryResult,Throwable> = SuspendableEvent { ): SuspendableEvent<PlatformQueryResult, Throwable> = SuspendableEvent {
PlatformQueryResult( PlatformQueryResult(
folderType = "", folderType = "",
subFolder = "", subFolder = "",
@ -102,7 +102,7 @@ class YoutubeProvider(
val videos = playlist.videos val videos = playlist.videos
coverUrl = "https://i.ytimg.com/vi/${ coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId videos.firstOrNull()?.videoId
}/hqdefault.jpg" }/hqdefault.jpg"
title = name title = name
@ -116,11 +116,11 @@ class YoutubeProvider(
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
downloaded = if (dir.isPresent( downloaded = if (dir.isPresent(
dir.finalOutputDir( dir.finalOutputDir(
itemName = it.title ?: "N/A", itemName = it.title ?: "N/A",
type = folderType, type = folderType,
subFolder = subFolder, subFolder = subFolder,
dir.defaultDir() dir.defaultDir()
) )
) )
) )
DownloadStatus.Downloaded DownloadStatus.Downloaded
@ -137,7 +137,7 @@ class YoutubeProvider(
@Suppress("DefaultLocale") @Suppress("DefaultLocale")
private suspend fun getYTTrack( private suspend fun getYTTrack(
searchId: String, searchId: String,
): SuspendableEvent<PlatformQueryResult,Throwable> = SuspendableEvent { ): SuspendableEvent<PlatformQueryResult, Throwable> = SuspendableEvent {
PlatformQueryResult( PlatformQueryResult(
folderType = "", folderType = "",
subFolder = "", subFolder = "",
@ -162,11 +162,11 @@ class YoutubeProvider(
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg", albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent( downloaded = if (dir.isPresent(
dir.finalOutputDir( dir.finalOutputDir(
itemName = name, itemName = name,
type = folderType, type = folderType,
subFolder = subFolder, subFolder = subFolder,
defaultDir = dir.defaultDir() defaultDir = dir.defaultDir()
) )
) )
) )
DownloadStatus.Downloaded DownloadStatus.Downloaded

View File

@ -32,10 +32,10 @@ interface AudioToMp3 {
suspend fun convertToMp3( suspend fun convertToMp3(
URL: String, URL: String,
audioQuality: AudioQuality = AudioQuality.getQuality(URL.substringBeforeLast(".").takeLast(3)), audioQuality: AudioQuality = AudioQuality.getQuality(URL.substringBeforeLast(".").takeLast(3)),
): SuspendableEvent<String,Throwable> = SuspendableEvent { ): SuspendableEvent<String, Throwable> = SuspendableEvent {
// Active Host ex - https://hostveryfast.onlineconverter.com/file/send // Active Host ex - https://hostveryfast.onlineconverter.com/file/send
// Convert Job Request ex - https://www.onlineconverter.com/convert/309a0f2bbaeb5687b04f96b6d65b47bfdd // Convert Job Request ex - https://www.onlineconverter.com/convert/309a0f2bbaeb5687b04f96b6d65b47bfdd
var (activeHost,jobLink) = convertRequest(URL, audioQuality).value var (activeHost, jobLink) = convertRequest(URL, audioQuality).value
// (jobStatus.contains("d")) == COMPLETION // (jobStatus.contains("d")) == COMPLETION
var jobStatus: String var jobStatus: String
@ -48,7 +48,7 @@ interface AudioToMp3 {
) )
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
if(e is ClientRequestException && e.response.status.value == 404) { if (e is ClientRequestException && e.response.status.value == 404) {
// No Need to Retry, Host/Converter is Busy // No Need to Retry, Host/Converter is Busy
throw SpotiFlyerException.MP3ConversionFailed(e.message) throw SpotiFlyerException.MP3ConversionFailed(e.message)
} }
@ -74,7 +74,7 @@ interface AudioToMp3 {
private suspend fun convertRequest( private suspend fun convertRequest(
URL: String, URL: String,
audioQuality: AudioQuality = AudioQuality.KBPS160, audioQuality: AudioQuality = AudioQuality.KBPS160,
): SuspendableEvent<Pair<String,String>,Throwable> = SuspendableEvent { ): SuspendableEvent<Pair<String, String>, Throwable> = SuspendableEvent {
val activeHost by getHost() val activeHost by getHost()
val convertJob = client.submitFormWithBinaryData<String>( val convertJob = client.submitFormWithBinaryData<String>(
url = activeHost, url = activeHost,
@ -104,17 +104,17 @@ interface AudioToMp3 {
}.execute() }.execute()
logger.i("Schedule Conversion Job") { job.status.isSuccess().toString() } logger.i("Schedule Conversion Job") { job.status.isSuccess().toString() }
Pair(activeHost,convertJob) Pair(activeHost, convertJob)
} }
// Active Host free to process conversion // Active Host free to process conversion
// ex - https://hostveryfast.onlineconverter.com/file/send // ex - https://hostveryfast.onlineconverter.com/file/send
private suspend fun getHost(): SuspendableEvent<String,Throwable> = SuspendableEvent { private suspend fun getHost(): SuspendableEvent<String, Throwable> = SuspendableEvent {
client.get<String>("https://www.onlineconverter.com/get/host") { client.get<String>("https://www.onlineconverter.com/get/host") {
headers { headers {
header("Host", "www.onlineconverter.com") header("Host", "www.onlineconverter.com")
} }
}//.also { logger.i("Active Host") { it } } } // .also { logger.i("Active Host") { it } }
} }
// Extract full Domain from URL // Extract full Domain from URL

View File

@ -25,7 +25,6 @@ import com.shabinder.common.models.gaana.GaanaSong
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.request.* import io.ktor.client.request.*
private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990" private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990"
private val BASE_URL get() = "${corsApi}https://api.gaana.com" private val BASE_URL get() = "${corsApi}https://api.gaana.com"

View File

@ -40,12 +40,12 @@ interface JioSaavnRequests {
trackName: String, trackName: String,
trackArtists: List<String>, trackArtists: List<String>,
preferredQuality: AudioQuality preferredQuality: AudioQuality
): SuspendableEvent<String,Throwable> = searchForSong(trackName).map { songs -> ): SuspendableEvent<String, Throwable> = searchForSong(trackName).map { songs ->
val bestMatches = sortByBestMatch(songs, trackName, trackArtists) val bestMatches = sortByBestMatch(songs, trackName, trackArtists)
val m4aLink: String by getSongFromID(bestMatches.keys.first()).map { song -> val m4aLink: String by getSongFromID(bestMatches.keys.first()).map { song ->
val optimalQuality = if(song.is320Kbps && ((preferredQuality.kbps.toIntOrNull() ?: 0) > 160)) AudioQuality.KBPS320 else AudioQuality.KBPS160 val optimalQuality = if (song.is320Kbps && ((preferredQuality.kbps.toIntOrNull() ?: 0) > 160)) AudioQuality.KBPS320 else AudioQuality.KBPS160
song.media_url.requireNotNull().replaceAfterLast("_","${optimalQuality.kbps}.mp4") song.media_url.requireNotNull().replaceAfterLast("_", "${optimalQuality.kbps}.mp4")
} }
val mp3Link by audioToMp3.convertToMp3(m4aLink) val mp3Link by audioToMp3.convertToMp3(m4aLink)
@ -56,7 +56,7 @@ interface JioSaavnRequests {
suspend fun searchForSong( suspend fun searchForSong(
query: String, query: String,
includeLyrics: Boolean = false includeLyrics: Boolean = false
): SuspendableEvent<List<SaavnSearchResult>,Throwable> = SuspendableEvent { ): SuspendableEvent<List<SaavnSearchResult>, Throwable> = SuspendableEvent {
val searchURL = search_base_url + query val searchURL = search_base_url + query
val results = mutableListOf<SaavnSearchResult>() val results = mutableListOf<SaavnSearchResult>()
@ -67,12 +67,12 @@ interface JioSaavnRequests {
(it as JsonObject).formatData().let { jsonObject -> (it as JsonObject).formatData().let { jsonObject ->
results.add(globalJson.decodeFromJsonElement(SaavnSearchResult.serializer(), jsonObject)) results.add(globalJson.decodeFromJsonElement(SaavnSearchResult.serializer(), jsonObject))
} }
} }
results results
} }
suspend fun getLyrics(ID: String): SuspendableEvent<String,Throwable> = SuspendableEvent { suspend fun getLyrics(ID: String): SuspendableEvent<String, Throwable> = SuspendableEvent {
(Json.parseToJsonElement(httpClient.get(lyrics_base_url + ID)) as JsonObject) (Json.parseToJsonElement(httpClient.get(lyrics_base_url + ID)) as JsonObject)
.getString("lyrics").requireNotNull() .getString("lyrics").requireNotNull()
} }
@ -80,7 +80,7 @@ interface JioSaavnRequests {
suspend fun getSong( suspend fun getSong(
URL: String, URL: String,
fetchLyrics: Boolean = false fetchLyrics: Boolean = false
): SuspendableEvent<SaavnSong,Throwable> = SuspendableEvent { ): SuspendableEvent<SaavnSong, Throwable> = SuspendableEvent {
val id = getSongID(URL) val id = getSongID(URL)
val data = ((globalJson.parseToJsonElement(httpClient.get(song_details_base_url + id)) as JsonObject)[id] as JsonObject) val data = ((globalJson.parseToJsonElement(httpClient.get(song_details_base_url + id)) as JsonObject)[id] as JsonObject)
.formatData(fetchLyrics) .formatData(fetchLyrics)
@ -91,7 +91,7 @@ interface JioSaavnRequests {
suspend fun getSongFromID( suspend fun getSongFromID(
ID: String, ID: String,
fetchLyrics: Boolean = false fetchLyrics: Boolean = false
): SuspendableEvent<SaavnSong,Throwable> = SuspendableEvent { ): SuspendableEvent<SaavnSong, Throwable> = SuspendableEvent {
val data = ((globalJson.parseToJsonElement(httpClient.get(song_details_base_url + ID)) as JsonObject)[ID] as JsonObject) val data = ((globalJson.parseToJsonElement(httpClient.get(song_details_base_url + ID)) as JsonObject)[ID] as JsonObject)
.formatData(fetchLyrics) .formatData(fetchLyrics)
@ -112,7 +112,7 @@ interface JioSaavnRequests {
suspend fun getPlaylist( suspend fun getPlaylist(
URL: String, URL: String,
includeLyrics: Boolean = false includeLyrics: Boolean = false
): SuspendableEvent<SaavnPlaylist,Throwable> = SuspendableEvent { ): SuspendableEvent<SaavnPlaylist, Throwable> = SuspendableEvent {
globalJson.decodeFromJsonElement( globalJson.decodeFromJsonElement(
SaavnPlaylist.serializer(), SaavnPlaylist.serializer(),
(globalJson.parseToJsonElement(httpClient.get(playlist_details_base_url + getPlaylistID(URL).value)) as JsonObject) (globalJson.parseToJsonElement(httpClient.get(playlist_details_base_url + getPlaylistID(URL).value)) as JsonObject)
@ -122,7 +122,7 @@ interface JioSaavnRequests {
private suspend fun getPlaylistID( private suspend fun getPlaylistID(
URL: String URL: String
): SuspendableEvent<String,Throwable> = SuspendableEvent { ): SuspendableEvent<String, Throwable> = SuspendableEvent {
val res = httpClient.get<String>(URL) val res = httpClient.get<String>(URL)
try { try {
res.split("\"type\":\"playlist\",\"id\":\"")[1].split('"')[0] res.split("\"type\":\"playlist\",\"id\":\"")[1].split('"')[0]
@ -134,7 +134,7 @@ interface JioSaavnRequests {
suspend fun getAlbum( suspend fun getAlbum(
URL: String, URL: String,
includeLyrics: Boolean = false includeLyrics: Boolean = false
): SuspendableEvent<SaavnAlbum,Throwable> = SuspendableEvent { ): SuspendableEvent<SaavnAlbum, Throwable> = SuspendableEvent {
globalJson.decodeFromJsonElement( globalJson.decodeFromJsonElement(
SaavnAlbum.serializer(), SaavnAlbum.serializer(),
(globalJson.parseToJsonElement(httpClient.get(album_details_base_url + getAlbumID(URL).value)) as JsonObject) (globalJson.parseToJsonElement(httpClient.get(album_details_base_url + getAlbumID(URL).value)) as JsonObject)
@ -144,7 +144,7 @@ interface JioSaavnRequests {
private suspend fun getAlbumID( private suspend fun getAlbumID(
URL: String URL: String
): SuspendableEvent<String,Throwable> = SuspendableEvent { ): SuspendableEvent<String, Throwable> = SuspendableEvent {
val res = httpClient.get<String>(URL) val res = httpClient.get<String>(URL)
try { try {
res.split("\"album_id\":\"")[1].split('"')[0] res.split("\"album_id\":\"")[1].split('"')[0]

View File

@ -31,7 +31,7 @@ import io.ktor.client.request.forms.*
import io.ktor.http.* import io.ktor.http.*
import kotlin.native.concurrent.SharedImmutable import kotlin.native.concurrent.SharedImmutable
suspend fun authenticateSpotify(): SuspendableEvent<TokenData,Throwable> = SuspendableEvent { suspend fun authenticateSpotify(): SuspendableEvent<TokenData, Throwable> = SuspendableEvent {
if (methods.value.isInternetAvailable) { if (methods.value.isInternetAvailable) {
spotifyAuthClient.post("https://accounts.spotify.com/api/token") { spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") }) body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") })

View File

@ -43,7 +43,7 @@ interface Yt1sMp3 {
/* /*
* Downloadable Mp3 Link for YT videoID. * Downloadable Mp3 Link for YT videoID.
* */ * */
suspend fun getLinkFromYt1sMp3(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = getKey(videoID,quality).flatMap { key -> suspend fun getLinkFromYt1sMp3(videoID: String, quality: AudioQuality): SuspendableEvent<String, Throwable> = getKey(videoID, quality).flatMap { key ->
getConvertedMp3Link(videoID, key).map { getConvertedMp3Link(videoID, key).map {
it["dlink"].requireNotNull() it["dlink"].requireNotNull()
.jsonPrimitive.content.replace("\"", "") .jsonPrimitive.content.replace("\"", "")
@ -54,7 +54,7 @@ interface Yt1sMp3 {
* POST:https://yt1s.com/api/ajaxSearch/index * POST:https://yt1s.com/api/ajaxSearch/index
* Body Form= q:yt video link ,vt:format=mp3 * Body Form= q:yt video link ,vt:format=mp3
* */ * */
private suspend fun getKey(videoID: String,quality: AudioQuality): SuspendableEvent<String,Throwable> = SuspendableEvent { private suspend fun getKey(videoID: String, quality: AudioQuality): SuspendableEvent<String, Throwable> = SuspendableEvent {
val response: JsonObject = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") { val response: JsonObject = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
body = FormDataContent( body = FormDataContent(
Parameters.build { Parameters.build {
@ -67,7 +67,7 @@ interface Yt1sMp3 {
val mp3Keys = response.getJsonObject("links") val mp3Keys = response.getJsonObject("links")
.getJsonObject("mp3") .getJsonObject("mp3")
val requestedKBPS = when(quality) { val requestedKBPS = when (quality) {
AudioQuality.KBPS128 -> "mp3128" AudioQuality.KBPS128 -> "mp3128"
else -> quality.kbps else -> quality.kbps
} }
@ -77,7 +77,7 @@ interface Yt1sMp3 {
specificQualityKey?.get("k").requireNotNull().jsonPrimitive.content specificQualityKey?.get("k").requireNotNull().jsonPrimitive.content
} }
private suspend fun getConvertedMp3Link(videoID: String, key: String): SuspendableEvent<JsonObject,Throwable> = SuspendableEvent { private suspend fun getConvertedMp3Link(videoID: String, key: String): SuspendableEvent<JsonObject, Throwable> = SuspendableEvent {
httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") { httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") {
body = FormDataContent( body = FormDataContent(
Parameters.build { Parameters.build {

View File

@ -165,7 +165,7 @@ private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<
componentContext = componentContext, componentContext = componentContext,
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies { dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val mainAnalytics = object : SpotiFlyerMain.Analytics , Analytics by analytics {} override val mainAnalytics = object : SpotiFlyerMain.Analytics, Analytics by analytics {}
} }
) )

View File

@ -28,7 +28,6 @@ dependencies {
implementation(project(":common:list")) implementation(project(":common:list"))
implementation(project(":common:list")) implementation(project(":common:list"))
// Decompose // Decompose
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) implementation(Decompose.extensionsCompose)

View File

@ -6,4 +6,4 @@ val String.byProperty: String get() = System.getenv(this)
val String.byOptionalProperty: String? get() = System.getenv(this) val String.byOptionalProperty: String? get() = System.getenv(this)
fun debug(message: String) = println("\n::debug::$message") fun debug(message: String) = println("\n::debug::$message")
fun debug(tag: String, message: String) = println("\n::debug::$tag:\n$message") fun debug(tag: String, message: String) = println("\n::debug::$tag:\n$message")

View File

@ -20,6 +20,7 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
id("ktlint-setup")
} }
group = "com.shabinder" group = "com.shabinder"
@ -88,4 +89,4 @@ compose.desktop {
} }
} }
} }
} }

View File

@ -56,9 +56,8 @@ import java.net.URI
import javax.swing.JFileChooser import javax.swing.JFileChooser
import javax.swing.JFileChooser.APPROVE_OPTION import javax.swing.JFileChooser.APPROVE_OPTION
private val koin = initKoin(enableNetworkLogs = true).koin private val koin = initKoin(enableNetworkLogs = true).koin
private lateinit var showToast: (String)->Unit private lateinit var showToast: (String) -> Unit
private val tracker: PiwikTracker by lazy { private val tracker: PiwikTracker by lazy {
PiwikTracker("https://matomo.spotiflyer.ml/matomo.php") PiwikTracker("https://matomo.spotiflyer.ml/matomo.php")
} }
@ -68,7 +67,7 @@ fun main() {
val lifecycle = LifecycleRegistry() val lifecycle = LifecycleRegistry()
lifecycle.resume() lifecycle.resume()
Window("SpotiFlyer",size = IntSize(450,800)) { Window("SpotiFlyer", size = IntSize(450, 800)) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = Color.Black, color = Color.Black,
@ -80,7 +79,7 @@ fun main() {
shapes = SpotiFlyerShapes shapes = SpotiFlyerShapes
) { ) {
val root: SpotiFlyerRoot = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot)) val root: SpotiFlyerRoot = SpotiFlyerRootContent(rememberRootComponent(factory = ::spotiFlyerRoot))
showToast = root.callBacks::showToast showToast = root.callBacks::showToast
} }
} }
} }
@ -98,11 +97,11 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override val database: Database? = dir.db override val database: Database? = dir.db
override val preferenceManager: PreferenceManager = koin.get() override val preferenceManager: PreferenceManager = koin.get()
override val downloadProgressFlow = DownloadProgressFlow override val downloadProgressFlow = DownloadProgressFlow
override val actions: Actions = object: Actions { override val actions: Actions = object : Actions {
override val platformActions = object : PlatformActions {} override val platformActions = object : PlatformActions {}
override fun showPopUpMessage(string: String, long: Boolean) { override fun showPopUpMessage(string: String, long: Boolean) {
if(::showToast.isInitialized){ if (::showToast.isInitialized) {
showToast(string) showToast(string)
} }
} }
@ -114,7 +113,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
when (fileChooser.showOpenDialog(AppManager.focusedWindow?.window)) { when (fileChooser.showOpenDialog(AppManager.focusedWindow?.window)) {
APPROVE_OPTION -> { APPROVE_OPTION -> {
val directory = fileChooser.selectedFile val directory = fileChooser.selectedFile
if(directory.canWrite()){ if (directory.canWrite()) {
preferenceManager.setDownloadDirectory(directory.absolutePath) preferenceManager.setDownloadDirectory(directory.absolutePath)
callBack(directory.absolutePath) callBack(directory.absolutePath)
showPopUpMessage("${Strings.setDownloadDirectory()} \n${dir.defaultDir()}") showPopUpMessage("${Strings.setDownloadDirectory()} \n${dir.defaultDir()}")
@ -128,7 +127,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
} }
} }
override fun queryActiveTracks() {/**/} override fun queryActiveTracks() { /**/ }
override fun giveDonation() { override fun giveDonation() {
openLink("https://razorpay.com/payment-button/pl_GnKuuDBdBu0ank/view/?utm_source=payment_button&utm_medium=button&utm_campaign=payment_button") openLink("https://razorpay.com/payment-button/pl_GnKuuDBdBu0ank/view/?utm_source=payment_button&utm_medium=button&utm_campaign=payment_button")
@ -143,22 +142,22 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override fun openPlatform(packageID: String, platformLink: String) = openLink(platformLink) override fun openPlatform(packageID: String, platformLink: String) = openLink(platformLink)
fun openLink(link:String) { fun openLink(link: String) {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(URI(link)) Desktop.getDesktop().browse(URI(link))
} }
} }
override fun writeMp3Tags(trackDetails: TrackDetails) {/*IMPLEMENTED*/} override fun writeMp3Tags(trackDetails: TrackDetails) { /*IMPLEMENTED*/ }
override val isInternetAvailable: Boolean override val isInternetAvailable: Boolean
get() = runBlocking { get() = runBlocking {
isInternetAccessible() isInternetAccessible()
} }
} }
override val analytics = object: SpotiFlyerRoot.Analytics { override val analytics = object : SpotiFlyerRoot.Analytics {
override fun appLaunchEvent() { override fun appLaunchEvent() {
if(preferenceManager.isFirstLaunch) { if (preferenceManager.isFirstLaunch) {
// Enable Analytics on First Launch // Enable Analytics on First Launch
preferenceManager.toggleAnalytics(true) preferenceManager.toggleAnalytics(true)
preferenceManager.firstLaunchDone() preferenceManager.firstLaunchDone()

View File

@ -4,9 +4,8 @@ import org.piwik.java.tracking.PiwikRequest
import org.piwik.java.tracking.PiwikTracker import org.piwik.java.tracking.PiwikTracker
import java.net.URL import java.net.URL
fun PiwikTracker.trackAsync( fun PiwikTracker.trackAsync(
baseURL:String = "https://com.shabinder.spotiflyer/", baseURL: String = "https://com.shabinder.spotiflyer/",
requestBuilder: PiwikRequest.() -> Unit = {} requestBuilder: PiwikRequest.() -> Unit = {}
) { ) {
val req = PiwikRequest( val req = PiwikRequest(
@ -18,7 +17,7 @@ fun PiwikTracker.trackAsync(
} }
fun PiwikTracker.trackScreenAsync( fun PiwikTracker.trackScreenAsync(
screenAddress:String, screenAddress: String,
requestBuilder: PiwikRequest.() -> Unit = {} requestBuilder: PiwikRequest.() -> Unit = {}
) { ) {
val req = PiwikRequest( val req = PiwikRequest(
@ -27,4 +26,4 @@ fun PiwikTracker.trackScreenAsync(
).apply { requestBuilder() } ).apply { requestBuilder() }
// Send Request // Send Request
sendRequestAsync(req) sendRequestAsync(req)
} }

View File

@ -22,7 +22,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m org.gradle.jvmargs=-Xmx3072m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects

View File

@ -7,10 +7,10 @@ fun getUpdatedContent(
oldContent: String, oldContent: String,
newInsertionText: String, newInsertionText: String,
tagName: String tagName: String
): String{ ): String {
return getReplaceableRegex(tagName).replace( return getReplaceableRegex(tagName).replace(
oldContent, oldContent,
getReplacementText(tagName,newInsertionText) getReplacementText(tagName, newInsertionText)
) )
} }
@ -26,5 +26,5 @@ private fun getReplacementText(
${Common.START_SECTION(tagName)} ${Common.START_SECTION(tagName)}
$newInsertionText $newInsertionText
${Common.END_SECTION(tagName)} ${Common.END_SECTION(tagName)}
""".trimIndent() """.trimIndent()
} }

View File

@ -13,4 +13,4 @@ fun getTodayDate(): String {
val year: Int = c.get(Calendar.YEAR) val year: Int = c.get(Calendar.YEAR)
val date: Int = c.get(Calendar.DATE) val date: Int = c.get(Calendar.DATE)
return " $date $month, $year" return " $date $month, $year"
} }

View File

@ -19,7 +19,6 @@ internal object GithubService {
private const val baseURL = Common.GITHUB_API private const val baseURL = Common.GITHUB_API
suspend fun getGithubRepoReleasesInfo( suspend fun getGithubRepoReleasesInfo(
ownerName: String, ownerName: String,
repoName: String, repoName: String,

View File

@ -25,8 +25,8 @@ fun main(args: Array<String>) {
updatedGithubContent, updatedGithubContent,
secrets secrets
) )
} catch (e:Exception) { } catch (e: Exception) {
debug("Analytics Image Updation Failed",e.message.toString()) debug("Analytics Image Updation Failed", e.message.toString())
} }
// TASK -> Update Total Downloads Card // TASK -> Update Total Downloads Card
@ -35,8 +35,8 @@ fun main(args: Array<String>) {
updatedGithubContent, updatedGithubContent,
secrets.copy(tagName = "DCI") secrets.copy(tagName = "DCI")
) )
} catch (e:Exception) { } catch (e: Exception) {
debug("Download Card Updation Failed",e.message.toString()) debug("Download Card Updation Failed", e.message.toString())
} }
// Write New Updated README.md // Write New Updated README.md

View File

@ -17,4 +17,4 @@ data class Asset(
val updated_at: String, val updated_at: String,
val uploader: Uploader, val uploader: Uploader,
val url: String val url: String
) )

View File

@ -22,4 +22,4 @@ data class Author(
val subscriptions_url: String, val subscriptions_url: String,
val type: String, val type: String,
val url: String val url: String
) )

View File

@ -6,4 +6,4 @@ import kotlinx.serialization.Serializable
data class GithubFileContent( data class GithubFileContent(
val decryptedContent: String, val decryptedContent: String,
val sha: String val sha: String
) )

View File

@ -23,4 +23,4 @@ data class GithubReleaseInfoItem(
val upload_url: String, val upload_url: String,
val url: String, val url: String,
val zipball_url: String val zipball_url: String
) )

View File

@ -1,3 +1,3 @@
package models.github package models.github
typealias GithubReleasesInfo = ArrayList<GithubReleaseInfoItem> typealias GithubReleasesInfo = ArrayList<GithubReleaseInfoItem>

View File

@ -1,7 +1,7 @@
package models.github package models.github
import kotlinx.serialization.json.JsonNames
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
@Serializable @Serializable
data class Reactions( data class Reactions(
@ -15,4 +15,4 @@ data class Reactions(
val rocket: Int = 0, val rocket: Int = 0,
val total_count: Int = 0, val total_count: Int = 0,
val url: String? = null val url: String? = null
) )

View File

@ -22,4 +22,4 @@ data class Uploader(
val subscriptions_url: String, val subscriptions_url: String,
val type: String, val type: String,
val url: String val url: String
) )

View File

@ -1,3 +1,3 @@
package models.matomo package models.matomo
typealias MatomoDownloads = ArrayList<MatomoDownloadsItem> typealias MatomoDownloads = ArrayList<MatomoDownloadsItem>

View File

@ -9,4 +9,4 @@ data class MatomoDownloadsItem(
val nb_hits: Int = 0, val nb_hits: Int = 0,
val nb_visits: Int = 0, val nb_visits: Int = 0,
val sum_time_spent: Int = 0 val sum_time_spent: Int = 0
) )

View File

@ -54,13 +54,13 @@ internal suspend fun getAnalyticsImage(): String {
} }
} }
contentLength = req.headers["Content-Length"]?.toLong() ?: 0 contentLength = req.headers["Content-Length"]?.toLong() ?: 0
debug("Content Length for Analytics Image",contentLength.toString()) debug("Content Length for Analytics Image", contentLength.toString())
if(retryCount-- == 0){ if (retryCount-- == 0) {
// FAIL Gracefully // FAIL Gracefully
throw(RETRY_LIMIT_EXHAUSTED()) throw(RETRY_LIMIT_EXHAUSTED())
} }
}while (contentLength<1_20_000) } while (contentLength <1_20_000)
return analyticsImage return analyticsImage
} }

View File

@ -18,15 +18,15 @@ internal suspend fun updateDownloadCards(
fileName = "README.md" fileName = "README.md"
).decryptedContent ).decryptedContent
var totalDownloads:Int = GithubService.getGithubRepoReleasesInfo( var totalDownloads: Int = GithubService.getGithubRepoReleasesInfo(
secrets.ownerName, secrets.ownerName,
secrets.repoName secrets.repoName
).let { allReleases -> ).let { allReleases ->
var totalCount = 0 var totalCount = 0
for(release in allReleases){ for (release in allReleases) {
release.assets.forEach { release.assets.forEach {
//debug("${it.name}: ${release.tag_name}" ,"Downloads: ${it.download_count}") // debug("${it.name}: ${release.tag_name}" ,"Downloads: ${it.download_count}")
totalCount += it.download_count totalCount += it.download_count
} }
} }
@ -75,19 +75,18 @@ private suspend fun getDownloadCard(
contentLength = req.headers["Content-Length"]?.toLong() ?: 0 contentLength = req.headers["Content-Length"]?.toLong() ?: 0
// debug(contentLength.toString()) // debug(contentLength.toString())
if(retryCount-- == 0){ if (retryCount-- == 0) {
// FAIL Gracefully // FAIL Gracefully
throw(RETRY_LIMIT_EXHAUSTED()) throw(RETRY_LIMIT_EXHAUSTED())
} }
}while (contentLength<40_000) } while (contentLength <40_000)
return downloadCard return downloadCard
} }
fun getDownloadCardHtml( fun getDownloadCardHtml(
count: Int, count: Int,
date: String, // ex: 06 Jun 2021 date: String, // ex: 06 Jun 2021
):String { ): String {
return """ return """
<div class="card-container"> <div class="card-container">
<div id="card" class="dark-bg"> <div id="card" class="dark-bg">

View File

@ -6,4 +6,4 @@ val String.byProperty: String get() = System.getenv(this)
val String.byOptionalProperty: String? get() = System.getenv(this) val String.byOptionalProperty: String? get() = System.getenv(this)
fun debug(message: String) = println("\n::debug::$message") fun debug(message: String) = println("\n::debug::$message")
fun debug(tag: String, message: String) = println("\n::debug::$tag:\n$message") fun debug(tag: String, message: String) = println("\n::debug::$tag:\n$message")