Revert Back to old/gold java.io.File!

- SAF is painfully slow.
- SAF brings nothing but more issues and hacks.
- External SdCard write Access wont be fixed (NO SAF).
This commit is contained in:
shabinder 2021-05-14 02:51:33 +05:30
parent a3e9b7c3c1
commit d9ba60dbbf
16 changed files with 178 additions and 48 deletions

View File

@ -120,12 +120,6 @@ dependencies {
implementation(MVIKotlin.mvikotlinLogging)
implementation(MVIKotlin.mvikotlinTimeTravel)
// Firebase
implementation(platform("com.google.firebase:firebase-bom:27.1.0"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-perf-ktx")
// Extras
Extras.Android.apply {
implementation(Acra.notification)
@ -134,6 +128,7 @@ dependencies {
implementation(matomo)
}
//implementation("com.jakewharton.timber:timber:4.7.1")
implementation("dev.icerock.moko:parcelize:0.6.1")
implementation("com.github.shabinder:storage-chooser:2.0.4.45")
implementation("com.google.accompanist:accompanist-insets:0.9.1")

View File

@ -29,8 +29,26 @@ import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.component.KoinComponent
import org.koin.core.logger.Level
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
class App: Application(), KoinComponent {
val tracker: Tracker by lazy {
TrackerBuilder.createDefault(
"https://kind-grasshopper-73.telebit.io/matomo/matomo.php", 1)
.build(Matomo.getInstance(this)).apply {
if (BuildConfig.DEBUG) {
/*Timber.plant(DebugTree())
addTrackingCallback {
Timber.d(it.toMap().toString())
it
}*/
}
}
}
override fun onCreate() {
super.onCreate()

View File

@ -56,22 +56,22 @@ import com.shabinder.common.models.Actions
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
import com.shabinder.common.models.Status
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.uikit.*
import com.shabinder.spotiflyer.utils.*
import com.shabinder.common.models.Status
import com.shabinder.common.models.methods
import com.shabinder.spotiflyer.ui.NetworkDialog
import com.shabinder.spotiflyer.ui.PermissionDialog
import com.shabinder.spotiflyer.utils.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.koin.android.ext.android.inject
import org.matomo.sdk.extra.TrackHelper
import java.io.File
const val disableDozeCode = 1223
@ExperimentalAnimationApi
class MainActivity : ComponentActivity() {
@ -85,6 +85,7 @@ class MainActivity : ComponentActivity() {
private lateinit var updateUIReceiver: BroadcastReceiver
private lateinit var queryReceiver: BroadcastReceiver
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
private val tracker get() = (application as App).tracker
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -118,7 +119,8 @@ class MainActivity : ComponentActivity() {
PermissionDialog(
permissionGranted.value,
{ requestStoragePermission() },
{ disableDozeMode(disableDozeCode) }
{ disableDozeMode(disableDozeCode) },
dir::enableAnalytics
)
}
}
@ -130,6 +132,10 @@ class MainActivity : ComponentActivity() {
private fun initialise() {
checkIfLatestVersion()
handleIntentFromExternalActivity()
if(dir.isAnalyticsEnabled){
// Download/App Install Event
TrackHelper.track().download().with(tracker)
}
}
@Composable
@ -380,4 +386,8 @@ class MainActivity : ComponentActivity() {
}
}
}
companion object {
const val disableDozeCode = 1223
}
}

View File

@ -8,11 +8,13 @@ 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.AlertDialog
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Insights
import androidx.compose.material.icons.rounded.SdStorage
import androidx.compose.material.icons.rounded.SystemSecurityUpdate
import androidx.compose.runtime.Composable
@ -28,6 +30,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorPrimary
@ -38,13 +41,50 @@ import kotlinx.coroutines.delay
fun PermissionDialog(
permissionGranted: Boolean,
requestStoragePermission:() -> Unit,
disableDozeMode:() -> Unit
disableDozeMode:() -> Unit,
enableAnalytics:() -> Unit
){
var askForPermission by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(2000)
askForPermission = true
}
// Analytics Permission Dialog
var askForAnalyticsPermission by remember { mutableStateOf(false) }
AnimatedVisibility(askForAnalyticsPermission) {
AlertDialog(
onDismissRequest = {
askForAnalyticsPermission = false
},
title = {
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)
}
},
backgroundColor = Color.DarkGray,
buttons = {
TextButton(
{
askForAnalyticsPermission = false
enableAnalytics()
},
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
.padding(horizontal = 8.dp),
) {
Text("Sure!",color = Color.Black,fontSize = 18.sp,textAlign = TextAlign.Center)
}
},
text = {
Text("Your Data is Anonymized and will never be shared with any 3rd party service",style = SpotiFlyerTypography.body2,textAlign = TextAlign.Center)
},
properties = DialogProperties(dismissOnBackPress = true,dismissOnClickOutside = false)
)
}
AnimatedVisibility(
askForPermission && !permissionGranted
) {
@ -55,6 +95,7 @@ fun PermissionDialog(
{
requestStoragePermission()
disableDozeMode()
askForAnalyticsPermission = true
},
Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp).fillMaxWidth()
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
@ -100,6 +141,23 @@ fun PermissionDialog(
)
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Insights,"Analytics")
Spacer(modifier = Modifier.padding(start = 16.dp))
Column {
Text(
text = "Analytics",
style = SpotiFlyerTypography.h6.copy(fontWeight = FontWeight.SemiBold)
)
Text(
text = "Share Analytics Data (optional) with App Devs (Self-Hosted), It will never be used/shared/sold to any third party service.",
style = SpotiFlyerTypography.subtitle2,
)
}
}
}
}
)

View File

@ -102,7 +102,7 @@ fun SpotiFlyerMainContent(component: SpotiFlyerMain) {
)
when (model.selectedCategory) {
HomeCategory.About -> AboutColumn()
HomeCategory.About -> AboutColumn { component.analytics.donationDialogVisit() }
HomeCategory.History -> HistoryColumn(
model.records.sortedByDescending { it.id },
component::loadImage,
@ -221,7 +221,10 @@ fun SearchPanel(
}
@Composable
fun AboutColumn(modifier: Modifier = Modifier) {
fun AboutColumn(
modifier: Modifier = Modifier,
donationDialogOpenEvent:() -> Unit
) {
Box {
val stateVertical = rememberScrollState(0)
@ -331,14 +334,18 @@ fun AboutColumn(modifier: Modifier = Modifier) {
var isDonationDialogVisible by remember { mutableStateOf(false) }
DonationDialog(
isDonationDialogVisible
) {
isDonationDialogVisible,
onDismiss = {
isDonationDialogVisible = false
}
)
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { isDonationDialogVisible = true }),
.clickable(onClick = {
isDonationDialogVisible = true
donationDialogOpenEvent()
}),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.CardGiftcard, "Support Developer")

View File

@ -41,7 +41,7 @@ import java.net.URL
actual class Dir actual constructor(
private val logger: Kermit,
private val settings: Settings,
private val spotiFlyerDatabase: SpotiFlyerDatabase,
spotiFlyerDatabase: SpotiFlyerDatabase,
) {
companion object {
const val DirKey = "downloadDir"
@ -91,7 +91,7 @@ actual class Dir actual constructor(
* */
if(!songFile.exists()) {
/*Make intermediate Dirs if they don't exist yet*/
songFile.parentFile.mkdirs()
songFile.parentFile?.mkdirs()
}
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)

View File

@ -17,7 +17,6 @@
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import co.touchlab.stately.ensureNeverFrozen
import com.russhwolf.settings.Settings
import com.shabinder.common.database.databaseModule
import com.shabinder.common.database.getLogger
@ -25,15 +24,10 @@ import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SpotifyProvider
import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.models.NativeAtomicReference
import io.ktor.client.HttpClient
import io.ktor.client.features.HttpTimeout
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import io.ktor.client.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import kotlinx.serialization.json.Json
import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration
@ -87,12 +81,6 @@ fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
}
)
}*/
// Timeout
install(HttpTimeout) {
// requestTimeoutMillis = 20000L
connectTimeoutMillis = 15000L
socketTimeoutMillis = 15000L
}
if (enableNetworkLogs) {
install(Logging) {
logger = Logger.DEFAULT

View File

@ -18,7 +18,6 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.russhwolf.settings.Settings
import com.russhwolf.settings.SettingsListener
import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult

View File

@ -45,6 +45,13 @@ actual class Dir actual constructor(
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
}
actual val isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
actual fun enableAnalytics() {
settings.putBoolean(AnalyticsKey,true)
}
init {

View File

@ -21,7 +21,6 @@ import platform.Foundation.sendSynchronousRequest
import platform.Foundation.writeToFile
import platform.UIKit.UIImage
import platform.UIKit.UIImageJPEGRepresentation
import java.lang.System
actual class Dir actual constructor(
val logger: Kermit,
@ -30,6 +29,13 @@ actual class Dir actual constructor(
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
}
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)

View File

@ -39,8 +39,17 @@ actual class Dir actual constructor(
) {
companion object {
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
}
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()
}*/

View File

@ -65,6 +65,11 @@ interface SpotiFlyerList {
val link: String
val listOutput: Consumer<Output>
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
val listAnalytics: Analytics
}
interface Analytics {
}
sealed class Output {

View File

@ -31,6 +31,8 @@ interface SpotiFlyerMain {
val models: Value<State>
val analytics: Analytics
/*
* We Intend to Move to List Screen
* Note: Implementation in Root
@ -57,6 +59,11 @@ interface SpotiFlyerMain {
val storeFactory: StoreFactory
val database: Database?
val dir: Dir
val mainAnalytics: Analytics
}
interface Analytics {
fun donationDialogVisit()
}
sealed class Output {

View File

@ -40,9 +40,6 @@ internal class SpotiFlyerMainImpl(
init {
instanceKeeper.ensureNeverFrozen()
lifecycle.doOnDestroy {
cache.invalidateAll()
}
}
private val store =
@ -55,11 +52,13 @@ internal class SpotiFlyerMainImpl(
private val cache = Cache.Builder
.newBuilder()
.maximumCacheSize(20)
.maximumCacheSize(25)
.build<String, Picture>()
override val models: Value<State> = store.asValue()
override val analytics = mainAnalytics
override fun onLinkSearch(link: String) {
if (methods.value.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else methods.value.showPopUpMessage("Check Network Connection Please")

View File

@ -54,6 +54,14 @@ interface SpotiFlyerRoot {
val directories: Dir
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>>
val actions:Actions
val analytics: Analytics
}
interface Analytics {
fun appLaunchEvent()
fun homeScreenVisit()
fun listScreenVisit()
fun donationDialogVisit()
}
}

View File

@ -37,6 +37,7 @@ import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.Consumer
import com.shabinder.common.models.methods
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
@ -49,7 +50,8 @@ internal class SpotiFlyerRootImpl(
componentContext: ComponentContext,
private val main: (ComponentContext, output:Consumer<SpotiFlyerMain.Output>)->SpotiFlyerMain,
private val list: (ComponentContext, link:String, output:Consumer<SpotiFlyerList.Output>)->SpotiFlyerList,
private val actions: Actions
private val actions: Actions,
private val analytics: Analytics
) : SpotiFlyerRoot, ComponentContext by componentContext {
constructor(
@ -72,7 +74,8 @@ internal class SpotiFlyerRootImpl(
dependencies
)
},
actions = dependencies.actions.freeze()
actions = dependencies.actions.freeze(),
analytics = dependencies.analytics
) {
instanceKeeper.ensureNeverFrozen()
methods.value = dependencies.actions.freeze()
@ -113,16 +116,23 @@ internal class SpotiFlyerRootImpl(
private fun onMainOutput(output: SpotiFlyerMain.Output) =
when (output) {
is SpotiFlyerMain.Output.Search -> router.push(Configuration.List(link = output.link))
is SpotiFlyerMain.Output.Search -> {
router.push(Configuration.List(link = output.link))
analytics.listScreenVisit()
}
}
private fun onListOutput(output: SpotiFlyerList.Output): Unit =
when (output) {
is SpotiFlyerList.Output.Finished -> router.pop()
is SpotiFlyerList.Output.Finished -> {
router.pop()
analytics.homeScreenVisit()
}
}
private fun authenticateSpotify(spotifyProvider: SpotifyProvider, override:Boolean){
GlobalScope.launch(Dispatchers.Default) {
analytics.appLaunchEvent()
/*Authenticate Spotify Client*/
spotifyProvider.authenticateSpotifyClient(override)
}
@ -143,6 +153,9 @@ private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val dir: Dir = directories
override val mainAnalytics = object : SpotiFlyerMain.Analytics {
override fun donationDialogVisit() = analytics.donationDialogVisit()
}
}
)
@ -155,5 +168,6 @@ private fun spotiFlyerList(componentContext: ComponentContext, link: String, out
override val link: String = link
override val listOutput: Consumer<SpotiFlyerList.Output> = output
override val downloadProgressFlow = downloadProgressReport
override val listAnalytics = object : SpotiFlyerList.Analytics {}
}
)