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