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)
buildToolsVersion = "30.0.3"
@ -104,14 +105,14 @@ dependencies {
implementation(Koin.android)
implementation(Koin.compose)
implementation("com.google.accompanist:accompanist-insets:0.7.1")
implementation("com.google.accompanist:accompanist-insets:0.8.1")
// DECOMPOSE
implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose)
// 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-crashlytics-ktx")
implementation("com.google.firebase:firebase-perf-ktx")

View File

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

View File

@ -17,10 +17,14 @@
package com.shabinder.spotiflyer
import android.annotation.SuppressLint
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
@ -31,18 +35,10 @@ import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
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 androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.arkivanov.decompose.ComponentContext
@ -57,21 +53,27 @@ import com.google.accompanist.insets.statusBarsHeight
import com.google.accompanist.insets.statusBarsPadding
import com.razorpay.Checkout
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.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.PlatformActions
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.uikit.*
import com.shabinder.spotiflyer.utils.*
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.flow.*
import org.json.JSONObject
import org.koin.android.ext.android.inject
import java.io.File
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
const val disableDozeCode = 1223
@ -87,6 +89,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
private var permissionGranted = mutableStateOf(true)
private lateinit var updateUIReceiver: BroadcastReceiver
private lateinit var queryReceiver: BroadcastReceiver
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -116,8 +119,12 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
LaunchedEffect(view) {
permissionGranted.value = checkPermissions()
}
NetworkDialog()
PermissionDialog()
NetworkDialog(isInternetAvailableState())
PermissionDialog(
permissionGranted.value,
{ requestStoragePermission() },
{ disableDozeMode(disableDozeCode) }
)
}
}
}
@ -127,10 +134,13 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
private fun initialise() {
checkIfLatestVersion()
dir.createDirectories()
Checkout.preload(applicationContext)
handleIntentFromExternalActivity()
Log.i("Download Path",dir.defaultDir())
}
@Composable
private fun isInternetAvailableState(): State<Boolean?> {
return internetAvailability.observeAsState()
}
@Suppress("DEPRECATION")
@ -140,7 +150,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
.withActivity(this)
.withFragmentManager(fragmentManager)
.withMemoryBar(true)
.setTheme(StorageChooser.Theme(appContext).apply {
.setTheme(StorageChooser.Theme(applicationContext).apply {
scheme = applicationContext.resources.getIntArray(R.array.default_dark)
})
.setDialogTitle("Set Download Directory")
@ -155,11 +165,11 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
if (f.canWrite()) {
// hell yeah :)
dir.setDownloadDirectory(path)
com.shabinder.common.uikit.showPopUpMessage(
showPopUpMessage(
"Download Directory Set to:\n${dir.defaultDir()} "
)
}else{
com.shabinder.common.uikit.showPopUpMessage(
showPopUpMessage(
"NO WRITE ACCESS on \n$path ,\nReverting Back to Previous"
)
}
@ -169,6 +179,14 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
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) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionGranted.value = checkPermissions()
@ -182,9 +200,74 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override val database = this@MainActivity.dir.db
override val fetchPlatformQueryResult = this@MainActivity.fetcher
override val directories: Dir = this@MainActivity.dir
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
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(){
val intentFilter = IntentFilter().apply {
addAction(Status.QUEUED.name)
@ -292,7 +378,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
while(!this@MainActivity::root.isInitialized){
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?) {
try{
uikitShowPopUpMessage("Payment Failed, Response:$response")
showPopUpMessage("Payment Failed, Response:$response")
}catch (e: Exception){
Log.d("Razorpay Payment","Exception in onPaymentSuccess $response")
}
@ -309,82 +395,39 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override fun onPaymentSuccess(razorpayPaymentId: String?) {
try{
uikitShowPopUpMessage("Payment Successful, ThankYou!")
showPopUpMessage("Payment Successful, ThankYou!")
}catch (e: Exception){
uikitShowPopUpMessage("Razorpay Payment, Error Occurred.")
showPopUpMessage("Razorpay Payment, Error Occurred.")
Log.d("Razorpay Payment","Exception in onPaymentSuccess, ${e.message}")
}
}
@Composable
private fun PermissionDialog(){
var askForPermission by remember { mutableStateOf(false) }
LaunchedEffect(Unit){
delay(2000)
askForPermission = true
}
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,
)
}
}
}
}
)
}
/*
* RazorPay Payment
* */
private fun startPayment(mainActivity: Activity) {
val co = Checkout().apply {
setKeyID("rzp_live_3ZQeoFYOxjmXye")
setImage(com.shabinder.common.di.R.drawable.ic_spotiflyer_logo)
}
init {
activityContext = this
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()
}
}
}

View File

@ -14,7 +14,7 @@
* * 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.foundation.Image
@ -30,7 +30,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.shabinder.common.di.isInternetAvailableState
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite
@ -39,7 +38,7 @@ import kotlinx.coroutines.delay
@ExperimentalAnimationApi
@Composable
fun NetworkDialog(
networkAvailability: State<Boolean?> = isInternetAvailableState()
networkAvailability: State<Boolean?>
){
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
const val ktor = "1.5.3"
const val kotlinxSerialization = "1.1.0"
// Database
const val sqlDelight = "1.4.4"
const val kotlinxSerialization = "1.2.0"
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"
// 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.FontWeight
import com.shabinder.common.database.R
import com.shabinder.common.database.appContext
import com.shabinder.common.di.Picture
import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.models.methods
import kotlinx.coroutines.withContext
@Composable
@ -51,7 +50,7 @@ actual fun ImageLoad(
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) {
withContext(dispatcherIO) {
withContext(methods.dispatcherIO) {
pic = loader(link).image
}
}
@ -137,7 +136,3 @@ actual fun Toast(
) {
// 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.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import kotlinx.coroutines.delay
@Composable
@ -62,7 +63,7 @@ fun SpotiFlyerListContent(
LaunchedEffect(model.errorOccurred) {
/*Handle if Any Exception Occurred*/
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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -25,10 +25,15 @@ kotlin {
sourceSets {
commonMain {
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-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.coroutineExtensions)
api(Extras.kermit)
// koin
api(Koin.core)
api(Koin.test)
}
}

View File

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

View File

@ -17,7 +17,7 @@
package com.shabinder.common.database
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

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 com.shabinder.database.Database
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import org.koin.dsl.module
import java.io.File
@Suppress("RedundantNullableReturnType")
actual fun createDatabase(): Database? {
val databasePath = File(System.getProperty("java.io.tmpdir"), "Database.db")
actual fun databaseModule() = module {
single {
val databasePath = File(System.getProperty("user.home") + File.separator + "SpotiFlyer" + File.separator + "Database", "Database.db")
databasePath.parentFile.mkdirs()
val driver = JdbcSqliteDriver(url = "jdbc:sqlite:${databasePath.absolutePath}")
.also { Database.Schema.create(it) }
return Database(driver)
SpotiFlyerDatabase(Database(driver))
}
}
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.NSLogLogger
import com.shabinder.database.Database
import org.koin.dsl.module
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
@Suppress("RedundantNullableReturnType")
actual fun createDatabase(): Database? {
actual fun databaseModule(): Module {
single {
val driver = NativeSqliteDriver(Database.Schema, "Database.db")
return Database(driver)
SpotiFlyerDatabase(Database(driver))
}
}
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.Logger
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()

View File

@ -52,24 +52,21 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.0")
implementation("com.shabinder.fuzzywuzzy:fuzzywuzzy:1.0")
implementation(Ktor.clientCore)
implementation(Ktor.clientSerialization)
implementation(Ktor.clientLogging)
implementation(Ktor.clientJson)
implementation(Ktor.auth)
api(Ktor.clientCore)
api(Ktor.clientSerialization)
api(Ktor.clientLogging)
api(Ktor.clientJson)
api(Ktor.auth)
api(Extras.youtubeDownloader)
// koin
api(Koin.core)
api(Koin.test)
api(Extras.kermit)
}
}
androidMain {
dependencies {
implementation(compose.materialIconsExtended)
implementation(Koin.android)
implementation(Ktor.clientAndroid)
implementation(Extras.Android.razorpay)
api(Koin.android)
api(Ktor.clientAndroid)
api(Extras.Android.razorpay)
api(Extras.mp3agic)
api(Extras.jaudioTagger)
api("com.github.shabinder:storage-chooser:2.0.4.45")
@ -79,18 +76,18 @@ kotlin {
desktopMain {
dependencies {
implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache)
implementation(Ktor.slf4j)
api(Ktor.clientApache)
api(Ktor.slf4j)
api(Extras.mp3agic)
api(Extras.jaudioTagger)
}
}
jsMain {
dependencies {
implementation(project(":common:data-models"))
implementation(Ktor.clientJs)
implementation(npm("browser-id3-writer", "4.4.0"))
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
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 kotlinx.coroutines.Dispatchers
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)
}
import com.shabinder.common.models.methods
actual suspend fun downloadTracks(
list: List<TrackDetails>,
@ -107,8 +25,6 @@ actual suspend fun downloadTracks(
dir: Dir
) {
if (!list.isNullOrEmpty()) {
val serviceIntent = Intent(activityContext, ForegroundService::class.java)
serviceIntent.putParcelableArrayListExtra("object", ArrayList<TrackDetails>(list))
activityContext.let { ContextCompat.startForegroundService(it, serviceIntent) }
methods.platformActions.sendTracksToService(ArrayList(list))
}
}

View File

@ -16,19 +16,16 @@
package com.shabinder.common.di
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaScannerConnection
import android.os.Environment
import android.widget.Toast
import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit
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.methods
import com.shabinder.database.Database
import kotlinx.coroutines.*
import java.io.File
@ -40,19 +37,13 @@ import java.net.URL
actual class Dir actual constructor(
private val logger: Kermit,
private val database: Database?,
private val spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val SharedPreferencesKey = "configurations"
const val DirKey = "downloadDir"
}
private val context: Context
get() = appContext
private val sharedPreferences:SharedPreferences by lazy {
context.getSharedPreferences(SharedPreferencesKey,MODE_PRIVATE)
}
private val sharedPreferences:SharedPreferences by lazy { methods.platformActions.sharedPreferences }
fun setDownloadDirectory(newBasePath:String){
sharedPreferences.edit().putString(DirKey,newBasePath).apply()
@ -63,7 +54,7 @@ actual class Dir actual constructor(
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
actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator +
@ -158,13 +149,7 @@ actual class Dir actual constructor(
}
}
actual fun addToLibrary(path: String) {
logger.d { "Scanning File" }
MediaScannerConnection.scanFile(
appContext,
listOf(path).toTypedArray(), null, null
)
}
actual fun addToLibrary(path: String) = methods.platformActions.addToLibrary(path)
actual suspend fun loadImage(url: String): Picture = withContext(Dispatchers.IO){
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.NetworkRequest
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.lifecycle.LiveData
import com.shabinder.common.database.appContext
import kotlinx.coroutines.*
import java.io.IOException
import java.lang.Exception
@ -34,13 +31,6 @@ import java.net.InetSocketAddress
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).
* As long as the size of the set > 0, this LiveData emits true.
@ -49,7 +39,7 @@ fun isInternetAvailableState(): State<Boolean?> {
* Inspired by:
* 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 val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

View File

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

View File

@ -17,26 +17,22 @@
package com.shabinder.common.di
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.models.DownloadResult
import com.shabinder.common.models.TrackDetails
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.statement.HttpStatement
import io.ktor.http.contentLength
import io.ktor.http.isSuccess
import io.ktor.utils.io.errors.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt
expect class Dir (
logger: Kermit,
database: Database? = createDatabase(),
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
val db: Database?
fun isPresent(path: String): Boolean

View File

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

View File

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

View File

@ -16,7 +16,6 @@
package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.corsProxy
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.GaanaPlaylist
import com.shabinder.common.models.gaana.GaanaSong
import com.shabinder.common.models.methods
import io.ktor.client.HttpClient
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
} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/"
else ""

View File

@ -19,7 +19,6 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir
import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.kotlinxSerializer
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.PlatformQueryResult
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.Image
import com.shabinder.common.models.spotify.Source
@ -45,14 +45,14 @@ class SpotifyProvider(
private val dir: Dir,
) : SpotifyRequests {
init {
/* init {
logger.d { "Creating Spotify Provider" }
GlobalScope.launch(Dispatchers.Default) {
if (currentPlatform is AllPlatforms.Js) {
if (methods.currentPlatform is AllPlatforms.Js) {
authenticateSpotifyClient(override = true)
} else authenticateSpotifyClient()
}
}
}*/
override suspend fun authenticateSpotifyClient(override: Boolean): HttpClient? {
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 com.shabinder.common.di.Dir
import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.methods
import io.ktor.client.HttpClient
class YoutubeMp3(
@ -31,7 +31,7 @@ class YoutubeMp3(
suspend fun getMp3DownloadLink(videoID: String): String? = try {
getLinkFromYt1sMp3(videoID)?.let {
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://spotiflyer.azurewebsites.net/$it" // Data OUT Limit issue
else it

View File

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

View File

@ -21,7 +21,7 @@ package com.shabinder.common.di.utils
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
// 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 kotlinx.atomicfu.atomic
import kotlinx.coroutines.CancellationException
@ -37,7 +37,7 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
class ParallelExecutor(
parentContext: CoroutineContext = dispatcherIO,
parentContext: CoroutineContext = methods.dispatcherIO,
) : Closeable {
private val concurrentOperationLimit = atomic(4)

View File

@ -17,43 +17,13 @@
package com.shabinder.common.di
import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableSharedFlow
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)

View File

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

View File

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

View File

@ -16,55 +16,14 @@
package com.shabinder.common.di
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import io.ktor.client.request.head
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
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)
// Error:https://github.com/Kotlin/kotlinx.atomicfu/issues/182
// val DownloadScope = ParallelExecutor(Dispatchers.Default) //Download Pool of 4 parallel
@ -76,7 +35,7 @@ actual suspend fun downloadTracks(
dir: Dir
) {
list.forEach {
withContext(dispatcherIO) {
withContext(methods.dispatcherIO) {
allTracksStatus[it.title] = DownloadStatus.Queued
if (!it.videoID.isNullOrBlank()) { // Video ID already known!
downloadTrack(it.videoID!!, it, fetcher, dir)

View File

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

View File

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

View File

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

View File

@ -25,12 +25,12 @@ import com.shabinder.common.database.getLogger
import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.queryActiveTracks
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
@ -39,7 +39,6 @@ internal class SpotiFlyerListStoreProvider(
private val storeFactory: StoreFactory,
private val fetchQuery: FetchPlatformQueryResult,
private val link: String,
private val showPopUpMessage: (String) -> Unit,
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
) {
val logger = getLogger()
@ -93,7 +92,7 @@ internal class SpotiFlyerListStoreProvider(
is Intent.StartDownloadAll -> {
val finalList =
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)
val list = intent.trackList.map {
@ -107,7 +106,7 @@ internal class SpotiFlyerListStoreProvider(
downloadTracks(listOf(intent.track), fetchQuery, dir)
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 database: Database?
val dir: Dir
val showPopUpMessage: (String) -> Unit
}
sealed class Output {

View File

@ -19,7 +19,6 @@ package com.shabinder.common.main.integration
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.di.Picture
import com.shabinder.common.di.isInternetAvailable
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.Dependencies
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.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore
import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerMainImpl(
@ -39,16 +39,15 @@ internal class SpotiFlyerMainImpl(
instanceKeeper.getStore {
SpotiFlyerMainStoreProvider(
storeFactory = storeFactory,
database = database,
showPopUpMessage = showPopUpMessage
database = database
).provide()
}
override val models: Flow<State> = store.states
override fun onLinkSearch(link: String) {
if (isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else showPopUpMessage("Check Network Connection Please")
if (methods.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else methods.showPopUpMessage("Check Network Connection Please")
}
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.StoreFactory
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.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.methods
import com.shabinder.database.Database
import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
@ -38,7 +36,6 @@ import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory,
private val showPopUpMessage: (String) -> Unit,
private val database: Database?
) {
@ -81,9 +78,9 @@ internal class SpotiFlyerMainStoreProvider(
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {
is Intent.OpenPlatform -> openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> giveDonation()
is Intent.ShareApp -> shareApp()
is Intent.OpenPlatform -> methods.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> methods.giveDonation()
is Intent.ShareApp -> methods.shareApp()
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
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.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
@ -47,9 +48,8 @@ interface SpotiFlyerRoot {
val database: Database?
val fetchPlatformQueryResult: FetchPlatformQueryResult
val directories: Dir
val showPopUpMessage: (String) -> Unit
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.list.SpotiFlyerList
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.methods
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
internal class SpotiFlyerRootImpl(
componentContext: ComponentContext,
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 =
router<Configuration, Child>(

View File

@ -38,6 +38,7 @@ kotlin {
implementation(project(":common:database"))
implementation(project(":common:dependency-injection"))
implementation(project(":common:compose"))
implementation(project(":common:data-models"))
implementation(project(":common:root"))
implementation(Decompose.decompose)
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.FetchPlatformQueryResult
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.uikit.SpotiFlyerColors
import com.shabinder.common.uikit.SpotiFlyerRootContent
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite
import com.shabinder.common.uikit.showToast
import com.shabinder.database.Database
import 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
@ -70,7 +77,33 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override val fetchPlatformQueryResult: FetchPlatformQueryResult = koin.get()
override val directories: Dir = koin.get()
override val database: Database? = directories.db
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
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.main.store.DefaultStoreFactory
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.database.Database
import extras.renderableChild
import kotlinx.coroutines.Dispatchers
import react.RBuilder
import react.RComponent
import react.RProps
@ -50,14 +54,33 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
private val dependencies = props.dependencies
private val root = SpotiFlyerRoot(ctx,
object : SpotiFlyerRoot.Dependencies{
object : SpotiFlyerRoot.Dependencies {
override val storeFactory: StoreFactory = LoggingStoreFactory(DefaultStoreFactory)
override val fetchPlatformQueryResult = dependencies.fetchPlatformQueryResult
override val directories = dependencies.directories
override val database: Database? = directories.db
override val showPopUpMessage: (String) -> Unit = {}//TODO
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
}
}
)