Image Compression Android, Jio-Saavn Complete,Code Cleanup

This commit is contained in:
shabinder 2021-05-26 19:41:43 +05:30
parent 3913bfa4b1
commit cceb6d04dc
33 changed files with 357 additions and 226 deletions

View File

@ -22,6 +22,7 @@
<queries>
<package android:name="com.gaana" />
<package android:name="com.spotify.music" />
<package android:name="com.jio.media.jiobeats" />
<package android:name="com.google.android.youtube" />
<package android:name="com.google.android.apps.youtube.music" />
</queries>

View File

@ -41,7 +41,7 @@ fun AnalyticsDialog(
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Rounded.Insights,"Analytics", Modifier.size(52.dp))
Spacer(Modifier.padding(horizontal = 4.dp))
Text("Grant Analytics Access",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center)
Text("Grant Analytics",style = SpotiFlyerTypography.h5,textAlign = TextAlign.Center)
}
},
backgroundColor = Color.DarkGray,

View File

@ -18,7 +18,8 @@
object Versions {
// App's Version (To be bumped at each update)
const val versionName = "3.0.1"
const val versionName = "3.1.0"
const val versionCode = 20
// Kotlin
const val kotlinVersion = "1.4.32"
@ -45,7 +46,6 @@ object Versions {
const val slf4j = "1.7.30"
// Android
const val versionCode = 19
const val minSdkVersion = 21
const val compileSdkVersion = 29
const val targetSdkVersion = 29

View File

@ -43,9 +43,11 @@ kotlin {
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin)
implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.animation)
implementation(Extras.kermit)
implementation("dev.icerock.moko:parcelize:0.6.1")

View File

@ -28,6 +28,7 @@ kotlin {
}
commonMain {
dependencies {
implementation(compose.material)
implementation(compose.materialIconsExtended)
implementation(project(":common:root"))
implementation(project(":common:main"))

View File

@ -18,7 +18,7 @@ import kotlinx.coroutines.withContext
@Composable
actual fun ImageLoad(
link: String,
loader: suspend (String) -> Picture,
loader: suspend () -> Picture,
desc: String,
modifier: Modifier,
// placeholder: ImageVector
@ -27,7 +27,7 @@ actual fun ImageLoad(
LaunchedEffect(link) {
withContext(dispatcherIO) {
pic = loader(link).image
pic = loader().image
}
}

View File

@ -82,6 +82,9 @@ actual fun HeartIcon() = painterResource(R.drawable.ic_heart)
@Composable
actual fun SpotifyLogo() = painterResource(R.drawable.ic_spotify_logo)
@Composable
actual fun SaavnLogo() = painterResource(R.drawable.ic_jio_saavn_logo)
@Composable
actual fun GaanaLogo() = painterResource(R.drawable.ic_gaana)
@ -97,6 +100,9 @@ actual fun GithubLogo() = painterResource(R.drawable.ic_github)
@Composable
actual fun PaypalLogo() = painterResource(R.drawable.ic_paypal_logo)
@Composable
actual fun OpenCollectiveLogo() = painterResource(R.drawable.ic_opencollective_icon)
@Composable
actual fun RazorPay() = painterResource(R.drawable.ic_indian_rupee)

View File

@ -4,15 +4,19 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -26,7 +30,8 @@ import com.shabinder.common.models.methods
@Composable
actual fun DonationDialog(
isVisible: Boolean,
onDismiss: () -> Unit
onDismiss: () -> Unit,
onSnooze: () -> Unit
) {
AnimatedVisibility(
isVisible
@ -39,13 +44,36 @@ actual fun DonationDialog(
) {
Column(Modifier.padding(16.dp)) {
Text(
"Support Us",
"We Need Your Support!",
style = SpotiFlyerTypography.h5,
textAlign = TextAlign.Center,
color = colorAccent,
modifier = Modifier
)
Spacer(modifier = Modifier.padding(vertical = 4.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(
onClick = {
onDismiss()
methods.value.openPlatform("", "https://opencollective.com/spotiflyer/donate")
}
)
.padding(vertical = 6.dp)
) {
Icon(OpenCollectiveLogo(), "Open Collective Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Open Collective",
style = SpotiFlyerTypography.h6
)
Text(
text = "Worldwide Donations",
style = SpotiFlyerTypography.subtitle2
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(
@ -56,7 +84,7 @@ actual fun DonationDialog(
)
.padding(vertical = 6.dp)
) {
Icon(PaypalLogo(), "Paypal Logo", tint = Color(0xFFCCCCCC))
Icon(PaypalLogo(), "Paypal Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
@ -79,7 +107,7 @@ actual fun DonationDialog(
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
Icon(RazorPay(), "Indian Rupee Logo", Modifier.size(24.dp), tint = Color(0xFFCCCCCC))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
@ -92,39 +120,21 @@ actual fun DonationDialog(
)
}
}
Spacer(modifier = Modifier.padding(vertical = 16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.padding(horizontal = 4.dp).fillMaxWidth()
) {
OutlinedButton(onClick = onSnooze) {
Text("Dismiss.")
}
TextButton(onClick = onDismiss, colors = ButtonDefaults.buttonColors()) {
Text("Remind Later!")
}
}
}
}
}
/*AlertDialog(
buttons = {
*//* TextButton({
//Retry Network Connection
},
Modifier.padding(bottom = 16.dp,start = 16.dp,end = 16.dp).fillMaxWidth().background(Color(0xFFFC5C7D),shape = RoundedCornerShape(size = 8.dp)).padding(horizontal = 8.dp),
){
Text("Retry",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
Icon(Icons.Rounded.SyncProblem,"Check Network Connection Again")
}
*//*},
*//*title = {
Column {
Text(
"Support Us",
style = SpotiFlyerTypography.h5,
textAlign = TextAlign.Center,
color = colorAccent,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.padding(vertical = 16.dp))
}
},*//*
backgroundColor = Color.DarkGray,
text = {
}
,shape = SpotiFlyerShapes.medium,
onDismissRequest = onDismiss
)*/
}
}

View File

@ -7,7 +7,7 @@ import com.shabinder.common.di.Picture
@Composable
expect fun ImageLoad(
link: String,
loader: suspend (String) -> Picture,
loader: suspend () -> Picture,
desc: String = "Album Art",
modifier: Modifier = Modifier,
// placeholder:Painter = PlaceHolderImage()

View File

@ -39,6 +39,9 @@ expect fun SpotiFlyerLogo(): Painter
@Composable
expect fun SpotifyLogo(): Painter
@Composable
expect fun SaavnLogo(): Painter
@Composable
expect fun YoutubeLogo(): Painter
@ -54,6 +57,9 @@ expect fun GithubLogo(): Painter
@Composable
expect fun PaypalLogo(): Painter
@Composable
expect fun OpenCollectiveLogo(): Painter
@Composable
expect fun RazorPay(): Painter
@ -69,5 +75,6 @@ expect fun DownloadImageArrow(modifier: Modifier)
@Composable
expect fun DonationDialog(
isVisible: Boolean,
onDismiss: () -> Unit
onDismiss: () -> Unit,
onSnooze: () -> Unit
)

View File

@ -32,6 +32,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
@ -39,6 +40,9 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -54,6 +58,7 @@ import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SpotiFlyerListContent(
component: SpotiFlyerList,
@ -68,7 +73,6 @@ fun SpotiFlyerListContent(
component.onBackPressed()
}
}
Box(modifier = modifier.fillMaxSize()) {
val result = model.queryResult
if (result == null) {
@ -92,16 +96,34 @@ fun SpotiFlyerListContent(
TrackCard(
track = item,
downloadTrack = { component.onDownloadClicked(item) },
loadImage = component::loadImage
loadImage = { component.loadImage(item.albumArtURL) }
)
}
},
state = listState,
modifier = Modifier.fillMaxSize(),
)
// Donation Dialog Visibility
var visibilty by remember { mutableStateOf(false) }
DonationDialog(
isVisible = visibilty,
onDismiss = {
visibilty = false
},
onSnooze = {
visibilty = false
component.snoozeDonationDialog()
}
)
DownloadAllButton(
onClick = { component.onDownloadAllClicked(model.trackList) },
onClick = {
component.onDownloadAllClicked(model.trackList)
// Check If we are allowed to show donation Dialog
if (model.askForDonation) {
// Show Donation Dialog
visibilty = true
}
},
modifier = Modifier.padding(bottom = 24.dp).align(Alignment.BottomCenter)
)
@ -121,12 +143,12 @@ fun SpotiFlyerListContent(
fun TrackCard(
track: TrackDetails,
downloadTrack: () -> Unit,
loadImage: suspend (String) -> Picture
loadImage: suspend () -> Picture
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
ImageLoad(
track.albumArtURL,
loadImage,
{ loadImage() },
"Album Art",
modifier = Modifier
.width(70.dp)
@ -177,7 +199,7 @@ fun TrackCard(
fun CoverImage(
title: String,
coverURL: String,
loadImage: suspend (String) -> Picture,
loadImage: suspend (URL: String, isCover: Boolean) -> Picture,
modifier: Modifier = Modifier,
) {
Column(
@ -186,7 +208,7 @@ fun CoverImage(
) {
ImageLoad(
coverURL,
loadImage,
{ loadImage(coverURL, true) },
"Cover Image",
modifier = Modifier
.padding(12.dp)

View File

@ -256,7 +256,16 @@ fun AboutColumn(
"Open Gaana",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.gaana", "http://gaana.com") }
onClick = { methods.value.openPlatform("com.gaana", "https://www.gaana.com") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
Icon(
SaavnLogo(),
"Open Jio Saavn",
tint = Color.Unspecified,
modifier = Modifier.clickable(
onClick = { methods.value.openPlatform("com.jio.media.jiobeats", "https://www.jiosaavn.com/") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -265,7 +274,7 @@ fun AboutColumn(
"Open Youtube",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.google.android.youtube", "http://m.youtube.com") }
onClick = { methods.value.openPlatform("com.google.android.youtube", "https://m.youtube.com") }
)
)
Spacer(modifier = modifier.padding(start = 12.dp))
@ -299,7 +308,7 @@ fun AboutColumn(
)
.padding(vertical = 6.dp)
) {
Icon(GithubLogo(), "Open Project Repo", tint = Color(0xFFCCCCCC))
Icon(GithubLogo(), "Open Project Repo", Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
@ -337,6 +346,9 @@ fun AboutColumn(
isDonationDialogVisible,
onDismiss = {
isDonationDialogVisible = false
},
onSnooze = {
isDonationDialogVisible = false
}
)
@ -350,7 +362,7 @@ fun AboutColumn(
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.CardGiftcard, "Support Developer")
Icon(Icons.Rounded.CardGiftcard, "Support Developer", Modifier.size(32.dp))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
@ -373,7 +385,7 @@ fun AboutColumn(
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Share, "Share SpotiFlyer App")
Icon(Icons.Rounded.Share, "Share SpotiFlyer App", Modifier.size(32.dp))
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
@ -455,7 +467,7 @@ fun DownloadRecordItem(
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
ImageLoad(
item.coverUrl,
loadImage,
{ loadImage(item.coverUrl) },
"Album Art",
modifier = Modifier.height(70.dp).width(70.dp).clip(SpotiFlyerShapes.medium)
)

View File

@ -18,7 +18,7 @@ import kotlinx.coroutines.withContext
@Composable
actual fun ImageLoad(
link: String,
loader: suspend (String) -> Picture,
loader: suspend () -> Picture,
desc: String,
modifier: Modifier,
// placeholder: ImageVector
@ -26,7 +26,7 @@ actual fun ImageLoad(
var pic by remember(link) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(link) {
withContext(dispatcherIO) {
pic = loader(link).image
pic = loader().image
}
}

View File

@ -84,6 +84,9 @@ actual fun HeartIcon() = rememberVectorPainter(vectorXmlResource("drawable/ic_he
@Composable
actual fun SpotifyLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_spotify_logo.xml")) as Painter
@Composable
actual fun SaavnLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_jio_saavn_logo.xml")) as Painter
@Composable
actual fun YoutubeLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_youtube.xml")) as Painter
@ -99,5 +102,8 @@ actual fun GithubLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_g
@Composable
actual fun PaypalLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_paypal_logo.xml")) as Painter
@Composable
actual fun OpenCollectiveLogo() = rememberVectorPainter(vectorXmlResource("drawable/ic_opencollective_icon")) as Painter
@Composable
actual fun RazorPay() = rememberVectorPainter(vectorXmlResource("drawable/ic_indian_rupee.xml")) as Painter

View File

@ -27,7 +27,8 @@ import com.shabinder.common.models.methods
@Composable
actual fun DonationDialog(
isVisible: Boolean,
onDismiss: () -> Unit
onDismiss: () -> Unit,
onSnooze: () -> Unit
) {
AnimatedVisibility(
isVisible

View File

@ -0,0 +1,8 @@
<vector android:height="42dp" android:viewportHeight="250"
android:viewportWidth="488" android:width="81.984dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#fff" android:pathData="M483.73,36A53.1,53.1 0,0 0,452 4.28C438.49,0 425.94,0 400.84,0H325.16C300.07,0 287.52,0 274,4.28A53.08,53.08 0,0 0,242.28 36a76.64,76.64 0,0 0,-2 7.74,140.32 140.32,0 0,1 14,24.86c0.38,-9.57 1.27,-17.22 3.46,-24.14 4.68,-12.86 11.88,-20.06 24.74,-24.74C294.25,16 308.12,16 330,16h66c21.88,0 35.76,0 47.54,3.73 12.86,4.68 20,11.88 24.74,24.74C472,56.25 472,70.13 472,92v66c0,21.88 0,35.76 -3.72,47.53 -4.69,12.86 -11.88,20.06 -24.74,24.74C431.76,234 417.88,234 396,234H330c-21.89,0 -35.76,0 -47.54,-3.73 -12.86,-4.68 -20.06,-11.88 -24.74,-24.74 -2.19,-6.92 -3.09,-14.58 -3.46,-24.15a140.51,140.51 0,0 1,-14 24.85,77.18 77.18,0 0,0 2,7.77A53.08,53.08 0,0 0,274 245.73C287.52,250 300.07,250 325.16,250h75.68c25.1,0 37.65,0 51.16,-4.27A53.11,53.11 0,0 0,483.73 214C488,200.49 488,187.94 488,162.84V87.17C488,62.07 488,49.52 483.73,36Z"/>
<path android:fillColor="#fff" android:pathData="M422,217L380.33,217c-1.76,0 -5.83,-2.79 -2.63,-6.67 21.36,-23 48,-30.93 73.4,-39.42 3.32,-1 3.91,2.51 3.91,3.48v8.68C455,202.61 441.57,217 422,217ZM343.73,212.69c-4,-29.73 -18.06,-80.79 -71,-118.55A3.78,3.78 0,0 1,271 90.63L271,66.36c0,-26.69 18,-33.31 26.37,-33.31a4.3,4.3 0,0 1,4.07 2.1c25.24,55 41,89.86 50.7,172.83 0.05,1.62 0.31,2.39 1.28,0 6.86,-15.07 39.35,-92 26.44,-170.68a3.64,3.64 0,0 1,3.5 -4.25L422,33.05c19.54,0 33,13.43 33,33.36L455,100.5a3.63,3.63 0,0 1,-2.07 3.36,180.12 180.12,0 0,0 -90.3,109.25c-0.79,2.21 -1.25,3.9 -3.71,3.9h-11.8C344.77,217 344.27,216.05 343.73,212.7ZM304.35,217c-20,0 -33.35,-12.37 -33.35,-33.93v-2.24c0,-0.9 0.71,-4.29 4.09,-3.63 20.24,6.23 41.92,12.52 57.77,33.49 1.82,2.56 0.23,6.3 -2.91,6.31Z"/>
<path android:fillColor="#fff" android:pathData="M124.991,239.991a115,115 54.655,1 0,2.007 -229.991a115,115 54.655,1 0,-2.007 229.991z"/>
<path android:fillColor="#2bc5b4" android:pathData="M180.77,114.59c-8.62,0 -15.61,7.39 -15.61,16.49s7,16.5 15.61,16.5 15.62,-7.38 15.62,-16.5S189.4,114.59 180.77,114.59Z"/>
<path android:fillColor="#2bc5b4" android:pathData="M125,0A125,125 0,1 0,250 125,125 125,0 0,0 125,0ZM95.37,132.09c0,63.82 -101.74,35.68 -60.49,2.93 9.65,13.39 28.18,12.5 30.15,-0.72l0.37,-52.05c0.95,-13.32 26.85,-16 30,0ZM133.31,156.32a12.05,12.05 0,0 1,-12 12L116.1,168.32a12.05,12.05 0,0 1,-12 -12L104.1,106a12,12 0,0 1,12 -12h5.21a12,12 0,0 1,12 12ZM133.31,74.56a11.84,11.84 0,0 1,-11.79 11.79L115.9,86.35a11.84,11.84 0,0 1,-11.81 -11.79L104.09,71.65A11.83,11.83 0,0 1,115.9 59.86h5.62a11.82,11.82 0,0 1,11.79 11.79ZM180.77,169.9c-22,0 -39.82,-17.37 -39.82,-38.82s17.84,-38.81 39.82,-38.81 39.81,17.38 39.81,38.81S202.76,169.9 180.77,169.9Z"/>
</vector>

View File

@ -0,0 +1,8 @@
<vector android:height="42dp" android:viewportHeight="250"
android:viewportWidth="488" android:width="81.984dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#fff" android:pathData="M483.73,36A53.1,53.1 0,0 0,452 4.28C438.49,0 425.94,0 400.84,0H325.16C300.07,0 287.52,0 274,4.28A53.08,53.08 0,0 0,242.28 36a76.64,76.64 0,0 0,-2 7.74,140.32 140.32,0 0,1 14,24.86c0.38,-9.57 1.27,-17.22 3.46,-24.14 4.68,-12.86 11.88,-20.06 24.74,-24.74C294.25,16 308.12,16 330,16h66c21.88,0 35.76,0 47.54,3.73 12.86,4.68 20,11.88 24.74,24.74C472,56.25 472,70.13 472,92v66c0,21.88 0,35.76 -3.72,47.53 -4.69,12.86 -11.88,20.06 -24.74,24.74C431.76,234 417.88,234 396,234H330c-21.89,0 -35.76,0 -47.54,-3.73 -12.86,-4.68 -20.06,-11.88 -24.74,-24.74 -2.19,-6.92 -3.09,-14.58 -3.46,-24.15a140.51,140.51 0,0 1,-14 24.85,77.18 77.18,0 0,0 2,7.77A53.08,53.08 0,0 0,274 245.73C287.52,250 300.07,250 325.16,250h75.68c25.1,0 37.65,0 51.16,-4.27A53.11,53.11 0,0 0,483.73 214C488,200.49 488,187.94 488,162.84V87.17C488,62.07 488,49.52 483.73,36Z"/>
<path android:fillColor="#fff" android:pathData="M422,217L380.33,217c-1.76,0 -5.83,-2.79 -2.63,-6.67 21.36,-23 48,-30.93 73.4,-39.42 3.32,-1 3.91,2.51 3.91,3.48v8.68C455,202.61 441.57,217 422,217ZM343.73,212.69c-4,-29.73 -18.06,-80.79 -71,-118.55A3.78,3.78 0,0 1,271 90.63L271,66.36c0,-26.69 18,-33.31 26.37,-33.31a4.3,4.3 0,0 1,4.07 2.1c25.24,55 41,89.86 50.7,172.83 0.05,1.62 0.31,2.39 1.28,0 6.86,-15.07 39.35,-92 26.44,-170.68a3.64,3.64 0,0 1,3.5 -4.25L422,33.05c19.54,0 33,13.43 33,33.36L455,100.5a3.63,3.63 0,0 1,-2.07 3.36,180.12 180.12,0 0,0 -90.3,109.25c-0.79,2.21 -1.25,3.9 -3.71,3.9h-11.8C344.77,217 344.27,216.05 343.73,212.7ZM304.35,217c-20,0 -33.35,-12.37 -33.35,-33.93v-2.24c0,-0.9 0.71,-4.29 4.09,-3.63 20.24,6.23 41.92,12.52 57.77,33.49 1.82,2.56 0.23,6.3 -2.91,6.31Z"/>
<path android:fillColor="#fff" android:pathData="M124.991,239.991a115,115 54.655,1 0,2.007 -229.991a115,115 54.655,1 0,-2.007 229.991z"/>
<path android:fillColor="#2bc5b4" android:pathData="M180.77,114.59c-8.62,0 -15.61,7.39 -15.61,16.49s7,16.5 15.61,16.5 15.62,-7.38 15.62,-16.5S189.4,114.59 180.77,114.59Z"/>
<path android:fillColor="#2bc5b4" android:pathData="M125,0A125,125 0,1 0,250 125,125 125,0 0,0 125,0ZM95.37,132.09c0,63.82 -101.74,35.68 -60.49,2.93 9.65,13.39 28.18,12.5 30.15,-0.72l0.37,-52.05c0.95,-13.32 26.85,-16 30,0ZM133.31,156.32a12.05,12.05 0,0 1,-12 12L116.1,168.32a12.05,12.05 0,0 1,-12 -12L104.1,106a12,12 0,0 1,12 -12h5.21a12,12 0,0 1,12 12ZM133.31,74.56a11.84,11.84 0,0 1,-11.79 11.79L115.9,86.35a11.84,11.84 0,0 1,-11.81 -11.79L104.09,71.65A11.83,11.83 0,0 1,115.9 59.86h5.62a11.82,11.82 0,0 1,11.79 11.79ZM180.77,169.9c-22,0 -39.82,-17.37 -39.82,-38.82s17.84,-38.81 39.82,-38.81 39.81,17.38 39.81,38.81S202.76,169.9 180.77,169.9Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:viewportHeight="64"
android:viewportWidth="64" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#BBFFFFFF" android:fillType="evenOdd" android:pathData="M52.402,31.916c0,4.03 -1.17,7.895 -3.178,11.087l8.196,8.23c4.014,-5.375 6.523,-12.094 6.523,-19.318s-2.51,-13.942 -6.523,-19.318l-8.196,8.23c2.007,3.192 3.178,6.887 3.178,11.087z"/>
<path android:fillColor="#FFFFFF" android:fillType="evenOdd" android:pathData="M32.004,52.41c-11.207,0 -20.406,-9.24 -20.406,-20.493s9.2,-20.493 20.406,-20.493c4.182,0 7.86,1.176 11.04,3.36l8.196,-8.23C45.887,2.52 39.197,0 32.004,0 14.44,0 0.057,14.278 0.057,32.084S14.44,64 32.004,64c7.36,0 14.05,-2.52 19.403,-6.55l-8.196,-8.23c-3.178,2.016 -7.025,3.192 -11.207,3.192z"/>
</vector>

View File

@ -24,17 +24,15 @@ import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
@ -45,33 +43,9 @@ import java.net.URL
@Suppress("DEPRECATION")
actual class Dir actual constructor(
private val logger: Kermit,
private val settings: Settings,
settingsPref: Settings,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val firstLaunch = "firstLaunch"
}
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
actual fun firstLaunchDone() {
settings.putBoolean(firstLaunch, false)
}
/*
* Do we have Analytics Permission?
* - Defaults to `False`
* */
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
actual fun enableAnalytics() {
settings.putBoolean(AnalyticsKey, true)
}
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
@Suppress("DEPRECATION")
private val defaultBaseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString()
@ -171,20 +145,17 @@ actual class Dir actual constructor(
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, reqWidth: Int, reqHeight: Int): Picture = withContext(dispatcherIO) {
val cachePath = imageCacheDir() + getNameURL(url)
Picture(image = (loadCachedImage(cachePath) ?: freshImage(url))?.asImageBitmap())
Picture(image = (loadCachedImage(cachePath, reqWidth, reqHeight) ?: freshImage(url, reqWidth, reqHeight))?.asImageBitmap())
}
private fun loadCachedImage(cachePath: String): Bitmap? {
private fun loadCachedImage(cachePath: String, reqWidth: Int, reqHeight: Int): Bitmap? {
return try {
BitmapFactory.decodeFile(cachePath)
getMemoryEfficientBitmap(cachePath, reqWidth, reqHeight)
} catch (e: Exception) {
e.printStackTrace()
null
} catch (e: OutOfMemoryError) {
e.printStackTrace()
null
}
}
@ -200,27 +171,36 @@ actual class Dir actual constructor(
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun freshImage(url: String): Bitmap? = withContext(dispatcherIO) {
private suspend fun freshImage(url: String, reqWidth: Int, reqHeight: Int): Bitmap? = withContext(dispatcherIO) {
try {
val source = URL(url)
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val result: Bitmap? = BitmapFactory.decodeStream(input)
val input: ByteArray = connection.inputStream.readBytes()
if (result != null) {
GlobalScope.launch(Dispatchers.IO) {
cacheImage(result, imageCacheDir() + getNameURL(url))
}
result
} else null
// Get Memory Efficient Bitmap
val bitmap: Bitmap? = getMemoryEfficientBitmap(input, reqWidth, reqHeight)
parallelExecutor.execute {
// Decode and Cache Full Sized Image in Background
cacheImage(BitmapFactory.decodeByteArray(input, 0, input.size), imageCacheDir() + getNameURL(url))
}
bitmap // return Memory Efficient Bitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}
/*
* Parallel Executor with 4 concurrent operation at a time.
* - We will use this to queue up operations and decode Full Sized Images
* - Will Decode Only 4 at a time , to avoid going into `Out of Memory`
* */
private val parallelExecutor = ParallelExecutor(Dispatchers.IO)
actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
}

View File

@ -16,8 +16,67 @@
package com.shabinder.common.di
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.ui.graphics.ImageBitmap
actual data class Picture(
var image: ImageBitmap?
)
fun getMemoryEfficientBitmap(
input: ByteArray,
reqWidth: Int,
reqHeight: Int,
offset: Int = 0,
size: Int = input.size
): Bitmap? {
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeByteArray(input, offset, size, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
// Return Mem. Efficient Bitmap
BitmapFactory.decodeByteArray(input, offset, size, this)
}
}
fun getMemoryEfficientBitmap(
filePath: String,
reqWidth: Int,
reqHeight: Int,
): Bitmap? {
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, this)
// Calculate inSampleSize
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
BitmapFactory.decodeFile(filePath, this)
}
}
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}

View File

@ -32,29 +32,72 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val FirstLaunch = "firstLaunch"
const val DonationInterval = "donationInterval"
expect class Dir(
logger: Kermit,
settings: Settings,
settingsPref: Settings,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
val db: Database?
val isAnalyticsEnabled: Boolean
val isFirstLaunch: Boolean
fun enableAnalytics()
fun firstLaunchDone()
val settings: Settings
fun isPresent(path: String): Boolean
fun fileSeparator(): String
fun defaultDir(): String
fun imageCacheDir(): String
fun createDirectory(dirPath: 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 loadImage(url: String, reqWidth: Int = 150, reqHeight: Int = 150): Picture
suspend fun clearCache()
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, trackDetails: TrackDetails, postProcess: (track: TrackDetails) -> Unit = {})
fun addToLibrary(path: String)
}
/*
* Do we have Analytics Permission?
* - Defaults to `False`
* */
val Dir.isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
fun Dir.enableAnalytics() = settings.putBoolean(AnalyticsKey, true)
fun Dir.setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
val Dir.getDonationOffset: Int get() = (settings.getIntOrNull(DonationInterval) ?: 3).also {
// Min. Donation Asking Interval is `3`
if (it < 3) setDonationOffset(3) else setDonationOffset(it - 1)
}
fun Dir.setDonationOffset(offset: Int = 5) = settings.putInt(DonationInterval, offset)
val Dir.isFirstLaunch get() = settings.getBooleanOrNull(FirstLaunch) ?: true
fun Dir.firstLaunchDone() {
settings.putBoolean(FirstLaunch, false)
}
/*
* Call this function at startup!
* */
fun Dir.createDirectories() {
createDirectory(defaultDir())
createDirectory(imageCacheDir())
createDirectory(defaultDir() + "Tracks/")
createDirectory(defaultDir() + "Albums/")
createDirectory(defaultDir() + "Playlists/")
createDirectory(defaultDir() + "YT_Downloads/")
}
fun Dir.finalOutputDir(itemName: String, type: String, subFolder: String, defaultDir: String, extension: String = ".mp3"): String =
defaultDir + removeIllegalChars(type) + this.fileSeparator() +
if (subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + this.fileSeparator() } +
removeIllegalChars(itemName) + extension
/*DIR Specific Operation End*/
fun getNameURL(url: String): String {
return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1, url.length).replace('/', '_')
}
suspend fun downloadFile(url: String): Flow<DownloadResult> {
return flow {
try {
@ -95,24 +138,3 @@ suspend fun downloadByteArray(
client.close()
return response
}
fun getNameURL(url: String): String {
return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1, url.length).replace('/', '_')
}
/*
* Call this function at startup!
* */
fun Dir.createDirectories() {
createDirectory(defaultDir())
createDirectory(imageCacheDir())
createDirectory(defaultDir() + "Tracks/")
createDirectory(defaultDir() + "Albums/")
createDirectory(defaultDir() + "Playlists/")
createDirectory(defaultDir() + "YT_Downloads/")
}
fun Dir.finalOutputDir(itemName: String, type: String, subFolder: String, defaultDir: String, extension: String = ".mp3"): String =
defaultDir + removeIllegalChars(type) + this.fileSeparator() +
if (subFolder.isEmpty())"" else { removeIllegalChars(subFolder) + this.fileSeparator() } +
removeIllegalChars(itemName) + extension

View File

@ -13,9 +13,7 @@ import io.github.shabinder.utils.getJsonArray
import io.github.shabinder.utils.getJsonObject
import io.github.shabinder.utils.getString
import io.ktor.client.HttpClient
import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.get
import io.ktor.http.Parameters
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
@ -88,13 +86,7 @@ interface JioSaavnRequests {
private suspend fun getSongID(
URL: String,
): String {
val res = httpClient.get<String>(URL) {
body = FormDataContent(
Parameters.build {
append("bitrate", "320")
}
)
}
val res = httpClient.get<String>(URL)
return try {
res.split("\"song\":{\"type\":\"")[1].split("\",\"image\":")[0].split("\"id\":\"").last()
} catch (e: IndexOutOfBoundsException) {

View File

@ -40,28 +40,10 @@ import javax.imageio.ImageIO
actual class Dir actual constructor(
private val logger: Kermit,
private val settings: Settings,
private val spotiFlyerDatabase: SpotiFlyerDatabase,
settingsPref: Settings,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val firstLaunch = "firstLaunch"
}
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
actual fun firstLaunchDone() {
settings.putBoolean(firstLaunch, false)
}
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
actual fun enableAnalytics() {
settings.putBoolean(AnalyticsKey, true)
}
init {
createDirectories()
}
@ -76,8 +58,6 @@ 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 isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath: String) {
@ -177,14 +157,14 @@ actual class Dir actual constructor(
}
actual fun addToLibrary(path: String) {}
actual suspend fun loadImage(url: String): Picture {
actual suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture {
val cachePath = imageCacheDir() + getNameURL(url)
var picture: ImageBitmap? = loadCachedImage(cachePath)
if (picture == null) picture = freshImage(url)
var picture: ImageBitmap? = loadCachedImage(cachePath, reqWidth, reqHeight)
if (picture == null) picture = freshImage(url, reqWidth, reqHeight)
return Picture(image = picture)
}
private fun loadCachedImage(cachePath: String): ImageBitmap? {
private fun loadCachedImage(cachePath: String, reqWidth: Int, reqHeight: Int): ImageBitmap? {
return try {
ImageIO.read(File(cachePath))?.toImageBitmap()
} catch (e: Exception) {
@ -194,7 +174,7 @@ actual class Dir actual constructor(
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun freshImage(url: String): ImageBitmap? {
private suspend fun freshImage(url: String, reqWidth: Int, reqHeight: Int): ImageBitmap? {
return withContext(Dispatchers.IO) {
try {
val source = URL(url)
@ -218,7 +198,8 @@ actual class Dir actual constructor(
}
}
actual val db: Database? get() = spotiFlyerDatabase.instance
actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
}
fun BufferedImage.toImageBitmap() = Image.makeFromEncoded(

View File

@ -24,26 +24,9 @@ import platform.UIKit.UIImageJPEGRepresentation
actual class Dir actual constructor(
val logger: Kermit,
private val settings: Settings,
private val spotiFlyerDatabase: SpotiFlyerDatabase,
settingsPref: Settings,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val firstLaunch = "firstLaunch"
}
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
actual fun firstLaunchDone() {
settings.putBoolean(firstLaunch, false)
}
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
actual fun enableAnalytics() {
settings.putBoolean(AnalyticsKey, true)
}
actual fun isPresent(path: String): Boolean = NSFileManager.defaultManager.fileExistsAtPath(path)
@ -55,8 +38,6 @@ 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)
private val defaultDirURL: NSURL by lazy {
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask, null, true, null)!!
musicDir.URLByAppendingPathComponent("SpotiFlyer", true)!!
@ -97,7 +78,7 @@ actual class Dir actual constructor(
}
}
actual suspend fun loadImage(url: String): Picture = withContext(dispatcherIO) {
actual suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture = withContext(dispatcherIO) {
try {
val cachePath = imageCacheURL.URLByAppendingPathComponent(getNameURL(url))
Picture(image = cachePath?.path?.let { loadCachedImage(it) } ?: loadFreshImage(url))
@ -107,7 +88,7 @@ actual class Dir actual constructor(
}
}
private fun loadCachedImage(filePath: String): UIImage? {
private fun loadCachedImage(filePath: String, reqWidth: Int = 150, reqHeight: Int = 150): UIImage? {
return try {
UIImage.imageWithContentsOfFile(filePath)
} catch (e: Exception) {
@ -116,7 +97,7 @@ actual class Dir actual constructor(
}
}
private suspend fun loadFreshImage(url: String): UIImage? = withContext(dispatcherIO) {
private suspend fun loadFreshImage(url: String, reqWidth: Int = 150, reqHeight: Int = 150): UIImage? = withContext(dispatcherIO) {
try {
val nsURL = NSURL(string = url)
val data = NSURLConnection.sendSynchronousRequest(NSURLRequest.requestWithURL(nsURL), null, null)
@ -195,5 +176,6 @@ actual class Dir actual constructor(
// TODO
}
actual val settings: Settings = settingsPref
actual val db: Database? = spotiFlyerDatabase.instance
}

View File

@ -34,29 +34,9 @@ import org.w3c.dom.ImageBitmap
actual class Dir actual constructor(
private val logger: Kermit,
private val settings: Settings,
private val spotiFlyerDatabase: SpotiFlyerDatabase,
settingsPref: Settings,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val firstLaunch = "firstLaunch"
}
actual val isFirstLaunch get() = settings.getBooleanOrNull(firstLaunch) ?: true
actual fun firstLaunchDone() {
settings.putBoolean(firstLaunch, false)
}
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
actual fun enableAnalytics() {
settings.putBoolean(AnalyticsKey, true)
}
actual fun setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
/*init {
createDirectories()
}*/
@ -127,7 +107,7 @@ actual class Dir actual constructor(
actual fun addToLibrary(path: String) {}
actual suspend fun loadImage(url: String): Picture {
actual suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture {
return Picture(url)
}
@ -135,7 +115,8 @@ actual class Dir actual constructor(
private suspend fun freshImage(url: String): ImageBitmap? = null
actual val db: Database? get() = spotiFlyerDatabase.instance
actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
}
fun ByteArray.toArrayBuffer(): ArrayBuffer {

View File

@ -51,13 +51,18 @@ interface SpotiFlyerList {
/*
* Load Image from cache/Internet and cache it
* */
suspend fun loadImage(url: String): Picture
suspend fun loadImage(url: String, isCover: Boolean = false): Picture
/*
* Sync Tracks Statuses
* */
fun onRefreshTracksStatuses()
/*
* Snooze Donation Dialog
* */
fun snoozeDonationDialog()
interface Dependencies {
val storeFactory: StoreFactory
val fetchQuery: FetchPlatformQueryResult
@ -78,7 +83,8 @@ interface SpotiFlyerList {
val queryResult: PlatformQueryResult? = null,
val link: String = "",
val trackList: List<TrackDetails> = emptyList(),
val errorOccurred: Exception? = null
val errorOccurred: Exception? = null,
val askForDonation: Boolean = false,
)
}

View File

@ -21,6 +21,7 @@ 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.setDonationOffset
import com.shabinder.common.di.utils.asValue
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.Dependencies
@ -52,7 +53,7 @@ internal class SpotiFlyerListImpl(
private val cache = Cache.Builder
.newBuilder()
.maximumCacheSize(150)
.maximumCacheSize(75)
.build<String, Picture>()
override val models: Value<State> = store.asValue()
@ -73,9 +74,14 @@ internal class SpotiFlyerListImpl(
store.accept(Intent.RefreshTracksStatuses)
}
override suspend fun loadImage(url: String): Picture {
override fun snoozeDonationDialog() {
dir.setDonationOffset(offset = 10)
}
override suspend fun loadImage(url: String, isCover: Boolean): Picture {
return cache.get(url) {
dir.loadImage(url)
if (isCover) dir.loadImage(url, 350, 350)
else dir.loadImage(url, 150, 150)
}
}
}

View File

@ -25,6 +25,7 @@ import com.shabinder.common.database.getLogger
import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.getDonationOffset
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus
@ -59,6 +60,7 @@ internal class SpotiFlyerListStoreProvider(
data class UpdateTrackList(val list: List<TrackDetails>) : Result()
data class UpdateTrackItem(val item: TrackDetails) : Result()
data class ErrorOccurred(val error: Exception) : Result()
data class AskForDonation(val isAllowed: Boolean) : Result()
}
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
@ -66,6 +68,18 @@ internal class SpotiFlyerListStoreProvider(
override suspend fun executeAction(action: Unit, getState: () -> State) {
executeIntent(Intent.SearchLink(link), getState)
dir.db?.downloadRecordDatabaseQueries?.getLastInsertId()?.executeAsOneOrNull()?.also {
// See if It's Time we can request for support for maintaining this project or not
logger.d(message = "Database List Last ID: $it", tag = "Database Last ID")
val offset = dir.getDonationOffset
dispatch(
Result.AskForDonation(
// Every 3rd Interval or After some offset
isAllowed = offset < 4 && (it % offset == 0L)
)
)
}
downloadProgressFlow.collectLatest { map ->
logger.d(map.size.toString(), "ListStore: flow Updated")
val updatedTrackList = getState().trackList.updateTracksStatuses(map)
@ -119,6 +133,7 @@ internal class SpotiFlyerListStoreProvider(
is Result.UpdateTrackList -> copy(trackList = result.list)
is Result.UpdateTrackItem -> updateTrackItem(result.item)
is Result.ErrorOccurred -> copy(errorOccurred = result.error)
is Result.AskForDonation -> copy(askForDonation = result.isAllowed)
}
private fun State.updateTrackItem(item: TrackDetails): State {

View File

@ -73,7 +73,7 @@ internal class SpotiFlyerMainImpl(
override suspend fun loadImage(url: String): Picture {
return cache.get(url) {
dir.loadImage(url)
dir.loadImage(url, 150, 150)
}
}
}

View File

@ -100,8 +100,10 @@ internal class SpotiFlyerRootImpl(
override val callBacks = object : SpotiFlyerRootCallBacks {
override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link))
override fun popBackToHomeScreen() {
router.popWhile {
it !is Configuration.Main
if (router.state.value.activeChild.instance is Child.List && router.state.value.backStack.isNotEmpty()) {
router.popWhile {
it !is Configuration.Main
}
}
}
override fun showToast(text: String) { toastState.value = text }
@ -125,7 +127,9 @@ internal class SpotiFlyerRootImpl(
private fun onListOutput(output: SpotiFlyerList.Output): Unit =
when (output) {
is SpotiFlyerList.Output.Finished -> {
router.pop()
if (router.state.value.activeChild.instance is Child.List && router.state.value.backStack.isNotEmpty()) {
router.pop()
}
analytics.homeScreenVisit()
}
}

View File

@ -32,6 +32,7 @@ import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin
import com.shabinder.common.di.isInternetAccessible
import com.shabinder.common.di.setDownloadDirectory
import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails

View File

@ -0,0 +1,8 @@
<vector android:height="42dp" android:viewportHeight="250"
android:viewportWidth="488" android:width="81.984dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#fff" android:pathData="M483.73,36A53.1,53.1 0,0 0,452 4.28C438.49,0 425.94,0 400.84,0H325.16C300.07,0 287.52,0 274,4.28A53.08,53.08 0,0 0,242.28 36a76.64,76.64 0,0 0,-2 7.74,140.32 140.32,0 0,1 14,24.86c0.38,-9.57 1.27,-17.22 3.46,-24.14 4.68,-12.86 11.88,-20.06 24.74,-24.74C294.25,16 308.12,16 330,16h66c21.88,0 35.76,0 47.54,3.73 12.86,4.68 20,11.88 24.74,24.74C472,56.25 472,70.13 472,92v66c0,21.88 0,35.76 -3.72,47.53 -4.69,12.86 -11.88,20.06 -24.74,24.74C431.76,234 417.88,234 396,234H330c-21.89,0 -35.76,0 -47.54,-3.73 -12.86,-4.68 -20.06,-11.88 -24.74,-24.74 -2.19,-6.92 -3.09,-14.58 -3.46,-24.15a140.51,140.51 0,0 1,-14 24.85,77.18 77.18,0 0,0 2,7.77A53.08,53.08 0,0 0,274 245.73C287.52,250 300.07,250 325.16,250h75.68c25.1,0 37.65,0 51.16,-4.27A53.11,53.11 0,0 0,483.73 214C488,200.49 488,187.94 488,162.84V87.17C488,62.07 488,49.52 483.73,36Z"/>
<path android:fillColor="#fff" android:pathData="M422,217L380.33,217c-1.76,0 -5.83,-2.79 -2.63,-6.67 21.36,-23 48,-30.93 73.4,-39.42 3.32,-1 3.91,2.51 3.91,3.48v8.68C455,202.61 441.57,217 422,217ZM343.73,212.69c-4,-29.73 -18.06,-80.79 -71,-118.55A3.78,3.78 0,0 1,271 90.63L271,66.36c0,-26.69 18,-33.31 26.37,-33.31a4.3,4.3 0,0 1,4.07 2.1c25.24,55 41,89.86 50.7,172.83 0.05,1.62 0.31,2.39 1.28,0 6.86,-15.07 39.35,-92 26.44,-170.68a3.64,3.64 0,0 1,3.5 -4.25L422,33.05c19.54,0 33,13.43 33,33.36L455,100.5a3.63,3.63 0,0 1,-2.07 3.36,180.12 180.12,0 0,0 -90.3,109.25c-0.79,2.21 -1.25,3.9 -3.71,3.9h-11.8C344.77,217 344.27,216.05 343.73,212.7ZM304.35,217c-20,0 -33.35,-12.37 -33.35,-33.93v-2.24c0,-0.9 0.71,-4.29 4.09,-3.63 20.24,6.23 41.92,12.52 57.77,33.49 1.82,2.56 0.23,6.3 -2.91,6.31Z"/>
<path android:fillColor="#fff" android:pathData="M124.991,239.991a115,115 54.655,1 0,2.007 -229.991a115,115 54.655,1 0,-2.007 229.991z"/>
<path android:fillColor="#2bc5b4" android:pathData="M180.77,114.59c-8.62,0 -15.61,7.39 -15.61,16.49s7,16.5 15.61,16.5 15.62,-7.38 15.62,-16.5S189.4,114.59 180.77,114.59Z"/>
<path android:fillColor="#2bc5b4" android:pathData="M125,0A125,125 0,1 0,250 125,125 125,0 0,0 125,0ZM95.37,132.09c0,63.82 -101.74,35.68 -60.49,2.93 9.65,13.39 28.18,12.5 30.15,-0.72l0.37,-52.05c0.95,-13.32 26.85,-16 30,0ZM133.31,156.32a12.05,12.05 0,0 1,-12 12L116.1,168.32a12.05,12.05 0,0 1,-12 -12L104.1,106a12,12 0,0 1,12 -12h5.21a12,12 0,0 1,12 12ZM133.31,74.56a11.84,11.84 0,0 1,-11.79 11.79L115.9,86.35a11.84,11.84 0,0 1,-11.81 -11.79L104.09,71.65A11.83,11.83 0,0 1,115.9 59.86h5.62a11.82,11.82 0,0 1,11.79 11.79ZM180.77,169.9c-22,0 -39.82,-17.37 -39.82,-38.82s17.84,-38.81 39.82,-38.81 39.81,17.38 39.81,38.81S202.76,169.9 180.77,169.9Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:viewportHeight="64"
android:viewportWidth="64" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#BBFFFFFF" android:fillType="evenOdd" android:pathData="M52.402,31.916c0,4.03 -1.17,7.895 -3.178,11.087l8.196,8.23c4.014,-5.375 6.523,-12.094 6.523,-19.318s-2.51,-13.942 -6.523,-19.318l-8.196,8.23c2.007,3.192 3.178,6.887 3.178,11.087z"/>
<path android:fillColor="#FFFFFF" android:fillType="evenOdd" android:pathData="M32.004,52.41c-11.207,0 -20.406,-9.24 -20.406,-20.493s9.2,-20.493 20.406,-20.493c4.182,0 7.86,1.176 11.04,3.36l8.196,-8.23C45.887,2.52 39.197,0 32.004,0 14.44,0 0.057,14.278 0.057,32.084S14.44,64 32.004,64c7.36,0 14.05,-2.52 19.403,-6.55l-8.196,-8.23c-3.178,2.016 -7.025,3.192 -11.207,3.192z"/>
</vector>