Removed Static Variables, Thorough Code Cleanup, Code Refactoring

This commit is contained in:
shabinder 2021-04-29 21:44:19 +05:30
parent 9f9a160e88
commit ff7acc9558
55 changed files with 532 additions and 464 deletions

View File

@ -46,6 +46,7 @@ android {
} }
} }
} }
compileSdkVersion(29) compileSdkVersion(29)
buildToolsVersion = "30.0.3" buildToolsVersion = "30.0.3"
@ -104,14 +105,14 @@ dependencies {
implementation(Koin.android) implementation(Koin.android)
implementation(Koin.compose) implementation(Koin.compose)
implementation("com.google.accompanist:accompanist-insets:0.7.1") implementation("com.google.accompanist:accompanist-insets:0.8.1")
// DECOMPOSE // DECOMPOSE
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) implementation(Decompose.extensionsCompose)
// Firebase // Firebase
implementation(platform("com.google.firebase:firebase-bom:27.0.0")) implementation(platform("com.google.firebase:firebase-bom:27.1.0"))
implementation("com.google.firebase:firebase-analytics-ktx") implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx") implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-perf-ktx") implementation("com.google.firebase:firebase-perf-ktx")

View File

@ -17,7 +17,6 @@
package com.shabinder.spotiflyer package com.shabinder.spotiflyer
import android.app.Application import android.app.Application
import com.shabinder.common.database.appContext
import com.shabinder.common.di.initKoin import com.shabinder.common.di.initKoin
import com.shabinder.spotiflyer.di.appModule import com.shabinder.spotiflyer.di.appModule
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
@ -29,7 +28,6 @@ class App: Application(), KoinComponent {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
appContext = this
val loggingEnabled = true val loggingEnabled = true
initKoin(loggingEnabled) { initKoin(loggingEnabled) {

View File

@ -17,10 +17,14 @@
package com.shabinder.spotiflyer package com.shabinder.spotiflyer
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PowerManager import android.os.PowerManager
@ -31,18 +35,10 @@ import androidx.compose.animation.*
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.SdStorage
import androidx.compose.material.icons.rounded.SystemSecurityUpdate
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight import androidx.core.content.ContextCompat
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
@ -57,21 +53,27 @@ import com.google.accompanist.insets.statusBarsHeight
import com.google.accompanist.insets.statusBarsPadding import com.google.accompanist.insets.statusBarsPadding
import com.razorpay.Checkout import com.razorpay.Checkout
import com.razorpay.PaymentResultListener import com.razorpay.PaymentResultListener
import com.shabinder.common.database.activityContext
import com.shabinder.common.database.appContext
import com.shabinder.common.di.* import com.shabinder.common.di.*
import com.shabinder.common.di.worker.ForegroundService
import com.shabinder.common.models.Actions
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.*
import com.shabinder.spotiflyer.utils.* import com.shabinder.spotiflyer.utils.*
import com.shabinder.common.models.Status import com.shabinder.common.models.Status
import com.shabinder.common.models.methods
import com.shabinder.spotiflyer.ui.NetworkDialog
import com.shabinder.spotiflyer.ui.PermissionDialog
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.json.JSONObject
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
const val disableDozeCode = 1223 const val disableDozeCode = 1223
@ -87,6 +89,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
private var permissionGranted = mutableStateOf(true) private var permissionGranted = mutableStateOf(true)
private lateinit var updateUIReceiver: BroadcastReceiver private lateinit var updateUIReceiver: BroadcastReceiver
private lateinit var queryReceiver: BroadcastReceiver private lateinit var queryReceiver: BroadcastReceiver
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -116,8 +119,12 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
LaunchedEffect(view) { LaunchedEffect(view) {
permissionGranted.value = checkPermissions() permissionGranted.value = checkPermissions()
} }
NetworkDialog() NetworkDialog(isInternetAvailableState())
PermissionDialog() PermissionDialog(
permissionGranted.value,
{ requestStoragePermission() },
{ disableDozeMode(disableDozeCode) }
)
} }
} }
} }
@ -127,10 +134,13 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
private fun initialise() { private fun initialise() {
checkIfLatestVersion() checkIfLatestVersion()
dir.createDirectories()
Checkout.preload(applicationContext) Checkout.preload(applicationContext)
handleIntentFromExternalActivity() handleIntentFromExternalActivity()
Log.i("Download Path",dir.defaultDir()) }
@Composable
private fun isInternetAvailableState(): State<Boolean?> {
return internetAvailability.observeAsState()
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -140,7 +150,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
.withActivity(this) .withActivity(this)
.withFragmentManager(fragmentManager) .withFragmentManager(fragmentManager)
.withMemoryBar(true) .withMemoryBar(true)
.setTheme(StorageChooser.Theme(appContext).apply { .setTheme(StorageChooser.Theme(applicationContext).apply {
scheme = applicationContext.resources.getIntArray(R.array.default_dark) scheme = applicationContext.resources.getIntArray(R.array.default_dark)
}) })
.setDialogTitle("Set Download Directory") .setDialogTitle("Set Download Directory")
@ -155,11 +165,11 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
if (f.canWrite()) { if (f.canWrite()) {
// hell yeah :) // hell yeah :)
dir.setDownloadDirectory(path) dir.setDownloadDirectory(path)
com.shabinder.common.uikit.showPopUpMessage( showPopUpMessage(
"Download Directory Set to:\n${dir.defaultDir()} " "Download Directory Set to:\n${dir.defaultDir()} "
) )
}else{ }else{
com.shabinder.common.uikit.showPopUpMessage( showPopUpMessage(
"NO WRITE ACCESS on \n$path ,\nReverting Back to Previous" "NO WRITE ACCESS on \n$path ,\nReverting Back to Previous"
) )
} }
@ -169,6 +179,14 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
chooser.show() chooser.show()
} }
private fun showPopUpMessage(string: String, long: Boolean = false) {
android.widget.Toast.makeText(
applicationContext,
string,
if(long) android.widget.Toast.LENGTH_LONG else android.widget.Toast.LENGTH_SHORT
).show()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionGranted.value = checkPermissions() permissionGranted.value = checkPermissions()
@ -182,9 +200,74 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override val database = this@MainActivity.dir.db override val database = this@MainActivity.dir.db
override val fetchPlatformQueryResult = this@MainActivity.fetcher override val fetchPlatformQueryResult = this@MainActivity.fetcher
override val directories: Dir = this@MainActivity.dir override val directories: Dir = this@MainActivity.dir
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
override val setDownloadDirectoryAction: () -> Unit = ::setUpOnPrefClickListener override val actions = object: Actions {
override val platformActions = object : PlatformActions {
override val imageCacheDir: String = applicationContext.cacheDir.absolutePath + File.separator
override val sharedPreferences = applicationContext.getSharedPreferences(SharedPreferencesKey,
MODE_PRIVATE
)
override fun addToLibrary(path: String) {
MediaScannerConnection.scanFile(
applicationContext,
listOf(path).toTypedArray(), null, null
)
}
override fun sendTracksToService(array: ArrayList<TrackDetails>) {
for (list in array.chunked(50)){
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
serviceIntent.putParcelableArrayListExtra("object", list as ArrayList)
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
}
}
}
override fun showPopUpMessage(string: String, long: Boolean) = this@MainActivity.showPopUpMessage(string,long)
override fun setDownloadDirectoryAction() = setUpOnPrefClickListener()
override fun queryActiveTracks() {
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java).apply {
action = "query"
}
ContextCompat.startForegroundService(this@MainActivity, serviceIntent)
}
override fun giveDonation() = startPayment(this@MainActivity)
override fun shareApp() {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer")
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
override fun openPlatform(packageID: String, platformLink: String) {
val manager: PackageManager = applicationContext.packageManager
try {
val intent = manager.getLaunchIntentForPackage(packageID)
?: throw PackageManager.NameNotFoundException()
intent.addCategory(Intent.CATEGORY_LAUNCHER)
startActivity(intent)
} catch (e: PackageManager.NameNotFoundException) {
val uri: Uri =
Uri.parse(platformLink)
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
}
}
override val isInternetAvailable get() = internetAvailability.value ?: true
override val dispatcherIO = Dispatchers.IO
override val currentPlatform = AllPlatforms.Jvm
}
} }
) )
@ -208,6 +291,9 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
} }
} }
/*
* Broadcast Handlers
* */
private fun initializeBroadcast(){ private fun initializeBroadcast(){
val intentFilter = IntentFilter().apply { val intentFilter = IntentFilter().apply {
addAction(Status.QUEUED.name) addAction(Status.QUEUED.name)
@ -292,7 +378,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
while(!this@MainActivity::root.isInitialized){ while(!this@MainActivity::root.isInitialized){
delay(100) delay(100)
} }
if(isInternetAvailable)callBacks.searchLink(link) if(methods.isInternetAvailable)callBacks.searchLink(link)
} }
} }
} }
@ -301,7 +387,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override fun onPaymentError(errorCode: Int, response: String?) { override fun onPaymentError(errorCode: Int, response: String?) {
try{ try{
uikitShowPopUpMessage("Payment Failed, Response:$response") showPopUpMessage("Payment Failed, Response:$response")
}catch (e: Exception){ }catch (e: Exception){
Log.d("Razorpay Payment","Exception in onPaymentSuccess $response") Log.d("Razorpay Payment","Exception in onPaymentSuccess $response")
} }
@ -309,82 +395,39 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override fun onPaymentSuccess(razorpayPaymentId: String?) { override fun onPaymentSuccess(razorpayPaymentId: String?) {
try{ try{
uikitShowPopUpMessage("Payment Successful, ThankYou!") showPopUpMessage("Payment Successful, ThankYou!")
}catch (e: Exception){ }catch (e: Exception){
uikitShowPopUpMessage("Razorpay Payment, Error Occurred.") showPopUpMessage("Razorpay Payment, Error Occurred.")
Log.d("Razorpay Payment","Exception in onPaymentSuccess, ${e.message}") Log.d("Razorpay Payment","Exception in onPaymentSuccess, ${e.message}")
} }
} }
@Composable /*
private fun PermissionDialog(){ * RazorPay Payment
var askForPermission by remember { mutableStateOf(false) } * */
LaunchedEffect(Unit){ private fun startPayment(mainActivity: Activity) {
delay(2000) val co = Checkout().apply {
askForPermission = true setKeyID("rzp_live_3ZQeoFYOxjmXye")
setImage(com.shabinder.common.di.R.drawable.ic_spotiflyer_logo)
} }
AnimatedVisibility(
askForPermission && !permissionGranted.value
){
AlertDialog(
onDismissRequest = {},
buttons = {
TextButton(
{
requestStoragePermission()
disableDozeMode(disableDozeCode)
},
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
.padding(horizontal = 8.dp),
){
Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
}
},title = {Text("Required Permissions:",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center)},
backgroundColor = Color.DarkGray,
text = {
Column{
Spacer(modifier = Modifier.padding(8.dp))
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
) {
Icon(Icons.Rounded.SdStorage,"Storage Permission.")
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Storage Permission.",
style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold)
)
Text(
text = "To download your favourite songs to this device.",
style = SpotiFlyerTypography.subtitle2,
)
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.SystemSecurityUpdate,"Allow Background Running")
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Background Running.",
style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold)
)
Text(
text = "To download all songs in background without any System Interruptions",
style = SpotiFlyerTypography.subtitle2,
)
}
}
}
}
)
}
}
init { try {
activityContext = this val preFill = JSONObject()
val options = JSONObject().apply {
put("name", "SpotiFlyer")
put("description", "Thanks For the Donation!")
// You can omit the image option to fetch the image from dashboard
// put("image","https://github.com/Shabinder/SpotiFlyer/raw/master/app/SpotifyDownload.png")
put("currency", "INR")
put("amount", "4900")
put("prefill", preFill)
}
co.open(mainActivity, options)
} catch (e: Exception) {
// showPop("Error in payment: "+ e.message)
e.printStackTrace()
}
} }
} }

View File

@ -14,7 +14,7 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.spotiflyer.utils package com.shabinder.spotiflyer.ui
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -30,7 +30,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.shabinder.common.di.isInternetAvailableState
import com.shabinder.common.uikit.SpotiFlyerShapes import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite import com.shabinder.common.uikit.colorOffWhite
@ -39,7 +38,7 @@ import kotlinx.coroutines.delay
@ExperimentalAnimationApi @ExperimentalAnimationApi
@Composable @Composable
fun NetworkDialog( fun NetworkDialog(
networkAvailability: State<Boolean?> = isInternetAvailableState() networkAvailability: State<Boolean?>
){ ){
var visible by remember { mutableStateOf(false) } var visible by remember { mutableStateOf(false) }

View File

@ -0,0 +1,107 @@
package com.shabinder.spotiflyer.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.AlertDialog
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.SdStorage
import androidx.compose.material.icons.rounded.SystemSecurityUpdate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorPrimary
import kotlinx.coroutines.delay
@ExperimentalAnimationApi
@Composable
fun PermissionDialog(
permissionGranted: Boolean,
requestStoragePermission:() -> Unit,
disableDozeMode:() -> Unit
){
var askForPermission by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(2000)
askForPermission = true
}
AnimatedVisibility(
askForPermission && !permissionGranted
) {
AlertDialog(
onDismissRequest = {},
buttons = {
TextButton(
{
requestStoragePermission()
disableDozeMode()
},
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
.padding(horizontal = 8.dp),
){
Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
}
},title = { Text("Required Permissions:",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center) },
backgroundColor = Color.DarkGray,
text = {
Column{
Spacer(modifier = Modifier.padding(8.dp))
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
) {
Icon(Icons.Rounded.SdStorage,"Storage Permission.")
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Storage Permission.",
style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold)
)
Text(
text = "To download your favourite songs to this device.",
style = SpotiFlyerTypography.subtitle2,
)
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.SystemSecurityUpdate,"Allow Background Running")
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Background Running.",
style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold)
)
Text(
text = "To download all songs in background without any System Interruptions",
style = SpotiFlyerTypography.subtitle2,
)
}
}
}
}
)
}
}

View File

@ -36,11 +36,12 @@ object Versions {
// Internet // Internet
const val ktor = "1.5.3" const val ktor = "1.5.3"
const val kotlinxSerialization = "1.1.0" const val kotlinxSerialization = "1.2.0"
// Database
const val sqlDelight = "1.4.4"
const val sqliteJdbcDriver = "3.30.1" // Database
const val sqlDelight = "1.5.0"
const val sqliteJdbcDriver = "3.34.0"
const val slf4j = "1.7.30" const val slf4j = "1.7.30"
// Android // Android

View File

@ -35,9 +35,8 @@ import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import com.shabinder.common.database.R import com.shabinder.common.database.R
import com.shabinder.common.database.appContext
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
@ -51,7 +50,7 @@ actual fun ImageLoad(
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) } var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) { LaunchedEffect(link) {
withContext(dispatcherIO) { withContext(methods.dispatcherIO) {
pic = loader(link).image pic = loader(link).image
} }
} }
@ -137,7 +136,3 @@ actual fun Toast(
) { ) {
// We Have Android's Implementation of Toast so its just Empty // We Have Android's Implementation of Toast so its just Empty
} }
actual fun showPopUpMessage(text: String) {
android.widget.Toast.makeText(appContext, text, android.widget.Toast.LENGTH_SHORT).show()
}

View File

@ -50,6 +50,7 @@ import com.shabinder.common.di.Picture
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@Composable @Composable
@ -62,7 +63,7 @@ fun SpotiFlyerListContent(
LaunchedEffect(model.errorOccurred) { LaunchedEffect(model.errorOccurred) {
/*Handle if Any Exception Occurred*/ /*Handle if Any Exception Occurred*/
model.errorOccurred?.let { model.errorOccurred?.let {
showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection") methods.showPopUpMessage(it.message ?: "An Error Occurred, Check your Link / Connection")
component.onBackPressed() component.onBackPressed()
} }
} }

View File

@ -73,12 +73,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.giveDonation
import com.shabinder.common.di.openPlatform
import com.shabinder.common.di.shareApp
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
import com.shabinder.common.models.DownloadRecord import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.methods
@Composable @Composable
fun SpotiFlyerMainContent(component: SpotiFlyerMain) { fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
@ -195,7 +193,7 @@ fun SearchPanel(
OutlinedButton( OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(), modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = { onClick = {
if (link.isBlank()) showPopUpMessage("Enter A Link!") if (link.isBlank()) methods.showPopUpMessage("Enter A Link!")
else { else {
// TODO if(!isOnline(ctx)) showPopUpMessage("Check Your Internet Connection") else // TODO if(!isOnline(ctx)) showPopUpMessage("Check Your Internet Connection") else
onSearch(link) onSearch(link)
@ -237,7 +235,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Spotify", "Open Spotify",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.spotify.music", "http://open.spotify.com") } onClick = { methods.openPlatform("com.spotify.music", "http://open.spotify.com") }
) )
) )
Spacer(modifier = modifier.padding(start = 16.dp)) Spacer(modifier = modifier.padding(start = 16.dp))
@ -246,7 +244,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Gaana", "Open Gaana",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.gaana", "http://gaana.com") } onClick = { methods.openPlatform("com.gaana", "http://gaana.com") }
) )
) )
Spacer(modifier = modifier.padding(start = 16.dp)) Spacer(modifier = modifier.padding(start = 16.dp))
@ -255,7 +253,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Youtube", "Open Youtube",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.google.android.youtube", "http://m.youtube.com") } onClick = { methods.openPlatform("com.google.android.youtube", "http://m.youtube.com") }
) )
) )
Spacer(modifier = modifier.padding(start = 12.dp)) Spacer(modifier = modifier.padding(start = 12.dp))
@ -264,7 +262,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
"Open Youtube Music", "Open Youtube Music",
tint = Color.Unspecified, tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable( modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") } onClick = { methods.openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
) )
) )
} }
@ -285,7 +283,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable( modifier = Modifier.fillMaxWidth().clickable(
onClick = { openPlatform("", "http://github.com/Shabinder/SpotiFlyer") } onClick = { methods.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }
) )
.padding(vertical = 6.dp) .padding(vertical = 6.dp)
) { ) {
@ -304,7 +302,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
} }
Row( Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }), .clickable(onClick = { methods.openPlatform("", "http://github.com/Shabinder/SpotiFlyer") }),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Rounded.Flag, "Help Translate", Modifier.size(32.dp)) Icon(Icons.Rounded.Flag, "Help Translate", Modifier.size(32.dp))
@ -322,7 +320,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
} }
Row( Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { giveDonation() }), .clickable(onClick = { methods.giveDonation() }),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(Icons.Rounded.CardGiftcard, "Support Developer") Icon(Icons.Rounded.CardGiftcard, "Support Developer")
@ -342,7 +340,7 @@ fun AboutColumn(modifier: Modifier = Modifier) {
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp) modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable( .clickable(
onClick = { onClick = {
shareApp() methods.shareApp()
} }
), ),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically

View File

@ -24,8 +24,6 @@ enum class ToastDuration(val value: Int) {
Short(1000), Long(3000) Short(1000), Long(3000)
} }
expect fun showPopUpMessage(text: String)
@Composable @Composable
expect fun Toast( expect fun Toast(
text: String, text: String,

View File

@ -28,8 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.vectorXmlResource import androidx.compose.ui.res.vectorXmlResource
@ -37,7 +35,7 @@ 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
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
@ -50,7 +48,7 @@ actual fun ImageLoad(
) { ) {
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) } var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) { LaunchedEffect(link) {
withContext(dispatcherIO) { withContext(methods.dispatcherIO) {
pic = loader(link).image pic = loader(link).image
} }
} }

View File

@ -38,7 +38,7 @@ import kotlinx.coroutines.launch
private val message: MutableState<String> = mutableStateOf("") private val message: MutableState<String> = mutableStateOf("")
private val state: MutableState<Boolean> = mutableStateOf(false) private val state: MutableState<Boolean> = mutableStateOf(false)
actual fun showPopUpMessage(text: String) { fun showToast(text: String) {
message.value = text message.value = text
state.value = true state.value = true
} }

View File

@ -25,10 +25,15 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api("dev.icerock.moko:parcelize:0.6.0") api("dev.icerock.moko:parcelize:0.6.1")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt")
} }
} }
if(HostOS.isMac){
val iosMain by getting {
}
}
} }
} }

View File

@ -0,0 +1,18 @@
package com.shabinder.common.models
import android.content.SharedPreferences
actual interface PlatformActions {
companion object {
const val SharedPreferencesKey = "configurations"
}
val imageCacheDir: String
val sharedPreferences: SharedPreferences
fun addToLibrary(path: String)
fun sendTracksToService(array: ArrayList<TrackDetails>)
}

View File

@ -0,0 +1,47 @@
package com.shabinder.common.models
import kotlinx.coroutines.CoroutineDispatcher
/*
* Holder to call platform actions from anywhere
* */
lateinit var methods: Actions
/*
* Interface Having All Platform Dependent Functions
* */
interface Actions {
// Platform Specific Actions
val platformActions: PlatformActions
// Show Toast
fun showPopUpMessage(string: String, long: Boolean = false)
// Change Download Directory
fun setDownloadDirectoryAction()
/*
* Query Downloading Tracks
* ex- Get Tracks from android service etc
* */
fun queryActiveTracks()
// Donate Money
fun giveDonation()
// Share SpotiFlyer App
fun shareApp()
// Open / Redirect to another Platform
fun openPlatform(packageID: String, platformLink: String)
// IO-Dispatcher
val dispatcherIO: CoroutineDispatcher
// Internet Connectivity Check
val isInternetAvailable: Boolean
// Current Platform Info
val currentPlatform: AllPlatforms
}

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2021 Shabinder Singh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.models
import kotlinx.serialization.Serializable
@Serializable
data class Optional<T>(val value: T?)

View File

@ -0,0 +1,3 @@
package com.shabinder.common.models
expect interface PlatformActions

View File

@ -0,0 +1,4 @@
package com.shabinder.common.models
actual interface PlatformActions {
}

View File

@ -0,0 +1,4 @@
package com.shabinder.common.models
actual interface PlatformActions {
}

View File

@ -35,6 +35,9 @@ kotlin {
implementation(SqlDelight.runtime) implementation(SqlDelight.runtime)
implementation(SqlDelight.coroutineExtensions) implementation(SqlDelight.coroutineExtensions)
api(Extras.kermit) api(Extras.kermit)
// koin
api(Koin.core)
api(Koin.test)
} }
} }

View File

@ -16,24 +16,17 @@
package com.shabinder.common.database package com.shabinder.common.database
import android.annotation.SuppressLint
import android.content.Context
import co.touchlab.kermit.LogcatLogger import co.touchlab.kermit.LogcatLogger
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.shabinder.database.Database import com.shabinder.database.Database
import com.squareup.sqldelight.android.AndroidSqliteDriver import com.squareup.sqldelight.android.AndroidSqliteDriver
import org.koin.dsl.module
lateinit var appContext: Context
/*
* As MainActivity is God Activity , hence its active almost throughout App's lifetime
* */
@SuppressLint("StaticFieldLeak")
lateinit var activityContext: Context
@Suppress("RedundantNullableReturnType") @Suppress("RedundantNullableReturnType")
actual fun createDatabase(): Database? { actual fun databaseModule() = module {
val driver = AndroidSqliteDriver(Database.Schema, appContext, "Database.db") single {
return Database(driver) val driver = AndroidSqliteDriver(Database.Schema, get(), "Database.db")
SpotiFlyerDatabase(Database(driver))
}
} }
actual fun getLogger(): Logger = LogcatLogger() actual fun getLogger(): Logger = LogcatLogger()

View File

@ -17,7 +17,7 @@
package com.shabinder.common.database package com.shabinder.common.database
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.shabinder.database.Database import org.koin.core.module.Module
expect fun createDatabase(): Database? expect fun databaseModule(): Module
expect fun getLogger(): Logger expect fun getLogger(): Logger

View File

@ -0,0 +1,6 @@
package com.shabinder.common.database
import com.shabinder.database.Database
/* Database Wrapper */
class SpotiFlyerDatabase(val instance: Database?)

View File

@ -20,13 +20,17 @@ import co.touchlab.kermit.CommonLogger
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.shabinder.database.Database import com.shabinder.database.Database
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import org.koin.dsl.module
import java.io.File import java.io.File
@Suppress("RedundantNullableReturnType") @Suppress("RedundantNullableReturnType")
actual fun createDatabase(): Database? { actual fun databaseModule() = module {
val databasePath = File(System.getProperty("java.io.tmpdir"), "Database.db") single {
val driver = JdbcSqliteDriver(url = "jdbc:sqlite:${databasePath.absolutePath}") val databasePath = File(System.getProperty("user.home") + File.separator + "SpotiFlyer" + File.separator + "Database", "Database.db")
.also { Database.Schema.create(it) } databasePath.parentFile.mkdirs()
return Database(driver) val driver = JdbcSqliteDriver(url = "jdbc:sqlite:${databasePath.absolutePath}")
.also { Database.Schema.create(it) }
SpotiFlyerDatabase(Database(driver))
}
} }
actual fun getLogger(): Logger = CommonLogger() actual fun getLogger(): Logger = CommonLogger()

View File

@ -3,12 +3,15 @@ package com.shabinder.common.database
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import co.touchlab.kermit.NSLogLogger import co.touchlab.kermit.NSLogLogger
import com.shabinder.database.Database import com.shabinder.database.Database
import org.koin.dsl.module
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
@Suppress("RedundantNullableReturnType") @Suppress("RedundantNullableReturnType")
actual fun createDatabase(): Database? { actual fun databaseModule(): Module {
val driver = NativeSqliteDriver(Database.Schema, "Database.db") single {
return Database(driver) val driver = NativeSqliteDriver(Database.Schema, "Database.db")
SpotiFlyerDatabase(Database(driver))
}
} }
actual fun getLogger(): Logger = NSLogLogger() actual fun getLogger(): Logger = NSLogLogger()

View File

@ -19,6 +19,7 @@ package com.shabinder.common.database
import co.touchlab.kermit.CommonLogger import co.touchlab.kermit.CommonLogger
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.shabinder.database.Database import com.shabinder.database.Database
import org.koin.dsl.module
actual fun createDatabase(): Database? = null actual fun databaseModule() = module { single { SpotiFlyerDatabase(null) } }
actual fun getLogger(): Logger = CommonLogger() actual fun getLogger(): Logger = CommonLogger()

View File

@ -52,24 +52,21 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0")
implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0") implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0")
implementation(Ktor.clientCore) api(Ktor.clientCore)
implementation(Ktor.clientSerialization) api(Ktor.clientSerialization)
implementation(Ktor.clientLogging) api(Ktor.clientLogging)
implementation(Ktor.clientJson) api(Ktor.clientJson)
implementation(Ktor.auth) api(Ktor.auth)
api(Extras.youtubeDownloader) api(Extras.youtubeDownloader)
// koin
api(Koin.core)
api(Koin.test)
api(Extras.kermit) api(Extras.kermit)
} }
} }
androidMain { androidMain {
dependencies { dependencies {
implementation(compose.materialIconsExtended) implementation(compose.materialIconsExtended)
implementation(Koin.android) api(Koin.android)
implementation(Ktor.clientAndroid) api(Ktor.clientAndroid)
implementation(Extras.Android.razorpay) api(Extras.Android.razorpay)
api(Extras.mp3agic) api(Extras.mp3agic)
api(Extras.jaudioTagger) api(Extras.jaudioTagger)
api("com.github.shabinder:storage-chooser:2.0.4.45") api("com.github.shabinder:storage-chooser:2.0.4.45")
@ -79,18 +76,18 @@ kotlin {
desktopMain { desktopMain {
dependencies { dependencies {
implementation(compose.materialIconsExtended) implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache) api(Ktor.clientApache)
implementation(Ktor.slf4j) api(Ktor.slf4j)
api(Extras.mp3agic) api(Extras.mp3agic)
api(Extras.jaudioTagger) api(Extras.jaudioTagger)
} }
} }
jsMain { jsMain {
dependencies { dependencies {
implementation(project(":common:data-models"))
implementation(Ktor.clientJs)
implementation(npm("browser-id3-writer", "4.4.0")) implementation(npm("browser-id3-writer", "4.4.0"))
implementation(npm("file-saver", "2.0.4")) implementation(npm("file-saver", "2.0.4"))
implementation(project(":common:data-models"))
api(Ktor.clientJs)
} }
} }
} }

View File

@ -16,90 +16,8 @@
package com.shabinder.common.di package com.shabinder.common.di
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.core.content.ContextCompat
import com.razorpay.Checkout
import com.shabinder.common.database.activityContext
import com.shabinder.common.di.worker.ForegroundService
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.Dispatchers import com.shabinder.common.models.methods
import org.json.JSONObject
import com.codekidlabs.storagechooser.StorageChooser
import com.codekidlabs.storagechooser.StorageChooser.OnSelectListener
actual fun openPlatform(packageID: String, platformLink: String) {
val manager: PackageManager = activityContext.packageManager
try {
val intent = manager.getLaunchIntentForPackage(packageID)
?: throw PackageManager.NameNotFoundException()
intent.addCategory(Intent.CATEGORY_LAUNCHER)
activityContext.startActivity(intent)
} catch (e: PackageManager.NameNotFoundException) {
val uri: Uri =
Uri.parse(platformLink)
val intent = Intent(Intent.ACTION_VIEW, uri)
activityContext.startActivity(intent)
}
}
actual val dispatcherIO = Dispatchers.IO
actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm
actual val isInternetAvailable: Boolean
get() = internetAvailability.value ?: true
actual fun shareApp() {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer")
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
activityContext.startActivity(shareIntent)
}
actual fun giveDonation() = startPayment()
private fun startPayment(mainActivity: Activity = activityContext as Activity) {
/*
* You need to pass current activity in order to let Razorpay create CheckoutActivity
* */
val co = Checkout().apply {
setKeyID("rzp_live_3ZQeoFYOxjmXye")
setImage(R.drawable.ic_spotiflyer_logo)
}
try {
val preFill = JSONObject()
val options = JSONObject().apply {
put("name", "SpotiFlyer")
put("description", "Thanks For the Donation!")
// You can omit the image option to fetch the image from dashboard
// put("image","https://github.com/Shabinder/SpotiFlyer/raw/master/app/SpotifyDownload.png")
put("currency", "INR")
put("amount", "4900")
put("prefill", preFill)
}
co.open(mainActivity, options)
} catch (e: Exception) {
// showPop("Error in payment: "+ e.message)
e.printStackTrace()
}
}
actual fun queryActiveTracks() {
val serviceIntent = Intent(activityContext, ForegroundService::class.java).apply {
action = "query"
}
ContextCompat.startForegroundService(activityContext, serviceIntent)
}
actual suspend fun downloadTracks( actual suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,
@ -107,8 +25,6 @@ actual suspend fun downloadTracks(
dir: Dir dir: Dir
) { ) {
if (!list.isNullOrEmpty()) { if (!list.isNullOrEmpty()) {
val serviceIntent = Intent(activityContext, ForegroundService::class.java) methods.platformActions.sendTracksToService(ArrayList(list))
serviceIntent.putParcelableArrayListExtra("object", ArrayList<TrackDetails>(list))
activityContext.let { ContextCompat.startForegroundService(it, serviceIntent) }
} }
} }

View File

@ -16,19 +16,16 @@
package com.shabinder.common.di package com.shabinder.common.di
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaScannerConnection
import android.os.Environment import android.os.Environment
import android.widget.Toast
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.shabinder.common.database.appContext import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
@ -40,19 +37,13 @@ import java.net.URL
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database?, private val spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
companion object { companion object {
const val SharedPreferencesKey = "configurations"
const val DirKey = "downloadDir" const val DirKey = "downloadDir"
} }
private val context: Context private val sharedPreferences:SharedPreferences by lazy { methods.platformActions.sharedPreferences }
get() = appContext
private val sharedPreferences:SharedPreferences by lazy {
context.getSharedPreferences(SharedPreferencesKey,MODE_PRIVATE)
}
fun setDownloadDirectory(newBasePath:String){ fun setDownloadDirectory(newBasePath:String){
sharedPreferences.edit().putString(DirKey,newBasePath).apply() sharedPreferences.edit().putString(DirKey,newBasePath).apply()
@ -63,7 +54,7 @@ actual class Dir actual constructor(
actual fun fileSeparator(): String = File.separator actual fun fileSeparator(): String = File.separator
actual fun imageCacheDir(): String = context.cacheDir.absolutePath + File.separator actual fun imageCacheDir(): String = methods.platformActions.imageCacheDir
// fun call in order to always access Updated Value // fun call in order to always access Updated Value
actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator + actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator +
@ -158,13 +149,7 @@ actual class Dir actual constructor(
} }
} }
actual fun addToLibrary(path: String) { actual fun addToLibrary(path: String) = methods.platformActions.addToLibrary(path)
logger.d { "Scanning File" }
MediaScannerConnection.scanFile(
appContext,
listOf(path).toTypedArray(), null, null
)
}
actual suspend fun loadImage(url: String): Picture = withContext(Dispatchers.IO){ actual suspend fun loadImage(url: String): Picture = withContext(Dispatchers.IO){
val cachePath = imageCacheDir() + getNameURL(url) val cachePath = imageCacheDir() + getNameURL(url)
@ -214,5 +199,5 @@ actual class Dir actual constructor(
} }
} }
actual val db: Database? = database actual val db: Database? = spotiFlyerDatabase.instance
} }

View File

@ -23,10 +23,7 @@ import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkRequest import android.net.NetworkRequest
import android.util.Log import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.shabinder.common.database.appContext
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.IOException import java.io.IOException
import java.lang.Exception import java.lang.Exception
@ -34,13 +31,6 @@ import java.net.InetSocketAddress
const val TAG = "C-Manager" const val TAG = "C-Manager"
val internetAvailability by lazy { ConnectionLiveData(appContext) }
@Composable
fun isInternetAvailableState(): State<Boolean?> {
return internetAvailability.observeAsState()
}
/** /**
* Save all available networks with an internet connection to a set (@validNetworks). * Save all available networks with an internet connection to a set (@validNetworks).
* As long as the size of the set > 0, this LiveData emits true. * As long as the size of the set > 0, this LiveData emits true.
@ -49,7 +39,7 @@ fun isInternetAvailableState(): State<Boolean?> {
* Inspired by: * Inspired by:
* https://github.com/AlexSheva-mason/Rick-Morty-Database/blob/master/app/src/main/java/com/shevaalex/android/rickmortydatabase/utils/networking/ConnectionLiveData.kt * https://github.com/AlexSheva-mason/Rick-Morty-Database/blob/master/app/src/main/java/com/shevaalex/android/rickmortydatabase/utils/networking/ConnectionLiveData.kt
*/ */
class ConnectionLiveData(context: Context = appContext) : LiveData<Boolean>() { class ConnectionLiveData(context: Context) : LiveData<Boolean>() {
private lateinit var networkCallback: ConnectivityManager.NetworkCallback private lateinit var networkCallback: ConnectivityManager.NetworkCallback
private val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager private val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

View File

@ -17,7 +17,7 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.createDatabase import com.shabinder.common.database.databaseModule
import com.shabinder.common.database.getLogger import com.shabinder.common.database.getLogger
import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.SpotifyProvider
@ -39,12 +39,12 @@ import org.koin.dsl.module
fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) = fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) =
startKoin { startKoin {
appDeclaration() appDeclaration()
modules(commonModule(enableNetworkLogs = enableNetworkLogs)) modules(commonModule(enableNetworkLogs = enableNetworkLogs), databaseModule())
} }
fun commonModule(enableNetworkLogs: Boolean) = module { fun commonModule(enableNetworkLogs: Boolean) = module {
single { createHttpClient(enableNetworkLogs = enableNetworkLogs) } single { createHttpClient(enableNetworkLogs = enableNetworkLogs) }
single { Dir(get(), createDatabase()) } single { Dir(get(), get()) }
single { Kermit(getLogger()) } single { Kermit(getLogger()) }
single { TokenStore(get(), get()) } single { TokenStore(get(), get()) }
single { YoutubeMusic(get(), get()) } single { YoutubeMusic(get(), get()) }

View File

@ -17,26 +17,22 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.createDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.utils.removeIllegalChars import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database import com.shabinder.database.Database
import com.shabinder.downloader.exceptions.YoutubeException
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.HttpStatement
import io.ktor.http.contentLength import io.ktor.http.contentLength
import io.ktor.http.isSuccess import io.ktor.http.isSuccess
import io.ktor.utils.io.errors.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt import kotlin.math.roundToInt
expect class Dir ( expect class Dir (
logger: Kermit, logger: Kermit,
database: Database? = createDatabase(), spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
val db: Database? val db: Database?
fun isPresent(path: String): Boolean fun isPresent(path: String): Boolean

View File

@ -16,37 +16,19 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import io.ktor.client.request.* import io.ktor.client.request.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
expect fun openPlatform(packageID: String, platformLink: String)
expect fun shareApp()
expect fun giveDonation()
expect val dispatcherIO: CoroutineDispatcher
expect val isInternetAvailable: Boolean
expect val currentPlatform: AllPlatforms
expect suspend fun downloadTracks( expect suspend fun downloadTracks(
list: List<TrackDetails>, list: List<TrackDetails>,
fetcher: FetchPlatformQueryResult, fetcher: FetchPlatformQueryResult,
dir: Dir dir: Dir
) )
expect fun queryActiveTracks()
/*
* Refactor This
* */
suspend fun isInternetAccessible(): Boolean { suspend fun isInternetAccessible(): Boolean {
return withContext(dispatcherIO) { return withContext(methods.dispatcherIO) {
try { try {
ktorHttpClient.head<String>("http://google.com") ktorHttpClient.head<String>("http://google.com")
true true

View File

@ -26,12 +26,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class FetchPlatformQueryResult( class FetchPlatformQueryResult(
private val gaanaProvider: GaanaProvider, val gaanaProvider: GaanaProvider,
private val spotifyProvider: SpotifyProvider, val spotifyProvider: SpotifyProvider,
val youtubeProvider: YoutubeProvider, val youtubeProvider: YoutubeProvider,
val youtubeMusic: YoutubeMusic, val youtubeMusic: YoutubeMusic,
val youtubeMp3: YoutubeMp3, val youtubeMp3: YoutubeMp3,
private val dir: Dir val dir: Dir
) { ) {
private val db: DownloadRecordDatabaseQueries? private val db: DownloadRecordDatabaseQueries?
get() = dir.db?.downloadRecordDatabaseQueries get() = dir.db?.downloadRecordDatabaseQueries

View File

@ -16,7 +16,6 @@
package com.shabinder.common.di.gaana package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.corsProxy import com.shabinder.common.models.corsProxy
import com.shabinder.common.models.gaana.GaanaAlbum import com.shabinder.common.models.gaana.GaanaAlbum
@ -24,10 +23,11 @@ import com.shabinder.common.models.gaana.GaanaArtistDetails
import com.shabinder.common.models.gaana.GaanaArtistTracks import com.shabinder.common.models.gaana.GaanaArtistTracks
import com.shabinder.common.models.gaana.GaanaPlaylist import com.shabinder.common.models.gaana.GaanaPlaylist
import com.shabinder.common.models.gaana.GaanaSong import com.shabinder.common.models.gaana.GaanaSong
import com.shabinder.common.models.methods
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
val corsApi get() = if (currentPlatform is AllPlatforms.Js) { val corsApi get() = if (methods.currentPlatform is AllPlatforms.Js) {
corsProxy.url corsProxy.url
} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/" } // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/"
else "" else ""

View File

@ -19,7 +19,6 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.TokenStore import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.di.spotify.SpotifyRequests import com.shabinder.common.di.spotify.SpotifyRequests
@ -27,6 +26,7 @@ import com.shabinder.common.di.spotify.authenticateSpotify
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.spotify.Album import com.shabinder.common.models.spotify.Album
import com.shabinder.common.models.spotify.Image import com.shabinder.common.models.spotify.Image
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
@ -45,14 +45,14 @@ class SpotifyProvider(
private val dir: Dir, private val dir: Dir,
) : SpotifyRequests { ) : SpotifyRequests {
init { /* init {
logger.d { "Creating Spotify Provider" } logger.d { "Creating Spotify Provider" }
GlobalScope.launch(Dispatchers.Default) { GlobalScope.launch(Dispatchers.Default) {
if (currentPlatform is AllPlatforms.Js) { if (methods.currentPlatform is AllPlatforms.Js) {
authenticateSpotifyClient(override = true) authenticateSpotifyClient(override = true)
} else authenticateSpotifyClient() } else authenticateSpotifyClient()
} }
} }*/
override suspend fun authenticateSpotifyClient(override: Boolean): HttpClient? { override suspend fun authenticateSpotifyClient(override: Boolean): HttpClient? {
val token = if (override) authenticateSpotify() else tokenStore.getToken() val token = if (override) authenticateSpotify() else tokenStore.getToken()

View File

@ -18,9 +18,9 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.youtubeMp3.Yt1sMp3 import com.shabinder.common.di.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.methods
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
class YoutubeMp3( class YoutubeMp3(
@ -31,7 +31,7 @@ class YoutubeMp3(
suspend fun getMp3DownloadLink(videoID: String): String? = try { suspend fun getMp3DownloadLink(videoID: String): String? = try {
getLinkFromYt1sMp3(videoID)?.let { getLinkFromYt1sMp3(videoID)?.let {
logger.i { "Download Link: $it" } logger.i { "Download Link: $it" }
if (currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/) if (methods.currentPlatform is AllPlatforms.Js/* && corsProxy !is CorsProxy.PublicProxyWithExtension*/)
"https://kind-grasshopper-73.telebit.io/cors/$it" "https://kind-grasshopper-73.telebit.io/cors/$it"
// "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue // "https://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue
else it else it

View File

@ -16,8 +16,8 @@
package com.shabinder.common.di.spotify package com.shabinder.common.di.spotify
import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.models.methods
import com.shabinder.common.models.spotify.TokenData import com.shabinder.common.models.spotify.TokenData
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.auth.Auth import io.ktor.client.features.auth.Auth
@ -29,7 +29,7 @@ import io.ktor.http.Parameters
suspend fun authenticateSpotify(): TokenData? { suspend fun authenticateSpotify(): TokenData? {
return try { return try {
if (isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") { if (methods.isInternetAvailable) 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") })
} else null } else null
}catch (e:Exception) { }catch (e:Exception) {

View File

@ -21,7 +21,7 @@ package com.shabinder.common.di.utils
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4") // implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e // Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
import com.shabinder.common.di.dispatcherIO import com.shabinder.common.models.methods
import io.ktor.utils.io.core.Closeable import io.ktor.utils.io.core.Closeable
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -37,7 +37,7 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class ParallelExecutor( class ParallelExecutor(
parentContext: CoroutineContext = dispatcherIO, parentContext: CoroutineContext = methods.dispatcherIO,
) : Closeable { ) : Closeable {
private val concurrentOperationLimit = atomic(4) private val concurrentOperationLimit = atomic(4)

View File

@ -17,43 +17,13 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader import com.shabinder.downloader.YoutubeDownloader
import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
actual fun openPlatform(packageID: String, platformLink: String) {
// TODO
}
actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm
actual val dispatcherIO = Dispatchers.IO
actual fun shareApp() {
// TODO
}
actual fun giveDonation() {
// TODO
}
actual fun queryActiveTracks() {}
actual val isInternetAvailable: Boolean
get() {
var result = false
val job = GlobalScope.launch { result = isInternetAccessible() }
while (job.isActive) {}
return result
}
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1) val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -38,7 +39,7 @@ import javax.imageio.ImageIO
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database?, private val spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
init { init {
@ -137,9 +138,9 @@ actual class Dir actual constructor(
} }
} }
actual val db: Database? actual val db: Database? get() = spotiFlyerDatabase.instance
get() = database
} }
fun BufferedImage.toImageBitmap() = Image.makeFromEncoded( fun BufferedImage.toImageBitmap() = Image.makeFromEncoded(
toByteArray(this) toByteArray(this)
).asImageBitmap() ).asImageBitmap()

View File

@ -12,7 +12,7 @@ import cocoapods.TagLibIOS.TLAudio
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database? private val spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
init { init {
@ -142,5 +142,5 @@ actual class Dir actual constructor(
// TODO // TODO
} }
actual val db: Database? = database actual val db: Database? = spotiFlyerDatabase.instance
} }

View File

@ -16,55 +16,14 @@
package com.shabinder.common.di package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import io.ktor.client.request.head import com.shabinder.common.models.methods
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
actual val currentPlatform: AllPlatforms = AllPlatforms.Js
actual fun openPlatform(packageID: String, platformLink: String) {
// TODO
}
actual fun shareApp() {
// TODO
}
actual fun giveDonation() {
// TODO
}
actual fun queryActiveTracks() {}
actual val dispatcherIO: CoroutineDispatcher = Dispatchers.Default
/*
* Refactor This
* */
private suspend fun isInternetAvailable(): Boolean {
return withContext(dispatcherIO) {
try {
ktorHttpClient.head<String>("http://google.com")
true
} catch (e: Exception) {
println(e.message)
false
}
}
}
actual val isInternetAvailable: Boolean
get() {
return true
}
val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1) val DownloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = MutableSharedFlow(1)
// Error:https://github.com/Kotlin/kotlinx.atomicfu/issues/182 // Error:https://github.com/Kotlin/kotlinx.atomicfu/issues/182
// val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel // val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel
@ -76,7 +35,7 @@ actual suspend fun downloadTracks(
dir: Dir dir: Dir
) { ) {
list.forEach { list.forEach {
withContext(dispatcherIO) { withContext(methods.dispatcherIO) {
allTracksStatus[it.title] = DownloadStatus.Queued allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) { // Video ID already known! if (!it.videoID.isNullOrBlank()) { // Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir) downloadTrack(it.videoID!!, it, fetcher, dir)

View File

@ -17,6 +17,7 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.di.utils.removeIllegalChars import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
@ -32,7 +33,7 @@ import org.w3c.dom.ImageBitmap
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database?, private val spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
/*init { /*init {
@ -112,8 +113,7 @@ actual class Dir actual constructor(
private suspend fun freshImage(url: String): ImageBitmap? = null private suspend fun freshImage(url: String): ImageBitmap? = null
actual val db: Database? actual val db: Database? get() = spotiFlyerDatabase.instance
get() = database
} }
fun ByteArray.toArrayBuffer(): ArrayBuffer { fun ByteArray.toArrayBuffer(): ArrayBuffer {

View File

@ -64,7 +64,6 @@ interface SpotiFlyerList {
val dir: Dir val dir: Dir
val link: String val link: String
val listOutput: Consumer<Output> val listOutput: Consumer<Output>
val showPopUpMessage: (String) -> Unit
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
} }
sealed class Output { sealed class Output {

View File

@ -40,8 +40,7 @@ internal class SpotiFlyerListImpl(
storeFactory = storeFactory, storeFactory = storeFactory,
fetchQuery = fetchQuery, fetchQuery = fetchQuery,
downloadProgressFlow = downloadProgressFlow, downloadProgressFlow = downloadProgressFlow,
link = link, link = link
showPopUpMessage = showPopUpMessage
).provide() ).provide()
} }

View File

@ -25,12 +25,12 @@ import com.shabinder.common.database.getLogger
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.queryActiveTracks
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -39,7 +39,6 @@ internal class SpotiFlyerListStoreProvider(
private val storeFactory: StoreFactory, private val storeFactory: StoreFactory,
private val fetchQuery: FetchPlatformQueryResult, private val fetchQuery: FetchPlatformQueryResult,
private val link: String, private val link: String,
private val showPopUpMessage: (String) -> Unit,
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
) { ) {
val logger = getLogger() val logger = getLogger()
@ -93,7 +92,7 @@ internal class SpotiFlyerListStoreProvider(
is Intent.StartDownloadAll -> { is Intent.StartDownloadAll -> {
val finalList = val finalList =
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded } intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
if (finalList.isNullOrEmpty()) showPopUpMessage("All Songs are Processed") if (finalList.isNullOrEmpty()) methods.showPopUpMessage("All Songs are Processed")
else downloadTracks(finalList, fetchQuery, dir) else downloadTracks(finalList, fetchQuery, dir)
val list = intent.trackList.map { val list = intent.trackList.map {
@ -107,7 +106,7 @@ internal class SpotiFlyerListStoreProvider(
downloadTracks(listOf(intent.track), fetchQuery, dir) downloadTracks(listOf(intent.track), fetchQuery, dir)
dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued))) dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
} }
is Intent.RefreshTracksStatuses -> queryActiveTracks() is Intent.RefreshTracksStatuses -> methods.queryActiveTracks()
} }
} }
} }

View File

@ -56,7 +56,6 @@ interface SpotiFlyerMain {
val storeFactory: StoreFactory val storeFactory: StoreFactory
val database: Database? val database: Database?
val dir: Dir val dir: Dir
val showPopUpMessage: (String) -> Unit
} }
sealed class Output { sealed class Output {

View File

@ -19,7 +19,6 @@ package com.shabinder.common.main.integration
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.Dependencies import com.shabinder.common.main.SpotiFlyerMain.Dependencies
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
@ -28,6 +27,7 @@ import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore import com.shabinder.common.main.store.getStore
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerMainImpl( internal class SpotiFlyerMainImpl(
@ -39,16 +39,15 @@ internal class SpotiFlyerMainImpl(
instanceKeeper.getStore { instanceKeeper.getStore {
SpotiFlyerMainStoreProvider( SpotiFlyerMainStoreProvider(
storeFactory = storeFactory, storeFactory = storeFactory,
database = database, database = database
showPopUpMessage = showPopUpMessage
).provide() ).provide()
} }
override val models: Flow<State> = store.states override val models: Flow<State> = store.states
override fun onLinkSearch(link: String) { override fun onLinkSearch(link: String) {
if (isInternetAvailable) mainOutput.callback(Output.Search(link = link)) if (methods.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else showPopUpMessage("Check Network Connection Please") else methods.showPopUpMessage("Check Network Connection Please")
} }
override fun onInputLinkChanged(link: String) { override fun onInputLinkChanged(link: String) {

View File

@ -21,13 +21,11 @@ import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.di.giveDonation
import com.shabinder.common.di.openPlatform
import com.shabinder.common.di.shareApp
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.models.DownloadRecord import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.methods
import com.shabinder.database.Database import com.shabinder.database.Database
import com.squareup.sqldelight.runtime.coroutines.asFlow import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList import com.squareup.sqldelight.runtime.coroutines.mapToList
@ -38,7 +36,6 @@ import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider( internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory, private val storeFactory: StoreFactory,
private val showPopUpMessage: (String) -> Unit,
private val database: Database? private val database: Database?
) { ) {
@ -81,9 +78,9 @@ internal class SpotiFlyerMainStoreProvider(
override suspend fun executeIntent(intent: Intent, getState: () -> State) { override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) { when (intent) {
is Intent.OpenPlatform -> openPlatform(intent.platformID, intent.platformLink) is Intent.OpenPlatform -> methods.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> giveDonation() is Intent.GiveDonation -> methods.giveDonation()
is Intent.ShareApp -> shareApp() is Intent.ShareApp -> methods.shareApp()
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link)) is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category)) is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
} }

View File

@ -24,6 +24,7 @@ import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
@ -47,9 +48,8 @@ interface SpotiFlyerRoot {
val database: Database? val database: Database?
val fetchPlatformQueryResult: FetchPlatformQueryResult val fetchPlatformQueryResult: FetchPlatformQueryResult
val directories: Dir val directories: Dir
val showPopUpMessage: (String) -> Unit
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
val setDownloadDirectoryAction:()->Unit val actions:Actions
} }
} }

View File

@ -28,16 +28,31 @@ import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.Consumer import com.shabinder.common.models.Consumer
import com.shabinder.common.models.methods
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Child import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
internal class SpotiFlyerRootImpl( internal class SpotiFlyerRootImpl(
componentContext: ComponentContext, componentContext: ComponentContext,
dependencies: Dependencies, dependencies: Dependencies,
) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies { ) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies, Actions by dependencies.actions {
init {
methods = actions
GlobalScope.launch {
/*Authenticate Spotify Client*/
if (methods.currentPlatform is AllPlatforms.Js) {
fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient(override = true)
} else fetchPlatformQueryResult.spotifyProvider.authenticateSpotifyClient()
}
}
private val router = private val router =
router<Configuration, Child>( router<Configuration, Child>(

View File

@ -38,6 +38,7 @@ kotlin {
implementation(project(":common:database")) implementation(project(":common:database"))
implementation(project(":common:dependency-injection")) implementation(project(":common:dependency-injection"))
implementation(project(":common:compose")) implementation(project(":common:compose"))
implementation(project(":common:data-models"))
implementation(project(":common:root")) implementation(project(":common:root"))
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) implementation(Decompose.extensionsCompose)

View File

@ -29,14 +29,21 @@ import com.shabinder.common.di.Dir
import com.shabinder.common.di.DownloadProgressFlow import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin import com.shabinder.common.di.initKoin
import com.shabinder.common.di.isInternetAccessible
import com.shabinder.common.models.Actions
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.uikit.SpotiFlyerColors import com.shabinder.common.uikit.SpotiFlyerColors
import com.shabinder.common.uikit.SpotiFlyerRootContent import com.shabinder.common.uikit.SpotiFlyerRootContent
import com.shabinder.common.uikit.SpotiFlyerShapes import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite import com.shabinder.common.uikit.colorOffWhite
import com.shabinder.common.uikit.showToast
import com.shabinder.database.Database import com.shabinder.database.Database
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
private val koin = initKoin(enableNetworkLogs = true).koin private val koin = initKoin(enableNetworkLogs = true).koin
@ -70,7 +77,33 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override val fetchPlatformQueryResult: FetchPlatformQueryResult = koin.get() override val fetchPlatformQueryResult: FetchPlatformQueryResult = koin.get()
override val directories: Dir = koin.get() override val directories: Dir = koin.get()
override val database: Database? = directories.db override val database: Database? = directories.db
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
override val downloadProgressReport = DownloadProgressFlow override val downloadProgressReport = DownloadProgressFlow
override val actions = object: Actions {
override val platformActions = object : PlatformActions {}
override fun showPopUpMessage(string: String, long: Boolean) = showToast(string)
override fun setDownloadDirectoryAction() {}
override fun queryActiveTracks() {}
override fun giveDonation() {}
override fun shareApp() {}
override fun openPlatform(packageID: String, platformLink: String) {}
override val dispatcherIO = Dispatchers.IO
override val isInternetAvailable: Boolean
get() {
var result = false
val job = GlobalScope.launch { result = isInternetAccessible() }
while (job.isActive) {/*TODO Better Way*/}
return result
}
override val currentPlatform = AllPlatforms.Jvm
}
} }
) )

View File

@ -22,9 +22,13 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.di.DownloadProgressFlow import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.models.Actions
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.database.Database import com.shabinder.database.Database
import extras.renderableChild import extras.renderableChild
import kotlinx.coroutines.Dispatchers
import react.RBuilder import react.RBuilder
import react.RComponent import react.RComponent
import react.RProps import react.RProps
@ -50,14 +54,33 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
private val dependencies = props.dependencies private val dependencies = props.dependencies
private val root = SpotiFlyerRoot(ctx, private val root = SpotiFlyerRoot(ctx,
object : SpotiFlyerRoot.Dependencies{ object : SpotiFlyerRoot.Dependencies {
override val storeFactory: StoreFactory = LoggingStoreFactory(DefaultStoreFactory) override val storeFactory: StoreFactory = LoggingStoreFactory(DefaultStoreFactory)
override val fetchPlatformQueryResult = dependencies.fetchPlatformQueryResult override val fetchPlatformQueryResult = dependencies.fetchPlatformQueryResult
override val directories = dependencies.directories override val directories = dependencies.directories
override val database: Database? = directories.db override val database: Database? = directories.db
override val showPopUpMessage: (String) -> Unit = {}//TODO
override val downloadProgressReport = DownloadProgressFlow override val downloadProgressReport = DownloadProgressFlow
override val actions = object : Actions {
override val platformActions = object : PlatformActions {}
override fun showPopUpMessage(string: String, long: Boolean) {
/*TODO("Not yet implemented")*/
}
override fun setDownloadDirectoryAction() {}
override fun queryActiveTracks() {}
override fun giveDonation() {}
override fun shareApp() {}
override fun openPlatform(packageID: String, platformLink: String) {}
override val dispatcherIO = Dispatchers.Default
override val isInternetAvailable: Boolean = true
override val currentPlatform = AllPlatforms.Js
}
} }
) )