mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04:32 +01:00
Web App Changes and Fixes
This commit is contained in:
parent
90b99cbb51
commit
d4d3835f8d
@ -0,0 +1,7 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
sealed class AllPlatforms{
|
||||||
|
object Js:AllPlatforms()
|
||||||
|
object Jvm:AllPlatforms()
|
||||||
|
object Native:AllPlatforms()
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.shabinder.common.models
|
||||||
|
|
||||||
|
sealed class CorsProxy(open val url: String){
|
||||||
|
data class SelfHostedCorsProxy(override val url:String = "https://kind-grasshopper-73.telebit.io/cors/"):CorsProxy(url)
|
||||||
|
data class PublicProxyWithExtension(override val url:String = "https://cors.bridged.cc/"):CorsProxy(url)
|
||||||
|
|
||||||
|
fun toggle(mode:CorsProxy? = null):CorsProxy{
|
||||||
|
mode?.let {
|
||||||
|
corsProxy = mode
|
||||||
|
return corsProxy
|
||||||
|
}
|
||||||
|
corsProxy = when(corsProxy){
|
||||||
|
is SelfHostedCorsProxy -> PublicProxyWithExtension()
|
||||||
|
is PublicProxyWithExtension -> SelfHostedCorsProxy()
|
||||||
|
}
|
||||||
|
return corsProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extensionMode():Boolean{
|
||||||
|
return when(corsProxy){
|
||||||
|
is SelfHostedCorsProxy -> false
|
||||||
|
is PublicProxyWithExtension -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Var Keeps Track for Cors Config in JS Platform
|
||||||
|
* Default Self Hosted, However ask user to use extension if possible.
|
||||||
|
* */
|
||||||
|
var corsProxy:CorsProxy = CorsProxy.SelfHostedCorsProxy()
|
@ -11,6 +11,7 @@ import com.github.kiulian.downloader.model.quality.AudioQuality
|
|||||||
import com.razorpay.Checkout
|
import com.razorpay.Checkout
|
||||||
import com.shabinder.common.database.activityContext
|
import com.shabinder.common.database.activityContext
|
||||||
import com.shabinder.common.di.worker.ForegroundService
|
import com.shabinder.common.di.worker.ForegroundService
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -30,6 +31,7 @@ actual fun openPlatform(packageID:String, platformLink:String){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual val dispatcherIO = Dispatchers.IO
|
actual val dispatcherIO = Dispatchers.IO
|
||||||
|
actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm
|
||||||
|
|
||||||
actual val isInternetAvailable:Boolean
|
actual val isInternetAvailable:Boolean
|
||||||
get() = internetAvailability.value ?: true
|
get() = internetAvailability.value ?: true
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
|
||||||
@ -13,6 +14,8 @@ expect val dispatcherIO: CoroutineDispatcher
|
|||||||
|
|
||||||
expect val isInternetAvailable:Boolean
|
expect val isInternetAvailable:Boolean
|
||||||
|
|
||||||
|
expect val currentPlatform: AllPlatforms
|
||||||
|
|
||||||
expect suspend fun downloadTracks(
|
expect suspend fun downloadTracks(
|
||||||
list: List<TrackDetails>,
|
list: List<TrackDetails>,
|
||||||
fetcher: FetchPlatformQueryResult,
|
fetcher: FetchPlatformQueryResult,
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
package com.shabinder.common.di.gaana
|
package com.shabinder.common.di.gaana
|
||||||
|
|
||||||
|
import com.shabinder.common.di.currentPlatform
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
|
import com.shabinder.common.models.corsProxy
|
||||||
import com.shabinder.common.models.gaana.*
|
import com.shabinder.common.models.gaana.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
|
|
||||||
|
val corsApi get() = if(currentPlatform is AllPlatforms.Js){
|
||||||
|
corsProxy.url
|
||||||
|
} // "https://spotiflyer-cors.azurewebsites.net/" //"https://spotiflyer-cors.herokuapp.com/"//"https://cors.bridged.cc/"
|
||||||
|
else ""
|
||||||
|
|
||||||
private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990"
|
private const val TOKEN = "b2e6d7fbc136547a940516e9b77e5990"
|
||||||
private const val BASE_URL = "https://api.gaana.com/"
|
private val BASE_URL get() = "${corsApi}https://api.gaana.com"
|
||||||
|
|
||||||
interface GaanaRequests {
|
interface GaanaRequests {
|
||||||
|
|
||||||
@ -76,6 +84,7 @@ interface GaanaRequests {
|
|||||||
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format"
|
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Api Request: http://api.gaana.com/?type=artist&subtype=artist_track_listing&seokey=neha-kakkar&limit=50&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
* Api Request: http://api.gaana.com/?type=artist&subtype=artist_track_listing&seokey=neha-kakkar&limit=50&token=b2e6d7fbc136547a940516e9b77e5990&format=JSON
|
||||||
*
|
*
|
||||||
|
@ -2,7 +2,11 @@ package com.shabinder.common.di.providers
|
|||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.Dir
|
import com.shabinder.common.di.Dir
|
||||||
|
import com.shabinder.common.di.currentPlatform
|
||||||
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
|
import com.shabinder.common.di.youtubeMp3.Yt1sMp3
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
|
import com.shabinder.common.models.CorsProxy
|
||||||
|
import com.shabinder.common.models.corsProxy
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
|
|
||||||
@ -11,5 +15,9 @@ class YoutubeMp3(
|
|||||||
private val logger: Kermit,
|
private val logger: Kermit,
|
||||||
private val dir: Dir,
|
private val dir: Dir,
|
||||||
):Yt1sMp3 {
|
):Yt1sMp3 {
|
||||||
suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID)
|
suspend fun getMp3DownloadLink(videoID:String):String? = getLinkFromYt1sMp3(videoID)?.let{
|
||||||
|
println("Is Self Hosted"+(corsProxy is CorsProxy.SelfHostedCorsProxy))
|
||||||
|
if (currentPlatform is AllPlatforms.Js && corsProxy !is CorsProxy.PublicProxyWithExtension) "https://kind-grasshopper-73.telebit.io/cors/$it"
|
||||||
|
else it
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.shabinder.common.di.providers
|
package com.shabinder.common.di.providers
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.common.models.YoutubeTrack
|
import com.shabinder.common.models.YoutubeTrack
|
||||||
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
import com.willowtreeapps.fuzzywuzzy.diffutils.FuzzySearch
|
||||||
@ -246,7 +247,7 @@ class YoutubeMusic constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getYoutubeMusicResponse(query: String):String{
|
private suspend fun getYoutubeMusicResponse(query: String):String{
|
||||||
return httpClient.post("https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){
|
return httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey"){
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
headers{
|
headers{
|
||||||
append("referer","https://music.youtube.com/search")
|
append("referer","https://music.youtube.com/search")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.di.spotify
|
package com.shabinder.common.di.spotify
|
||||||
|
|
||||||
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
import com.shabinder.common.models.spotify.Album
|
import com.shabinder.common.models.spotify.Album
|
||||||
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
|
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
|
||||||
import com.shabinder.common.models.spotify.Playlist
|
import com.shabinder.common.models.spotify.Playlist
|
||||||
@ -7,7 +8,7 @@ import com.shabinder.common.models.spotify.Track
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
|
|
||||||
private const val BASE_URL = "https://api.spotify.com/v1"
|
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"
|
||||||
|
|
||||||
interface SpotifyRequests {
|
interface SpotifyRequests {
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.di.youtubeMp3
|
package com.shabinder.common.di.youtubeMp3
|
||||||
|
|
||||||
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
@ -19,14 +20,14 @@ interface Yt1sMp3 {
|
|||||||
* Downloadable Mp3 Link for YT videoID.
|
* Downloadable Mp3 Link for YT videoID.
|
||||||
* */
|
* */
|
||||||
suspend fun getLinkFromYt1sMp3(videoID: String):String? =
|
suspend fun getLinkFromYt1sMp3(videoID: String):String? =
|
||||||
getConvertedMp3Link(videoID,getKey(videoID))?.get("dlink")?.jsonPrimitive?.toString()?.replace("\"", "");
|
getConvertedMp3Link(videoID,getKey(videoID))?.get("dlink")?.jsonPrimitive?.toString()?.replace("\"", "")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* POST:https://yt1s.com/api/ajaxSearch/index
|
* POST:https://yt1s.com/api/ajaxSearch/index
|
||||||
* Body Form= q:yt video link ,vt:format=mp3
|
* Body Form= q:yt video link ,vt:format=mp3
|
||||||
* */
|
* */
|
||||||
private suspend fun getKey(videoID:String):String{
|
private suspend fun getKey(videoID:String):String{
|
||||||
val response:JsonObject? = httpClient.post("https://yt1s.com/api/ajaxSearch/index"){
|
val response:JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index"){
|
||||||
body = FormDataContent(Parameters.build {
|
body = FormDataContent(Parameters.build {
|
||||||
append("q","https://www.youtube.com/watch?v=$videoID")
|
append("q","https://www.youtube.com/watch?v=$videoID")
|
||||||
append("vt","mp3")
|
append("vt","mp3")
|
||||||
@ -36,7 +37,7 @@ interface Yt1sMp3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getConvertedMp3Link(videoID: String,key:String):JsonObject?{
|
private suspend fun getConvertedMp3Link(videoID: String,key:String):JsonObject?{
|
||||||
return httpClient.post("https://yt1s.com/api/ajaxConvert/convert"){
|
return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert"){
|
||||||
body = FormDataContent(Parameters.build {
|
body = FormDataContent(Parameters.build {
|
||||||
append("vid", videoID)
|
append("vid", videoID)
|
||||||
append("k",key)
|
append("k",key)
|
||||||
|
@ -4,6 +4,7 @@ import com.github.kiulian.downloader.YoutubeDownloader
|
|||||||
import com.github.kiulian.downloader.model.YoutubeVideo
|
import com.github.kiulian.downloader.model.YoutubeVideo
|
||||||
import com.github.kiulian.downloader.model.formats.Format
|
import com.github.kiulian.downloader.model.formats.Format
|
||||||
import com.github.kiulian.downloader.model.quality.AudioQuality
|
import com.github.kiulian.downloader.model.quality.AudioQuality
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
@ -18,6 +19,7 @@ import kotlinx.coroutines.withContext
|
|||||||
actual fun openPlatform(packageID:String, platformLink:String){
|
actual fun openPlatform(packageID:String, platformLink:String){
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
actual val currentPlatform: AllPlatforms = AllPlatforms.Jvm
|
||||||
|
|
||||||
actual val dispatcherIO = Dispatchers.IO
|
actual val dispatcherIO = Dispatchers.IO
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
@ -8,6 +9,8 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
|
||||||
|
actual val currentPlatform:AllPlatforms = AllPlatforms.Js
|
||||||
|
|
||||||
actual fun openPlatform(packageID:String, platformLink:String){
|
actual fun openPlatform(packageID:String, platformLink:String){
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
@ -87,7 +90,6 @@ suspend fun downloadTrack(videoID: String, track: TrackDetails, fetcher:FetchPla
|
|||||||
when(it){
|
when(it){
|
||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
println("Download Completed")
|
println("Download Completed")
|
||||||
allTracksStatus[track.title] = DownloadStatus.Downloaded
|
|
||||||
dir.saveFileWithMetadata(it.byteArray, track)
|
dir.saveFileWithMetadata(it.byteArray, track)
|
||||||
}
|
}
|
||||||
is DownloadResult.Error -> {
|
is DownloadResult.Error -> {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package com.shabinder.common.di
|
package com.shabinder.common.di
|
||||||
|
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
|
import com.shabinder.common.di.gaana.corsApi
|
||||||
import com.shabinder.common.models.DownloadResult
|
import com.shabinder.common.models.DownloadResult
|
||||||
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.database.Database
|
import com.shabinder.database.Database
|
||||||
import kotlinext.js.Object
|
import kotlinext.js.Object
|
||||||
@ -50,7 +52,7 @@ actual class Dir actual constructor(
|
|||||||
trackDetails: TrackDetails
|
trackDetails: TrackDetails
|
||||||
) {
|
) {
|
||||||
val writer = ID3Writer(mp3ByteArray.toArrayBuffer())
|
val writer = ID3Writer(mp3ByteArray.toArrayBuffer())
|
||||||
val albumArt = downloadFile(trackDetails.albumArtURL)
|
val albumArt = downloadFile(corsApi+trackDetails.albumArtURL)
|
||||||
albumArt.collect {
|
albumArt.collect {
|
||||||
when(it){
|
when(it){
|
||||||
is DownloadResult.Success -> {
|
is DownloadResult.Success -> {
|
||||||
@ -83,7 +85,9 @@ actual class Dir actual constructor(
|
|||||||
albumArt?.let { setFrame("APIC", it) }
|
albumArt?.let { setFrame("APIC", it) }
|
||||||
}
|
}
|
||||||
writer.addTag()
|
writer.addTag()
|
||||||
|
allTracksStatus[trackDetails.title] = DownloadStatus.Downloaded
|
||||||
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
|
saveAs(writer.getBlob(), "${removeIllegalChars(trackDetails.title)}.mp3")
|
||||||
|
DownloadProgressFlow.emit(allTracksStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun addToLibrary(path:String){}
|
actual fun addToLibrary(path:String){}
|
||||||
|
@ -42,6 +42,7 @@ interface SpotiFlyerMain {
|
|||||||
val dir: Dir
|
val dir: Dir
|
||||||
val showPopUpMessage:(String)->Unit
|
val showPopUpMessage:(String)->Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Output {
|
sealed class Output {
|
||||||
data class Search(val link: String) : Output()
|
data class Search(val link: String) : Output()
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,5 @@ package com.shabinder.common.root.callbacks
|
|||||||
|
|
||||||
interface SpotiFlyerRootCallBacks {
|
interface SpotiFlyerRootCallBacks {
|
||||||
fun searchLink(link:String)
|
fun searchLink(link:String)
|
||||||
|
fun popBackToHomeScreen()
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ internal class SpotiFlyerRootImpl(
|
|||||||
|
|
||||||
override val callBacks = object : SpotiFlyerRootCallBacks{
|
override val callBacks = object : SpotiFlyerRootCallBacks{
|
||||||
override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link))
|
override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link))
|
||||||
|
override fun popBackToHomeScreen() {
|
||||||
|
router.popWhile {
|
||||||
|
it !is Configuration.Main
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =
|
private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child =
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
|
import com.shabinder.common.di.currentPlatform
|
||||||
import com.shabinder.common.main.SpotiFlyerMain
|
import com.shabinder.common.main.SpotiFlyerMain
|
||||||
import com.shabinder.common.main.SpotiFlyerMain.State
|
import com.shabinder.common.main.SpotiFlyerMain.State
|
||||||
|
import com.shabinder.common.models.AllPlatforms
|
||||||
import extras.RenderableComponent
|
import extras.RenderableComponent
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
@ -19,7 +21,6 @@ class HomeScreen(
|
|||||||
override val stateFlow: Flow<SpotiFlyerMain.State> = model.models
|
override val stateFlow: Flow<SpotiFlyerMain.State> = model.models
|
||||||
|
|
||||||
override fun RBuilder.render() {
|
override fun RBuilder.render() {
|
||||||
println("Rendering New State = \"${state.data}\" ")
|
|
||||||
styledDiv{
|
styledDiv{
|
||||||
css {
|
css {
|
||||||
display = Display.flex
|
display = Display.flex
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
import kotlinx.html.InputType
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.js.onChangeFunction
|
import kotlinx.html.js.onChangeFunction
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
|
import kotlinx.html.js.onKeyDownFunction
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.Window
|
||||||
import react.*
|
import react.*
|
||||||
import styled.*
|
import styled.*
|
||||||
|
|
||||||
@ -34,6 +37,12 @@ val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props ->
|
|||||||
val target = it.target as HTMLInputElement
|
val target = it.target as HTMLInputElement
|
||||||
props.onLinkChange(target.value)
|
props.onLinkChange(target.value)
|
||||||
}
|
}
|
||||||
|
this.onKeyDownFunction = {
|
||||||
|
if(it.asDynamic().key == "Enter") {
|
||||||
|
if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
|
||||||
|
else props.search(props.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
value = props.link
|
value = props.link
|
||||||
}
|
}
|
||||||
css {
|
css {
|
||||||
@ -43,7 +52,8 @@ val searchbar = functionalComponent<SearchbarProps>("SearchBar"){ props ->
|
|||||||
styledButton {
|
styledButton {
|
||||||
attrs {
|
attrs {
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
props.search(props.link)
|
if(props.link.isEmpty()) window.alert("Enter a Link from Supported Platforms")
|
||||||
|
else props.search(props.link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
css {
|
css {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import kotlinx.css.px
|
import kotlinx.css.*
|
||||||
import kotlinx.css.width
|
|
||||||
import react.*
|
import react.*
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
@ -31,8 +30,11 @@ private val circularProgressBar = functionalComponent<CircularProgressBarProps>(
|
|||||||
styledDiv{ css { classes = mutableListOf("value-bar") } }
|
styledDiv{ css { classes = mutableListOf("value-bar") } }
|
||||||
}
|
}
|
||||||
css{
|
css{
|
||||||
|
display = Display.flex
|
||||||
|
justifyContent = JustifyContent.center
|
||||||
classes = mutableListOf("progress-circle","p${props.progress}").apply { if(props.progress>50) add("over50") }
|
classes = mutableListOf("progress-circle","p${props.progress}").apply { if(props.progress>50) add("over50") }
|
||||||
width = 50.px
|
width = 50.px
|
||||||
|
marginBottom = 65.px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package list
|
|||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.*
|
||||||
import styled.css
|
import styled.css
|
||||||
import styled.styledDiv
|
import styled.styledDiv
|
||||||
@ -10,6 +11,8 @@ import styled.styledImg
|
|||||||
|
|
||||||
external interface DownloadAllButtonProps : RProps {
|
external interface DownloadAllButtonProps : RProps {
|
||||||
var isActive:Boolean
|
var isActive:Boolean
|
||||||
|
var link : String
|
||||||
|
var downloadAll:()->Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
@ -22,37 +25,65 @@ fun RBuilder.DownloadAllButton(handler: DownloadAllButtonProps.() -> Unit): Reac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val downloadAllButton = functionalComponent<DownloadAllButtonProps>("DownloadAllButton") { props->
|
private val downloadAllButton = functionalComponent<DownloadAllButtonProps>("DownloadAllButton") { props->
|
||||||
styledDiv {
|
|
||||||
styledDiv {
|
|
||||||
|
|
||||||
styledImg(src = "download.svg",alt = "Download All Button") {
|
val (isClicked,setClicked) = useState(false)
|
||||||
|
|
||||||
|
useEffect(mutableListOf(props.link)){
|
||||||
|
setClicked(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(props.isActive){
|
||||||
|
if(isClicked) {
|
||||||
|
styledDiv{
|
||||||
css {
|
css {
|
||||||
classes = mutableListOf("download-all-icon")
|
display = Display.flex
|
||||||
height = 32.px
|
alignItems = Align.center
|
||||||
|
justifyContent = JustifyContent.center
|
||||||
|
height = 52.px
|
||||||
}
|
}
|
||||||
}
|
LoadingSpinner { }
|
||||||
|
|
||||||
styledH5 {
|
|
||||||
attrs {
|
|
||||||
id = "download-all-text"
|
|
||||||
}
|
|
||||||
+ "Download All"
|
|
||||||
css {
|
|
||||||
whiteSpace = WhiteSpace.nowrap
|
|
||||||
fontSize = 15.px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
css {
|
|
||||||
classes = mutableListOf("download-icon")
|
|
||||||
display = Display.flex
|
|
||||||
alignItems = Align.center
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
css {
|
else{
|
||||||
classes = mutableListOf("download-button")
|
styledDiv {
|
||||||
display = if(props.isActive) Display.flex else Display.none
|
attrs {
|
||||||
alignItems = Align.center
|
onClickFunction = {
|
||||||
|
//props.downloadAll()
|
||||||
|
setClicked(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styledDiv {
|
||||||
|
|
||||||
|
styledImg(src = "download.svg",alt = "Download All Button") {
|
||||||
|
css {
|
||||||
|
classes = mutableListOf("download-all-icon")
|
||||||
|
height = 32.px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styledH5 {
|
||||||
|
attrs {
|
||||||
|
id = "download-all-text"
|
||||||
|
}
|
||||||
|
+ "Download All"
|
||||||
|
css {
|
||||||
|
whiteSpace = WhiteSpace.nowrap
|
||||||
|
fontSize = 15.px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
css {
|
||||||
|
classes = mutableListOf("download-icon")
|
||||||
|
display = Display.flex
|
||||||
|
alignItems = Align.center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
css {
|
||||||
|
classes = mutableListOf("download-button")
|
||||||
|
display = Display.flex
|
||||||
|
alignItems = Align.center
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,11 @@ class ListScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DownloadAllButton {
|
DownloadAllButton {
|
||||||
isActive = state.data.trackList.isNotEmpty()
|
isActive = state.data.trackList.size > 1
|
||||||
|
downloadAll = {
|
||||||
|
model.onDownloadAllClicked(state.data.trackList)
|
||||||
|
}
|
||||||
|
link = state.data.link
|
||||||
}
|
}
|
||||||
|
|
||||||
styledDiv{
|
styledDiv{
|
||||||
|
@ -22,9 +22,12 @@ fun RBuilder.TrackItem(handler: TrackItemProps.() -> Unit): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props ->
|
private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props ->
|
||||||
|
val (downloadStatus,setDownloadStatus) = useState(props.details.downloaded)
|
||||||
val details = props.details
|
val details = props.details
|
||||||
|
useEffect(listOf(props.details)){
|
||||||
|
setDownloadStatus(props.details.downloaded)
|
||||||
|
}
|
||||||
styledDiv {
|
styledDiv {
|
||||||
|
|
||||||
styledImg(src = details.albumArtURL) {
|
styledImg(src = details.albumArtURL) {
|
||||||
css {
|
css {
|
||||||
height = 90.px
|
height = 90.px
|
||||||
@ -89,20 +92,23 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
|||||||
whiteSpace = WhiteSpace.nowrap
|
whiteSpace = WhiteSpace.nowrap
|
||||||
overflow = Overflow.hidden
|
overflow = Overflow.hidden
|
||||||
}
|
}
|
||||||
+ details.durationSec.toString()
|
+ "${details.durationSec/60} min, ${details.durationSec%60} sec"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when(details.downloaded){
|
when(downloadStatus){
|
||||||
is DownloadStatus.NotDownloaded ->{
|
is DownloadStatus.NotDownloaded ->{
|
||||||
DownloadButton {
|
DownloadButton {
|
||||||
onClick = { props.downloadTrack(details) }
|
onClick = {
|
||||||
status = details.downloaded
|
setDownloadStatus(DownloadStatus.Queued)
|
||||||
|
props.downloadTrack(details)
|
||||||
|
}
|
||||||
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DownloadStatus.Downloading -> {
|
is DownloadStatus.Downloading -> {
|
||||||
CircularProgressBar {
|
CircularProgressBar {
|
||||||
progress = (details.downloaded as DownloadStatus.Downloading).progress
|
progress = downloadStatus.progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadStatus.Queued -> {
|
DownloadStatus.Queued -> {
|
||||||
@ -111,7 +117,7 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
|||||||
DownloadStatus.Downloaded -> {
|
DownloadStatus.Downloaded -> {
|
||||||
DownloadButton {
|
DownloadButton {
|
||||||
onClick = {}
|
onClick = {}
|
||||||
status = details.downloaded
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DownloadStatus.Converting -> {
|
DownloadStatus.Converting -> {
|
||||||
@ -120,7 +126,7 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
|||||||
DownloadStatus.Failed -> {
|
DownloadStatus.Failed -> {
|
||||||
DownloadButton {
|
DownloadButton {
|
||||||
onClick = {}
|
onClick = {}
|
||||||
status = details.downloaded
|
status = downloadStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +134,7 @@ private val trackItem = functionalComponent<TrackItemProps>("Track-Item"){ props
|
|||||||
css {
|
css {
|
||||||
alignItems = Align.center
|
alignItems = Align.center
|
||||||
display =Display.flex
|
display =Display.flex
|
||||||
flexGrow = 1.0
|
paddingRight = 16.px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ package navbar
|
|||||||
|
|
||||||
import kotlinx.css.*
|
import kotlinx.css.*
|
||||||
import kotlinx.html.id
|
import kotlinx.html.id
|
||||||
|
import kotlinx.html.js.onBlurFunction
|
||||||
|
import kotlinx.html.js.onClickFunction
|
||||||
import react.*
|
import react.*
|
||||||
import styled.*
|
import styled.*
|
||||||
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
|
fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
|
||||||
return child(navBar){
|
return child(navBar){
|
||||||
@ -17,47 +18,107 @@ fun RBuilder.NavBar(handler: NavBarProps.() -> Unit): ReactElement{
|
|||||||
|
|
||||||
external interface NavBarProps:RProps{
|
external interface NavBarProps:RProps{
|
||||||
var isBackVisible: Boolean
|
var isBackVisible: Boolean
|
||||||
|
var popBackToHomeScreen: () -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val navBar = functionalComponent<NavBarProps>("NavBar") { props ->
|
private val navBar = functionalComponent<NavBarProps>("NavBar") { props ->
|
||||||
|
|
||||||
styledNav {
|
styledNav {
|
||||||
css {
|
css {
|
||||||
+NavBarStyles.nav
|
+NavBarStyles.nav
|
||||||
}
|
}
|
||||||
styledImg(src = "left-arrow.svg",alt = "Back Arrow"){
|
styledDiv{
|
||||||
css {
|
|
||||||
height = 42.px
|
|
||||||
width = 42.px
|
|
||||||
display = if(props.isBackVisible) Display.inline else Display.none
|
|
||||||
filter = "invert(100)"
|
|
||||||
marginRight = 12.px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styledImg(src = "spotiflyer.svg",alt = "Logo") {
|
|
||||||
css {
|
|
||||||
height = 42.px
|
|
||||||
width = 42.px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styledH1 {
|
|
||||||
+"SpotiFlyer"
|
|
||||||
attrs {
|
attrs {
|
||||||
id = "appName"
|
onClickFunction = {
|
||||||
|
props.popBackToHomeScreen()
|
||||||
|
}
|
||||||
|
onBlurFunction = {
|
||||||
|
props.popBackToHomeScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
css{
|
styledImg(src = "left-arrow.svg",alt = "Back Arrow"){
|
||||||
fontSize = 46.px
|
css {
|
||||||
margin(horizontal = 14.px)
|
height = 42.px
|
||||||
|
width = 42.px
|
||||||
|
display = if(props.isBackVisible) Display.inline else Display.none
|
||||||
|
filter = "invert(100)"
|
||||||
|
marginRight = 12.px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){
|
styledA(href = "TODO Website Link") {
|
||||||
styledImg(src = "github.svg"){
|
css {
|
||||||
|
display = Display.flex
|
||||||
|
alignItems = Align.center
|
||||||
|
}
|
||||||
|
styledImg(src = "spotiflyer.svg",alt = "Logo") {
|
||||||
css {
|
css {
|
||||||
height = 42.px
|
height = 42.px
|
||||||
width = 42.px
|
width = 42.px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
styledH1 {
|
||||||
|
+"SpotiFlyer"
|
||||||
|
attrs {
|
||||||
|
id = "appName"
|
||||||
|
}
|
||||||
|
css{
|
||||||
|
fontSize = 46.px
|
||||||
|
margin(horizontal = 14.px)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*val (corsMode,setCorsMode) = useState(CorsProxy.SelfHostedCorsProxy() as CorsProxy)
|
||||||
|
|
||||||
|
useEffect {
|
||||||
|
setCorsMode(corsProxy)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
styledDiv{
|
||||||
|
|
||||||
|
/*styledH4 { + "Extension" }
|
||||||
|
|
||||||
|
styledDiv {
|
||||||
|
styledInput(type = InputType.checkBox) {
|
||||||
|
attrs{
|
||||||
|
id = "cmn-toggle-4"
|
||||||
|
value = "Extension"
|
||||||
|
checked = corsMode.extensionMode()
|
||||||
|
onChangeFunction = {
|
||||||
|
val state = it.target as HTMLInputElement
|
||||||
|
if(state.checked){
|
||||||
|
setCorsMode(corsProxy.toggle(CorsProxy.PublicProxyWithExtension()))
|
||||||
|
} else{
|
||||||
|
setCorsMode(corsProxy.toggle(CorsProxy.SelfHostedCorsProxy()))
|
||||||
|
}
|
||||||
|
println("Active Proxy: ${corsProxy.url}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
css{
|
||||||
|
classes = mutableListOf("cmn-toggle","cmn-toggle-round-flat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styledLabel { attrs { htmlFor = "cmn-toggle-4" } }
|
||||||
|
css{
|
||||||
|
classes = mutableListOf("switch")
|
||||||
|
marginLeft = 8.px
|
||||||
|
marginRight = 16.px
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
styledA(href = "https://github.com/Shabinder/SpotiFlyer/"){
|
||||||
|
styledImg(src = "github.svg"){
|
||||||
|
css {
|
||||||
|
height = 42.px
|
||||||
|
width = 42.px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
css {
|
css {
|
||||||
|
display = Display.flex
|
||||||
|
alignItems = Align.center
|
||||||
marginLeft = LinearDimension.auto
|
marginLeft = LinearDimension.auto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import com.shabinder.common.root.SpotiFlyerRoot.*
|
|||||||
import extras.RenderableRootComponent
|
import extras.RenderableRootComponent
|
||||||
import extras.renderableChild
|
import extras.renderableChild
|
||||||
import home.HomeScreen
|
import home.HomeScreen
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import list.ListScreen
|
import list.ListScreen
|
||||||
import navbar.NavBar
|
import navbar.NavBar
|
||||||
import react.RBuilder
|
import react.RBuilder
|
||||||
@ -19,9 +18,12 @@ class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
|
|||||||
private val component: Child
|
private val component: Child
|
||||||
get() = model.routerState.value.activeChild.component
|
get() = model.routerState.value.activeChild.component
|
||||||
|
|
||||||
|
private val callBacks get() = model.callBacks
|
||||||
|
|
||||||
override fun RBuilder.render() {
|
override fun RBuilder.render() {
|
||||||
NavBar {
|
NavBar {
|
||||||
isBackVisible = (component is Child.List)
|
isBackVisible = (component is Child.List)
|
||||||
|
popBackToHomeScreen = callBacks::popBackToHomeScreen
|
||||||
}
|
}
|
||||||
when(component){
|
when(component){
|
||||||
is Child.Main -> renderableChild(HomeScreen::class, (component as Child.Main).component)
|
is Child.Main -> renderableChild(HomeScreen::class, (component as Child.Main).component)
|
||||||
@ -33,7 +35,7 @@ class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
|
|||||||
model.routerState.bindToState { routerState = it }
|
model.routerState.bindToState { routerState = it }
|
||||||
}
|
}
|
||||||
class State(
|
class State(
|
||||||
var routerState: RouterState<*, Child>,
|
var routerState: RouterState<*, Child>
|
||||||
) : RState
|
) : RState
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
.progress-circle {
|
.progress-circle {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
position: relative; /* so that children can be absolutely positioned */
|
|
||||||
line-height: 5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-circle:after{
|
.progress-circle:after{
|
||||||
@ -14,6 +12,7 @@
|
|||||||
height: 4.3em;
|
height: 4.3em;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
content: " ";
|
content: " ";
|
||||||
|
margin-top: 0.35em;
|
||||||
}
|
}
|
||||||
/* Text inside the control */
|
/* Text inside the control */
|
||||||
.progress-circle span {
|
.progress-circle span {
|
||||||
|
@ -16,6 +16,10 @@ html {
|
|||||||
background-image: url("header-dark.jpg");
|
background-image: url("header-dark.jpg");
|
||||||
font-family: Lora,Helvetica Neue,Helvetica,Arial,sans-serif;
|
font-family: Lora,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||||
}
|
}
|
||||||
|
a:link, a:visited {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
body, html {
|
body, html {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -238,4 +242,57 @@ body, html {
|
|||||||
-webkit-transform: scale3D(0, 0, 1);
|
-webkit-transform: scale3D(0, 0, 1);
|
||||||
transform: scale3D(0, 0, 1);
|
transform: scale3D(0, 0, 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Extension Switch*/
|
||||||
|
.cmn-toggle {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -9999px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmn-toggle + label {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.cmn-toggle-round-flat + label {
|
||||||
|
/* width = 2*height or 2*border-radius */
|
||||||
|
padding: 2px;
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid #dddddd;
|
||||||
|
border-radius: 60px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.cmn-toggle-round-flat + label:before,
|
||||||
|
input.cmn-toggle-round-flat + label:after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
input.cmn-toggle-round-flat + label:after {
|
||||||
|
/* width = 2*border-radius */
|
||||||
|
top: 4px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: #dddddd;
|
||||||
|
border-radius: 52px;
|
||||||
|
transition: margin 0.3s, background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.cmn-toggle-round-flat:checked + label {
|
||||||
|
border-color: #8ce196;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.cmn-toggle-round-flat:checked + label:after {
|
||||||
|
/* margin-left = border-radius from 'input.cmn-toggle-round-flat + label' */
|
||||||
|
margin-left: 20px;
|
||||||
|
background-color: #8ce196;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user