mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 12:47:54 +01:00
Maintenance Tasks, workflows, code cleanup
This commit is contained in:
parent
68dc52c6a5
commit
e7a983fe00
35
.github/workflows/maintenance.yml
vendored
Normal file
35
.github/workflows/maintenance.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Maintenance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '*/30 * * * *' #every 30 minutes
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test and Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
# Setup Java environment for the next steps
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 15
|
||||
|
||||
# Check out current repository
|
||||
- name: Fetch Sources
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
# Run Maintenance Tasks
|
||||
- name: Android App
|
||||
run: ./gradlew :maintenance-tasks:run
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
OWNER_NAME: 'Shabinder'
|
||||
REPO_NAME: 'SpotiFlyer'
|
||||
BRANCH_NAME: 'main'
|
||||
FILE_PATH: 'README.md'
|
||||
IMAGE_DESCRIPTION: 'Analytics'
|
||||
COMMIT_MESSAGE: 'Analytics Updated'
|
||||
TAG_NAME: 'HTI'
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,4 +10,5 @@ terraform.tfvars
|
||||
/fastlane/report.xml
|
||||
/fastlane/README.md
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Gemfile.lock
|
||||
/maintenance-tasks/build/
|
||||
|
@ -77,7 +77,7 @@ android {
|
||||
}
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
coreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
@ -31,9 +31,8 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.tools.build:gradle:4.2.0")
|
||||
implementation("com.android.tools.build:gradle:4.0.2")
|
||||
implementation("org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktLint}")
|
||||
//implementation("io.github.gradle-nexus.publish-plugin:1.1.0")
|
||||
implementation(JetBrains.Compose.gradlePlugin)
|
||||
implementation(JetBrains.Kotlin.gradlePlugin)
|
||||
implementation(JetBrains.Kotlin.serialization)
|
||||
|
@ -36,7 +36,7 @@ object Versions {
|
||||
// Internet
|
||||
const val ktor = "1.5.4"
|
||||
|
||||
const val kotlinxSerialization = "1.2.0"
|
||||
const val kotlinxSerialization = "1.2.1"
|
||||
|
||||
// Database
|
||||
const val sqlDelight = "1.5.0"
|
||||
@ -158,7 +158,7 @@ object JetpackDataStore {
|
||||
}
|
||||
|
||||
object Serialization {
|
||||
val core = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinxSerialization}"
|
||||
val json = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerialization}"
|
||||
}
|
||||
|
||||
object SqlDelight {
|
||||
|
@ -28,7 +28,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import com.shabinder.common.database.R
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
||||
actual fun montserratFont() = FontFamily(
|
||||
Font(R.font.montserrat_light, FontWeight.Light),
|
||||
Font(R.font.montserrat_regular, FontWeight.Normal),
|
||||
|
@ -3,7 +3,6 @@ package com.shabinder.common.uikit
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -30,4 +29,4 @@ actual fun VerticalScrollbar(
|
||||
modifier: Modifier,
|
||||
adapter: ScrollbarAdapter
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -10,37 +10,24 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.CardGiftcard
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.shabinder.common.models.methods
|
||||
import com.shabinder.common.uikit.PaypalLogo
|
||||
import com.shabinder.common.uikit.RazorPay
|
||||
import com.shabinder.common.uikit.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.colorAccent
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun DonationDialog(
|
||||
isVisible:Boolean,
|
||||
onDismiss:()->Unit
|
||||
){
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
isVisible
|
||||
) {
|
||||
@ -84,10 +71,12 @@ actual fun DonationDialog(
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp)
|
||||
.clickable(onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}),
|
||||
.clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
|
||||
@ -138,4 +127,4 @@ actual fun DonationDialog(
|
||||
onDismissRequest = onDismiss
|
||||
)*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,12 @@ package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
@ -10,7 +15,6 @@ import com.shabinder.common.di.Picture
|
||||
import com.shabinder.common.di.dispatcherIO
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
@Composable
|
||||
actual fun ImageLoad(
|
||||
link: String,
|
||||
|
@ -20,7 +20,6 @@ package com.shabinder.common.uikit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import com.shabinder.common.di.Picture
|
||||
|
||||
@Composable
|
||||
expect fun DownloadImageTick()
|
||||
@ -69,6 +68,6 @@ expect fun DownloadImageArrow(modifier: Modifier)
|
||||
|
||||
@Composable
|
||||
expect fun DonationDialog(
|
||||
isVisible:Boolean,
|
||||
onDismiss:()->Unit
|
||||
)
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
)
|
||||
|
@ -11,4 +11,4 @@ expect fun ImageLoad(
|
||||
desc: String = "Album Art",
|
||||
modifier: Modifier = Modifier,
|
||||
// placeholder:Painter = PlaceHolderImage()
|
||||
)
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ package com.shabinder.common.uikit
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@ -27,4 +26,4 @@ expect fun rememberScrollbarAdapter(
|
||||
expect fun VerticalScrollbar(
|
||||
modifier: Modifier,
|
||||
adapter: ScrollbarAdapter
|
||||
)
|
||||
)
|
||||
|
@ -223,7 +223,7 @@ fun SearchPanel(
|
||||
@Composable
|
||||
fun AboutColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
donationDialogOpenEvent:() -> Unit
|
||||
donationDialogOpenEvent: () -> Unit
|
||||
) {
|
||||
|
||||
Box {
|
||||
@ -342,10 +342,12 @@ fun AboutColumn(
|
||||
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = {
|
||||
isDonationDialogVisible = true
|
||||
donationDialogOpenEvent()
|
||||
}),
|
||||
.clickable(
|
||||
onClick = {
|
||||
isDonationDialogVisible = true
|
||||
donationDialogOpenEvent()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.CardGiftcard, "Support Developer")
|
||||
@ -357,7 +359,7 @@ fun AboutColumn(
|
||||
)
|
||||
Text(
|
||||
text = "If you think I deserve to get paid for my work, you can support me here.",
|
||||
//text = "SpotiFlyer will always be, Free and Open-Source. You can however show us that you care by sending a small donation.",
|
||||
// text = "SpotiFlyer will always be, Free and Open-Source. You can however show us that you care by sending a small donation.",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
|
@ -37,15 +37,16 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.rounded.ArrowBackIosNew
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -110,7 +111,7 @@ fun SpotiFlyerRootContent(component: SpotiFlyerRoot, modifier: Modifier = Modifi
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp, component: SpotiFlyerRoot) {
|
||||
fun MainScreen(modifier: Modifier = Modifier, alpha: Float, topPadding: Dp = 0.dp, component: SpotiFlyerRoot) {
|
||||
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.65f)
|
||||
|
||||
@ -128,7 +129,7 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
|
||||
val callBacks = component.callBacks
|
||||
AppBar(
|
||||
backgroundColor = appBarColor,
|
||||
onBackPressed = callBacks::popBackToHomeScreen ,
|
||||
onBackPressed = callBacks::popBackToHomeScreen,
|
||||
setDownloadDirectory = callBacks::setDownloadDirectory,
|
||||
isBackButtonVisible = activeComponent.value.activeChild.instance is Child.List,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@ -150,8 +151,8 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
|
||||
@Composable
|
||||
fun AppBar(
|
||||
backgroundColor: Color,
|
||||
onBackPressed:()->Unit,
|
||||
setDownloadDirectory:()->Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
setDownloadDirectory: () -> Unit,
|
||||
isBackButtonVisible: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@ -181,11 +182,11 @@ fun AppBar(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { setDownloadDirectory() }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings,"Preferences", tint = Color.Gray)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { setDownloadDirectory() }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, "Preferences", tint = Color.Gray)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
elevation = 0.dp
|
||||
|
@ -17,8 +17,6 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
enum class ToastDuration(val value: Int) {
|
||||
|
@ -1,2 +1 @@
|
||||
package com.shabinder.common.uikit.dialogs
|
||||
|
||||
|
@ -17,27 +17,17 @@
|
||||
@file:Suppress("FunctionName")
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.Image
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.vectorXmlResource
|
||||
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
|
||||
actual fun DownloadImageTick() {
|
||||
@ -79,11 +69,11 @@ actual fun DownloadImageArrow(modifier: Modifier) {
|
||||
actual fun DownloadAllImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_download_arrow.xml")) as Painter
|
||||
|
||||
@Composable
|
||||
actual fun ShareImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_share_open.xml")) as Painter
|
||||
actual fun ShareImage() = rememberVectorPainter(vectorXmlResource("drawable/ic_share_open.xml")) as Painter
|
||||
|
||||
@Composable
|
||||
actual fun PlaceHolderImage() = rememberVectorPainter(vectorXmlResource("drawable/music.xml"))
|
||||
as Painter
|
||||
as Painter
|
||||
@Composable
|
||||
actual fun SpotiFlyerLogo() =
|
||||
rememberVectorPainter(vectorXmlResource("drawable/ic_spotiflyer_logo.xml")) as Painter
|
||||
@ -92,10 +82,10 @@ actual fun SpotiFlyerLogo() =
|
||||
actual fun HeartIcon() = rememberVectorPainter(vectorXmlResource("drawable/ic_heart.xml")) as Painter
|
||||
|
||||
@Composable
|
||||
actual fun SpotifyLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_spotify_logo.xml")) as Painter
|
||||
actual fun SpotifyLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_spotify_logo.xml")) as Painter
|
||||
|
||||
@Composable
|
||||
actual fun YoutubeLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube.xml")) as Painter
|
||||
actual fun YoutubeLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube.xml")) as Painter
|
||||
|
||||
@Composable
|
||||
actual fun GaanaLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_gaana.xml")) as Painter
|
||||
|
@ -43,4 +43,4 @@ actual fun VerticalScrollbar(
|
||||
modifier = modifier,
|
||||
adapter = adapter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -26,16 +26,13 @@ import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@ -57,7 +54,7 @@ actual fun Toast(
|
||||
val state = flow.collectAsState("")
|
||||
val message = state.value
|
||||
|
||||
AnimatedVisibility (
|
||||
AnimatedVisibility(
|
||||
visible = message != "",
|
||||
enter = fadeIn() + slideInVertically(initialOffsetY = { it / 4 }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { it / 4 }) + fadeOut()
|
||||
@ -67,12 +64,12 @@ actual fun Toast(
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.sizeIn(maxWidth = 250.dp,maxHeight = 80.dp),
|
||||
modifier = Modifier.sizeIn(maxWidth = 250.dp, maxHeight = 80.dp),
|
||||
color = Color(23, 23, 23),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
border = BorderStroke(1.dp, colorOffWhite)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center,modifier = Modifier.fillMaxSize()){
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
text = message,
|
||||
color = Color(210, 210, 210),
|
||||
|
@ -26,9 +26,9 @@ import com.shabinder.common.models.methods
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
actual fun DonationDialog(
|
||||
isVisible:Boolean,
|
||||
onDismiss:()->Unit
|
||||
){
|
||||
isVisible: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
isVisible
|
||||
) {
|
||||
@ -72,10 +72,12 @@ actual fun DonationDialog(
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp)
|
||||
.clickable(onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}),
|
||||
.clickable(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
methods.value.giveDonation()
|
||||
}
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
|
||||
@ -95,4 +97,4 @@ actual fun DonationDialog(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package com.shabinder.common.uikit
|
||||
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
@ -1,3 +1,3 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
|
@ -17,7 +17,7 @@ actual interface PlatformActions {
|
||||
fun sendTracksToService(array: ArrayList<TrackDetails>)
|
||||
}
|
||||
|
||||
actual val StubPlatformActions = object: PlatformActions {
|
||||
actual val StubPlatformActions = object : PlatformActions {
|
||||
override val imageCacheDir = ""
|
||||
|
||||
override val sharedPreferences: SharedPreferences? = null
|
||||
@ -25,4 +25,4 @@ actual val StubPlatformActions = object: PlatformActions {
|
||||
override fun addToLibrary(path: String) {}
|
||||
|
||||
override fun sendTracksToService(array: ArrayList<TrackDetails>) {}
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ internal class RealCache<Key : Any, Value : Any>(
|
||||
*/
|
||||
private fun CacheEntry<Key, Value>.isExpired(): Boolean {
|
||||
return expiresAfterAccess && (accessTimeMark.get() + expireAfterAccessDuration).hasPassedNow() ||
|
||||
expiresAfterWrite && (writeTimeMark.get() + expireAfterWriteDuration).hasPassedNow()
|
||||
expiresAfterWrite && (writeTimeMark.get() + expireAfterWriteDuration).hasPassedNow()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,8 +41,7 @@ interface Actions {
|
||||
fun writeMp3Tags(trackDetails: TrackDetails)
|
||||
}
|
||||
|
||||
|
||||
private fun stubActions(): Actions = object :Actions {
|
||||
private fun stubActions(): Actions = object : Actions {
|
||||
override val platformActions = StubPlatformActions
|
||||
override fun showPopUpMessage(string: String, long: Boolean) {}
|
||||
override fun setDownloadDirectoryAction() {}
|
||||
@ -53,4 +52,4 @@ private fun stubActions(): Actions = object :Actions {
|
||||
override fun writeMp3Tags(trackDetails: TrackDetails) {}
|
||||
|
||||
override val isInternetAvailable: Boolean = true
|
||||
}
|
||||
}
|
||||
|
@ -20,4 +20,4 @@ sealed class AllPlatforms {
|
||||
object Js : AllPlatforms()
|
||||
object Jvm : AllPlatforms()
|
||||
object Native : AllPlatforms()
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,4 @@ package com.shabinder.common.models
|
||||
|
||||
expect interface PlatformActions
|
||||
|
||||
expect val StubPlatformActions : PlatformActions
|
||||
expect val StubPlatformActions: PlatformActions
|
||||
|
@ -58,7 +58,5 @@ enum class Status constructor(val value: Int) {
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
|
@ -1,5 +1,5 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
actual interface PlatformActions {}
|
||||
actual interface PlatformActions
|
||||
|
||||
actual val StubPlatformActions = object: PlatformActions {}
|
||||
actual val StubPlatformActions = object : PlatformActions {}
|
||||
|
@ -2,8 +2,8 @@ package com.shabinder.common.models
|
||||
|
||||
import kotlin.native.concurrent.AtomicReference
|
||||
|
||||
actual interface PlatformActions {}
|
||||
actual interface PlatformActions
|
||||
|
||||
actual val StubPlatformActions = object: PlatformActions {}
|
||||
actual val StubPlatformActions = object : PlatformActions {}
|
||||
|
||||
actual typealias NativeAtomicReference<T> = AtomicReference<T>
|
||||
actual typealias NativeAtomicReference<T> = AtomicReference<T>
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
actual interface PlatformActions {}
|
||||
actual val StubPlatformActions = object: PlatformActions {}
|
||||
actual interface PlatformActions
|
||||
actual val StubPlatformActions = object : PlatformActions {}
|
||||
|
@ -1,3 +1,3 @@
|
||||
package com.shabinder.common.models
|
||||
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
actual class NativeAtomicReference<T> actual constructor(actual var value: T)
|
||||
|
@ -54,7 +54,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
if(HostOS.isMac) {
|
||||
if (HostOS.isMac) {
|
||||
val iosMain by getting {
|
||||
dependencies {
|
||||
implementation(SqlDelight.nativeDriver)
|
||||
|
@ -3,8 +3,8 @@ 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
|
||||
import org.koin.dsl.module
|
||||
|
||||
@Suppress("RedundantNullableReturnType")
|
||||
actual fun databaseModule() = module {
|
||||
@ -14,4 +14,4 @@ actual fun databaseModule() = module {
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getLogger(): Logger = NSLogLogger()
|
||||
actual fun getLogger(): Logger = NSLogLogger()
|
||||
|
@ -18,7 +18,6 @@ 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 databaseModule() = module { single { SpotiFlyerDatabase(null) } }
|
||||
|
@ -36,4 +36,4 @@ actual suspend fun downloadTracks(
|
||||
if (!list.isNullOrEmpty()) {
|
||||
methods.value.platformActions.sendTracksToService(ArrayList(list))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,10 +54,10 @@ actual class Dir actual constructor(
|
||||
const val firstLaunch = "firstLaunch"
|
||||
}
|
||||
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
|
||||
actual fun firstLaunchDone(){
|
||||
settings.putBoolean(firstLaunch,false)
|
||||
actual fun firstLaunchDone() {
|
||||
settings.putBoolean(firstLaunch, false)
|
||||
}
|
||||
|
||||
/*
|
||||
@ -67,10 +67,10 @@ actual class Dir actual constructor(
|
||||
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
|
||||
|
||||
actual fun enableAnalytics() {
|
||||
settings.putBoolean(AnalyticsKey,true)
|
||||
settings.putBoolean(AnalyticsKey, true)
|
||||
}
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private val defaultBaseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString()
|
||||
@ -81,7 +81,7 @@ actual class Dir actual constructor(
|
||||
|
||||
// fun call in order to always access Updated Value
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) +
|
||||
File.separator + "SpotiFlyer" + File.separator
|
||||
File.separator + "SpotiFlyer" + File.separator
|
||||
|
||||
actual fun isPresent(path: String): Boolean = File(path).exists()
|
||||
|
||||
@ -106,14 +106,14 @@ actual class Dir actual constructor(
|
||||
actual suspend fun saveFileWithMetadata(
|
||||
mp3ByteArray: ByteArray,
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
) = withContext(dispatcherIO) {
|
||||
postProcess: (track: TrackDetails) -> Unit
|
||||
) = withContext(dispatcherIO) {
|
||||
val songFile = File(trackDetails.outputFilePath)
|
||||
try {
|
||||
/*
|
||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||
* */
|
||||
if(!songFile.exists()) {
|
||||
if (!songFile.exists()) {
|
||||
/*Make intermediate Dirs if they don't exist yet*/
|
||||
songFile.parentFile?.mkdirs()
|
||||
}
|
||||
@ -163,15 +163,15 @@ actual class Dir actual constructor(
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
if(songFile.exists()) songFile.delete()
|
||||
} catch (e: Exception) {
|
||||
if (songFile.exists()) songFile.delete()
|
||||
logger.e { "${songFile.absolutePath} could not be created" }
|
||||
}
|
||||
}
|
||||
|
||||
actual fun addToLibrary(path: String) = methods.value.platformActions.addToLibrary(path)
|
||||
|
||||
actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){
|
||||
actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO) {
|
||||
val cachePath = imageCacheDir() + getNameURL(url)
|
||||
Picture(image = (loadCachedImage(cachePath) ?: freshImage(url))?.asImageBitmap())
|
||||
}
|
||||
@ -189,7 +189,7 @@ actual class Dir actual constructor(
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
actual suspend fun cacheImage(image: Any, path: String):Unit = withContext(dispatcherIO) {
|
||||
actual suspend fun cacheImage(image: Any, path: String): Unit = withContext(dispatcherIO) {
|
||||
try {
|
||||
FileOutputStream(path).use { out ->
|
||||
(image as? Bitmap)?.compress(Bitmap.CompressFormat.JPEG, 100, out)
|
||||
|
@ -24,7 +24,10 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
import android.net.NetworkRequest
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.lang.Exception
|
||||
import java.net.InetSocketAddress
|
||||
@ -104,7 +107,7 @@ class ConnectionLiveData(context: Context) : LiveData<Boolean>() {
|
||||
* If successful, that means we have internet.
|
||||
*/
|
||||
object DoesNetworkHaveInternet {
|
||||
suspend fun execute(network: Network): Boolean = withContext(Dispatchers.IO) {
|
||||
suspend fun execute(network: Network): Boolean = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d(TAG, "PINGING google.")
|
||||
val socket = network.socketFactory.createSocket() ?: throw IOException("Socket is null.")
|
||||
@ -112,7 +115,7 @@ class ConnectionLiveData(context: Context) : LiveData<Boolean>() {
|
||||
socket.close()
|
||||
Log.d(TAG, "PING success.")
|
||||
true
|
||||
}catch (e:Exception){
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// Handle VPN Connection / Google DNS Blocked Cases
|
||||
isInternetAccessible()
|
||||
|
@ -17,13 +17,13 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import android.util.Log
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import java.io.File
|
||||
import com.mpatric.mp3agic.ID3v1Tag
|
||||
import com.mpatric.mp3agic.ID3v24Tag
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
fun Mp3File.removeAllTags(): Mp3File {
|
||||
|
@ -33,7 +33,10 @@ import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.di.*
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.R
|
||||
import com.shabinder.common.di.downloadFile
|
||||
import com.shabinder.common.di.providers.get
|
||||
import com.shabinder.common.di.utils.ParallelExecutor
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
@ -120,7 +123,8 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
|
||||
val downloadObjects: ArrayList<TrackDetails>? = (
|
||||
it.getParcelableArrayListExtra("object") ?: it.extras?.getParcelableArrayList(
|
||||
"object")
|
||||
"object"
|
||||
)
|
||||
)
|
||||
|
||||
downloadObjects?.let { list ->
|
||||
@ -184,7 +188,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID)
|
||||
if (url == null) {
|
||||
val audioData: Format = ytDownloader?.getVideo(videoID)?.get() ?: throw Exception("Java YT Dependency Error")
|
||||
val ytUrl = audioData.url!! //We Will catch NPE
|
||||
val ytUrl = audioData.url!! // We Will catch NPE
|
||||
enqueueDownload(ytUrl, track)
|
||||
} else enqueueDownload(url, track)
|
||||
} catch (e: Exception) {
|
||||
@ -210,7 +214,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
failed++
|
||||
updateNotification()
|
||||
sendTrackBroadcast(Status.FAILED.name,track)
|
||||
sendTrackBroadcast(Status.FAILED.name, track)
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +233,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
is DownloadResult.Success -> {
|
||||
try {
|
||||
// Save File and Embed Metadata
|
||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track){} }
|
||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) {} }
|
||||
allTracksStatus[track.title] = DownloadStatus.Converting
|
||||
sendTrackBroadcast("Converting", track)
|
||||
addToNotification("Processing ${track.title}")
|
||||
@ -288,7 +292,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||
downloadService.close()
|
||||
updateNotification()
|
||||
cleanFiles(File(dir.defaultDir()),logger)
|
||||
cleanFiles(File(dir.defaultDir()), logger)
|
||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
||||
messageList = mutableListOf("", "", "", "", "")
|
||||
releaseWakeLock()
|
||||
|
@ -8,7 +8,7 @@ import java.io.File
|
||||
/**
|
||||
* Cleaning All Residual Files except Mp3 Files
|
||||
**/
|
||||
fun cleanFiles(dir: File,logger: Kermit) {
|
||||
fun cleanFiles(dir: File, logger: Kermit) {
|
||||
try {
|
||||
logger.d("File Cleaning") { "Starting Cleaning in ${dir.path} " }
|
||||
val fList = dir.listFiles()
|
||||
@ -24,12 +24,12 @@ fun cleanFiles(dir: File,logger: Kermit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e:Exception) { e.printStackTrace() }
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
/**
|
||||
* Cleaning All Residual Files except Mp3 Files
|
||||
**/
|
||||
fun cleanFiles(directory: AbstractFile,fm: FileManager,logger: Kermit) {
|
||||
fun cleanFiles(directory: AbstractFile, fm: FileManager, logger: Kermit) {
|
||||
try {
|
||||
logger.d("Files Cleaning") { "Starting Cleaning in ${directory.getFullPath()} " }
|
||||
val fList = fm.listFiles(directory)
|
||||
@ -37,14 +37,13 @@ fun cleanFiles(directory: AbstractFile,fm: FileManager,logger: Kermit) {
|
||||
if (fm.isDirectory(file)) {
|
||||
cleanFiles(file, fm, logger)
|
||||
} else if (fm.isFile(file)) {
|
||||
if (file.getFullPath().substringAfterLast(".") != "mp3"
|
||||
||
|
||||
if (file.getFullPath().substringAfterLast(".") != "mp3" ||
|
||||
fm.getLength(file) == 0L
|
||||
) {
|
||||
) {
|
||||
logger.d("Files Cleaning") { "Cleaning ${file.getFullPath()}" }
|
||||
fm.delete(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e:Exception) { e.printStackTrace() }
|
||||
}
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
|
@ -25,10 +25,13 @@ import com.shabinder.common.di.providers.SpotifyProvider
|
||||
import com.shabinder.common.di.providers.YoutubeMp3
|
||||
import com.shabinder.common.di.providers.YoutubeMusic
|
||||
import com.shabinder.common.di.providers.YoutubeProvider
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.features.logging.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||
import io.ktor.client.features.logging.DEFAULT
|
||||
import io.ktor.client.features.logging.LogLevel
|
||||
import io.ktor.client.features.logging.Logger
|
||||
import io.ktor.client.features.logging.Logging
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.dsl.KoinAppDeclaration
|
||||
|
@ -23,7 +23,8 @@ 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 io.ktor.client.request.*
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.HttpStatement
|
||||
import io.ktor.http.contentLength
|
||||
import io.ktor.http.isSuccess
|
||||
@ -31,14 +32,14 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
expect class Dir (
|
||||
expect class Dir(
|
||||
logger: Kermit,
|
||||
settings: Settings,
|
||||
spotiFlyerDatabase: SpotiFlyerDatabase,
|
||||
) {
|
||||
val db: Database?
|
||||
val isAnalyticsEnabled:Boolean
|
||||
val isFirstLaunch:Boolean
|
||||
val isAnalyticsEnabled: Boolean
|
||||
val isFirstLaunch: Boolean
|
||||
fun enableAnalytics()
|
||||
fun firstLaunchDone()
|
||||
fun isPresent(path: String): Boolean
|
||||
@ -46,11 +47,11 @@ expect class Dir (
|
||||
fun defaultDir(): String
|
||||
fun imageCacheDir(): String
|
||||
fun createDirectory(dirPath: String)
|
||||
fun setDownloadDirectory(newBasePath:String)
|
||||
fun setDownloadDirectory(newBasePath: String)
|
||||
suspend fun cacheImage(image: Any, path: String) // in Android = ImageBitmap, Desktop = BufferedImage
|
||||
suspend fun loadImage(url: String): Picture
|
||||
suspend fun clearCache()
|
||||
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails,postProcess:(track: TrackDetails)->Unit = {})
|
||||
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess: (track: TrackDetails) -> Unit = {})
|
||||
fun addToLibrary(path: String)
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
||||
emit(DownloadResult.Error("File not downloaded"))
|
||||
}
|
||||
client.close()
|
||||
} catch (e:Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
emit(DownloadResult.Error(e.message ?: "File not downloaded"))
|
||||
}
|
||||
@ -83,12 +84,12 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
||||
|
||||
suspend fun downloadByteArray(
|
||||
url: String,
|
||||
httpBuilder: HttpRequestBuilder.()->Unit = {}
|
||||
httpBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): ByteArray? {
|
||||
val client = createHttpClient()
|
||||
val response = try {
|
||||
client.get<ByteArray>(url,httpBuilder)
|
||||
} catch (e: Exception){
|
||||
client.get<ByteArray>(url, httpBuilder)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
client.close()
|
||||
|
@ -18,8 +18,7 @@ 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 io.ktor.client.request.head
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -31,7 +30,6 @@ expect suspend fun downloadTracks(
|
||||
dir: Dir
|
||||
)
|
||||
|
||||
|
||||
// IO-Dispatcher
|
||||
@SharedImmutable
|
||||
expect val dispatcherIO: CoroutineDispatcher
|
||||
|
@ -79,7 +79,7 @@ class GaanaProvider(
|
||||
it.updateStatusIfPresent(folderType, subFolder)
|
||||
trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
|
||||
title = it.track_title
|
||||
coverUrl = it.artworkLink.replace("http:","https:")
|
||||
coverUrl = it.artworkLink.replace("http:", "https:")
|
||||
}
|
||||
}
|
||||
"album" -> {
|
||||
@ -91,7 +91,7 @@ class GaanaProvider(
|
||||
}
|
||||
trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
|
||||
title = link
|
||||
coverUrl = it.custom_artworks.size_480p.replace("http:","https:")
|
||||
coverUrl = it.custom_artworks.size_480p.replace("http:", "https:")
|
||||
}
|
||||
}
|
||||
"playlist" -> {
|
||||
@ -114,7 +114,7 @@ class GaanaProvider(
|
||||
getGaanaArtistDetails(seokey = link).artist.firstOrNull()
|
||||
?.also {
|
||||
title = it.name
|
||||
coverUrl = it.artworkLink?.replace("http:","https:") ?: gaanaPlaceholderImageUrl
|
||||
coverUrl = it.artworkLink?.replace("http:", "https:") ?: gaanaPlaceholderImageUrl
|
||||
}
|
||||
getGaanaArtistTracks(seokey = link).also {
|
||||
it.tracks?.forEach { track ->
|
||||
@ -143,7 +143,7 @@ class GaanaProvider(
|
||||
trackUrl = it.lyrics_url,
|
||||
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
|
||||
source = Source.Gaana,
|
||||
albumArtURL = it.artworkLink.replace("http:","https:"),
|
||||
albumArtURL = it.artworkLink.replace("http:", "https:"),
|
||||
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
|
||||
)
|
||||
}
|
||||
|
@ -62,9 +62,9 @@ class SpotifyProvider(
|
||||
defaultRequest {
|
||||
header("Authorization", "Bearer ${token.access_token}")
|
||||
}
|
||||
install(JsonFeature) {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(globalJson)
|
||||
}
|
||||
}
|
||||
}.also { httpClientRef.value = it }
|
||||
}
|
||||
}
|
||||
@ -98,7 +98,7 @@ class SpotifyProvider(
|
||||
type,
|
||||
link
|
||||
)
|
||||
}catch (e: Exception){
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// Try Reinitialising Client // Handle 401 Token Expiry ,etc Exceptions
|
||||
authenticateSpotifyClient(true)
|
||||
@ -108,7 +108,7 @@ class SpotifyProvider(
|
||||
type,
|
||||
link
|
||||
)
|
||||
} catch (e:Exception){
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ 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(
|
||||
|
@ -54,7 +54,7 @@ class YoutubeMusic constructor(
|
||||
trackArtists = trackDetails.artists,
|
||||
trackDurationSec = trackDetails.durationSec
|
||||
).keys.firstOrNull()
|
||||
} catch (e:Exception) {
|
||||
} catch (e: Exception) {
|
||||
// All Internet/Client Related Errors
|
||||
e.printStackTrace()
|
||||
null
|
||||
|
@ -38,7 +38,7 @@ class YoutubeProvider(
|
||||
private val dir: Dir,
|
||||
) {
|
||||
// Youtube Downloader isn't fully compatible with JS Yet
|
||||
val ytDownloader: YoutubeDownloader? = if(currentPlatform == AllPlatforms.Js) null else YoutubeDownloader()
|
||||
val ytDownloader: YoutubeDownloader? = if (currentPlatform == AllPlatforms.Js) null else YoutubeDownloader()
|
||||
|
||||
/*
|
||||
* YT Album Art Schema
|
||||
@ -102,25 +102,25 @@ class YoutubeProvider(
|
||||
val videos = playlist.videos
|
||||
|
||||
coverUrl = "https://i.ytimg.com/vi/${
|
||||
videos.firstOrNull()?.videoId
|
||||
videos.firstOrNull()?.videoId
|
||||
}/hqdefault.jpg"
|
||||
title = name
|
||||
|
||||
trackList = videos.map {
|
||||
TrackDetails(
|
||||
title = it.title ?: "N/A",
|
||||
artists = listOf(it.author ?: "N/A"),
|
||||
artists = listOf(it.author ?: "N/A"),
|
||||
durationSec = it.lengthSeconds,
|
||||
albumArtPath = dir.imageCacheDir() + it.videoId + ".jpeg",
|
||||
source = Source.YouTube,
|
||||
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
|
||||
downloaded = if (dir.isPresent(
|
||||
dir.finalOutputDir(
|
||||
itemName = it.title ?: "N/A",
|
||||
type = folderType,
|
||||
subFolder = subFolder,
|
||||
dir.defaultDir()
|
||||
)
|
||||
itemName = it.title ?: "N/A",
|
||||
type = folderType,
|
||||
subFolder = subFolder,
|
||||
dir.defaultDir()
|
||||
)
|
||||
)
|
||||
)
|
||||
DownloadStatus.Downloaded
|
||||
@ -170,11 +170,11 @@ class YoutubeProvider(
|
||||
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
|
||||
downloaded = if (dir.isPresent(
|
||||
dir.finalOutputDir(
|
||||
itemName = name,
|
||||
type = folderType,
|
||||
subFolder = subFolder,
|
||||
defaultDir = dir.defaultDir()
|
||||
)
|
||||
itemName = name,
|
||||
type = folderType,
|
||||
subFolder = subFolder,
|
||||
defaultDir = dir.defaultDir()
|
||||
)
|
||||
)
|
||||
)
|
||||
DownloadStatus.Downloaded
|
||||
|
@ -34,7 +34,7 @@ suspend fun authenticateSpotify(): TokenData? {
|
||||
if (methods.value.isInternetAvailable) spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
|
||||
body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") })
|
||||
} else null
|
||||
}catch (e:Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
|
||||
import com.shabinder.common.models.spotify.Playlist
|
||||
import com.shabinder.common.models.spotify.Track
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.get
|
||||
|
||||
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"
|
||||
|
||||
interface SpotifyRequests {
|
||||
|
||||
val httpClientRef: NativeAtomicReference<HttpClient>
|
||||
val httpClient:HttpClient get() = httpClientRef.value
|
||||
val httpClient: HttpClient get() = httpClientRef.value
|
||||
|
||||
suspend fun authenticateSpotifyClient(override: Boolean = false)
|
||||
|
||||
|
@ -22,7 +22,6 @@ package com.shabinder.common.di.utils
|
||||
// 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
|
||||
|
@ -16,21 +16,16 @@
|
||||
|
||||
package com.shabinder.common.di.utils
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.serializer
|
||||
import kotlin.native.concurrent.SharedImmutable
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
|
||||
@ThreadLocal
|
||||
val json by lazy { Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
} }
|
||||
val json by lazy {
|
||||
Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removing Illegal Chars from File Name
|
||||
|
@ -59,7 +59,7 @@ actual suspend fun downloadTracks(
|
||||
) { hashMapOf() }.apply { set(it.title, DownloadStatus.Failed) }
|
||||
)
|
||||
} else { // Found Youtube Video ID
|
||||
downloadTrack(videoId, it, dir::saveFileWithMetadata,fetcher.youtubeMp3)
|
||||
downloadTrack(videoId, it, dir::saveFileWithMetadata, fetcher.youtubeMp3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,7 +102,7 @@ suspend fun downloadTrack(
|
||||
)
|
||||
}
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
saveFileWithMetaData(it.byteArray, trackDetails){}
|
||||
saveFileWithMetaData(it.byteArray, trackDetails) {}
|
||||
DownloadProgressFlow.emit(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
|
@ -50,16 +50,16 @@ actual class Dir actual constructor(
|
||||
const val firstLaunch = "firstLaunch"
|
||||
}
|
||||
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
|
||||
actual fun firstLaunchDone(){
|
||||
settings.putBoolean(firstLaunch,false)
|
||||
actual fun firstLaunchDone() {
|
||||
settings.putBoolean(firstLaunch, false)
|
||||
}
|
||||
|
||||
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
|
||||
|
||||
actual fun enableAnalytics() {
|
||||
settings.putBoolean(AnalyticsKey,true)
|
||||
settings.putBoolean(AnalyticsKey, true)
|
||||
}
|
||||
|
||||
init {
|
||||
@ -76,7 +76,7 @@ actual class Dir actual constructor(
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) + fileSeparator() +
|
||||
"SpotiFlyer" + fileSeparator()
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
|
||||
|
||||
actual fun isPresent(path: String): Boolean = File(path).exists()
|
||||
|
||||
@ -110,19 +110,19 @@ actual class Dir actual constructor(
|
||||
actual suspend fun saveFileWithMetadata(
|
||||
mp3ByteArray: ByteArray,
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
postProcess: (track: TrackDetails) -> Unit
|
||||
) {
|
||||
val songFile = File(trackDetails.outputFilePath)
|
||||
try {
|
||||
/*
|
||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||
* */
|
||||
if(!songFile.exists()) {
|
||||
if (!songFile.exists()) {
|
||||
/*Make intermediate Dirs if they don't exist yet*/
|
||||
songFile.parentFile.mkdirs()
|
||||
}
|
||||
|
||||
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)
|
||||
if (mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)
|
||||
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
@ -167,11 +167,11 @@ actual class Dir actual constructor(
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
withContext(Dispatchers.Main){
|
||||
//Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
// Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
if(songFile.exists()) songFile.delete()
|
||||
if (songFile.exists()) songFile.delete()
|
||||
logger.e { "${songFile.absolutePath} could not be created" }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import com.shabinder.common.di.providers.getData
|
||||
import com.shabinder.common.di.providers.get
|
||||
import com.shabinder.common.di.utils.ParallelExecutor
|
||||
import com.shabinder.common.models.AllPlatforms
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
@ -30,7 +30,7 @@ actual suspend fun downloadTracks(
|
||||
Downloader.execute {
|
||||
if (!track.videoID.isNullOrBlank()) { // Video ID already known!
|
||||
dir.logger.i { "VideoID: ${track.title} -> ${track.videoID}" }
|
||||
downloadTrack(track.videoID!!, track, dir::saveFileWithMetadata,fetcher)
|
||||
downloadTrack(track.videoID!!, track, dir::saveFileWithMetadata, fetcher)
|
||||
} else {
|
||||
val searchQuery = "${track.title} - ${track.artists.joinToString(",")}"
|
||||
val videoId = fetcher.youtubeMusic.getYTIDBestMatch(searchQuery, track)
|
||||
@ -42,7 +42,7 @@ actual suspend fun downloadTracks(
|
||||
) { hashMapOf() }.apply { set(track.title, DownloadStatus.Failed) }
|
||||
)
|
||||
} else { // Found Youtube Video ID
|
||||
downloadTrack(videoId, track, dir::saveFileWithMetadata,fetcher)
|
||||
downloadTrack(videoId, track, dir::saveFileWithMetadata, fetcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ suspend fun downloadTrack(
|
||||
|
||||
fetcher.dir.logger.i { "LINK: $videoID -> $link" }
|
||||
if (link == null) {
|
||||
link = fetcher.youtubeProvider.ytDownloader.getVideo(videoID).get()?.url ?: return
|
||||
link = fetcher.youtubeProvider.ytDownloader?.getVideo(videoID)?.get()?.url ?: return
|
||||
}
|
||||
fetcher.dir.logger.i { "LINK: $videoID -> $link" }
|
||||
downloadFile(link).collect {
|
||||
@ -81,7 +81,7 @@ suspend fun downloadTrack(
|
||||
DownloadProgressFlow.replayCache.getOrElse(
|
||||
0
|
||||
) { hashMapOf() }.toMutableMap().apply {
|
||||
set(trackDetails.title,DownloadStatus.Downloading(it.progress))
|
||||
set(trackDetails.title, DownloadStatus.Downloading(it.progress))
|
||||
}
|
||||
}
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
|
@ -1,17 +1,15 @@
|
||||
package com.shabinder.common.di
|
||||
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
/*
|
||||
* Dependency Provider for IOS
|
||||
* */
|
||||
object IOSDeps: KoinComponent {
|
||||
object IOSDeps : KoinComponent {
|
||||
val dir: Dir by inject() // = get()
|
||||
val fetchPlatformQueryResult: FetchPlatformQueryResult by inject() // get()
|
||||
val database get() = dir.db
|
||||
val sharedFlow = DownloadProgressFlow
|
||||
val defaultDispatcher = dispatcherDefault
|
||||
}
|
||||
}
|
||||
|
@ -33,71 +33,71 @@ actual class Dir actual constructor(
|
||||
const val firstLaunch = "firstLaunch"
|
||||
}
|
||||
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
|
||||
actual fun firstLaunchDone() {
|
||||
settings.putBoolean(firstLaunch,false)
|
||||
settings.putBoolean(firstLaunch, false)
|
||||
}
|
||||
|
||||
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
|
||||
|
||||
actual fun enableAnalytics() {
|
||||
settings.putBoolean(AnalyticsKey,true)
|
||||
settings.putBoolean(AnalyticsKey, true)
|
||||
}
|
||||
|
||||
actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path)
|
||||
|
||||
actual fun fileSeparator(): String = "/"
|
||||
|
||||
private val defaultBaseDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!.path!!
|
||||
private val defaultBaseDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask, null, true, null)!!.path!!
|
||||
|
||||
// TODO Error Handling
|
||||
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) +
|
||||
fileSeparator() + "SpotiFlyer" + fileSeparator()
|
||||
fileSeparator() + "SpotiFlyer" + fileSeparator()
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
|
||||
|
||||
private val defaultDirURL: NSURL by lazy {
|
||||
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!
|
||||
musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!!
|
||||
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask, null, true, null)!!
|
||||
musicDir.URLByAppendingPathComponent("SpotiFlyer", true)!!
|
||||
}
|
||||
|
||||
actual fun imageCacheDir(): String = imageCacheURL.path!! + fileSeparator()
|
||||
|
||||
private val imageCacheURL: NSURL by lazy {
|
||||
val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null)
|
||||
cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!!
|
||||
val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask, null, true, null)
|
||||
cacheDir?.URLByAppendingPathComponent("SpotiFlyer", true)!!
|
||||
}
|
||||
|
||||
actual fun createDirectory(dirPath: String) {
|
||||
try {
|
||||
NSFileManager.defaultManager.createDirectoryAtPath(dirPath,true,null,null)
|
||||
} catch (e:Exception) {
|
||||
NSFileManager.defaultManager.createDirectoryAtPath(dirPath, true, null, null)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun createDirectory(dirURL: NSURL) {
|
||||
try {
|
||||
NSFileManager.defaultManager.createDirectoryAtURL(dirURL,true,null,null)
|
||||
} catch (e:Exception) {
|
||||
NSFileManager.defaultManager.createDirectoryAtURL(dirURL, true, null, null)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
actual suspend fun cacheImage(image: Any, path: String): Unit = withContext(dispatcherIO){
|
||||
actual suspend fun cacheImage(image: Any, path: String): Unit = withContext(dispatcherIO) {
|
||||
try {
|
||||
(image as? UIImage)?.let {
|
||||
// We Will Be Using JPEG as default format everywhere
|
||||
UIImageJPEGRepresentation(it,1.0)
|
||||
?.writeToFile(path,true)
|
||||
UIImageJPEGRepresentation(it, 1.0)
|
||||
?.writeToFile(path, true)
|
||||
}
|
||||
}catch (e:Exception){
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO){
|
||||
actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO) {
|
||||
try {
|
||||
val cachePath = imageCacheURL.URLByAppendingPathComponent(getNameURL(url))
|
||||
Picture(image = cachePath?.path?.let { loadCachedImage(it) } ?: loadFreshImage(url))
|
||||
@ -110,24 +110,24 @@ actual class Dir actual constructor(
|
||||
private fun loadCachedImage(filePath: String): UIImage? {
|
||||
return try {
|
||||
UIImage.imageWithContentsOfFile(filePath)
|
||||
}catch (e:Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFreshImage(url: String):UIImage? = withContext(dispatcherIO){
|
||||
private suspend fun loadFreshImage(url: String): UIImage? = withContext(dispatcherIO) {
|
||||
try {
|
||||
val nsURL = NSURL(string = url)
|
||||
val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL),null,null)
|
||||
val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL), null, null)
|
||||
if (data != null) {
|
||||
UIImage.imageWithData(data)?.also {
|
||||
GlobalScope.launch {
|
||||
cacheImage(it, imageCacheDir() + getNameURL(url))
|
||||
}
|
||||
}
|
||||
}else null
|
||||
}catch (e: Exception) {
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
@ -136,7 +136,8 @@ actual class Dir actual constructor(
|
||||
actual suspend fun clearCache(): Unit = withContext(dispatcherIO) {
|
||||
try {
|
||||
val fileManager = NSFileManager.defaultManager
|
||||
val paths = fileManager.contentsOfDirectoryAtURL(imageCacheURL,
|
||||
val paths = fileManager.contentsOfDirectoryAtURL(
|
||||
imageCacheURL,
|
||||
null,
|
||||
NSDirectoryEnumerationSkipsHiddenFiles,
|
||||
null
|
||||
@ -144,20 +145,19 @@ actual class Dir actual constructor(
|
||||
paths?.forEach {
|
||||
(it as? NSURL)?.let { nsURL ->
|
||||
// Lets Remove Cached File
|
||||
fileManager.removeItemAtURL(nsURL,null)
|
||||
fileManager.removeItemAtURL(nsURL, null)
|
||||
}
|
||||
}
|
||||
}catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
actual suspend fun saveFileWithMetadata(
|
||||
mp3ByteArray: ByteArray,
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
) : Unit = withContext(dispatcherIO) {
|
||||
postProcess: (track: TrackDetails) -> Unit
|
||||
): Unit = withContext(dispatcherIO) {
|
||||
try {
|
||||
if (mp3ByteArray.isNotEmpty()) {
|
||||
mp3ByteArray.toNSData().writeToFile(
|
||||
@ -167,9 +167,9 @@ actual class Dir actual constructor(
|
||||
}
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
if(!isPresent(trackDetails.albumArtPath)) {
|
||||
if (!isPresent(trackDetails.albumArtPath)) {
|
||||
val imageData = downloadByteArray(
|
||||
trackDetails.albumArtURL
|
||||
trackDetails.albumArtURL
|
||||
)?.toNSData()
|
||||
if (imageData != null) {
|
||||
UIImage.imageWithData(imageData)?.also {
|
||||
@ -186,7 +186,7 @@ actual class Dir actual constructor(
|
||||
)*/
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@ -196,4 +196,4 @@ actual class Dir actual constructor(
|
||||
}
|
||||
|
||||
actual val db: Database? = spotiFlyerDatabase.instance
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,4 @@ import platform.UIKit.UIImage
|
||||
|
||||
actual data class Picture(
|
||||
val image: UIImage?
|
||||
)
|
||||
)
|
||||
|
@ -21,4 +21,4 @@ fun NSData.toByteArray(): ByteArray = memScoped {
|
||||
val nsData = ByteArray(size)
|
||||
memcpy(nsData.refTo(0), bytes, size.toULong())
|
||||
return nsData
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ 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.common.models.methods
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@ -75,7 +74,7 @@ suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher: FetchPl
|
||||
when (it) {
|
||||
is DownloadResult.Success -> {
|
||||
println("Download Completed")
|
||||
dir.saveFileWithMetadata(it.byteArray, track){}
|
||||
dir.saveFileWithMetadata(it.byteArray, track) {}
|
||||
}
|
||||
is DownloadResult.Error -> {
|
||||
allTracksStatus[track.title] = DownloadStatus.Failed
|
||||
|
@ -43,19 +43,19 @@ actual class Dir actual constructor(
|
||||
const val firstLaunch = "firstLaunch"
|
||||
}
|
||||
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
|
||||
|
||||
actual fun firstLaunchDone() {
|
||||
settings.putBoolean(firstLaunch,false)
|
||||
settings.putBoolean(firstLaunch, false)
|
||||
}
|
||||
|
||||
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
|
||||
|
||||
actual fun enableAnalytics() {
|
||||
settings.putBoolean(AnalyticsKey,true)
|
||||
settings.putBoolean(AnalyticsKey, true)
|
||||
}
|
||||
|
||||
actual fun setDownloadDirectory(newBasePath:String) = settings.putString(DirKey,newBasePath)
|
||||
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
|
||||
|
||||
/*init {
|
||||
createDirectories()
|
||||
@ -84,7 +84,7 @@ actual class Dir actual constructor(
|
||||
actual suspend fun saveFileWithMetadata(
|
||||
mp3ByteArray: ByteArray,
|
||||
trackDetails: TrackDetails,
|
||||
postProcess:(track: TrackDetails)->Unit
|
||||
postProcess: (track: TrackDetails) -> Unit
|
||||
) {
|
||||
val writer = ID3Writer(mp3ByteArray.toArrayBuffer())
|
||||
val albumArt = downloadFile(corsApi + trackDetails.albumArtURL)
|
||||
|
@ -68,9 +68,7 @@ interface SpotiFlyerList {
|
||||
val listAnalytics: Analytics
|
||||
}
|
||||
|
||||
interface Analytics {
|
||||
|
||||
}
|
||||
interface Analytics
|
||||
|
||||
sealed class Output {
|
||||
object Finished : Output()
|
||||
|
@ -19,6 +19,7 @@ package com.shabinder.common.list.integration
|
||||
import co.touchlab.stately.ensureNeverFrozen
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.shabinder.common.caching.Cache
|
||||
import com.shabinder.common.di.Picture
|
||||
import com.shabinder.common.di.utils.asValue
|
||||
import com.shabinder.common.list.SpotiFlyerList
|
||||
@ -28,7 +29,6 @@ import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
||||
import com.shabinder.common.list.store.SpotiFlyerListStoreProvider
|
||||
import com.shabinder.common.list.store.getStore
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.caching.Cache
|
||||
|
||||
internal class SpotiFlyerListImpl(
|
||||
componentContext: ComponentContext,
|
||||
|
@ -24,7 +24,6 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||
import com.shabinder.common.database.getLogger
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.dispatcherIO
|
||||
import com.shabinder.common.di.downloadTracks
|
||||
import com.shabinder.common.list.SpotiFlyerList.State
|
||||
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
|
||||
@ -34,7 +33,6 @@ import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.methods
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class SpotiFlyerListStoreProvider(
|
||||
private val dir: Dir,
|
||||
@ -80,14 +78,14 @@ internal class SpotiFlyerListStoreProvider(
|
||||
is Intent.SearchLink -> {
|
||||
try {
|
||||
val result = fetchQuery.query(link)
|
||||
if( result != null) {
|
||||
if (result != null) {
|
||||
result.trackList = result.trackList.toMutableList()
|
||||
dispatch((Result.ResultFetched(result, result.trackList.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() }))))
|
||||
executeIntent(Intent.RefreshTracksStatuses, getState)
|
||||
} else {
|
||||
throw Exception("An Error Occurred, Check your Link / Connection")
|
||||
}
|
||||
} catch (e:Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
dispatch(Result.ErrorOccurred(e))
|
||||
}
|
||||
@ -101,7 +99,6 @@ internal class SpotiFlyerListStoreProvider(
|
||||
}
|
||||
dispatch(Result.UpdateTrackList(list.updateTracksStatuses(downloadProgressFlow.replayCache.getOrElse(0) { hashMapOf() })))
|
||||
|
||||
|
||||
val finalList =
|
||||
intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
|
||||
if (finalList.isNullOrEmpty()) methods.value.showPopUpMessage("All Songs are Processed")
|
||||
|
@ -19,6 +19,7 @@ package com.shabinder.common.main.integration
|
||||
import co.touchlab.stately.ensureNeverFrozen
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.shabinder.common.caching.Cache
|
||||
import com.shabinder.common.di.Picture
|
||||
import com.shabinder.common.di.utils.asValue
|
||||
import com.shabinder.common.main.SpotiFlyerMain
|
||||
@ -30,7 +31,6 @@ 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 com.shabinder.common.caching.Cache
|
||||
|
||||
internal class SpotiFlyerMainImpl(
|
||||
componentContext: ComponentContext,
|
||||
|
@ -38,17 +38,17 @@ fun org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer.generateFramewor
|
||||
kotlin {
|
||||
|
||||
/*IOS Target Can be only built on Mac*/
|
||||
if(HostOS.isMac) {
|
||||
if (HostOS.isMac) {
|
||||
val sdkName: String? = System.getenv("SDK_NAME")
|
||||
val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos")
|
||||
if (isiOSDevice) {
|
||||
iosArm64("ios"){
|
||||
iosArm64("ios") {
|
||||
binaries {
|
||||
generateFramework()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iosX64("ios"){
|
||||
iosX64("ios") {
|
||||
binaries {
|
||||
generateFramework()
|
||||
}
|
||||
@ -68,7 +68,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(HostOS.isMac){
|
||||
if (HostOS.isMac) {
|
||||
/*Required to Export `packForXcode`*/
|
||||
sourceSets {
|
||||
named("iosMain") {
|
||||
@ -88,7 +88,7 @@ kotlin {
|
||||
}
|
||||
|
||||
val packForXcode by tasks.creating(Sync::class) {
|
||||
if(HostOS.isMac){
|
||||
if (HostOS.isMac) {
|
||||
group = "build"
|
||||
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
|
||||
val targetName = "ios"
|
||||
|
@ -30,7 +30,6 @@ import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
|
||||
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
|
||||
import com.shabinder.common.root.integration.SpotiFlyerRootImpl
|
||||
import com.shabinder.database.Database
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@ -53,7 +52,7 @@ interface SpotiFlyerRoot {
|
||||
val fetchPlatformQueryResult: FetchPlatformQueryResult
|
||||
val directories: Dir
|
||||
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
|
||||
val actions:Actions
|
||||
val actions: Actions
|
||||
val analytics: Analytics
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ package com.shabinder.common.root.callbacks
|
||||
|
||||
interface SpotiFlyerRootCallBacks {
|
||||
fun searchLink(link: String)
|
||||
fun showToast(text:String)
|
||||
fun showToast(text: String)
|
||||
fun popBackToHomeScreen()
|
||||
fun setDownloadDirectory()
|
||||
}
|
||||
|
@ -48,8 +48,8 @@ import kotlinx.coroutines.launch
|
||||
|
||||
internal class SpotiFlyerRootImpl(
|
||||
componentContext: ComponentContext,
|
||||
private val main: (ComponentContext, output:Consumer<SpotiFlyerMain.Output>)->SpotiFlyerMain,
|
||||
private val list: (ComponentContext, link:String, output:Consumer<SpotiFlyerList.Output>)->SpotiFlyerList,
|
||||
private val main: (ComponentContext, output: Consumer<SpotiFlyerMain.Output>) -> SpotiFlyerMain,
|
||||
private val list: (ComponentContext, link: String, output: Consumer<SpotiFlyerList.Output>) -> SpotiFlyerList,
|
||||
private val actions: Actions,
|
||||
private val analytics: Analytics
|
||||
) : SpotiFlyerRoot, ComponentContext by componentContext {
|
||||
@ -57,9 +57,9 @@ internal class SpotiFlyerRootImpl(
|
||||
constructor(
|
||||
componentContext: ComponentContext,
|
||||
dependencies: Dependencies,
|
||||
):this(
|
||||
) : this(
|
||||
componentContext = componentContext,
|
||||
main = { childContext,output ->
|
||||
main = { childContext, output ->
|
||||
spotiFlyerMain(
|
||||
childContext,
|
||||
output,
|
||||
@ -104,7 +104,7 @@ internal class SpotiFlyerRootImpl(
|
||||
it !is Configuration.Main
|
||||
}
|
||||
}
|
||||
override fun showToast(text:String) { toastState.value = text }
|
||||
override fun showToast(text: String) { toastState.value = text }
|
||||
override fun setDownloadDirectory() { actions.setDownloadDirectoryAction() }
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ internal class SpotiFlyerRootImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun authenticateSpotify(spotifyProvider: SpotifyProvider, override:Boolean){
|
||||
private fun authenticateSpotify(spotifyProvider: SpotifyProvider, override: Boolean) {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
analytics.appLaunchEvent()
|
||||
/*Authenticate Spotify Client*/
|
||||
@ -147,7 +147,7 @@ internal class SpotiFlyerRootImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<SpotiFlyerMain.Output> ,dependencies: Dependencies): SpotiFlyerMain =
|
||||
private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<SpotiFlyerMain.Output>, dependencies: Dependencies): SpotiFlyerMain =
|
||||
SpotiFlyerMain(
|
||||
componentContext = componentContext,
|
||||
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
|
||||
|
@ -28,7 +28,7 @@ version = Versions.versionName
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "14"
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,5 +28,6 @@ include(
|
||||
":fuzzywuzzy:app",
|
||||
":android",
|
||||
":desktop",
|
||||
":web-app"
|
||||
)
|
||||
":web-app",
|
||||
":maintenance-tasks"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user