Actions and Code Refactoring

This commit is contained in:
shabinder 2021-09-25 01:03:46 +05:30
parent 6fb1815252
commit e4c22c5d0c
58 changed files with 602 additions and 539 deletions

View File

@ -31,7 +31,7 @@ jobs:
uses: ncipollo/release-action@v1
with:
draft: true
tag: "SpotiFlyer-v3.3.0"
tag: "v3.3.0"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*"
token: ${{ secrets.GH_TOKEN }}
commit: main
@ -65,7 +65,7 @@ jobs:
uses: ncipollo/release-action@v1
with:
draft: true
tag: "SpotiFlyer-v3.3.0"
tag: "v3.3.0"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*"
token: ${{ secrets.GH_TOKEN }}
commit: main
@ -98,7 +98,7 @@ jobs:
uses: ncipollo/release-action@v1
with:
draft: true
tag: "SpotiFlyer-v3.3.0"
tag: "v3.3.0"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*"
token: ${{ secrets.GH_TOKEN }}
commit: main

View File

@ -36,7 +36,7 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
tools:ignore="ProtectedPermissions,ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@ -58,16 +58,22 @@
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="q">
<activity android:name=".MainActivity"
android:launchMode="singleTask"
android:label="SpotiFlyer"
<activity
android:name=".ui.SplashScreenActivity"
android:theme="@style/SplashTheme"
android:hardwareAccelerated="true"
android:theme="@style/Theme.SpotiFlyer"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"
android:launchMode="singleTask"
android:hardwareAccelerated="true"
android:theme="@style/Theme.SpotiFlyer"
>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.SEND" />
@ -76,13 +82,15 @@
</activity>
<service android:name=".service.ForegroundService"/>
<service android:name="org.openudid.OpenUDID_service">
<service android:name="org.openudid.OpenUDID_service"
tools:ignore="ExportedService,IntentFilterExportedReceiver">
<intent-filter>
<action android:name="org.openudid.GETUDID" />
</intent-filter>
</service>
<receiver android:name="ly.count.android.sdk.ReferrerReceiver" android:exported="true">
<receiver android:name="ly.count.android.sdk.ReferrerReceiver" android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>

View File

@ -55,6 +55,7 @@ import com.shabinder.common.core_components.ConnectionLiveData
import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.preference_manager.PreferenceManager
import com.shabinder.common.di.ApplicationInit
import com.shabinder.common.di.observeAsState
import com.shabinder.common.models.*
import com.shabinder.common.models.PlatformActions.Companion.SharedPreferencesKey
@ -79,13 +80,14 @@ import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import java.io.File
@ExperimentalAnimationApi
@OptIn(ExperimentalAnimationApi::class)
class MainActivity : ComponentActivity() {
private val fetcher: FetchPlatformQueryResult by inject()
private val fileManager: FileManager by inject()
private val preferenceManager: PreferenceManager by inject()
private val analyticsManager: AnalyticsManager by inject { parametersOf(this) }
private val applicationInit: ApplicationInit by inject()
private val callBacks: SpotiFlyerRootCallBacks get() = this.rootComponent.callBacks
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
private var permissionGranted = mutableStateOf(true)
@ -263,6 +265,7 @@ class MainActivity : ComponentActivity() {
override val analyticsManager: AnalyticsManager = this@MainActivity.analyticsManager
override val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> =
trackStatusFlow
override val appInit: ApplicationInit = applicationInit
override val actions = object : Actions {
override val platformActions = object : PlatformActions {
@ -431,7 +434,7 @@ class MainActivity : ComponentActivity() {
while (!this@MainActivity::rootComponent.isInitialized) {
delay(100)
}
if (methods.value.isInternetAvailable) callBacks.searchLink(link)
if (Actions.instance.isInternetAvailable) callBacks.searchLink(link)
}
}
}

View File

@ -0,0 +1,34 @@
package com.shabinder.spotiflyer.ui
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.lifecycle.lifecycleScope
import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.di.ApplicationInit
import com.shabinder.spotiflyer.MainActivity
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.androidx.scope.ScopeActivity
import org.koin.core.parameter.parametersOf
class SplashScreenActivity : AppCompatActivity() {
private val applicationInit: ApplicationInit by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
applicationInit.init()
lifecycleScope.launch {
delay(SPLASH_DELAY)
startActivity(Intent(this@SplashScreenActivity, MainActivity::class.java))
finish()
}
}
companion object {
private const val SPLASH_DELAY = 2000L
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,75 +0,0 @@
<!--
~ Copyright (c) 2021 Shabinder Singh
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.10546875"
android:scaleY="0.10546875"
android:translateX="27"
android:translateY="27">
<path
android:pathData="M256,256m-256,0a256,256 0,1 1,512 0a256,256 0,1 1,-512 0">
<aapt:attr name="android:fillColor">
<gradient
android:startY="437.0193"
android:startX="74.9807"
android:endY="74.9807"
android:endX="437.0193"
android:type="linear">
<item android:offset="0" android:color="#FF736BFD"/>
<item android:offset="1" android:color="#FFF54187"/>
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FF000000"
android:pathData="M377,356.7c-68.9,-45.4 -155.6,-56.4 -257.6,-32.7c-20.5,4.8 -13.6,35.8 7.3,31.2C290.7,317 351.6,386 368.2,386C384,386 390.2,365.4 377,356.7z"/>
<path
android:fillColor="#FF000000"
android:pathData="M112.1,275.1C203.9,253.4 308.1,266 384,308c18.5,10.2 34,-17.8 15.5,-28c-82.7,-45.7 -195.6,-59.5 -294.7,-36C84.2,248.8 91.5,280 112.1,275.1L112.1,275.1z"/>
<path
android:fillColor="#FF000000"
android:pathData="M100,191.9c96.6,-29.6 232.2,-13.4 308.7,36.9c17.6,11.5 35.3,-15.1 17.6,-26.7c-84.9,-55.8 -229.2,-73.3 -335.6,-40.8C70.4,167.5 79.9,198.1 100,191.9L100,191.9z"/>
<path
android:pathData="M507.8,438.2c-1.6,97.2 -141.9,97.1 -143.5,0C365.9,341 506.2,341 507.8,438.2z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="386.3741"
android:startX="487.8323"
android:endY="490.009"
android:endX="384.1974"
android:type="linear">
<item android:offset="0" android:color="#FF736BFD"/>
<item android:offset="1" android:color="#FFF54187"/>
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FF000000"
android:pathData="M486.8,456.8c-0.6,-2.4 -6.9,-1 -8.5,-1.4c11.5,-82 -82.4,-86.7 -87.1,-22.2c0.3,1.8 -1,6.7 2.2,6.6c0,0 8.6,0 8.6,0c3.1,0.1 2,-4.7 2.2,-6.6c0.1,-23.3 35,-23.3 35.2,0c0,0 0,6.9 0,6.9c-0.1,2.8 4.4,2.8 4.3,0c5,-35.2 -43.8,-40.1 -43.8,-4.7h-4.3c-1.6,-53.7 77.2,-55.9 78.4,-2.2c0,0 0,24.4 0,24.4c-0.1,2.9 3.8,2.1 5.6,2.2l-20.7,21l-20.7,-21c1.8,-0.1 5.6,0.7 5.6,-2.2c0,0 0,-8.8 0,-8.8c0,-2.8 -4.4,-2.8 -4.3,0c0,0 0,6.6 0,6.6c-2.2,0.2 -11.3,-1.3 -8,3.7c0,0 25.9,26.3 25.9,26.3c0.8,0.9 2.2,0.9 3.1,0C460.6,484.4 489.4,458.3 486.8,456.8z"
android:strokeWidth="0.75"
android:strokeColor="#000000"/>
<path
android:pathData="M510,437.5c-1.7,96.2 -142.1,96.2 -143.8,0C367.9,341.3 508.4,341.3 510,437.5z"
android:strokeWidth="6"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
</group>
</vector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingBottom="20dp"
android:paddingRight="20dp"
android:paddingLeft="20dp"
android:paddingTop="20dp">
<!-- Your product logo - 144dp color version of your app icon -->
<item
android:drawable="@drawable/spotiflyer"
android:gravity="center">
</item>
</layer-list>

View File

@ -17,5 +17,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<foreground android:drawable="@drawable/ic_spotiflyer_logo"/>
</adaptive-icon>

View File

@ -17,5 +17,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<foreground android:drawable="@drawable/ic_spotiflyer_logo"/>
</adaptive-icon>

View File

@ -22,8 +22,12 @@
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">@color/white</item>
<item name="android:background">@android:color/black</item>
<item name="android:backgroundTint">@android:color/black</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="SplashTheme" parent="Theme.SpotiFlyer">
<item name="android:windowBackground">@drawable/ic_splash</item>
<item name="android:background">@android:color/transparent</item>
</style>
</resources>

View File

@ -35,10 +35,16 @@ allprojects {
download = false
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().configureEach {
dependsOn(":common:data-models:generateI18n4kFiles")
kotlinOptions { jvmTarget = "1.8" }
kotlinOptions {
if(this is org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions) {
jvmTarget = "1.8"
}
freeCompilerArgs = (freeCompilerArgs + listOf("-Xopt-in=kotlin.RequiresOptIn"))
}
}
afterEvaluate {
project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt ->
kmpExt.sourceSets.run {

View File

@ -19,6 +19,7 @@
plugins {
id("com.android.library")
id("ktlint-setup")
id("compiler-args")
}
android {

View File

@ -0,0 +1,13 @@
plugins {
kotlin("multiplatform")
}
kotlin {
sourceSets {
all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.useExperimentalAnnotation("androidx.compose.animation")
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
}
}
}

View File

@ -20,6 +20,7 @@ plugins {
id("org.jetbrains.compose")
id("kotlin-parcelize")
id("ktlint-setup")
id("compiler-args")
}
kotlin {

View File

@ -17,6 +17,7 @@
plugins {
id("com.android.library")
id("kotlin-multiplatform")
id("compiler-args")
}
kotlin {

View File

@ -20,6 +20,7 @@ plugins {
id("org.jetbrains.compose")
id("ktlint-setup")
id("kotlin-parcelize")
id("compiler-args")
}
kotlin {

View File

@ -23,9 +23,6 @@ plugins {
kotlin {
sourceSets {
all {
languageSettings.useExperimentalAnnotation("androidx.compose.animation")
}
commonMain {
dependencies {
implementation(compose.material)

View File

@ -26,7 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.Dialog
import com.shabinder.common.uikit.OpenCollectiveLogo
@ -87,7 +87,7 @@ fun DonationDialog(
modifier = Modifier.fillMaxWidth().clickable(
onClick = {
onDismiss()
methods.value.openPlatform("", "https://opencollective.com/spotiflyer/donate")
Actions.instance.openPlatform("", "https://opencollective.com/spotiflyer/donate")
}
)
.padding(vertical = 6.dp)
@ -110,7 +110,7 @@ fun DonationDialog(
modifier = Modifier.fillMaxWidth().clickable(
onClick = {
onDismiss()
methods.value.openPlatform("", "https://www.paypal.com/paypalme/shabinder")
Actions.instance.openPlatform("", "https://www.paypal.com/paypalme/shabinder")
}
)
.padding(vertical = 6.dp)
@ -133,7 +133,7 @@ fun DonationDialog(
.clickable(
onClick = {
onDismiss()
methods.value.giveDonation()
Actions.instance.giveDonation()
}
),
verticalAlignment = Alignment.CenterVertically

View File

@ -25,7 +25,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.Dialog
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
@ -67,7 +67,7 @@ fun ErrorInfoDialog(error: Throwable): ErrorInfoDialogCallBacks {
TextButton(onClick = onDismissDialog, colors = ButtonDefaults.buttonColors()) {
Text(Strings.dismiss())
}
TextButton(onClick = { methods.value.copyToClipboard(error.stackTraceToString()) }, colors = ButtonDefaults.buttonColors()) {
TextButton(onClick = { Actions.instance.copyToClipboard(error.stackTraceToString()) }, colors = ButtonDefaults.buttonColors()) {
Text(Strings.copyToClipboard())
}
}

View File

@ -57,7 +57,7 @@ import com.shabinder.common.core_components.picture.Picture
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.DownloadAllImage
import com.shabinder.common.uikit.DownloadImageArrow
@ -84,7 +84,7 @@ fun SpotiFlyerListContent(
LaunchedEffect(model.errorOccurred) {
/*Handle if Any Exception Occurred*/
model.errorOccurred?.let {
methods.value.showPopUpMessage(it.message ?: Strings.errorOccurred())
Actions.instance.showPopUpMessage(it.message ?: Strings.errorOccurred())
component.onBackPressed()
}
}

View File

@ -82,7 +82,7 @@ import com.shabinder.common.core_components.picture.Picture
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.translations.Strings
import com.shabinder.common.uikit.GaanaLogo
import com.shabinder.common.uikit.GithubLogo
@ -229,7 +229,7 @@ fun SearchPanel(
OutlinedButton(
modifier = Modifier.padding(12.dp).wrapContentWidth(),
onClick = {
if (link.isBlank()) methods.value.showPopUpMessage(Strings.enterALink())
if (link.isBlank()) Actions.instance.showPopUpMessage(Strings.enterALink())
else {
// TODO if(!isOnline(ctx)) showPopUpMessage("Check Your Internet Connection") else
onSearch(link)
@ -279,7 +279,7 @@ fun AboutColumn(
"${Strings.open()} Spotify",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.spotify.music", "https://open.spotify.com") }
onClick = { Actions.instance.openPlatform("com.spotify.music", "https://open.spotify.com") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -288,7 +288,7 @@ fun AboutColumn(
"${Strings.open()} Gaana",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.gaana", "https://www.gaana.com") }
onClick = { Actions.instance.openPlatform("com.gaana", "https://www.gaana.com") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -297,7 +297,7 @@ fun AboutColumn(
"${Strings.open()} Jio Saavn",
tint = Color.Unspecified,
modifier = Modifier.clickable(
onClick = { methods.value.openPlatform("com.jio.media.jiobeats", "https://www.jiosaavn.com/") }
onClick = { Actions.instance.openPlatform("com.jio.media.jiobeats", "https://www.jiosaavn.com/") }
)
)
Spacer(modifier = modifier.padding(start = 16.dp))
@ -306,7 +306,7 @@ fun AboutColumn(
"${Strings.open()} Youtube",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.google.android.youtube", "https://m.youtube.com") }
onClick = { Actions.instance.openPlatform("com.google.android.youtube", "https://m.youtube.com") }
)
)
Spacer(modifier = modifier.padding(start = 12.dp))
@ -315,7 +315,7 @@ fun AboutColumn(
"${Strings.open()} Youtube Music",
tint = Color.Unspecified,
modifier = Modifier.clip(SpotiFlyerShapes.small).clickable(
onClick = { methods.value.openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
onClick = { Actions.instance.openPlatform("com.google.android.apps.youtube.music", "https://music.youtube.com/") }
)
)
}
@ -336,7 +336,7 @@ fun AboutColumn(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(
onClick = { methods.value.openPlatform("", "https://github.com/Shabinder/SpotiFlyer") }
onClick = { Actions.instance.openPlatform("", "https://github.com/Shabinder/SpotiFlyer") }
)
.padding(vertical = 6.dp)
) {
@ -355,7 +355,7 @@ fun AboutColumn(
}
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(onClick = { methods.value.openPlatform("", "https://github.com/Shabinder/SpotiFlyer/blob/main/CONTRIBUTING.md") }),
.clickable(onClick = { Actions.instance.openPlatform("", "https://github.com/Shabinder/SpotiFlyer/blob/main/CONTRIBUTING.md") }),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Rounded.Flag, Strings.help() + Strings.translate(), Modifier.size(32.dp))
@ -395,7 +395,7 @@ fun AboutColumn(
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
.clickable(
onClick = {
methods.value.shareApp()
Actions.instance.shareApp()
}
),
verticalAlignment = Alignment.CenterVertically

View File

@ -29,14 +29,7 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@ -52,6 +45,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.jetbrains.Children
@ -179,7 +173,8 @@ fun AppBar(
Image(
SpotiFlyerLogo(),
Strings.spotiflyerLogo(),
Modifier.size(32.dp),
Modifier.requiredHeight(66.dp).requiredWidth(42.dp),
contentScale = ContentScale.FillHeight
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(

View File

@ -17,13 +17,7 @@
package com.shabinder.common.uikit.screens.splash
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -56,7 +50,7 @@ fun Splash(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
delay(SplashWaitTime)
currentOnTimeout()
}
Image(SpotiFlyerLogo(), Strings.spotiflyerLogo())
Image(SpotiFlyerLogo(), Strings.spotiflyerLogo(),modifier = Modifier.fillMaxSize())
MadeInIndia(Modifier.align(Alignment.BottomCenter))
}
}

View File

@ -10,9 +10,17 @@ import org.koin.dsl.module
internal class AndroidAnalyticsManager(private val mainActivity: Activity) : AnalyticsManager {
companion object {
private var isInitialised = false
}
init {
// Don't Init If Instantiated on Diff Activities
if (!isInitialised) {
isInitialised = true
init()
}
}
override fun init() {
Countly.sharedInstance().init(
@ -45,7 +53,8 @@ internal class AndroidAnalyticsManager(private val mainActivity: Activity) : Ana
Countly.sharedInstance().consent().giveConsentAll()
}
override fun isTracking(): Boolean = Countly.sharedInstance().consent().getConsent(Countly.CountlyFeatureNames.events)
override fun isTracking(): Boolean =
Countly.sharedInstance().consent().getConsent(Countly.CountlyFeatureNames.events)
override fun revokeConsent() {
Countly.sharedInstance().consent().removeConsentAll()

View File

@ -35,9 +35,8 @@ import com.shabinder.common.di.getMemoryEfficientBitmap
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.models.event.coroutines.map
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -70,7 +69,7 @@ class AndroidFileManager(
override fun fileSeparator(): String = File.separator
override fun imageCacheDir(): String = methods.value.platformActions.imageCacheDir
override fun imageCacheDir(): String = Actions.instance.platformActions.imageCacheDir
// fun call in order to always access Updated Value
override fun defaultDir(): String = (preferenceManager.downloadDir ?: defaultBaseDir) +
@ -159,7 +158,7 @@ class AndroidFileManager(
}
}
override fun addToLibrary(path: String) = methods.value.platformActions.addToLibrary(path)
override fun addToLibrary(path: String) = Actions.instance.platformActions.addToLibrary(path)
override suspend fun loadImage(url: String, reqWidth: Int, reqHeight: Int): Picture =
withContext(dispatcherIO) {

View File

@ -36,7 +36,7 @@ import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.failure
import com.shabinder.common.models.event.coroutines.map
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.database.Database
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
@ -165,7 +165,7 @@ class DesktopFileManager(
}
SuspendableEvent.success(trackDetails.outputFilePath)
} catch (e: Throwable) {
if(e is JaffreeException) methods.value.showPopUpMessage("No FFmpeg found at path.")
if(e is JaffreeException) Actions.instance.showPopUpMessage("No FFmpeg found at path.")
if (songFile.exists()) songFile.delete()
logger.e { "${songFile.absolutePath} could not be created" }
SuspendableEvent.error(e)

View File

@ -17,7 +17,7 @@ actual interface PlatformActions {
fun sendTracksToService(array: List<TrackDetails>)
}
actual val StubPlatformActions = object : PlatformActions {
internal actual val StubPlatformActions = object : PlatformActions {
override val imageCacheDir = ""
override val sharedPreferences: SharedPreferences? = null

View File

@ -1,11 +1,7 @@
package com.shabinder.common.models
import co.touchlab.stately.freeze
/*
* Holder to call platform actions from anywhere
* */
val methods: NativeAtomicReference<Actions> = NativeAtomicReference(stubActions().freeze())
import kotlin.jvm.JvmStatic
/*
* Interface Having All Platform Dependent Functions
@ -42,6 +38,20 @@ interface Actions {
// Open / Redirect to another Platform
fun openPlatform(packageID: String, platformLink: String)
fun writeMp3Tags(trackDetails: TrackDetails)
companion object {
/*
* Holder to call platform actions from anywhere
* */
@JvmStatic
var instance: Actions
get() = methodsAtomicRef.value
set(value) {
methodsAtomicRef.value = value
}
private val methodsAtomicRef = NativeAtomicReference(stubActions().freeze())
}
}
private fun stubActions(): Actions = object : Actions {

View File

@ -2,4 +2,4 @@ package com.shabinder.common.models
expect interface PlatformActions
expect val StubPlatformActions: PlatformActions
internal expect val StubPlatformActions: PlatformActions

View File

@ -2,4 +2,4 @@ package com.shabinder.common.models
actual interface PlatformActions
actual val StubPlatformActions = object : PlatformActions {}
internal actual val StubPlatformActions = object : PlatformActions {}

View File

@ -2,4 +2,4 @@ package com.shabinder.common.models
actual interface PlatformActions
actual val StubPlatformActions = object : PlatformActions {}
internal actual val StubPlatformActions = object : PlatformActions {}

View File

@ -14,34 +14,63 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector android:height="150dp" android:viewportHeight="512"
android:viewportWidth="512" android:width="150dp"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:pathData="M256,256m-256,0a256,256 0,1 1,512 0a256,256 0,1 1,-512 0">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:gravity="center"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.10546875"
android:scaleY="0.10546875"
android:translateX="27"
android:translateY="27">
<path
android:pathData="M256,256m-256,0a256,256 0,1 1,512 0a256,256 0,1 1,-512 0">
<aapt:attr name="android:fillColor">
<gradient android:endX="437.019" android:endY="74.981"
android:startX="74.981" android:startY="437.019" android:type="linear">
<item android:color="#FF736BFD" android:offset="0"/>
<item android:color="#FFF54187" android:offset="1"/>
<gradient
android:startY="437.0193"
android:startX="74.9807"
android:endY="74.9807"
android:endX="437.0193"
android:type="linear">
<item android:offset="0" android:color="#FF736BFD"/>
<item android:offset="1" android:color="#FFF54187"/>
</gradient>
</aapt:attr>
</path>
<path android:fillColor="#FF000000" android:pathData="M377,356.7c-68.9,-45.4 -155.6,-56.4 -257.6,-32.7c-20.5,4.8 -13.6,35.8 7.3,31.2C290.7,317 351.6,386 368.2,386C384,386 390.2,365.4 377,356.7z"/>
<path android:fillColor="#FF000000" android:pathData="M112.1,275.1C203.9,253.4 308.1,266 384,308c18.5,10.2 34,-17.8 15.5,-28c-82.7,-45.7 -195.6,-59.5 -294.7,-36C84.2,248.8 91.5,280 112.1,275.1L112.1,275.1z"/>
<path android:fillColor="#FF000000" android:pathData="M100,191.9c96.6,-29.6 232.2,-13.4 308.7,36.9c17.6,11.5 35.3,-15.1 17.6,-26.7c-84.9,-55.8 -229.2,-73.3 -335.6,-40.8C70.4,167.5 79.9,198.1 100,191.9L100,191.9z"/>
<path android:pathData="M507.8,438.2c-1.6,97.2 -141.9,97.1 -143.5,0C365.9,341 506.2,341 507.8,438.2z">
<path
android:fillColor="#FF000000"
android:pathData="M377,356.7c-68.9,-45.4 -155.6,-56.4 -257.6,-32.7c-20.5,4.8 -13.6,35.8 7.3,31.2C290.7,317 351.6,386 368.2,386C384,386 390.2,365.4 377,356.7z"/>
<path
android:fillColor="#FF000000"
android:pathData="M112.1,275.1C203.9,253.4 308.1,266 384,308c18.5,10.2 34,-17.8 15.5,-28c-82.7,-45.7 -195.6,-59.5 -294.7,-36C84.2,248.8 91.5,280 112.1,275.1L112.1,275.1z"/>
<path
android:fillColor="#FF000000"
android:pathData="M100,191.9c96.6,-29.6 232.2,-13.4 308.7,36.9c17.6,11.5 35.3,-15.1 17.6,-26.7c-84.9,-55.8 -229.2,-73.3 -335.6,-40.8C70.4,167.5 79.9,198.1 100,191.9L100,191.9z"/>
<path
android:pathData="M507.8,438.2c-1.6,97.2 -141.9,97.1 -143.5,0C365.9,341 506.2,341 507.8,438.2z">
<aapt:attr name="android:fillColor">
<gradient android:endX="384.197" android:endY="490.009"
android:startX="487.832" android:startY="386.374" android:type="linear">
<item android:color="#FF736BFD" android:offset="0"/>
<item android:color="#FFF54187" android:offset="1"/>
<gradient
android:startY="386.3741"
android:startX="487.8323"
android:endY="490.009"
android:endX="384.1974"
android:type="linear">
<item android:offset="0" android:color="#FF736BFD"/>
<item android:offset="1" android:color="#FFF54187"/>
</gradient>
</aapt:attr>
</path>
<path android:fillColor="#FF000000"
<path
android:fillColor="#FF000000"
android:pathData="M486.8,456.8c-0.6,-2.4 -6.9,-1 -8.5,-1.4c11.5,-82 -82.4,-86.7 -87.1,-22.2c0.3,1.8 -1,6.7 2.2,6.6c0,0 8.6,0 8.6,0c3.1,0.1 2,-4.7 2.2,-6.6c0.1,-23.3 35,-23.3 35.2,0c0,0 0,6.9 0,6.9c-0.1,2.8 4.4,2.8 4.3,0c5,-35.2 -43.8,-40.1 -43.8,-4.7h-4.3c-1.6,-53.7 77.2,-55.9 78.4,-2.2c0,0 0,24.4 0,24.4c-0.1,2.9 3.8,2.1 5.6,2.2l-20.7,21l-20.7,-21c1.8,-0.1 5.6,0.7 5.6,-2.2c0,0 0,-8.8 0,-8.8c0,-2.8 -4.4,-2.8 -4.3,0c0,0 0,6.6 0,6.6c-2.2,0.2 -11.3,-1.3 -8,3.7c0,0 25.9,26.3 25.9,26.3c0.8,0.9 2.2,0.9 3.1,0C460.6,484.4 489.4,458.3 486.8,456.8z"
android:strokeColor="#000" android:strokeWidth=".75"/>
<path android:fillColor="#00000000"
android:strokeWidth="0.75"
android:strokeColor="#000000"/>
<path
android:pathData="M510,437.5c-1.7,96.2 -142.1,96.2 -143.8,0C367.9,341.3 508.4,341.3 510,437.5z"
android:strokeColor="#000" android:strokeWidth="6"/>
android:strokeWidth="6"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
</group>
</vector>

View File

@ -0,0 +1,36 @@
package com.shabinder.common.di
import com.shabinder.common.core_components.analytics.AnalyticsEvent
import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.providers.spotify.SpotifyProvider
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.dsl.module
class ApplicationInit(
private val spotifyProvider: SpotifyProvider,
) {
companion object {
private var isFirstLaunch = true
}
/*
* Init Basic Necessary Items in here,
* will be called,
* Android / IOS: Splash Screen
* Desktop: App Startup
* */
@OptIn(DelicateCoroutinesApi::class)
fun init() = GlobalScope.launch(dispatcherIO) {
isFirstLaunch = false
spotifyProvider.authenticateSpotifyClient()
}
}
internal fun appInitModule() = module {
single {
ApplicationInit(get())
}
}

View File

@ -33,6 +33,7 @@ fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclarat
listOf(
providersModule(enableNetworkLogs),
databaseModule(),
appInitModule(),
)
)
}

View File

@ -23,10 +23,7 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
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
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.*
import com.shabinder.common.providers.downloadTracks
import kotlinx.coroutines.flow.collect
@ -112,14 +109,14 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
)
val finalList = intent.trackList.filter { it.downloaded == DownloadStatus.NotDownloaded }
if (finalList.isEmpty()) methods.value.showPopUpMessage("All Songs are Processed")
if (finalList.isEmpty()) Actions.instance.showPopUpMessage("All Songs are Processed")
else downloadTracks(finalList, fetchQuery, fileManager)
}
is Intent.StartDownload -> {
dispatch(Result.UpdateTrackItem(intent.track.copy(downloaded = DownloadStatus.Queued)))
downloadTracks(listOf(intent.track), fetchQuery, fileManager)
}
is Intent.RefreshTracksStatuses -> methods.value.queryActiveTracks()
is Intent.RefreshTracksStatuses -> Actions.instance.queryActiveTracks()
}
}
}
@ -161,6 +158,7 @@ internal class SpotiFlyerListStoreProvider(dependencies: SpotiFlyerList.Dependen
}
}
}
return updatedList
}
}

View File

@ -28,7 +28,7 @@ import com.shabinder.common.main.SpotiFlyerMain.*
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
internal class SpotiFlyerMainImpl(
componentContext: ComponentContext,
@ -57,8 +57,8 @@ internal class SpotiFlyerMainImpl(
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")
if (Actions.instance.isInternetAvailable) mainOutput.callback(Output.Search(link = link))
else Actions.instance.showPopUpMessage("Check Network Connection Please")
}
override fun onInputLinkChanged(link: String) {

View File

@ -24,7 +24,7 @@ import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.models.DownloadRecord
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.Dispatchers
@ -75,9 +75,9 @@ internal class SpotiFlyerMainStoreProvider(dependencies: SpotiFlyerMain.Dependen
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {
is Intent.OpenPlatform -> methods.value.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> methods.value.giveDonation()
is Intent.ShareApp -> methods.value.shareApp()
is Intent.OpenPlatform -> Actions.instance.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> Actions.instance.giveDonation()
is Intent.ShareApp -> Actions.instance.shareApp()
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
is Intent.ToggleAnalytics -> {

View File

@ -21,7 +21,7 @@ import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.preference.SpotiFlyerPreference
import com.shabinder.common.preference.SpotiFlyerPreference.State
import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent
@ -56,9 +56,9 @@ internal class SpotiFlyerPreferenceStoreProvider(
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {
is Intent.OpenPlatform -> methods.value.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> methods.value.giveDonation()
is Intent.ShareApp -> methods.value.shareApp()
is Intent.OpenPlatform -> Actions.instance.openPlatform(intent.platformID, intent.platformLink)
is Intent.GiveDonation -> Actions.instance.giveDonation()
is Intent.ShareApp -> Actions.instance.shareApp()
is Intent.ToggleAnalytics -> {
dispatch(Result.AnalyticsToggled(intent.enabled))
preferenceManager.toggleAnalytics(intent.enabled)

View File

@ -18,7 +18,7 @@ package com.shabinder.common.providers
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
actual suspend fun downloadTracks(
@ -27,6 +27,6 @@ actual suspend fun downloadTracks(
fileManager: FileManager
) {
if (list.isNotEmpty()) {
methods.value.platformActions.sendTracksToService(ArrayList(list))
Actions.instance.platformActions.sendTracksToService(ArrayList(list))
}
}

View File

@ -18,7 +18,7 @@ package com.shabinder.common.providers.spotify.requests
import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.models.spotify.TokenData
import com.shabinder.common.utils.globalJson
import io.ktor.client.*
@ -32,9 +32,12 @@ import io.ktor.http.*
import kotlin.native.concurrent.SharedImmutable
suspend fun authenticateSpotify(): SuspendableEvent<TokenData, Throwable> = SuspendableEvent {
if (methods.value.isInternetAvailable) {
if (Actions.instance.isInternetAvailable) {
spotifyAuthClient.post("https://accounts.spotify.com/api/token") {
body = FormDataContent(Parameters.build { append("grant_type", "client_credentials") })
body = FormDataContent(Parameters.build {
@Suppress("EXPERIMENTAL_API_USAGE_FUTURE_ERROR")
append("grant_type", "client_credentials")
})
}
} else throw SpotiFlyerException.NoInternetException()
}

View File

@ -5,7 +5,7 @@ import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
@ -48,7 +48,7 @@ actual suspend fun downloadTracks(
}
}
is DownloadResult.Success -> { // Todo clear map
dir.saveFileWithMetadata(it.byteArray, track, methods.value::writeMp3Tags)
dir.saveFileWithMetadata(it.byteArray, track, Actions.instance::writeMp3Tags)
DownloadProgressFlow.replayCache.getOrElse(
0
) { hashMapOf() }.toMutableMap().apply {

View File

@ -23,6 +23,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.preference_manager.PreferenceManager
import com.shabinder.common.di.ApplicationInit
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions
@ -51,6 +52,7 @@ interface SpotiFlyerRoot {
}
interface Dependencies {
val appInit: ApplicationInit
val storeFactory: StoreFactory
val database: Database?
val fetchQuery: FetchPlatformQueryResult

View File

@ -26,19 +26,14 @@ import com.shabinder.common.core_components.analytics.AnalyticsEvent
import com.shabinder.common.core_components.analytics.AnalyticsView
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions
import com.shabinder.common.models.Consumer
import com.shabinder.common.models.dispatcherIO
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.preference.SpotiFlyerPreference
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
internal class SpotiFlyerRootImpl(
componentContext: ComponentContext,
@ -47,11 +42,10 @@ internal class SpotiFlyerRootImpl(
Actions by dependencies.actions {
init {
AnalyticsEvent.AppLaunch.track(analyticsManager)
instanceKeeper.ensureNeverFrozen()
methods.value = dependencies.actions.freeze()
/*Init App Launch & Authenticate Spotify Client*/
initAppLaunchAndAuthenticateSpotify(dependencies.fetchQuery::authenticateSpotifyClient)
Actions.instance = dependencies.actions.freeze()
appInit.init()
}
private val router =
@ -183,15 +177,6 @@ internal class SpotiFlyerRootImpl(
}
}
@OptIn(DelicateCoroutinesApi::class)
private fun initAppLaunchAndAuthenticateSpotify(authenticator: suspend () -> Unit) {
GlobalScope.launch(dispatcherIO) {
AnalyticsEvent.AppLaunch.track(analyticsManager)
/*Authenticate Spotify Client*/
authenticator()
}
}
private sealed class Configuration : Parcelable {
@Parcelize
object Main : Configuration()

View File

@ -38,10 +38,9 @@ import com.shabinder.common.core_components.file_manager.DownloadProgressFlow
import com.shabinder.common.core_components.file_manager.FileManager
import com.shabinder.common.core_components.preference_manager.PreferenceManager
import com.shabinder.common.core_components.utils.isInternetAccessible
import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods
import com.shabinder.common.models.Actions
import com.shabinder.common.providers.FetchPlatformQueryResult
import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.translations.Strings
@ -98,7 +97,7 @@ fun main() {
try {
FFmpeg.atPath().addArgument("-version").execute()
} catch (e: Exception) {
if (e is JaffreeException) methods.value.showPopUpMessage("WARNING!\nFFmpeg not found at path")
if (e is JaffreeException) Actions.instance.showPopUpMessage("WARNING!\nFFmpeg not found at path")
}
}
}
@ -113,6 +112,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
SpotiFlyerRoot(
componentContext = componentContext,
dependencies = object : SpotiFlyerRoot.Dependencies {
override val appInit: ApplicationInit = koin.get()
override val storeFactory = DefaultStoreFactory
override val fetchQuery: FetchPlatformQueryResult = koin.get()
override val fileManager: FileManager = koin.get()

View File

@ -1,88 +0,0 @@
package nl.bravobit.ffmpeg;
import android.content.Context;
import android.os.AsyncTask;
import java.io.File;
import java.io.IOException;
import java.util.Map;
public class FFmpeg implements FFbinaryInterface {
private final FFbinaryContextProvider context;
private static final long MINIMUM_TIMEOUT = 10 * 1000;
private long timeout = Long.MAX_VALUE;
private static FFmpeg instance = null;
private FFmpeg(FFbinaryContextProvider context) {
this.context = context;
Log.setDebug(Util.isDebug(this.context.provide()));
}
public static FFmpeg getInstance(final Context context) {
if (instance == null) {
instance = new FFmpeg(new FFbinaryContextProvider() {
@Override
public Context provide() {
return context;
}
});
}
return instance;
}
@Override
public boolean isSupported() {
// get ffmpeg file
File ffmpeg = FileUtils.getFFmpeg(context.provide());
// check if ffmpeg can be executed
if (!ffmpeg.canExecute()) {
// try to make executable
Log.e("ffmpeg cannot execute");
return false;
}
Log.d("ffmpeg is ready!");
return true;
}
@Override
public FFtask execute(Map<String, String> environvenmentVars, String[] cmd, FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler) {
if (cmd.length != 0) {
final String[] command = new String[cmd.length + 1];
command[0] = FileUtils.getFFmpeg(context.provide()).getAbsolutePath();
System.arraycopy(cmd, 0, command, 1, cmd.length);
FFcommandExecuteAsyncTask task = new FFcommandExecuteAsyncTask(command, environvenmentVars, timeout, ffmpegExecuteResponseHandler);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return task;
} else {
throw new IllegalArgumentException("shell command cannot be empty");
}
}
@Override
public FFtask execute(String[] cmd, FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler) {
return execute(null, cmd, ffmpegExecuteResponseHandler);
}
@Override
public boolean isCommandRunning(FFtask task) {
return task != null && !task.isProcessCompleted();
}
@Override
public boolean killRunningProcesses(FFtask task) {
return task != null && task.killRunningProcess();
}
@Override
public void setTimeout(long timeout) {
if (timeout >= MINIMUM_TIMEOUT) {
this.timeout = timeout;
}
}
}

View File

@ -0,0 +1,86 @@
package nl.bravobit.ffmpeg
import android.content.Context
import nl.bravobit.ffmpeg.Log.setDebug
import nl.bravobit.ffmpeg.Util.isDebug
import nl.bravobit.ffmpeg.FileUtils.getFFmpeg
import nl.bravobit.ffmpeg.Log.e
import nl.bravobit.ffmpeg.Log.d
import android.os.AsyncTask
import java.lang.IllegalArgumentException
class FFmpeg private constructor(private val context: FFbinaryContextProvider) : FFbinaryInterface {
private var timeout = Long.MAX_VALUE
init {
setDebug(isDebug(context.provide()))
}
override fun isSupported(): Boolean {
// get ffmpeg file
val ffmpeg = getFFmpeg(context.provide())
// check if ffmpeg can be executed
if (!ffmpeg.canExecute()) {
// try to make executable
e("ffmpeg cannot execute")
return false
}
d("ffmpeg is ready!")
return true
}
override fun execute(
environmentVars: Map<String, String>,
cmd: Array<String>,
ffmpegExecuteResponseHandler: FFcommandExecuteResponseHandler
): FFtask {
return if (cmd.isNotEmpty()) {
val command = arrayOfNulls<String>(cmd.size + 1)
command[0] = getFFmpeg(context.provide()).absolutePath
System.arraycopy(cmd, 0, command, 1, cmd.size)
val task = FFcommandExecuteAsyncTask(
command,
environmentVars,
timeout,
ffmpegExecuteResponseHandler
)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
task
} else {
throw IllegalArgumentException("shell command cannot be empty")
}
}
override fun execute(
cmd: Array<String>,
ffmpegExecuteResponseHandler: FFcommandExecuteResponseHandler
): FFtask {
return execute(emptyMap(), cmd, ffmpegExecuteResponseHandler)
}
override fun isCommandRunning(task: FFtask): Boolean {
return !task.isProcessCompleted
}
override fun killRunningProcesses(task: FFtask): Boolean {
return task.killRunningProcess()
}
override fun setTimeout(timeout: Long) {
if (timeout >= MINIMUM_TIMEOUT) {
this.timeout = timeout
}
}
companion object {
private const val MINIMUM_TIMEOUT = (10 * 1000).toLong()
private var instance: FFmpeg? = null
fun getInstance(context: Context): FFmpeg {
return instance ?: FFmpeg { context }.also {
instance = it
}
}
}
}

View File

@ -1,22 +1,21 @@
package nl.bravobit.ffmpeg;
public interface FFtask {
package nl.bravobit.ffmpeg
interface FFtask {
/**
* Sends 'q' to the ff binary running process asynchronously
*/
void sendQuitSignal();
fun sendQuitSignal()
/**
* Checks if process is completed
* @return <code>true</code> if a process is running
* @return `true` if a process is running
*/
boolean isProcessCompleted();
val isProcessCompleted: Boolean
/**
* Kill given running process
*
* @return true if process is killed successfully
*/
boolean killRunningProcess();
fun killRunningProcess(): Boolean
}

View File

@ -1,20 +0,0 @@
package nl.bravobit.ffmpeg;
import android.content.Context;
import java.io.File;
class FileUtils {
private static final String FFMPEG_FILE_NAME = "lib..ffmpeg..so";
private static final String FFPROBE_FILE_NAME = "lib..ffprobe..so";
static File getFFmpeg(Context context) {
File folder = new File(context.getApplicationInfo().nativeLibraryDir);
return new File(folder, FFMPEG_FILE_NAME);
}
static File getFFprobe(Context context) {
File folder = new File(context.getApplicationInfo().nativeLibraryDir);
return new File(folder, FFPROBE_FILE_NAME);
}
}

View File

@ -0,0 +1,22 @@
package nl.bravobit.ffmpeg
import android.content.Context
import java.io.File
internal object FileUtils {
private const val FFMPEG_FILE_NAME = "lib..ffmpeg..so"
private const val FFPROBE_FILE_NAME = "lib..ffprobe..so"
@JvmStatic
fun getFFmpeg(context: Context): File {
val folder = File(context.applicationInfo.nativeLibraryDir)
return File(folder, FFMPEG_FILE_NAME)
}
@JvmStatic
fun getFFprobe(context: Context): File {
val folder = File(context.applicationInfo.nativeLibraryDir)
return File(folder, FFPROBE_FILE_NAME)
}
}

View File

@ -1,58 +0,0 @@
package nl.bravobit.ffmpeg;
class Log {
private static String TAG = FFmpeg.class.getSimpleName();
private static boolean DEBUG = false;
public static void setDebug(boolean debug) {
Log.DEBUG = debug;
}
public static void setTag(String tag) {
Log.TAG = tag;
}
static void d(Object obj) {
if (DEBUG) {
android.util.Log.d(TAG, obj != null ? obj.toString() : "");
}
}
static void e(Object obj) {
if (DEBUG) {
android.util.Log.e(TAG, obj != null ? obj.toString() : "");
}
}
static void w(Object obj) {
if (DEBUG) {
android.util.Log.w(TAG, obj != null ? obj.toString() : "");
}
}
static void i(Object obj) {
if (DEBUG) {
android.util.Log.i(TAG, obj != null ? obj.toString() : "");
}
}
static void v(Object obj) {
if (DEBUG) {
android.util.Log.v(TAG, obj != null ? obj.toString() : "");
}
}
static void e(Object obj, Throwable throwable) {
if (DEBUG) {
android.util.Log.e(TAG, obj != null ? obj.toString() : "", throwable);
}
}
static void e(Throwable throwable) {
if (DEBUG) {
android.util.Log.e(TAG, "", throwable);
}
}
}

View File

@ -0,0 +1,67 @@
package nl.bravobit.ffmpeg
import android.util.Log
internal object Log {
private var TAG = FFmpeg::class.java.simpleName
private var DEBUG = false
@JvmStatic
fun setDebug(debug: Boolean) {
DEBUG = debug
}
fun setTag(tag: String) {
TAG = tag
}
@JvmStatic
fun d(obj: Any?) {
if (DEBUG) {
Log.d(TAG, obj?.toString() ?: "")
}
}
@JvmStatic
fun e(obj: Any?) {
if (DEBUG) {
Log.e(TAG, obj?.toString() ?: "")
}
}
@JvmStatic
fun w(obj: Any?) {
if (DEBUG) {
Log.w(TAG, obj?.toString() ?: "")
}
}
@JvmStatic
fun i(obj: Any?) {
if (DEBUG) {
Log.i(TAG, obj?.toString() ?: "")
}
}
@JvmStatic
fun v(obj: Any?) {
if (DEBUG) {
Log.v(TAG, obj?.toString() ?: "")
}
}
@JvmStatic
fun e(obj: Any?, throwable: Throwable?) {
if (DEBUG) {
Log.e(TAG, obj?.toString() ?: "", throwable)
}
}
@JvmStatic
fun e(throwable: Throwable?) {
if (DEBUG) {
Log.e(TAG, "", throwable)
}
}
}

View File

@ -1,15 +0,0 @@
package nl.bravobit.ffmpeg;
public interface ResponseHandler {
/**
* on Start
*/
void onStart();
/**
* on Finish
*/
void onFinish();
}

View File

@ -0,0 +1,13 @@
package nl.bravobit.ffmpeg
interface ResponseHandler {
/**
* on Start
*/
fun onStart()
/**
* on Finish
*/
fun onFinish()
}

View File

@ -1,22 +0,0 @@
package nl.bravobit.ffmpeg;
import java.util.Arrays;
import java.util.Map;
class ShellCommand {
Process run(String[] commandString, Map<String, String> environment) {
Process process = null;
try {
ProcessBuilder processBuilder = new ProcessBuilder(commandString);
if (environment != null) {
processBuilder.environment().putAll(environment);
}
process = processBuilder.start();
} catch (Throwable t) {
Log.e("Exception while trying to run: " + Arrays.toString(commandString), t);
}
return process;
}
}

View File

@ -0,0 +1,17 @@
package nl.bravobit.ffmpeg
internal class ShellCommand {
fun run(commandString: Array<String?>, environment: Map<String?, String?>?): Process? {
var process: Process? = null
try {
val processBuilder = ProcessBuilder(*commandString)
if (environment != null) {
processBuilder.environment().putAll(environment)
}
process = processBuilder.start()
} catch (t: Throwable) {
Log.e("Exception while trying to run: " + commandString.contentToString(), t)
}
return process
}
}

View File

@ -1,105 +0,0 @@
package nl.bravobit.ffmpeg;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
class Util {
static boolean isDebug(Context context) {
return (context.getApplicationContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
static String convertInputStreamToString(InputStream inputStream) {
try {
BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
String str;
StringBuilder sb = new StringBuilder();
while ((str = r.readLine()) != null) {
sb.append(str);
}
return sb.toString();
} catch (IOException e) {
Log.e("error converting input stream to string", e);
}
return null;
}
static void destroyProcess(Process process) {
if (process != null) {
try {
process.destroy();
} catch (Exception e) {
Log.e("progress destroy error", e);
}
}
}
static boolean killAsync(AsyncTask asyncTask) {
return asyncTask != null && !asyncTask.isCancelled() && asyncTask.cancel(true);
}
static boolean isProcessCompleted(Process process) {
try {
if (process == null) return true;
process.exitValue();
return true;
} catch (IllegalThreadStateException e) {
// do nothing
}
return false;
}
public interface ObservePredicate {
Boolean isReadyToProceed();
}
static FFbinaryObserver observeOnce(final ObservePredicate predicate, final Runnable run, final int timeout) {
final android.os.Handler observer = new android.os.Handler();
final FFbinaryObserver observeAction = new FFbinaryObserver() {
private boolean canceled = false;
private int timeElapsed = 0;
@Override
public void run() {
if (timeElapsed + 40 > timeout) cancel();
timeElapsed += 40;
if (canceled) return;
boolean readyToProceed = false;
try {
readyToProceed = predicate.isReadyToProceed();
} catch (Exception e) {
Log.v("Observing " + e.getMessage());
observer.postDelayed(this, 40);
return;
}
if (readyToProceed) {
Log.v("Observed");
run.run();
} else {
Log.v("Observing");
observer.postDelayed(this, 40);
}
}
@Override
public void cancel() {
canceled = true;
}
};
observer.post(observeAction);
return observeAction;
}
}

View File

@ -0,0 +1,100 @@
package nl.bravobit.ffmpeg
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.AsyncTask
import android.os.Handler
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
internal object Util {
@JvmStatic
fun isDebug(context: Context): Boolean {
return context.applicationContext.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
}
@JvmStatic
fun convertInputStreamToString(inputStream: InputStream?): String? {
try {
val r = BufferedReader(InputStreamReader(inputStream))
var str: String?
val sb = StringBuilder()
while (r.readLine().also { str = it } != null) {
sb.append(str)
}
return sb.toString()
} catch (e: IOException) {
Log.e("error converting input stream to string", e)
}
return null
}
@JvmStatic
fun destroyProcess(process: Process?) {
if (process != null) {
try {
process.destroy()
} catch (e: Exception) {
Log.e("progress destroy error", e)
}
}
}
@JvmStatic
fun killAsync(asyncTask: AsyncTask<*, *, *>?): Boolean {
return asyncTask != null && !asyncTask.isCancelled && asyncTask.cancel(true)
}
@JvmStatic
fun isProcessCompleted(process: Process?): Boolean {
try {
if (process == null) return true
process.exitValue()
return true
} catch (e: IllegalThreadStateException) {
// do nothing
}
return false
}
fun observeOnce(predicate: ObservePredicate, run: Runnable, timeout: Int): FFbinaryObserver {
val observer = Handler()
val observeAction: FFbinaryObserver = object : FFbinaryObserver {
private var canceled = false
private var timeElapsed = 0
override fun run() {
if (timeElapsed + 40 > timeout) cancel()
timeElapsed += 40
if (canceled) return
var readyToProceed = false
readyToProceed = try {
predicate.isReadyToProceed
} catch (e: Exception) {
Log.v("Observing " + e.message)
observer.postDelayed(this, 40)
return
}
if (readyToProceed) {
Log.v("Observed")
run.run()
} else {
Log.v("Observing")
observer.postDelayed(this, 40)
}
}
override fun cancel() {
canceled = true
}
}
observer.post(observeAction)
return observeAction
}
interface ObservePredicate {
val isReadyToProceed: Boolean
}
}