Code Cleanup and Refactoring

This commit is contained in:
shabinder 2021-08-21 01:06:05 +05:30
parent 399d22c3cc
commit 04dbff4d7f
13 changed files with 136 additions and 126 deletions

View File

@ -60,7 +60,6 @@ import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.*
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Analytics
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.configurations.SpotiFlyerTheme
@ -78,7 +77,6 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import org.matomo.sdk.extra.TrackHelper
import java.io.File
@ExperimentalAnimationApi
@ -94,7 +92,7 @@ class MainActivity : ComponentActivity() {
private var permissionGranted = mutableStateOf(true)
private val internetAvailability by lazy { ConnectionLiveData(applicationContext) }
private val visibleChild get(): SpotiFlyerRoot.Child = root.routerState.value.activeChild.instance
// private val visibleChild get(): SpotiFlyerRoot.Child = root.routerState.value.activeChild.instance
// Variable for storing instance of our service class
var foregroundService: ForegroundService? = null
@ -139,7 +137,8 @@ class MainActivity : ComponentActivity() {
AnalyticsDialog(
askForAnalyticsPermission,
enableAnalytics = {
preferenceManager.toggleAnalytics(true)
// preferenceManager.toggleAnalytics(true)
analyticsManager.giveConsent()
preferenceManager.firstLaunchDone()
},
dismissDialog = {
@ -257,6 +256,7 @@ class MainActivity : ComponentActivity() {
override val fetchQuery = this@MainActivity.fetcher
override val dir: Dir = this@MainActivity.dir
override val preferenceManager = this@MainActivity.preferenceManager
override val analyticsManager: AnalyticsManager = this@MainActivity.analyticsManager
override val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
override val actions = object : Actions {
@ -329,32 +329,12 @@ class MainActivity : ComponentActivity() {
}
}
override fun writeMp3Tags(trackDetails: TrackDetails) { /*IMPLEMENTED*/
override fun writeMp3Tags(trackDetails: TrackDetails) {
/*IMPLEMENTED*/
}
override val isInternetAvailable get() = internetAvailability.value ?: true
}
/*
* Analytics Will Only Be Sent if User Granted us the Permission
* */
override val analytics = object : Analytics {
override fun appLaunchEvent() {
analyticsManager.sendEvent("app_launch")
}
override fun homeScreenVisit() {
analyticsManager.sendView("home_screen")
}
override fun listScreenVisit() {
analyticsManager.sendView("list_screen")
}
override fun donationDialogVisit() {
analyticsManager.sendEvent("open_donation_dialog")
}
}
}
)

View File

@ -11,7 +11,14 @@ interface AnalyticsManager {
fun revokeConsent()
fun sendView(name: String, extras: Map<String, Any> = emptyMap())
fun sendEvent(eventName: String, extras: Map<String, Any> = emptyMap())
fun track(event: AnalyticsAction) = event.track(this)
fun sendCrashReport(error: Throwable, extras: Map<String, Any> = emptyMap())
companion object {
abstract class AnalyticsAction {
abstract fun track(analyticsManager: AnalyticsManager)
}
}
}
@Suppress("ClassName", "SpellCheckingInspection")

View File

@ -0,0 +1,9 @@
package com.shabinder.common.di.analytics
sealed class AnalyticsEvent(private val eventName: String, private val extras: Map<String, Any> = emptyMap()): AnalyticsManager.Companion.AnalyticsAction() {
override fun track(analyticsManager: AnalyticsManager) = analyticsManager.sendEvent(eventName,extras)
object AppLaunch: AnalyticsEvent("app_launch")
object DonationDialogOpen: AnalyticsEvent("donation_dialog_open")
}

View File

@ -0,0 +1,10 @@
package com.shabinder.common.di.analytics
import com.shabinder.common.di.analytics.AnalyticsManager.Companion.AnalyticsAction
sealed class AnalyticsView(private val viewName: String, private val extras: Map<String, Any> = emptyMap()) : AnalyticsAction() {
override fun track(analyticsManager: AnalyticsManager) = analyticsManager.sendView(viewName,extras)
object HomeScreen: AnalyticsView("home_screen")
object ListScreen: AnalyticsView("list_screen")
}

View File

@ -3,7 +3,9 @@ package com.shabinder.common.di.preference
import com.russhwolf.settings.Settings
import com.shabinder.common.models.AudioQuality
class PreferenceManager(settings: Settings) : Settings by settings {
class PreferenceManager(
settings: Settings
) : Settings by settings {
companion object {
const val DIR_KEY = "downloadDir"
@ -14,8 +16,8 @@ class PreferenceManager(settings: Settings) : Settings by settings {
}
/* ANALYTICS */
// val isAnalyticsEnabled get() = getBooleanOrNull(ANALYTICS_KEY) ?: false
// fun toggleAnalytics(enabled: Boolean) = putBoolean(ANALYTICS_KEY, enabled)
val isAnalyticsEnabled get() = getBooleanOrNull(ANALYTICS_KEY) ?: false
fun toggleAnalytics(enabled: Boolean) = putBoolean(ANALYTICS_KEY, enabled)
/* DOWNLOAD DIRECTORY */
val downloadDir get() = getStringOrNull(DIR_KEY)

View File

@ -25,6 +25,7 @@ import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus
@ -34,14 +35,7 @@ import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
internal class SpotiFlyerListStoreProvider(
private val dir: Dir,
private val preferenceManager: PreferenceManager,
private val storeFactory: StoreFactory,
private val fetchQuery: FetchPlatformQueryResult,
private val link: String,
private val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
) {
internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependencies): SpotiFlyerList.Dependencies by dependencies {
fun provide(): SpotiFlyerListStore =
object :
SpotiFlyerListStore,

View File

@ -21,6 +21,7 @@ import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir
import com.shabinder.common.di.Picture
import com.shabinder.common.di.analytics.AnalyticsManager
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.main.integration.SpotiFlyerMainImpl
import com.shabinder.common.models.Consumer
@ -67,6 +68,7 @@ interface SpotiFlyerMain {
val database: Database?
val dir: Dir
val preferenceManager: PreferenceManager
val analyticsManager: AnalyticsManager
val mainAnalytics: Analytics
}

View File

@ -41,13 +41,14 @@ internal class SpotiFlyerMainImpl(
init {
instanceKeeper.ensureNeverFrozen()
lifecycle.doOnResume {
store.accept(Intent.ToggleAnalytics(preferenceManager.isAnalyticsEnabled))
store.accept(Intent.ToggleAnalytics(analyticsManager.isTracking()))
}
}
private val store =
instanceKeeper.getStore {
SpotiFlyerMainStoreProvider(
analyticsManager = analyticsManager,
preferenceManager = preferenceManager,
storeFactory = storeFactory,
database = database,

View File

@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.di.Dir
import com.shabinder.common.di.analytics.AnalyticsManager
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State
@ -36,12 +37,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory,
private val preferenceManager: PreferenceManager,
private val dir: Dir,
database: Database?
) {
internal class SpotiFlyerMainStoreProvider(dependencies: SpotiFlyerMain.Dependencies): SpotiFlyerMain.Dependencies by dependencies {
fun provide(): SpotiFlyerMainStore =
object :
@ -76,7 +72,7 @@ internal class SpotiFlyerMainStoreProvider(
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) {
dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled))
dispatch(Result.AnalyticsToggled(analyticsManager.isTracking()))
updates?.collect {
dispatch(Result.ItemsLoaded(it))
}
@ -91,7 +87,7 @@ internal class SpotiFlyerMainStoreProvider(
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
is Intent.ToggleAnalytics -> {
dispatch(Result.AnalyticsToggled(intent.enabled))
preferenceManager.toggleAnalytics(intent.enabled)
analyticsManager.giveConsent()
}
}
}

View File

@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.di.Dir
import com.shabinder.common.di.analytics.AnalyticsManager
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.Actions
import com.shabinder.common.models.AudioQuality
@ -31,6 +32,7 @@ import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent
internal class SpotiFlyerPreferenceStoreProvider(
private val storeFactory: StoreFactory,
private val analyticsManager: AnalyticsManager,
private val preferenceManager: PreferenceManager,
private val dir: Dir,
private val actions: Actions
@ -55,7 +57,7 @@ internal class SpotiFlyerPreferenceStoreProvider(
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) {
dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled))
dispatch(Result.AnalyticsToggled(analyticsManager.isTracking()))
dispatch(Result.PreferredAudioQualityChanged(preferenceManager.audioQuality))
dispatch(Result.DownloadPathSet(dir.defaultDir()))
}

View File

@ -22,6 +22,8 @@ import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.analytics.AnalyticsEvent
import com.shabinder.common.di.analytics.AnalyticsManager
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain
@ -55,16 +57,9 @@ interface SpotiFlyerRoot {
val fetchQuery: FetchPlatformQueryResult
val dir: Dir
val preferenceManager: PreferenceManager
val analyticsManager: AnalyticsManager
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
val actions: Actions
val analytics: Analytics
}
interface Analytics {
fun appLaunchEvent()
fun homeScreenVisit()
fun listScreenVisit()
fun donationDialogVisit()
}
}

View File

@ -24,9 +24,11 @@ import com.arkivanov.decompose.pop
import com.arkivanov.decompose.popWhile
import com.arkivanov.decompose.push
import com.arkivanov.decompose.router
import com.arkivanov.decompose.statekeeper.Parcelable
import com.arkivanov.decompose.statekeeper.Parcelize
import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize
import com.shabinder.common.di.analytics.AnalyticsEvent
import com.shabinder.common.di.analytics.AnalyticsView
import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain
@ -35,7 +37,6 @@ import com.shabinder.common.models.Consumer
import com.shabinder.common.models.methods
import com.shabinder.common.preference.SpotiFlyerPreference
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
@ -46,30 +47,10 @@ import kotlinx.coroutines.launch
internal class SpotiFlyerRootImpl(
componentContext: ComponentContext,
private val main: (ComponentContext, output: Consumer<SpotiFlyerMain.Output>) -> SpotiFlyerMain,
private val list: (ComponentContext, link: String, output: Consumer<SpotiFlyerList.Output>) -> SpotiFlyerList,
private val preference: (ComponentContext, output: Consumer<SpotiFlyerPreference.Output>) -> SpotiFlyerPreference,
private val actions: Actions,
private val analytics: Analytics
) : SpotiFlyerRoot, ComponentContext by componentContext {
dependencies: Dependencies,
) : SpotiFlyerRoot, ComponentContext by componentContext, Dependencies by dependencies, Actions by dependencies.actions {
constructor(
componentContext: ComponentContext,
dependencies: Dependencies,
) : this(
componentContext = componentContext,
main = { childContext, output ->
spotiFlyerMain(childContext, output, dependencies)
},
list = { childContext, link, output ->
spotiFlyerList(childContext, link, output, dependencies)
},
preference = { childContext, output ->
spotiFlyerPreference(childContext, output, dependencies)
},
actions = dependencies.actions.freeze(),
analytics = dependencies.analytics
) {
init {
instanceKeeper.ensureNeverFrozen()
methods.value = dependencies.actions.freeze()
@ -102,21 +83,85 @@ internal class SpotiFlyerRootImpl(
router.push(Configuration.Preference)
}
override fun showToast(text: String) { toastState.value = text }
override fun showToast(text: String) {
toastState.value = text
}
}
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =
private fun createChild(
configuration: Configuration,
componentContext: ComponentContext,
): Child =
when (configuration) {
is Configuration.Main -> Child.Main(main(componentContext, Consumer(::onMainOutput)))
is Configuration.List -> Child.List(list(componentContext, configuration.link, Consumer(::onListOutput)))
is Configuration.Preference -> Child.Preference(preference(componentContext, Consumer(::onPreferenceOutput)),)
is Configuration.Main -> Child.Main(
spotiFlyerMain(
componentContext,
Consumer(::onMainOutput),
)
)
is Configuration.List -> Child.List(
spotiFlyerList(
componentContext,
configuration.link,
Consumer(::onListOutput),
)
)
is Configuration.Preference -> Child.Preference(
spotiFlyerPreference(
componentContext,
Consumer(::onPreferenceOutput),
)
)
}
private fun spotiFlyerMain(
componentContext: ComponentContext,
output: Consumer<SpotiFlyerMain.Output>,
): SpotiFlyerMain =
SpotiFlyerMain(
componentContext = componentContext,
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by this {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val mainAnalytics = object : SpotiFlyerMain.Analytics {
override fun donationDialogVisit() {
AnalyticsEvent.DonationDialogOpen.track(analyticsManager)
}
}
}
)
private fun spotiFlyerList(
componentContext: ComponentContext,
link: String,
output: Consumer<SpotiFlyerList.Output>
): SpotiFlyerList =
SpotiFlyerList(
componentContext = componentContext,
dependencies = object : SpotiFlyerList.Dependencies, Dependencies by this {
override val link: String = link
override val listOutput: Consumer<SpotiFlyerList.Output> = output
override val listAnalytics = object : SpotiFlyerList.Analytics {}
}
)
private fun spotiFlyerPreference(
componentContext: ComponentContext,
output: Consumer<SpotiFlyerPreference.Output>
): SpotiFlyerPreference =
SpotiFlyerPreference(
componentContext = componentContext,
dependencies = object : SpotiFlyerPreference.Dependencies, Dependencies by this {
override val prefOutput: Consumer<SpotiFlyerPreference.Output> = output
override val preferenceAnalytics = object : SpotiFlyerPreference.Analytics {}
}
)
private fun onMainOutput(output: SpotiFlyerMain.Output) =
when (output) {
is SpotiFlyerMain.Output.Search -> {
router.push(Configuration.List(link = output.link))
analytics.listScreenVisit()
AnalyticsView.ListScreen.track(analyticsManager)
}
}
@ -126,9 +171,10 @@ internal class SpotiFlyerRootImpl(
if (router.state.value.activeChild.instance is Child.List && router.state.value.backStack.isNotEmpty()) {
router.pop()
}
analytics.homeScreenVisit()
AnalyticsView.HomeScreen.track(analyticsManager)
}
}
private fun onPreferenceOutput(output: SpotiFlyerPreference.Output): Unit =
when (output) {
is SpotiFlyerPreference.Output.Finished -> {
@ -142,7 +188,7 @@ internal class SpotiFlyerRootImpl(
@OptIn(DelicateCoroutinesApi::class)
private fun initAppLaunchAndAuthenticateSpotify(authenticator: suspend () -> Unit) {
GlobalScope.launch(dispatcherIO) {
analytics.appLaunchEvent()
AnalyticsEvent.AppLaunch.track(analyticsManager)
/*Authenticate Spotify Client*/
authenticator()
}
@ -159,31 +205,3 @@ internal class SpotiFlyerRootImpl(
data class List(val link: String) : Configuration()
}
}
private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<SpotiFlyerMain.Output>, dependencies: Dependencies): SpotiFlyerMain =
SpotiFlyerMain(
componentContext = componentContext,
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val mainAnalytics = object : SpotiFlyerMain.Analytics, Analytics by analytics {}
}
)
private fun spotiFlyerList(componentContext: ComponentContext, link: String, output: Consumer<SpotiFlyerList.Output>, dependencies: Dependencies): SpotiFlyerList =
SpotiFlyerList(
componentContext = componentContext,
dependencies = object : SpotiFlyerList.Dependencies, Dependencies by dependencies {
override val link: String = link
override val listOutput: Consumer<SpotiFlyerList.Output> = output
override val listAnalytics = object : SpotiFlyerList.Analytics, Analytics by analytics {}
}
)
private fun spotiFlyerPreference(componentContext: ComponentContext, output: Consumer<SpotiFlyerPreference.Output>, dependencies: Dependencies): SpotiFlyerPreference =
SpotiFlyerPreference(
componentContext = componentContext,
dependencies = object : SpotiFlyerPreference.Dependencies, Dependencies by dependencies {
override val prefOutput: Consumer<SpotiFlyerPreference.Output> = output
override val preferenceAnalytics = object : SpotiFlyerPreference.Analytics, Analytics by analytics {}
}
)

View File

@ -30,6 +30,7 @@ import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.di.Dir
import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.analytics.AnalyticsManager
import com.shabinder.common.di.initKoin
import com.shabinder.common.di.isInternetAccessible
import com.shabinder.common.di.preference.PreferenceManager
@ -58,9 +59,6 @@ import javax.swing.JFileChooser.APPROVE_OPTION
private val koin = initKoin(enableNetworkLogs = true).koin
private lateinit var showToast: (String) -> Unit
private val tracker: PiwikTracker by lazy {
PiwikTracker("https://matomo.spotiflyer.ml/matomo.php")
}
fun main() {
@ -96,6 +94,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override val dir: Dir = koin.get()
override val database: Database? = dir.db
override val preferenceManager: PreferenceManager = koin.get()
override val analyticsManager: AnalyticsManager = koin.get()
override val downloadProgressFlow = DownloadProgressFlow
override val actions: Actions = object : Actions {
override val platformActions = object : PlatformActions {}
@ -159,14 +158,9 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
override fun appLaunchEvent() {
if (preferenceManager.isFirstLaunch) {
// Enable Analytics on First Launch
preferenceManager.toggleAnalytics(true)
analyticsManager.giveConsent()
preferenceManager.firstLaunchDone()
}
tracker.trackAsync {
eventName = "App Launch"
eventAction = "App_Launch"
eventCategory = "events"
}
}
override fun homeScreenVisit() {