Custom Download Directory Func.

This commit is contained in:
shabinder 2021-04-19 23:54:48 +05:30
parent 289ada76b0
commit 49562de503
15 changed files with 169 additions and 36 deletions

View File

@ -35,6 +35,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" /> <uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@ -48,6 +50,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:configChanges="orientation|screenSize"
android:forceDarkAllowed="true" android:forceDarkAllowed="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
tools:targetApi="q"> tools:targetApi="q">

View File

@ -50,6 +50,8 @@ import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponent import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponent
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.codekidlabs.storagechooser.R
import com.codekidlabs.storagechooser.StorageChooser
import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.navigationBarsPadding
import com.google.accompanist.insets.statusBarsHeight import com.google.accompanist.insets.statusBarsHeight
@ -57,6 +59,7 @@ import com.google.accompanist.insets.statusBarsPadding
import com.razorpay.Checkout import com.razorpay.Checkout
import com.razorpay.PaymentResultListener import com.razorpay.PaymentResultListener
import com.shabinder.common.database.activityContext import com.shabinder.common.database.activityContext
import com.shabinder.common.database.appContext
import com.shabinder.common.di.* import com.shabinder.common.di.*
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -69,6 +72,7 @@ import com.tonyodev.fetch2.Status
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.io.File
import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage import com.shabinder.common.uikit.showPopUpMessage as uikitShowPopUpMessage
const val disableDozeCode = 1223 const val disableDozeCode = 1223
@ -128,6 +132,43 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
dir.createDirectories() dir.createDirectories()
Checkout.preload(applicationContext) Checkout.preload(applicationContext)
handleIntentFromExternalActivity() handleIntentFromExternalActivity()
Log.i("Download Path",dir.defaultDir())
}
@Suppress("DEPRECATION")
private fun setUpOnPrefClickListener() {
// Initialize Builder
val chooser = StorageChooser.Builder()
.withActivity(this)
.withFragmentManager(fragmentManager)
.withMemoryBar(true)
.setTheme(StorageChooser.Theme(appContext).apply {
scheme = applicationContext.resources.getIntArray(R.array.default_dark)
})
.setDialogTitle("Set Download Directory")
.allowCustomPath(true)
.setType(StorageChooser.DIRECTORY_CHOOSER)
.build()
// get path that the user has chosen
chooser.setOnSelectListener { path ->
Log.d("Setting Base Path", path)
val f = File(path)
if (f.canWrite()) {
// hell yeah :)
dir.setDownloadDirectory(path)
com.shabinder.common.uikit.showPopUpMessage(
"Download Directory Set to:\n${dir.defaultDir()} "
)
}else{
com.shabinder.common.uikit.showPopUpMessage(
"NO WRITE ACCESS on \n$path ,\nReverting Back to Previous"
)
}
}
// Show dialog whenever you want by
chooser.show()
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
@ -145,6 +186,7 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
override val directories: Dir = this@MainActivity.dir override val directories: Dir = this@MainActivity.dir
override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage override val showPopUpMessage: (String) -> Unit = ::uikitShowPopUpMessage
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
override val setDownloadDirectoryAction: () -> Unit = ::setUpOnPrefClickListener
} }
) )
@ -289,10 +331,14 @@ class MainActivity : ComponentActivity(), PaymentResultListener {
AlertDialog( AlertDialog(
onDismissRequest = {}, onDismissRequest = {},
buttons = { buttons = {
TextButton({ TextButton(
{
requestStoragePermission() requestStoragePermission()
disableDozeMode(disableDozeCode) disableDozeMode(disableDozeCode)
},Modifier.padding(bottom = 16.dp,start = 16.dp,end = 16.dp).fillMaxWidth().background(colorPrimary,shape = SpotiFlyerShapes.medium).padding(horizontal = 8.dp), },
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
.padding(horizontal = 8.dp),
){ ){
Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center) Text("Grant Permissions",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
} }

View File

@ -76,3 +76,8 @@ fun Activity.requestStoragePermission() {
) )
} }
} }
/*
fun Activity.requestBroaderStoragePermission() {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
startActivity(intent)
}*/

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- <color name="colorPrimary">#2d6c55</color>
<color name="colorPrimaryDark">#235644</color>
<color name="colorAccent">#ff9c40</color>
<color name="colorCyanListClick">#d6c8f5f3</color>
<color name="colorListDivider">#89606060</color>
<color name="pathLayoutBgColor">#70ffffff</color>
<color name="chevronBgColor">#50ffffff</color>
<color name="inactiveGradientColor">#53000000</color>
&lt;!&ndash; dialog colors &ndash;&gt;
<color name="memory_status_color">#de6565</color>
<color name="memory_bar_color">#7bde65</color>
<color name="new_folder_color">#c14b84</color>
<color name="select_color">#6b3fa1</color>
<color name="cancel_color">#3fa19f</color>-->
<array name="default_light">
<!-- Overview -->
<item>@color/colorPrimary</item> <!-- Top Header bg -->
<item>@android:color/white</item> <!-- header text -->
<item>@android:color/white</item> <!-- list bg -->
<item>@android:color/black</item> <!-- storage list name text -->
<item>@color/colorPrimary</item> <!-- free space text -->
<item>@color/colorAccent</item> <!-- memory bar -->
<!-- secondary dialog colors -->
<item>@color/colorPrimary</item> <!-- address bar bg -->
<item>@android:color/white</item> <!-- list bg -->
<item>@android:color/black</item> <!-- list text -->
<item>@android:color/white</item> <!-- address bar tint -->
<item>@color/chevronBgColor</item> <!-- new folder hint tint -->
<item>#da6c6c</item> <!-- select button color -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>@color/colorPrimary</item> <!-- new folder layour bg -->
</array>
<array name="default_dark">
<!-- Overview -->
<item>@color/colorPrimary</item> <!-- Top Header bg -->
<item>@android:color/white</item> <!-- header text -->
<item>@android:color/black</item> <!-- list bg -->
<item>@android:color/white</item> <!-- storage list name text -->
<item>#da6c6c</item> <!-- free space text -->
<item>@color/colorPrimary</item> <!-- memory bar -->
<!-- secondary dialog colors -->
<item>@color/colorPrimary</item> <!-- address bar bg -->
<item>@android:color/black</item> <!-- list bg -->
<item>@android:color/white</item> <!-- list text -->
<item>@android:color/white</item> <!-- address bar tint -->
<item>@color/grey</item> <!-- new folder hint tint -->
<item>#da6c6c</item> <!-- select button color -->
<item>#da6c6c</item> <!-- new folder layour bg -->
<item>#da6c6c</item> <!-- new multi fab -->
<item>#da6c6c</item> <!-- new multi fab -->
</array>
</resources>

View File

@ -34,9 +34,9 @@ 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.MaterialTheme import androidx.compose.material.*
import androidx.compose.material.Text import androidx.compose.material.icons.Icons
import androidx.compose.material.TopAppBar import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -114,6 +114,7 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
AppBar( AppBar(
backgroundColor = appBarColor, backgroundColor = appBarColor,
setDownloadDirectory = component.callBacks::setDownloadDirectory,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(Modifier.padding(top = topPadding)) Spacer(Modifier.padding(top = topPadding))
@ -132,6 +133,7 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float,topPadding: Dp = 0.dp
@Composable @Composable
fun AppBar( fun AppBar(
backgroundColor: Color, backgroundColor: Color,
setDownloadDirectory:()->Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TopAppBar( TopAppBar(
@ -149,14 +151,14 @@ fun AppBar(
style = appNameStyle style = appNameStyle
) )
} }
}, /* },
actions = { actions = {
IconButton( IconButton(
onClick = { *//*TODO: Open Preferences*//* } onClick = { setDownloadDirectory() }
) { ) {
Icon(Icons.Filled.Settings,"Preferences", tint = Color.Gray) Icon(Icons.Filled.Settings,"Preferences", tint = Color.Gray)
} }
},*/ },
modifier = modifier, modifier = modifier,
elevation = 0.dp elevation = 0.dp
) )

View File

@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class GaanaAlbum( data class GaanaAlbum(
val tracks: List<GaanaTrack>, val tracks: List<GaanaTrack>?,
val count: Int, val count: Int,
val custom_artworks: CustomArtworks, val custom_artworks: CustomArtworks,
val release_year: Int, val release_year: Int,

View File

@ -53,6 +53,7 @@ kotlin {
implementation(Extras.Android.razorpay) implementation(Extras.Android.razorpay)
api(Extras.mp3agic) api(Extras.mp3agic)
api(Extras.jaudioTagger) api(Extras.jaudioTagger)
api("com.github.shabinder:storage-chooser:2.0.4.45")
// api(files("$rootDir/libs/mobile-ffmpeg.aar")) // api(files("$rootDir/libs/mobile-ffmpeg.aar"))
} }
} }

View File

@ -28,6 +28,10 @@ import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.json.JSONObject import org.json.JSONObject
import com.codekidlabs.storagechooser.StorageChooser
import com.codekidlabs.storagechooser.StorageChooser.OnSelectListener
actual fun openPlatform(packageID: String, platformLink: String) { actual fun openPlatform(packageID: String, platformLink: String) {
val manager: PackageManager = activityContext.packageManager val manager: PackageManager = activityContext.packageManager

View File

@ -17,6 +17,8 @@
package com.shabinder.common.di package com.shabinder.common.di
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
@ -37,21 +39,33 @@ import java.net.URL
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
private val database: Database? private val database: Database?,
) { ) {
private val scope = CoroutineScope(Dispatchers.IO) companion object {
const val SharedPreferencesKey = "configurations"
const val DirKey = "downloadDir"
}
private val context: Context private val context: Context
get() = appContext get() = appContext
private val sharedPreferences:SharedPreferences by lazy {
context.getSharedPreferences(SharedPreferencesKey,MODE_PRIVATE)
}
fun setDownloadDirectory(newBasePath:String){
sharedPreferences.edit().putString(DirKey,newBasePath).apply()
}
@Suppress("DEPRECATION")
private val defaultBaseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString()
actual fun fileSeparator(): String = File.separator actual fun fileSeparator(): String = File.separator
actual fun imageCacheDir(): String = context.cacheDir.absolutePath + File.separator actual fun imageCacheDir(): String = context.cacheDir.absolutePath + File.separator
@Suppress("DEPRECATION") // fun call in order to always access Updated Value
actual fun defaultDir(): String = actual fun defaultDir(): String = sharedPreferences.getString(DirKey,defaultBaseDir)!! + File.separator +
Environment.getExternalStorageDirectory().toString() + File.separator +
Environment.DIRECTORY_MUSIC + File.separator +
"SpotiFlyer" + File.separator "SpotiFlyer" + File.separator
actual fun isPresent(path: String): Boolean = File(path).exists() actual fun isPresent(path: String): Boolean = File(path).exists()

View File

@ -36,7 +36,7 @@ import kotlin.math.roundToInt
expect class Dir( expect class Dir(
logger: Kermit, logger: Kermit,
database: Database? = createDatabase() database: Database? = createDatabase(),
) { ) {
val db: Database? val db: Database?
fun isPresent(path: String): Boolean fun isPresent(path: String): Boolean

View File

@ -81,10 +81,10 @@ class GaanaProvider(
getGaanaAlbum(seokey = link).also { getGaanaAlbum(seokey = link).also {
folderType = "Albums" folderType = "Albums"
subFolder = link subFolder = link
it.tracks.forEach { track -> it.tracks?.forEach { track ->
track.updateStatusIfPresent(folderType, subFolder) track.updateStatusIfPresent(folderType, subFolder)
} }
trackList = it.tracks.toTrackDetailsList(folderType, subFolder) trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
title = link title = link
coverUrl = it.custom_artworks.size_480p coverUrl = it.custom_artworks.size_480p
} }

View File

@ -68,7 +68,7 @@ class SpotifyProvider(
install(JsonFeature) { install(JsonFeature) {
serializer = kotlinxSerializer serializer = kotlinxSerializer
} }
}?.also { httpClient = it } }.also { httpClient = it }
} }
} }
@ -127,10 +127,7 @@ class SpotifyProvider(
it.updateStatusIfPresent(folderType, subFolder) it.updateStatusIfPresent(folderType, subFolder)
trackList = listOf(it).toTrackDetailsList(folderType, subFolder) trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.name.toString() title = it.name.toString()
coverUrl = ( coverUrl = it.album?.images?.elementAtOrNull(0)?.url.toString()
it.album?.images?.elementAtOrNull(1)?.url
?: it.album?.images?.elementAtOrNull(0)?.url
).toString()
} }
} }
@ -143,22 +140,19 @@ class SpotifyProvider(
it.album = Album( it.album = Album(
images = listOf( images = listOf(
Image( Image(
url = albumObject.images?.elementAtOrNull(1)?.url url = albumObject.images?.elementAtOrNull(0)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
) )
) )
) )
} }
albumObject.tracks?.items?.toTrackDetailsList(folderType, subFolder).let { albumObject.tracks?.items?.toTrackDetailsList(folderType, subFolder).let {
if (it.isNullOrEmpty()) { if (it.isNullOrEmpty()) {
// TODO Handle Error // TODO Handle Error
} else { } else {
trackList = it trackList = it
title = albumObject.name.toString() title = albumObject.name.toString()
coverUrl = ( coverUrl = albumObject.images?.elementAtOrNull(0)?.url.toString()
albumObject.images?.elementAtOrNull(1)?.url
?: albumObject.images?.elementAtOrNull(0)?.url
).toString()
} }
} }
} }
@ -189,8 +183,7 @@ class SpotifyProvider(
// log("Total Tracks Fetched", tempTrackList.size.toString()) // log("Total Tracks Fetched", tempTrackList.size.toString())
trackList = tempTrackList.toTrackDetailsList(folderType, subFolder) trackList = tempTrackList.toTrackDetailsList(folderType, subFolder)
title = playlistObject.name.toString() title = playlistObject.name.toString()
coverUrl = playlistObject.images?.elementAtOrNull(1)?.url coverUrl = playlistObject.images?.firstOrNull()?.url.toString()
?: playlistObject.images?.firstOrNull()?.url.toString()
} }
"episode" -> { // TODO "episode" -> { // TODO
} }
@ -221,14 +214,14 @@ class SpotifyProvider(
title = it.name.toString(), title = it.name.toString(),
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
durationSec = (it.duration_ms / 1000).toInt(), durationSec = (it.duration_ms / 1000).toInt(),
albumArtPath = dir.imageCacheDir() + (it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg", albumArtPath = dir.imageCacheDir() + (it.album?.images?.firstOrNull()?.url.toString()).substringAfterLast('/') + ".jpeg",
albumName = it.album?.name, albumName = it.album?.name,
year = it.album?.release_date, year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}", comment = "Genres:${it.album?.genres?.joinToString()}",
trackUrl = it.href, trackUrl = it.href,
downloaded = it.downloaded, downloaded = it.downloaded,
source = Source.Spotify, source = Source.Spotify,
albumArtURL = it.album?.images?.elementAtOrNull(1)?.url ?: it.album?.images?.firstOrNull()?.url.toString(), albumArtURL = it.album?.images?.firstOrNull()?.url.toString(),
outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/) outputFilePath = dir.finalOutputDir(it.name.toString(), type, subFolder, dir.defaultDir()/*,".m4a"*/)
) )
} }

View File

@ -49,6 +49,7 @@ interface SpotiFlyerRoot {
val directories: Dir val directories: Dir
val showPopUpMessage: (String) -> Unit val showPopUpMessage: (String) -> Unit
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
val setDownloadDirectoryAction:()->Unit
} }
} }

View File

@ -19,4 +19,5 @@ package com.shabinder.common.root.callbacks
interface SpotiFlyerRootCallBacks { interface SpotiFlyerRootCallBacks {
fun searchLink(link: String) fun searchLink(link: String)
fun popBackToHomeScreen() fun popBackToHomeScreen()
fun setDownloadDirectory()
} }

View File

@ -55,6 +55,7 @@ internal class SpotiFlyerRootImpl(
it !is Configuration.Main it !is Configuration.Main
} }
} }
override fun setDownloadDirectory() { setDownloadDirectoryAction() }
} }
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =