mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
Refactor and Icons Added
This commit is contained in:
parent
475e1834bf
commit
7d7e82b3b7
@ -91,6 +91,13 @@ dependencies {
|
||||
implementation(fetch)
|
||||
}
|
||||
|
||||
implementation(MVIKotlin.mvikotlin)
|
||||
implementation(MVIKotlin.mvikotlinMain)
|
||||
implementation(MVIKotlin.mvikotlinLogging)
|
||||
implementation(MVIKotlin.mvikotlinTimeTravel)
|
||||
implementation(Decompose.decompose)
|
||||
implementation(Decompose.extensionsCompose)
|
||||
|
||||
//Test
|
||||
testImplementation("junit:junit:4.13.1")
|
||||
androidTestImplementation(Androidx.junit)
|
||||
|
@ -1,16 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.shabinder.android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.shabinder.android">
|
||||
|
||||
<queries>
|
||||
<package android:name="com.gaana" />
|
||||
<package android:name="com.spotify.music" />
|
||||
<package android:name="com.google.android.youtube" />
|
||||
<package android:name="com.google.android.apps.youtube.music" />
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" />
|
||||
<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" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<activity android:name=".MainActivity">
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:forceDarkAllowed="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:targetApi="q">
|
||||
<activity android:name=".MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true"
|
||||
android:theme="@style/Theme.SpotiFlyer"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="com.razorpay.ApiKey"
|
||||
android:value="rzp_live_3ZQeoFYOxjmXye"
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
@ -2,21 +2,43 @@ package com.shabinder.android
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.setContent
|
||||
import com.shabinder.android.di.appModule
|
||||
import com.shabinder.common.database.appContext
|
||||
import com.shabinder.common.initKoin
|
||||
import com.shabinder.common.ui.SpotiFlyerMain
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.rootComponent
|
||||
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
|
||||
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
|
||||
import com.shabinder.common.Dir
|
||||
import com.shabinder.common.FetchPlatformQueryResult
|
||||
import com.shabinder.common.root.SpotiFlyerRoot
|
||||
import com.shabinder.common.root.SpotiFlyerRootContent
|
||||
import com.shabinder.common.ui.SpotiFlyerTheme
|
||||
import com.shabinder.database.Database
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
val database: Database by inject()
|
||||
val fetcher: FetchPlatformQueryResult by inject()
|
||||
val dir: Dir by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
val scope = rememberCoroutineScope()
|
||||
SpotiFlyerMain()
|
||||
|
||||
SpotiFlyerTheme {
|
||||
SpotiFlyerRootContent(rootComponent(::spotiFlyerRoot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
|
||||
SpotiFlyerRoot(
|
||||
componentContext,
|
||||
dependencies = object : SpotiFlyerRoot.Dependencies{
|
||||
override val storeFactory = LoggingStoreFactory(DefaultStoreFactory)
|
||||
override val database = this@MainActivity.database
|
||||
override val fetchPlatformQueryResult = this@MainActivity.fetcher
|
||||
override val directories: Dir = this@MainActivity.dir
|
||||
}
|
||||
)
|
||||
}
|
@ -1,122 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.android
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.ViewModel
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.DownloadStatus
|
||||
import com.shabinder.common.TrackDetails
|
||||
import com.shabinder.common.YoutubeProvider
|
||||
import com.shabinder.common.providers.GaanaProvider
|
||||
import com.shabinder.common.providers.SpotifyProvider
|
||||
import com.shabinder.database.Database
|
||||
import com.shabinder.spotiflyer.ui.colorPrimaryDark
|
||||
import com.tonyodev.fetch2.Status
|
||||
|
||||
class SharedViewModel(
|
||||
val database: Database,
|
||||
val logger: Kermit,
|
||||
val spotifyProvider: SpotifyProvider,
|
||||
val gaanaProvider : GaanaProvider,
|
||||
val youtubeProvider: YoutubeProvider
|
||||
) : ViewModel() {
|
||||
var isAuthenticated by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
fun authenticated(s:Boolean) {
|
||||
isAuthenticated = s
|
||||
}
|
||||
|
||||
/*
|
||||
* Nav Gives Error on YT links with ? sign
|
||||
* */
|
||||
var link by mutableStateOf("")
|
||||
private set
|
||||
|
||||
fun updateLink(s:String) {
|
||||
link = s
|
||||
}
|
||||
|
||||
|
||||
val trackList = mutableStateListOf<TrackDetails>()
|
||||
|
||||
fun updateTrackList(list:List<TrackDetails>){
|
||||
trackList.clear()
|
||||
trackList.addAll(list)
|
||||
}
|
||||
fun updateTrackStatus(position:Int, status: DownloadStatus){
|
||||
if(position != -1){
|
||||
val track = trackList[position].apply { downloaded = status }
|
||||
trackList[position] = track
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTrackStatus(intent: Intent){
|
||||
val trackDetails = intent.getSerializableExtra("track") as TrackDetails?
|
||||
trackDetails?.let {
|
||||
val position: Int =
|
||||
trackList.map { trackState -> trackState.title }.indexOf(it.title)
|
||||
logger.d{"$position, ${intent.action} , ${it.title}"}
|
||||
if (position != -1) {
|
||||
trackList.getOrNull(position)?.let{ track ->
|
||||
when (intent.action) {
|
||||
Status.QUEUED.name -> {
|
||||
track.downloaded = DownloadStatus.Queued
|
||||
}
|
||||
Status.FAILED.name -> {
|
||||
track.downloaded = DownloadStatus.Failed
|
||||
}
|
||||
Status.DOWNLOADING.name -> {
|
||||
track.downloaded = DownloadStatus.Downloading
|
||||
}
|
||||
"Progress" -> {
|
||||
//Progress Update
|
||||
track.progress = intent.getIntExtra("progress", 0)
|
||||
track.downloaded = DownloadStatus.Downloading
|
||||
}
|
||||
"Converting" -> {
|
||||
//Progress Update
|
||||
track.downloaded = DownloadStatus.Converting
|
||||
}
|
||||
"track_download_completed" -> {
|
||||
track.downloaded = DownloadStatus.Downloaded
|
||||
}
|
||||
}
|
||||
trackList[position] = track
|
||||
logger.d{"TrackListUpdated"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var gradientColor by mutableStateOf(Color.Transparent)
|
||||
private set
|
||||
|
||||
fun updateGradientColor(color: Color) {
|
||||
gradientColor = color
|
||||
}
|
||||
|
||||
fun resetGradient() {
|
||||
gradientColor = colorPrimaryDark
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package com.shabinder.android.di
|
||||
|
||||
import com.shabinder.android.SharedViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val appModule = module {
|
||||
viewModel { SharedViewModel(get(),get(),get(),get(),get()) }
|
||||
|
||||
}
|
||||
|
@ -1,78 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.android.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.*
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
import com.shabinder.spotiflyer.providers.GaanaProvider
|
||||
import com.shabinder.spotiflyer.providers.SpotifyProvider
|
||||
import com.shabinder.spotiflyer.providers.YoutubeProvider
|
||||
import com.shabinder.common.ui.home.Home
|
||||
import com.shabinder.spotiflyer.ui.tracklist.TrackList
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
|
||||
@Composable
|
||||
fun ComposeNavigation(
|
||||
mainActivity: MainActivity,
|
||||
navController: NavHostController,
|
||||
spotifyProvider: SpotifyProvider,
|
||||
gaanaProvider: GaanaProvider,
|
||||
youtubeProvider: YoutubeProvider,
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "home"
|
||||
) {
|
||||
|
||||
//HomeScreen - Starting Point
|
||||
composable("home") {
|
||||
Home(
|
||||
navController = navController,
|
||||
mainActivity,
|
||||
)
|
||||
}
|
||||
|
||||
//Spotify Screen
|
||||
//Argument `link` = Link of Track/Album/Playlist
|
||||
composable(
|
||||
"track_list/{link}",
|
||||
arguments = listOf(navArgument("link") { type = NavType.StringType })
|
||||
) {
|
||||
TrackList(
|
||||
fullLink = it.arguments?.getString("link") ?: "error",
|
||||
navController = navController,
|
||||
spotifyProvider,
|
||||
gaanaProvider,
|
||||
youtubeProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToTrackList(link:String, singleInstance: Boolean = true, inclusive:Boolean = false) {
|
||||
sharedViewModel.updateLink(link)
|
||||
navigate("track_list/$link") {
|
||||
launchSingleTop = singleInstance
|
||||
popUpTo(route = "home") {
|
||||
this.inclusive = inclusive
|
||||
}
|
||||
}
|
||||
}
|
75
android/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
75
android/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,75 @@
|
||||
<!--
|
||||
~ 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>
|
21
android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
21
android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<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"/>
|
||||
</adaptive-icon>
|
21
android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
21
android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<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"/>
|
||||
</adaptive-icon>
|
29
android/src/main/res/values/colors.xml
Normal file
29
android/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="colorPrimary">#FC5C7D</color>
|
||||
<color name="colorPrimaryDark">#CE1CFF</color>
|
||||
<color name="colorAccent">#9AB3FF</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="grey">#99FFFFFF</color>
|
||||
<color name="darkGrey">#E6333333</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="dark">#121212</color>
|
||||
<color name="successGreen">#59C351</color>
|
||||
<color name="errorRed">#FF9494</color>
|
||||
</resources>
|
20
android/src/main/res/values/ic_launcher_background.xml
Normal file
20
android/src/main/res/values/ic_launcher_background.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
33
android/src/main/res/values/strings.xml
Normal file
33
android/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">SpotiFlyer</string>
|
||||
<string name="home_about">About</string>
|
||||
<string name="home_history">History</string>
|
||||
<string name="supported_platform">Supported Platforms</string>
|
||||
<string name="support_development">Support Development</string>
|
||||
<string name="github_star">Star / Fork the project on Github.</string>
|
||||
<string name="github">GitHub</string>
|
||||
<string name="translate">Translate</string>
|
||||
<string name="help_us_translate">Help us translate this app in your local language.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="donate_subtitle">If you think I deserve to get paid for my work, you can leave me some money here.</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="share_subtitle">Share this app with your friends and family.</string>
|
||||
<string name="made_with_love">Made with</string>
|
||||
<string name="in_india">in India</string>
|
||||
</resources>
|
29
android/src/main/res/values/themes.xml
Normal file
29
android/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.SpotiFlyer" parent="Theme.AppCompat.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<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>
|
||||
</resources>
|
@ -34,3 +34,23 @@ actual fun DownloadImageArrow(modifier: Modifier){
|
||||
|
||||
@Composable
|
||||
actual fun DownloadAllImage() = vectorResource(R.drawable.ic_download_arrow)
|
||||
|
||||
@Composable
|
||||
actual fun SpotiFlyerLogo() = vectorResource(R.drawable.ic_spotiflyer_logo)
|
||||
|
||||
@Composable
|
||||
actual fun HeartIcon() = vectorResource(R.drawable.ic_heart)
|
||||
|
||||
@Composable
|
||||
actual fun SpotifyLogo() = vectorResource(R.drawable.ic_spotify_logo)
|
||||
|
||||
@Composable
|
||||
actual fun GaanaLogo() = vectorResource(R.drawable.ic_gaana)
|
||||
|
||||
@Composable
|
||||
actual fun YoutubeLogo() = vectorResource(R.drawable.ic_youtube)
|
||||
|
||||
@Composable
|
||||
actual fun YoutubeMusicLogo() = vectorResource(R.drawable.ic_youtube_music_logo)
|
||||
@Composable
|
||||
actual fun GithubLogo() = vectorResource(R.drawable.ic_github)
|
@ -20,8 +20,8 @@ import com.shabinder.common.DownloadStatus
|
||||
import com.shabinder.common.Picture
|
||||
import com.shabinder.common.TrackDetails
|
||||
import com.shabinder.common.ui.*
|
||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||
import com.shabinder.spotiflyer.ui.colorAccent
|
||||
import com.shabinder.common.ui.SpotiFlyerTypography
|
||||
import com.shabinder.common.ui.colorAccent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Composable
|
||||
|
@ -2,7 +2,9 @@ package com.shabinder.common.main
|
||||
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.mvikotlin.core.store.StoreFactory
|
||||
import com.shabinder.common.Dir
|
||||
import com.shabinder.common.DownloadRecord
|
||||
import com.shabinder.common.Picture
|
||||
import com.shabinder.common.main.integration.SpotiFlyerMainImpl
|
||||
import com.shabinder.common.utils.Consumer
|
||||
import com.shabinder.database.Database
|
||||
@ -23,10 +25,21 @@ interface SpotiFlyerMain {
|
||||
* */
|
||||
fun onInputLinkChanged(link: String)
|
||||
|
||||
/*
|
||||
* change TabBar Selected Category
|
||||
* */
|
||||
fun selectCategory(category: HomeCategory)
|
||||
|
||||
/*
|
||||
* Load Image from cache/Internet and cache it
|
||||
* */
|
||||
fun loadImage(url:String):Picture?
|
||||
|
||||
interface Dependencies {
|
||||
fun mainOutput(searched: Output): Consumer<Output>
|
||||
val storeFactory: StoreFactory
|
||||
val database: Database
|
||||
val dir: Dir
|
||||
}
|
||||
sealed class Output {
|
||||
data class Search(val link: String) : Output()
|
||||
@ -34,8 +47,12 @@ interface SpotiFlyerMain {
|
||||
|
||||
data class State(
|
||||
val records: List<DownloadRecord> = emptyList(),
|
||||
val link: String = ""
|
||||
val link: String = "",
|
||||
val selectedCategory: HomeCategory = HomeCategory.About
|
||||
)
|
||||
enum class HomeCategory {
|
||||
About, History
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionName") // Factory function
|
||||
|
@ -1,10 +1,359 @@
|
||||
package com.shabinder.common.main
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.AmbientTextStyle
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.TabDefaults.tabIndicatorOffset
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DateRange
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.Edit
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.common.DownloadRecord
|
||||
import com.shabinder.common.Picture
|
||||
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
|
||||
import com.shabinder.common.openPlatform
|
||||
import com.shabinder.common.ui.*
|
||||
import com.shabinder.common.ui.SpotiFlyerTypography
|
||||
|
||||
@Composable
|
||||
fun SpotiFlyerMainContent(component: SpotiFlyerMain){
|
||||
val model by component.models.collectAsState(SpotiFlyerMain.State())
|
||||
|
||||
Column {
|
||||
|
||||
SearchPanel(
|
||||
model.link,
|
||||
component::onInputLinkChanged,
|
||||
component::onLinkSearch
|
||||
)
|
||||
|
||||
HomeTabBar(
|
||||
model.selectedCategory,
|
||||
HomeCategory.values(),
|
||||
component::selectCategory
|
||||
)
|
||||
|
||||
when(model.selectedCategory){
|
||||
HomeCategory.About -> AboutColumn()
|
||||
HomeCategory.History -> HistoryColumn(
|
||||
model.records,
|
||||
component::loadImage,
|
||||
component::onLinkSearch
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeTabBar(
|
||||
selectedCategory: HomeCategory,
|
||||
categories: Array<HomeCategory>,
|
||||
selectCategory: (HomeCategory) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val selectedIndex =categories.indexOfFirst { it == selectedCategory }
|
||||
val indicator = @Composable { tabPositions: List<TabPosition> ->
|
||||
HomeCategoryTabIndicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[selectedIndex])
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("USELESS_CAST")//Showing Error in Latest Android Studio Canary
|
||||
TabRow(
|
||||
selectedTabIndex = selectedIndex,
|
||||
indicator = indicator as @Composable (List<TabPosition>) -> Unit,
|
||||
modifier = modifier,
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
Tab(
|
||||
selected = index == selectedIndex,
|
||||
onClick = { selectCategory(category) },
|
||||
text = {
|
||||
Text(
|
||||
text = when (category) {
|
||||
HomeCategory.About -> "About"
|
||||
HomeCategory.History -> "History"
|
||||
},
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
when (category) {
|
||||
HomeCategory.About -> Icon(Icons.Outlined.Info)
|
||||
HomeCategory.History -> Icon(Icons.Outlined.DateRange)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchPanel(
|
||||
link:String,
|
||||
updateLink:(String) -> Unit,
|
||||
onSearch:(String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
){
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.padding(top = 16.dp)
|
||||
){
|
||||
TextField(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Edit,tint = Color.LightGray)
|
||||
},
|
||||
label = { Text(text = "Paste Link Here...",color = Color.LightGray) },
|
||||
value = link,
|
||||
onValueChange = { updateLink(it) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
modifier = Modifier.padding(12.dp).fillMaxWidth()
|
||||
.border(
|
||||
BorderStroke(2.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent))),
|
||||
RoundedCornerShape(30.dp)
|
||||
),
|
||||
backgroundColor = Color.Black,
|
||||
textStyle = AmbientTextStyle.current.merge(TextStyle(fontSize = 18.sp,color = Color.White)),
|
||||
shape = RoundedCornerShape(size = 30.dp),
|
||||
activeColor = Color.Transparent,
|
||||
inactiveColor = Color.Transparent
|
||||
)
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(12.dp).wrapContentWidth(),
|
||||
onClick = {
|
||||
if(link.isBlank()) showPopUpMessage("Enter A Link!")
|
||||
else{
|
||||
//TODO if(!isOnline(ctx)) showPopUpMessage("Check Your Internet Connection") else
|
||||
onSearch(link)
|
||||
}
|
||||
},
|
||||
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
|
||||
){
|
||||
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AboutColumn(modifier: Modifier = Modifier) {
|
||||
ScrollableColumn(modifier.fillMaxSize(),contentPadding = PaddingValues(16.dp)) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp,Color.Gray)
|
||||
) {
|
||||
Column(modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = "Supported Platforms",
|
||||
style = SpotiFlyerTypography.body1,
|
||||
color = colorAccent
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 12.dp))
|
||||
Row(horizontalArrangement = Arrangement.Center,modifier = modifier.fillMaxWidth()) {
|
||||
Icon(
|
||||
imageVector = SpotifyLogo(), tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.spotify.music","http://open.spotify.com") })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||
Icon(imageVector = GaanaLogo(),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.gaana","http://gaana.com") })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||
Icon(imageVector = YoutubeLogo(),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com") })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 12.dp))
|
||||
Icon(imageVector = YoutubeMusicLogo(),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.google.android.apps.youtube.music","https://music.youtube.com/") })
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp,Color.Gray)
|
||||
) {
|
||||
Column(modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = "Support Development",
|
||||
style = SpotiFlyerTypography.body1,
|
||||
color = colorAccent
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 6.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = { openPlatform("","http://github.com/Shabinder/SpotiFlyer") })
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(imageVector = GithubLogo(),tint = Color.LightGray)
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "GitHub",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = "Star / Fork the project on Github.",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { openPlatform("","http://github.com/Shabinder/SpotiFlyer") }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.Info.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Translate",
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = "Help us translate this app in your local language.",
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
/*Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { startPayment(mainActivity) }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.MailOutline.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.donate),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.donate_subtitle),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}*/
|
||||
/*Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer")
|
||||
type = "text/plain"
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
ctx.startActivity(shareIntent)
|
||||
}),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.Share.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.share),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.share_subtitle),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HistoryColumn(
|
||||
list: List<DownloadRecord>,
|
||||
loadImage:(String)->Picture?,
|
||||
onItemClicked: (String) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = {
|
||||
items(list) {
|
||||
DownloadRecordItem(
|
||||
item = it,
|
||||
loadImage,
|
||||
onItemClicked
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 8.dp).fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadRecordItem(
|
||||
item: DownloadRecord,
|
||||
loadImage:(String)->Picture?,
|
||||
onItemClicked:(String)->Unit
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
|
||||
val pic = loadImage(item.coverUrl)
|
||||
ImageLoad(
|
||||
pic,
|
||||
modifier = Modifier.preferredHeight(75.dp).preferredWidth(90.dp)
|
||||
)
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp).preferredHeight(60.dp).weight(1f),verticalArrangement = Arrangement.SpaceEvenly) {
|
||||
Text(item.name,maxLines = 1,overflow = TextOverflow.Ellipsis,style = SpotiFlyerTypography.h6,color = colorAccent)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
|
||||
){
|
||||
Text(item.type,fontSize = 13.sp)
|
||||
Text("Tracks: ${item.totalFiles}",fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
Image(
|
||||
imageVector = Icons.Rounded.Share,
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
//if(!isOnline(ctx)) showDialog("Check Your Internet Connection") else
|
||||
onItemClicked(item.link)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun HomeCategoryTabIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colors.onSurface
|
||||
) {
|
||||
Spacer(
|
||||
modifier.padding(horizontal = 24.dp)
|
||||
.preferredHeight(4.dp)
|
||||
.background(color, RoundedCornerShape(topLeftPercent = 100, topRightPercent = 100))
|
||||
)
|
||||
}
|
@ -2,6 +2,7 @@ package com.shabinder.common.main.integration
|
||||
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.mvikotlin.extensions.coroutines.states
|
||||
import com.shabinder.common.Picture
|
||||
import com.shabinder.common.main.SpotiFlyerMain
|
||||
import com.shabinder.common.main.SpotiFlyerMain.*
|
||||
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
|
||||
@ -31,4 +32,10 @@ internal class SpotiFlyerMainImpl(
|
||||
override fun onInputLinkChanged(link: String) {
|
||||
store.accept(Intent.SetLink(link))
|
||||
}
|
||||
|
||||
override fun selectCategory(category: HomeCategory) {
|
||||
store.accept(Intent.SelectCategory(category))
|
||||
}
|
||||
|
||||
override fun loadImage(url: String): Picture? = dir.loadImage(url)
|
||||
}
|
@ -9,6 +9,7 @@ internal interface SpotiFlyerMainStore: Store<Intent, SpotiFlyerMain.State, Noth
|
||||
sealed class Intent {
|
||||
data class OpenPlatform(val platformID:String,val platformLink:String):Intent()
|
||||
data class SetLink(val link:String):Intent()
|
||||
data class SelectCategory(val category: SpotiFlyerMain.HomeCategory):Intent()
|
||||
object GiveDonation : Intent()
|
||||
object ShareApp: Intent()
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
|
||||
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||
import com.shabinder.common.DownloadRecord
|
||||
import com.shabinder.common.giveDonation
|
||||
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.openPlatform
|
||||
@ -49,6 +50,7 @@ internal class SpotiFlyerMainStoreProvider(
|
||||
|
||||
private sealed class Result {
|
||||
data class ItemsLoaded(val items: List<DownloadRecord>) : Result()
|
||||
data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result()
|
||||
data class LinkChanged(val link: String) : Result()
|
||||
}
|
||||
|
||||
@ -65,6 +67,7 @@ internal class SpotiFlyerMainStoreProvider(
|
||||
is Intent.GiveDonation -> giveDonation()
|
||||
is Intent.ShareApp -> shareApp()
|
||||
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
|
||||
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,6 +77,7 @@ internal class SpotiFlyerMainStoreProvider(
|
||||
when (result) {
|
||||
is Result.ItemsLoaded -> copy(records = result.items)
|
||||
is Result.LinkChanged -> copy(link = result.link)
|
||||
}
|
||||
is Result.CategoryChanged -> copy(selectedCategory = result.category)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,80 @@
|
||||
package com.shabinder.common.root
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.Children
|
||||
import com.shabinder.common.ui.utils.verticalGradientScrim
|
||||
import com.shabinder.common.list.SpotiFlyerListContent
|
||||
import com.shabinder.common.main.SpotiFlyerMainContent
|
||||
import com.shabinder.common.root.SpotiFlyerRoot.Child
|
||||
import com.shabinder.common.ui.SpotiFlyerLogo
|
||||
import com.shabinder.common.ui.appNameStyle
|
||||
|
||||
@Composable
|
||||
fun SpotiFlyerRootContent(component: SpotiFlyerRoot) {
|
||||
Children(
|
||||
routerState = component.routerState,
|
||||
//TODO animation = crossfade()
|
||||
) { child, _ ->
|
||||
when (child) {
|
||||
is Child.Main -> SpotiFlyerMainContent(component = child.component)
|
||||
is Child.List -> SpotiFlyerListContent(component = child.component)
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.65f)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().verticalGradientScrim(
|
||||
//color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||
color = appBarColor.copy(alpha = 0.38f),
|
||||
startYPercentage = 0.29f,
|
||||
endYPercentage = 0f,
|
||||
)
|
||||
) {
|
||||
AppBar(
|
||||
backgroundColor = appBarColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Children(
|
||||
routerState = component.routerState,
|
||||
//TODO animation = crossfade()
|
||||
) { child, _ ->
|
||||
when (child) {
|
||||
is Child.Main -> SpotiFlyerMainContent(component = child.component)
|
||||
is Child.List -> SpotiFlyerListContent(component = child.component)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TopAppBar(
|
||||
backgroundColor = backgroundColor,
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
imageVector = SpotiFlyerLogo(),
|
||||
Modifier.preferredSize(32.dp)
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
Text(
|
||||
text = "SpotiFlyer",
|
||||
style = appNameStyle
|
||||
)
|
||||
}
|
||||
},
|
||||
/*actions = {
|
||||
Providers(AmbientContentAlpha provides ContentAlpha.medium) {
|
||||
IconButton(
|
||||
onClick = { *//* TODO: Open Preferences*//* }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, tint = Color.Gray)
|
||||
}
|
||||
}
|
||||
},*/
|
||||
modifier = modifier,
|
||||
elevation = 0.dp
|
||||
)
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ internal class SpotiFlyerRootImpl(
|
||||
componentContext = componentContext,
|
||||
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by this {
|
||||
override fun mainOutput(searched: SpotiFlyerMain.Output): Consumer<SpotiFlyerMain.Output> = Consumer(::onMainOutput)
|
||||
override val dir: Dir = directories
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui
|
||||
package com.shabinder.common.ui
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.darkColors
|
||||
|
@ -4,6 +4,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.font.font
|
||||
import androidx.compose.ui.text.font.fontFamily
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.shabinder.common.Picture
|
||||
|
||||
|
@ -10,6 +10,27 @@ expect fun DownloadImageTick(modifier: Modifier = Modifier)
|
||||
@Composable
|
||||
expect fun DownloadAllImage():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun SpotiFlyerLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun SpotifyLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun YoutubeLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun GaanaLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun YoutubeMusicLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun GithubLogo():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun HeartIcon():ImageVector
|
||||
|
||||
@Composable
|
||||
expect fun DownloadImageError(modifier: Modifier = Modifier)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui
|
||||
package com.shabinder.common.ui
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
|
@ -1,12 +0,0 @@
|
||||
package com.shabinder.common.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun SpotiFlyerMain(){
|
||||
Column {
|
||||
Text("Hello World")
|
||||
}
|
||||
}
|
@ -14,13 +14,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui
|
||||
package com.shabinder.common.ui
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun ComposeLearnTheme(content: @Composable() () -> Unit) {
|
||||
fun SpotiFlyerTheme(content: @Composable() () -> Unit) {
|
||||
MaterialTheme(
|
||||
colors = SpotiFlyerColors,
|
||||
typography = SpotiFlyerTypography,
|
||||
|
@ -14,63 +14,57 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui
|
||||
package com.shabinder.common.ui
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.font.font
|
||||
import androidx.compose.ui.text.font.fontFamily
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.spotiflyer.R
|
||||
|
||||
private val Montserrat = fontFamily(
|
||||
/*private val Montserrat = fontFamily(
|
||||
font(R.font.montserrat_light, FontWeight.Light),
|
||||
font(R.font.montserrat_regular, FontWeight.Normal),
|
||||
font(R.font.montserrat_medium, FontWeight.Medium),
|
||||
font(R.font.montserrat_semibold, FontWeight.SemiBold),
|
||||
)
|
||||
|
||||
val pristineFont = fontFamily(
|
||||
font(R.font.pristine_script, FontWeight.Bold)
|
||||
)
|
||||
)*/
|
||||
|
||||
val SpotiFlyerTypography = Typography(
|
||||
h1 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 96.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
lineHeight = 117.sp,
|
||||
letterSpacing = (-1.5).sp
|
||||
),
|
||||
h2 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 60.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
lineHeight = 73.sp,
|
||||
letterSpacing = (-0.5).sp
|
||||
),
|
||||
h3 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 48.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = 59.sp
|
||||
),
|
||||
h4 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 37.sp
|
||||
),
|
||||
h5 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 29.sp
|
||||
),
|
||||
h6 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
lineHeight = 26.sp,
|
||||
@ -78,49 +72,49 @@ val SpotiFlyerTypography = Typography(
|
||||
|
||||
),
|
||||
subtitle1 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
),
|
||||
subtitle2 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
lineHeight = 17.sp,
|
||||
letterSpacing = 0.1.sp
|
||||
),
|
||||
body1 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.15.sp,
|
||||
),
|
||||
body2 = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.25.sp
|
||||
),
|
||||
button = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 1.25.sp
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
overline = TextStyle(
|
||||
fontFamily = Montserrat,
|
||||
//fontFamily = Montserrat,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp,
|
||||
@ -129,7 +123,7 @@ val SpotiFlyerTypography = Typography(
|
||||
)
|
||||
|
||||
val appNameStyle = TextStyle(
|
||||
fontFamily = pristineFont,
|
||||
fontFamily = FontFamily.Cursive,
|
||||
fontSize = 40.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 42.sp,
|
||||
|
@ -1,436 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.common.ui.home
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.AmbientTextStyle
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.TabDefaults.tabIndicatorOffset
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.CardGiftcard
|
||||
import androidx.compose.material.icons.rounded.Flag
|
||||
import androidx.compose.material.icons.rounded.InsertLink
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.AmbientContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.viewModel
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.NavController
|
||||
import com.razorpay.Checkout
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.navigation.navigateToTrackList
|
||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||
import com.shabinder.spotiflyer.ui.colorAccent
|
||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||
import com.shabinder.spotiflyer.ui.home.HomeCategory
|
||||
import com.shabinder.spotiflyer.ui.home.HomeViewModel
|
||||
import com.shabinder.spotiflyer.utils.isOnline
|
||||
import com.shabinder.spotiflyer.utils.openPlatform
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import com.shabinder.spotiflyer.utils.showDialog
|
||||
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||
import org.json.JSONObject
|
||||
|
||||
@Composable
|
||||
fun Home(
|
||||
navController: NavController,
|
||||
mainActivity: MainActivity,
|
||||
modifier: Modifier = Modifier) {
|
||||
val viewModel: HomeViewModel = viewModel()
|
||||
|
||||
Column(modifier = modifier) {
|
||||
|
||||
AuthenticationBanner(sharedViewModel.isAuthenticated,modifier)
|
||||
|
||||
SearchPanel(
|
||||
sharedViewModel.link,
|
||||
sharedViewModel::updateLink,
|
||||
navController,
|
||||
modifier
|
||||
)
|
||||
|
||||
HomeTabBar(
|
||||
viewModel.selectedCategory,
|
||||
HomeCategory.values(),
|
||||
viewModel::selectCategory,
|
||||
modifier
|
||||
)
|
||||
|
||||
when(viewModel.selectedCategory){
|
||||
HomeCategory.About -> AboutColumn(mainActivity)
|
||||
HomeCategory.History -> HistoryColumn(viewModel.downloadRecordList,navController)
|
||||
}
|
||||
}
|
||||
//Update Download List
|
||||
viewModel.getDownloadRecordList()
|
||||
//reset Gradient
|
||||
sharedViewModel.resetGradient()
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun AboutColumn(mainActivity: MainActivity,modifier: Modifier = Modifier) {
|
||||
val ctx = AmbientContext.current
|
||||
ScrollableColumn(modifier.fillMaxSize(),contentPadding = PaddingValues(16.dp)) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp,Color.Gray)
|
||||
) {
|
||||
Column(modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.supported_platform),
|
||||
style = SpotiFlyerTypography.body1,
|
||||
color = colorAccent
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 12.dp))
|
||||
Row(horizontalArrangement = Arrangement.Center,modifier = modifier.fillMaxWidth()) {
|
||||
Icon(
|
||||
imageVector = vectorResource(id = R.drawable.ic_spotify_logo), tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.spotify.music","http://open.spotify.com",ctx) })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_gaana ),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.gaana","http://gaana.com",ctx) })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 16.dp))
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_youtube),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.google.android.youtube","http://m.youtube.com",ctx) })
|
||||
)
|
||||
Spacer(modifier = modifier.padding(start = 12.dp))
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_youtube_music_logo),tint = Color.Unspecified,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = { openPlatform("com.google.android.apps.youtube.music","https://music.youtube.com/",ctx) })
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp,Color.Gray)
|
||||
) {
|
||||
Column(modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.support_development),
|
||||
style = SpotiFlyerTypography.body1,
|
||||
color = colorAccent
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 6.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().clickable(
|
||||
onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer",ctx) })
|
||||
.padding(vertical = 6.dp)
|
||||
) {
|
||||
Icon(imageVector = vectorResource(id = R.drawable.ic_github ),tint = Color.LightGray)
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.github),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.github_star),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { openPlatform("http://github.com/Shabinder/SpotiFlyer", ctx) }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.Flag.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.translate),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.help_us_translate),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = { startPayment(mainActivity) }),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.CardGiftcard.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.donate),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.donate_subtitle),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
.clickable(onClick = {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, "Hey, checkout this excellent Music Downloader http://github.com/Shabinder/SpotiFlyer")
|
||||
type = "text/plain"
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
ctx.startActivity(shareIntent)
|
||||
}),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(Icons.Rounded.Share.copy(defaultHeight = 32.dp,defaultWidth = 32.dp))
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.share),
|
||||
style = SpotiFlyerTypography.h6
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.share_subtitle),
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HistoryColumn(
|
||||
list: List<DownloadRecord>,
|
||||
navController: NavController
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
content = {
|
||||
items(list) {
|
||||
DownloadRecordItem(item = it,navController = navController)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 8.dp).fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DownloadRecordItem(item: DownloadRecord,navController: NavController) {
|
||||
val ctx = AmbientContext.current
|
||||
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.fillMaxWidth().padding(end = 8.dp)) {
|
||||
val imgUri = item.coverUrl.toUri().buildUpon().scheme("https").build()
|
||||
CoilImage(
|
||||
data = imgUri,
|
||||
//Loading Placeholder Makes Scrolling very stuttery
|
||||
// loading = { Image(vectorResource(id = R.drawable.ic_song_placeholder)) },
|
||||
error = {Image(vectorResource(id = R.drawable.ic_musicplaceholder))},
|
||||
contentScale = ContentScale.Inside,
|
||||
// fadeIn = true,
|
||||
modifier = Modifier.preferredHeight(75.dp).preferredWidth(90.dp)
|
||||
)
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp).preferredHeight(60.dp).weight(1f),verticalArrangement = Arrangement.SpaceEvenly) {
|
||||
Text(item.name,maxLines = 1,overflow = TextOverflow.Ellipsis,style = SpotiFlyerTypography.h6,color = colorAccent)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
modifier = Modifier.padding(horizontal = 8.dp).fillMaxSize()
|
||||
){
|
||||
Text(item.type,fontSize = 13.sp)
|
||||
Text("Tracks: ${item.totalFiles}",fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
Image(
|
||||
imageVector = vectorResource(id = R.drawable.ic_share_open),
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||
else navController.navigateToTrackList(item.link)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPayment(mainActivity: MainActivity) {
|
||||
/*
|
||||
* You need to pass current activity in order to let Razorpay create CheckoutActivity
|
||||
* */
|
||||
val co = Checkout().apply {
|
||||
setKeyID("rzp_live_3ZQeoFYOxjmXye")
|
||||
setImage(R.drawable.ic_launcher_foreground)
|
||||
}
|
||||
|
||||
try {
|
||||
val preFill = JSONObject()
|
||||
|
||||
val options = JSONObject().apply {
|
||||
put("name","SpotiFlyer")
|
||||
put("description","Thanks For the Donation!")
|
||||
//You can omit the image option to fetch the image from dashboard
|
||||
//put("image","https://github.com/Shabinder/SpotiFlyer/raw/master/app/SpotifyDownload.png")
|
||||
put("currency","INR")
|
||||
put("amount","4900")
|
||||
put("prefill",preFill)
|
||||
}
|
||||
|
||||
co.open(mainActivity,options)
|
||||
}catch (e: Exception){
|
||||
showDialog("Error in payment: "+ e.message)
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun AuthenticationBanner(isAuthenticated: Boolean, modifier: Modifier) {
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// TODO show a progress indicator or similar
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeTabBar(
|
||||
selectedCategory: HomeCategory,
|
||||
categories: Array<HomeCategory>,
|
||||
selectCategory: (HomeCategory) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val selectedIndex =categories.indexOfFirst { it == selectedCategory }
|
||||
val indicator = @Composable { tabPositions: List<TabPosition> ->
|
||||
HomeCategoryTabIndicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[selectedIndex])
|
||||
)
|
||||
}
|
||||
|
||||
TabRow(
|
||||
selectedTabIndex = selectedIndex,
|
||||
indicator = indicator,
|
||||
modifier = modifier,
|
||||
) {
|
||||
categories.forEachIndexed { index, category ->
|
||||
Tab(
|
||||
selected = index == selectedIndex,
|
||||
onClick = { selectCategory(category) },
|
||||
text = {
|
||||
Text(
|
||||
text = when (category) {
|
||||
HomeCategory.About -> stringResource(R.string.home_about)
|
||||
HomeCategory.History -> stringResource(R.string.home_history)
|
||||
},
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
when (category) {
|
||||
HomeCategory.About -> Icon(Icons.Outlined.Info)
|
||||
HomeCategory.History -> Icon(Icons.Outlined.History)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchPanel(
|
||||
link:String,
|
||||
updateLink:(s:String) -> Unit,
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier
|
||||
){
|
||||
val ctx = AmbientContext.current
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.padding(top = 16.dp)
|
||||
){
|
||||
TextField(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.InsertLink,tint = Color.LightGray)
|
||||
},
|
||||
label = {Text(text = "Paste Link Here...",color = Color.LightGray)},
|
||||
value = link,
|
||||
onValueChange = { updateLink(it) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
modifier = Modifier.padding(12.dp).fillMaxWidth()
|
||||
.border(
|
||||
BorderStroke(2.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent))),
|
||||
RoundedCornerShape(30.dp)
|
||||
),
|
||||
backgroundColor = Color.Black,
|
||||
textStyle = AmbientTextStyle.current.merge(TextStyle(fontSize = 18.sp,color = Color.White)),
|
||||
shape = RoundedCornerShape(size = 30.dp),
|
||||
activeColor = Color.Transparent,
|
||||
inactiveColor = Color.Transparent
|
||||
)
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(12.dp).wrapContentWidth(),
|
||||
onClick = {
|
||||
if(link.isBlank()) showDialog("Enter A Link!")
|
||||
else{
|
||||
if(!isOnline(ctx)) showDialog("Check Your Internet Connection")
|
||||
else navController.navigateToTrackList(link)
|
||||
}
|
||||
},
|
||||
border = BorderStroke(1.dp, Brush.horizontalGradient(listOf(colorPrimary, colorAccent)))
|
||||
){
|
||||
Text(text = "Search",style = SpotiFlyerTypography.h6,modifier = Modifier.padding(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun HomeCategoryTabIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colors.onSurface
|
||||
) {
|
||||
Spacer(
|
||||
modifier.padding(horizontal = 24.dp)
|
||||
.preferredHeight(4.dp)
|
||||
.background(color, RoundedCornerShape(topLeftPercent = 100, topRightPercent = 100))
|
||||
)
|
||||
}
|
@ -1,54 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.home
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.shabinder.spotiflyer.database.DownloadRecord
|
||||
import com.shabinder.spotiflyer.utils.sharedViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
|
||||
var selectedCategory by mutableStateOf(HomeCategory.About)
|
||||
private set
|
||||
|
||||
fun selectCategory(s:HomeCategory) {
|
||||
selectedCategory = s
|
||||
}
|
||||
|
||||
var downloadRecordList by mutableStateOf<List<DownloadRecord>>(listOf())
|
||||
|
||||
fun getDownloadRecordList() {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
delay(100) //TEMP
|
||||
downloadRecordList = sharedViewModel.databaseDAO.getRecord()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class HomeCategory {
|
||||
About, History
|
||||
}
|
@ -1,111 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.home
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.example.jetcaster.util.verticalGradientScrim
|
||||
import com.shabinder.spotiflyer.MainActivity
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.SharedViewModel
|
||||
import com.shabinder.spotiflyer.navigation.ComposeNavigation
|
||||
import com.shabinder.spotiflyer.ui.appNameStyle
|
||||
import dev.chrisbanes.accompanist.insets.statusBarsHeight
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
modifier: Modifier,
|
||||
mainActivity: MainActivity,
|
||||
sharedViewModel: SharedViewModel,
|
||||
navController: NavHostController,
|
||||
topPadding: Dp = 0.dp
|
||||
){
|
||||
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.65f)
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize().verticalGradientScrim(
|
||||
color = sharedViewModel.gradientColor.copy(alpha = 0.38f),
|
||||
startYPercentage = 0.29f,
|
||||
endYPercentage = 0f,
|
||||
)
|
||||
) {
|
||||
// Draw a scrim over the status bar which matches the app bar
|
||||
Spacer(
|
||||
Modifier.background(appBarColor).fillMaxWidth()
|
||||
.statusBarsHeight()
|
||||
)
|
||||
AppBar(
|
||||
backgroundColor = appBarColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
//Space for Animation
|
||||
Spacer(Modifier.padding(top = topPadding))
|
||||
ComposeNavigation(
|
||||
mainActivity,
|
||||
navController,
|
||||
sharedViewModel.spotifyProvider,
|
||||
sharedViewModel.gaanaProvider,
|
||||
sharedViewModel.youtubeProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TopAppBar(
|
||||
backgroundColor = backgroundColor,
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
imageVector = vectorResource(R.drawable.ic_spotiflyer_logo),
|
||||
Modifier.preferredSize(32.dp)
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
Text(
|
||||
text = "SpotiFlyer",
|
||||
style = appNameStyle
|
||||
)
|
||||
}
|
||||
},
|
||||
/*actions = {
|
||||
Providers(AmbientContentAlpha provides ContentAlpha.medium) {
|
||||
IconButton(
|
||||
onClick = { *//* TODO: Open Preferences*//* }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, tint = Color.Gray)
|
||||
}
|
||||
}
|
||||
},*/
|
||||
modifier = modifier,
|
||||
elevation = 0.dp
|
||||
)
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.splash
|
||||
package com.shabinder.common.ui.splash
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
@ -27,14 +27,9 @@ import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.shabinder.spotiflyer.R
|
||||
import com.shabinder.spotiflyer.ui.SpotiFlyerTypography
|
||||
import com.shabinder.spotiflyer.ui.colorAccent
|
||||
import com.shabinder.spotiflyer.ui.colorPrimary
|
||||
import com.shabinder.common.ui.*
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val SplashWaitTime: Long = 1100
|
||||
@ -49,7 +44,7 @@ fun Splash(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
|
||||
delay(SplashWaitTime)
|
||||
currentOnTimeout()
|
||||
}
|
||||
Image(imageVector = vectorResource(id = R.drawable.ic_spotiflyer_logo))
|
||||
Image(imageVector = SpotiFlyerLogo())
|
||||
MadeInIndia(Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
@ -67,15 +62,15 @@ fun MadeInIndia(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.made_with_love),
|
||||
text = "Made with ",
|
||||
color = colorPrimary,
|
||||
fontSize = 22.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(start = 4.dp))
|
||||
Icon(vectorResource(id = R.drawable.ic_heart),tint = Color.Unspecified)
|
||||
Icon(HeartIcon(),tint = Color.Unspecified)
|
||||
Spacer(modifier = Modifier.padding(start = 4.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.in_india),
|
||||
text = " in India",
|
||||
color = colorPrimary,
|
||||
fontSize = 22.sp
|
||||
)
|
||||
|
@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.utils
|
||||
package com.shabinder.common.ui.utils
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
|
@ -1,87 +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/>.
|
||||
*/
|
||||
|
||||
package com.shabinder.spotiflyer.ui.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.palette.graphics.Palette
|
||||
import coil.Coil
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.size.Scale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Immutable
|
||||
data class DominantColors(val color: Color, val onColor: Color)
|
||||
|
||||
|
||||
suspend fun calculateDominantColor(url: String,ctx:Context): DominantColors? {
|
||||
// we calculate the swatches in the image, and return the first valid color
|
||||
return calculateSwatchesInImage(ctx, url)
|
||||
// First we want to sort the list by the color's population
|
||||
.sortedByDescending { swatch -> swatch.population }
|
||||
// Then we want to find the first valid color
|
||||
.firstOrNull { swatch -> Color(swatch.rgb).contrastAgainst(Color.Black) >= 3f }
|
||||
// If we found a valid swatch, wrap it in a [DominantColors]
|
||||
?.let { swatch ->
|
||||
DominantColors(
|
||||
color = Color(swatch.rgb),
|
||||
onColor = Color(swatch.bodyTextColor).copy(alpha = 1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the given [imageUrl] with [Coil], then uses [Palette] to calculate the dominant color.
|
||||
*/
|
||||
suspend fun calculateSwatchesInImage(
|
||||
context: Context,
|
||||
imageUrl: String
|
||||
): List<Palette.Swatch> {
|
||||
val r = ImageRequest.Builder(context)
|
||||
.data(imageUrl)
|
||||
// We scale the image to cover 128px x 128px (i.e. min dimension == 128px)
|
||||
.size(128).scale(Scale.FILL)
|
||||
// Disable hardware bitmaps, since Palette uses Bitmap.getPixels()
|
||||
.allowHardware(false)
|
||||
.build()
|
||||
|
||||
val bitmap = when (val result = Coil.execute(r)) {
|
||||
is SuccessResult -> result.drawable.toBitmap()
|
||||
else -> null
|
||||
}
|
||||
|
||||
return bitmap?.let {
|
||||
withContext(Dispatchers.Default) {
|
||||
val palette = Palette.Builder(bitmap)
|
||||
// Disable any bitmap resizing in Palette. We've already loaded an appropriately
|
||||
// sized bitmap through Coil
|
||||
.resizeBitmapArea(0)
|
||||
// Clear any built-in filters. We want the unfiltered dominant color
|
||||
.clearFilters()
|
||||
// We reduce the maximum color count down to 8
|
||||
.maximumColorCount(8)
|
||||
.generate()
|
||||
|
||||
palette.swatches
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
@ -14,9 +14,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.example.jetcaster.util
|
||||
package com.shabinder.common.ui.utils
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@ -41,8 +40,10 @@ import kotlin.math.pow
|
||||
*/
|
||||
fun Modifier.verticalGradientScrim(
|
||||
color: Color,
|
||||
@FloatRange(from = 0.0, to = 1.0) startYPercentage: Float = 0f,
|
||||
@FloatRange(from = 0.0, to = 1.0) endYPercentage: Float = 1f,
|
||||
//@FloatRange(from = 0.0, to = 1.0)
|
||||
startYPercentage: Float = 0f,
|
||||
//@FloatRange(from = 0.0, to = 1.0)
|
||||
endYPercentage: Float = 1f,
|
||||
decay: Float = 1.0f,
|
||||
numStops: Int = 16,
|
||||
fixedHeight: Float? = null
|
||||
|
@ -32,3 +32,24 @@ actual fun DownloadImageArrow(modifier: Modifier){
|
||||
|
||||
@Composable
|
||||
actual fun DownloadAllImage():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_download_arrow.xml")
|
||||
|
||||
@Composable
|
||||
actual fun SpotiFlyerLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_spotiflyer_logo.xml")
|
||||
|
||||
@Composable
|
||||
actual fun HeartIcon():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_heart.xml")
|
||||
|
||||
@Composable
|
||||
actual fun SpotifyLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_spotify_logo.xml")
|
||||
|
||||
@Composable
|
||||
actual fun YoutubeLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_youtube.xml")
|
||||
|
||||
@Composable
|
||||
actual fun GaanaLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_gaana.xml")
|
||||
|
||||
@Composable
|
||||
actual fun YoutubeMusicLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_youtube_music_logo.xml")
|
||||
|
||||
@Composable
|
||||
actual fun GithubLogo():ImageVector = vectorXmlResource("common/compose-ui/src/main/res/drawable/ic_github.xml")
|
||||
|
21
common/compose-ui/src/main/res/drawable/ic_github.xml
Normal file
21
common/compose-ui/src/main/res/drawable/ic_github.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<!--
|
||||
~ 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" android:width="38dp"
|
||||
android:height="38dp" android:viewportWidth="512" android:viewportHeight="512">
|
||||
<path android:fillColor="#4EA4FF" android:pathData="M255.968,5.329C114.624,5.329 0,120.401 0,262.353c0,113.536 73.344,209.856 175.104,243.872c12.8,2.368 17.472,-5.568 17.472,-12.384c0,-6.112 -0.224,-22.272 -0.352,-43.712c-71.2,15.52 -86.24,-34.464 -86.24,-34.464c-11.616,-29.696 -28.416,-37.6 -28.416,-37.6c-23.264,-15.936 1.728,-15.616 1.728,-15.616c25.696,1.824 39.2,26.496 39.2,26.496c22.848,39.264 59.936,27.936 74.528,21.344c2.304,-16.608 8.928,-27.936 16.256,-34.368c-56.832,-6.496 -116.608,-28.544 -116.608,-127.008c0,-28.064 9.984,-51.008 26.368,-68.992c-2.656,-6.496 -11.424,-32.64 2.496,-68c0,0 21.504,-6.912 70.4,26.336c20.416,-5.696 42.304,-8.544 64.096,-8.64c21.728,0.128 43.648,2.944 64.096,8.672c48.864,-33.248 70.336,-26.336 70.336,-26.336c13.952,35.392 5.184,61.504 2.56,68c16.416,17.984 26.304,40.928 26.304,68.992c0,98.72 -59.84,120.448 -116.864,126.816c9.184,7.936 17.376,23.616 17.376,47.584c0,34.368 -0.32,62.08 -0.32,70.496c0,6.88 4.608,14.88 17.6,12.352C438.72,472.145 512,375.857 512,262.353C512,120.401 397.376,5.329 255.968,5.329z"/>
|
||||
</vector>
|
||||
|
BIN
common/compose-ui/src/main/res/font/montserrat_light.ttf
Normal file
BIN
common/compose-ui/src/main/res/font/montserrat_light.ttf
Normal file
Binary file not shown.
BIN
common/compose-ui/src/main/res/font/montserrat_medium.ttf
Normal file
BIN
common/compose-ui/src/main/res/font/montserrat_medium.ttf
Normal file
Binary file not shown.
BIN
common/compose-ui/src/main/res/font/montserrat_regular.ttf
Normal file
BIN
common/compose-ui/src/main/res/font/montserrat_regular.ttf
Normal file
Binary file not shown.
BIN
common/compose-ui/src/main/res/font/montserrat_semibold.ttf
Normal file
BIN
common/compose-ui/src/main/res/font/montserrat_semibold.ttf
Normal file
Binary file not shown.
BIN
common/compose-ui/src/main/res/font/pristine_script.ttf
Normal file
BIN
common/compose-ui/src/main/res/font/pristine_script.ttf
Normal file
Binary file not shown.
@ -7,13 +7,16 @@ import android.os.Environment
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
import com.shabinder.common.database.appContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.*
|
||||
import java.lang.Exception
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
actual open class Dir actual constructor(
|
||||
actual class Dir actual constructor(
|
||||
private val logger: Kermit
|
||||
) {
|
||||
|
||||
@ -53,7 +56,7 @@ actual open class Dir actual constructor(
|
||||
File(imageCacheDir()).deleteRecursively()
|
||||
}
|
||||
|
||||
actual fun cacheImage(picture: Picture) {
|
||||
actual suspend fun cacheImage(picture: Picture) {
|
||||
try {
|
||||
val path = imageCacheDir() + picture.name
|
||||
FileOutputStream(path).use { out ->
|
||||
@ -92,9 +95,10 @@ actual open class Dir actual constructor(
|
||||
.setId3v2TagsAndSaveFile(trackDetails,path)
|
||||
}
|
||||
|
||||
actual fun loadImage(url: String, cachePath: String):Picture? {
|
||||
actual fun loadImage(url: String):Picture? {
|
||||
val cachePath = imageCacheDir() + getNameURL(url)
|
||||
var picture: Picture? = loadCachedImage(cachePath)
|
||||
if (picture == null) picture = freshImage(url,cachePath)
|
||||
if (picture == null) picture = freshImage(url)
|
||||
return picture
|
||||
}
|
||||
|
||||
@ -129,7 +133,7 @@ actual open class Dir actual constructor(
|
||||
null
|
||||
}
|
||||
}
|
||||
private fun freshImage(url:String,cachePath: String):Picture?{
|
||||
private fun freshImage(url:String):Picture?{
|
||||
return try {
|
||||
val source = URL(url)
|
||||
val connection: HttpURLConnection = source.openConnection() as HttpURLConnection
|
||||
@ -147,8 +151,9 @@ actual open class Dir actual constructor(
|
||||
result.width,
|
||||
result.height
|
||||
)
|
||||
|
||||
cacheImage(picture)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
cacheImage(picture)
|
||||
}
|
||||
picture
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
|
@ -32,6 +32,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
|
||||
single { SpotifyProvider(get(),get(),get(),get()) }
|
||||
single { GaanaProvider(get(),get(),get(),get()) }
|
||||
single { YoutubeProvider(get(),get(),get(),get()) }
|
||||
single { FetchPlatformQueryResult(get(),get(),get(),get()) }
|
||||
single { createHttpClient(enableNetworkLogs = enableNetworkLogs) }
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
expect open class Dir(
|
||||
expect class Dir(
|
||||
logger: Kermit,
|
||||
) {
|
||||
fun isPresent(path:String):Boolean
|
||||
@ -18,7 +18,7 @@ expect open class Dir(
|
||||
fun imageCacheDir(): String
|
||||
fun createDirectory(dirPath:String)
|
||||
suspend fun cacheImage(picture: Picture)
|
||||
fun loadImage(url:String, cachePath:String = imageCacheDir() + getNameURL(url)):Picture?
|
||||
fun loadImage(url:String):Picture?
|
||||
suspend fun clearCache()
|
||||
suspend fun saveFileWithMetadata(mp3ByteArray: ByteArray, path: String, trackDetails: TrackDetails)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
actual open class Dir actual constructor(private val logger: Kermit) {
|
||||
actual class Dir actual constructor(private val logger: Kermit) {
|
||||
|
||||
actual fun fileSeparator(): String = File.separator
|
||||
|
||||
@ -85,7 +85,8 @@ actual open class Dir actual constructor(private val logger: Kermit) {
|
||||
.setId3v2TagsAndSaveFile(trackDetails,path)
|
||||
}
|
||||
|
||||
actual fun loadImage(url: String, cachePath: String):Picture? {
|
||||
actual fun loadImage(url: String):Picture? {
|
||||
val cachePath = imageCacheDir() + getNameURL(url)
|
||||
var picture: Picture? = loadCachedImage(cachePath)
|
||||
if (picture == null) picture = freshImage(url,cachePath)
|
||||
return picture
|
||||
|
Loading…
Reference in New Issue
Block a user