Ui Anim ,Database,List View updated

This commit is contained in:
shabinder 2020-08-07 09:10:24 +05:30
parent 2455684416
commit 72fd6a4c73
81 changed files with 1691 additions and 72 deletions

View File

@ -89,10 +89,10 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
implementation "androidx.room:room-runtime:2.2.5"
implementation project(path: ':mobile-ffmpeg')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
implementation project(path: ':mobile-ffmpeg')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
transitive = true
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 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.database
import androidx.room.*
@Dao
interface DatabaseDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(record: DownloadRecord)
@Update
fun update(record: DownloadRecord)
@Query("SELECT * from download_record_table ORDER BY id DESC")
suspend fun getRecord():List<DownloadRecord>
@Query("DELETE FROM download_record_table")
suspend fun deleteAll()
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 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.database
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(
tableName = "download_record_table",
indices = [Index(value = ["id","link"], unique = true)]
)
data class DownloadRecord(
@PrimaryKey(autoGenerate = true)
var id:Int = 0,
@ColumnInfo(name = "type")
var type:String,
@ColumnInfo(name = "name")
var name:String,
@ColumnInfo(name = "link")
var link:String,
@ColumnInfo(name = "coverUrl")
var coverUrl:String,
@ColumnInfo(name = "totalFiles")
var totalFiles:Int = 1,
@ColumnInfo(name = "downloaded")
var downloaded:Boolean=false,
@ColumnInfo(name = "directory")
var directory:String?=null
):Parcelable

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 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.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [DownloadRecord::class], version = 1, exportSchema = false)
abstract class DownloadRecordDatabase:RoomDatabase() {
abstract val databaseDAO: DatabaseDAO
companion object {
@Volatile
private var INSTANCE: DownloadRecordDatabase? = null
fun getInstance(context: Context): DownloadRecordDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
DownloadRecordDatabase::class.java,
"download_record_database")
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}

View File

@ -34,10 +34,10 @@ import androidx.core.content.ContextCompat
import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel
import com.shabinder.spotiflyer.worker.ForegroundService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -49,7 +49,7 @@ object SpotifyDownloadHelper {
var context : Context? = null
var statusBar:TextView? = null
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator
var sharedViewModel:SharedViewModel? = null
var spotifyViewModel: SpotifyViewModel? = null
private var isBrowserLoading = false
private var total = 0
private var Processed = 0
@ -66,9 +66,7 @@ object SpotifyDownloadHelper {
withContext(Dispatchers.Main){
total += trackList.size // Adding New Download List Count to StatusBar
trackList.forEach {
val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator +
defaultDir + removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ removeIllegalChars(subFolder) + File.separator} + removeIllegalChars(it.name!!)+".mp3")
if(File(outputFile).exists()){//Download Already Present!!
if(it.downloaded == "Downloaded"){//Download Already Present!!
Processed++
}else{
if(isBrowserLoading){//WebView Busy!!
@ -124,7 +122,7 @@ object SpotifyDownloadHelper {
}
if(youtubeList.isNotEmpty()){
val request = youtubeList[0]
sharedViewModel!!.uiScope.launch {
spotifyViewModel!!.uiScope.launch {
getYTLink(request.spotifyFragment,request.type,request.subFolder,request.ytDownloader,request.searchQuery,request.track)
}
youtubeList.remove(request)
@ -147,7 +145,7 @@ object SpotifyDownloadHelper {
fun downloadFile(subFolder: String?, type: String, track:Track, ytDownloader: YoutubeDownloader?, id: String) {
sharedViewModel!!.uiScope.launch {
spotifyViewModel!!.uiScope.launch {
withContext(Dispatchers.IO) {
val video = ytDownloader?.getVideo(id)
val detail = video?.details()

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 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.downloadHelper
import android.content.Context
import android.content.Intent
import android.os.Environment
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.github.kiulian.downloader.model.formats.Format
import com.shabinder.spotiflyer.models.DownloadObject
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.worker.ForegroundService
import java.io.File
object YTDownloadHelper {
var context : Context? = null
var statusBar: TextView? = null
fun downloadFile(subFolder: String?, type: String,ytTrack: Track,format: Format?) {
format?.let {
val url:String = format.url()
// Log.i("DHelper Link Found", url)
val outputFile:String = Environment.getExternalStorageDirectory().toString() + File.separator +
SpotifyDownloadHelper.defaultDir + SpotifyDownloadHelper.removeIllegalChars(type) + File.separator + (if(subFolder == null){""}else{ SpotifyDownloadHelper.removeIllegalChars(subFolder) + File.separator} + SpotifyDownloadHelper.removeIllegalChars(
ytTrack.name!!
) +".m4a")
val downloadObject = DownloadObject(
track = ytTrack,
url = url,
outputDir = outputFile
)
Log.i("DH",outputFile)
startService(context!!, downloadObject)
statusBar?.visibility= View.VISIBLE
}
}
private fun startService(context:Context, obj: DownloadObject? = null ) {
val serviceIntent = Intent(context, ForegroundService::class.java)
serviceIntent.putExtra("object",obj)
ContextCompat.startForegroundService(context, serviceIntent)
}
}

View File

@ -40,4 +40,5 @@ data class Track(
var album: Album? = null,
var external_ids: Map<String?, String?>? = null,
var popularity: Int? = null,
var ytCoverUrl:String? = null):Parcelable
var ytCoverUrl:String? = null,
var downloaded:String? = "notDownloaded"):Parcelable

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 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.models
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
data class YTTrack(
var id:String?,
var title:String?,
var duration:Int?,
var author:String?,
var viewCount:Long?,
var thumbnails:List<String?>?
):Parcelable

View File

@ -20,59 +20,84 @@ package com.shabinder.spotiflyer.recyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.context
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.getYTLink
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyFragment
import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel
import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.rotateAnim
import kotlinx.coroutines.launch
class SpotifyTrackListAdapter:RecyclerView.Adapter<SpotifyTrackListAdapter.ViewHolder>() {
class SpotifyTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(SpotifyTrackDiffCallback()) {
var trackList = listOf<Track>()
var totalItems:Int = 0
var sharedViewModel = SharedViewModel()
var spotifyViewModel = SpotifyViewModel()
var isAlbum:Boolean = false
var spotifyFragment: SpotifyFragment? = null
override fun getItemCount():Int = totalItems
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.track_list_item,parent,false)
return ViewHolder(view)
val binding = TrackListItemBinding.inflate(layoutInflater,parent,false)
// val view = layoutInflater.inflate(R.layout.track_list_item,parent,false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = trackList[position]
if(totalItems == 1 || isAlbum){holder.coverImage.visibility = View.GONE}else{
sharedViewModel.uiScope.launch {
bindImage(holder.coverImage, item.album!!.images?.get(0)?.url)
val item = getItem(position)
if(itemCount ==1 || isAlbum){
holder.binding.imageUrl.visibility = View.GONE}else{
spotifyViewModel.uiScope.launch {
bindImage(holder.binding.imageUrl, item.album!!.images?.get(0)?.url)
}
}
holder.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
holder.artistName.text = "${item.artists?.get(0)?.name?:""}..."
holder.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.downloadBtn.setOnClickListener{
sharedViewModel.uiScope.launch {
getYTLink(spotifyFragment,"Tracks",null,sharedViewModel.ytDownloader.value,"${item.name} ${item.artists?.get(0)!!.name?:""}",track = item)
holder.binding.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
holder.binding.artist.text = "${item.artists?.get(0)?.name?:""}..."
holder.binding.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
when (item.downloaded) {
"Downloaded" -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_tick)
holder.binding.btnDownload.clearAnimation()
}
"Downloading" -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(holder.binding.btnDownload)
}
"notDownloaded" -> {
holder.binding.btnDownload.setImageResource(R.drawable.ic_arrow)
holder.binding.btnDownload.clearAnimation()
holder.binding.btnDownload.setOnClickListener{
Toast.makeText(context,"Starting Download",Toast.LENGTH_SHORT).show()
holder.binding.btnDownload.setImageResource(R.drawable.ic_refresh)
rotateAnim(it)
item.downloaded = "Downloading"
spotifyViewModel.uiScope.launch {
getYTLink(spotifyFragment,"Tracks",null,spotifyViewModel.ytDownloader,"${item.name} ${item.artists?.get(0)!!.name?:""}",track = item)
}
notifyItemChanged(position)
}
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val trackName:TextView = itemView.findViewById(R.id.track_name)
val artistName:TextView = itemView.findViewById(R.id.artist)
val duration:TextView = itemView.findViewById(R.id.duration)
val downloadBtn:ImageButton = itemView.findViewById(R.id.btn_download)
val coverImage:ImageView = itemView.findViewById(R.id.imageUrl)
}
class ViewHolder(val binding: TrackListItemBinding) : RecyclerView.ViewHolder(binding.root)
}
class SpotifyTrackDiffCallback: DiffUtil.ItemCallback<Track>(){
override fun areItemsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem == newItem //Downloaded Check
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2020 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.recyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.github.kiulian.downloader.model.formats.Format
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.TrackListItemBinding
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.bindImage
import kotlinx.coroutines.launch
class YoutubeTrackListAdapter: ListAdapter<Track,SpotifyTrackListAdapter.ViewHolder>(YouTubeTrackDiffCallback()) {
var format:Format? = null
var sharedViewModel = SharedViewModel()
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SpotifyTrackListAdapter.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = TrackListItemBinding.inflate(layoutInflater,parent,false)
// val view = layoutInflater.inflate(R.layout.track_list_item,parent,false)
return SpotifyTrackListAdapter.ViewHolder(binding)
}
override fun onBindViewHolder(holder: SpotifyTrackListAdapter.ViewHolder, position: Int) {
val item = getItem(position)
if(itemCount == 1){
holder.binding.imageUrl.visibility = View.GONE}else{
sharedViewModel.uiScope.launch {
bindImage(holder.binding.imageUrl, item.ytCoverUrl)
}
}
holder.binding.trackName.text = "${if(item.name!!.length > 17){"${item.name!!.subSequence(0,16)}..."}else{item.name}}"
holder.binding.artist.text = "${item.artists?.get(0)?.name?:""}..."
holder.binding.duration.text = "${item.duration_ms/1000/60} minutes, ${(item.duration_ms/1000)%60} sec"
holder.binding.btnDownload.setOnClickListener{
sharedViewModel.uiScope.launch {
YTDownloadHelper.downloadFile(null,"YT_Downloads",item,format)
}
}
}
}
class YouTubeTrackDiffCallback: DiffUtil.ItemCallback<Track>(){
override fun areItemsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItem: Track, newItem: Track): Boolean {
return oldItem == newItem
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright (C) 2020 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.mainfragment
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.MainFragmentBinding
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
private lateinit var sharedViewModel: SharedViewModel
private lateinit var binding: MainFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.main_fragment,container,false)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
initializeAll()
binding.btnSearch.setOnClickListener {
val link = binding.linkSearch.text.toString()
if (link.contains("spotify",true)){
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSpotifyFragment(link))
}else if(link.contains("youtube.com",true) || link.contains("youtu.be",true) ){
findNavController().navigate(MainFragmentDirections.actionMainFragmentToYoutubeFragment(link))
}else{Toast.makeText(context,"Link is Not Valid",Toast.LENGTH_SHORT).show()}
}
handleIntent()
return binding.root
}
private fun initializeAll() {
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
setUpUsageText()
openYTButton()
openSpotifyButton()
openGithubButton()
openInstaButton()
openLinkedInButton()
binding.btnDonate.setOnClickListener {
sharedViewModel.easyUpiPayment?.startPayment()
}
}
/**
* Handle Intent If there is any!
**/
private fun handleIntent() {
sharedViewModel.accessToken.observe(viewLifecycleOwner, Observer {
//Waiting for Authentication to Finish with Spotify()Access Token Observe
if (it != ""){
if(sharedViewModel.intentString != ""){
binding.linkSearch.setText(sharedViewModel.intentString)
binding.btnSearch.performClick()
sharedViewModel.intentString = ""
}
}
})
}
private fun setUpUsageText() {
val spanStringBuilder = SpannableStringBuilder()
spanStringBuilder.append(getText(R.string.d_one)).append("\n")
spanStringBuilder.append(getText(R.string.d_two)).append("\n")
spanStringBuilder.append(getText(R.string.d_three)).append("\n")
spanStringBuilder.append(getText(R.string.d_four)).append("\n")
binding.usage.text = spanStringBuilder
}
/**
* Implementing buttons
**/
private fun openSpotifyButton() {
val manager: PackageManager = requireActivity().packageManager
try {
val i = manager.getLaunchIntentForPackage("com.spotify.music")
?: throw PackageManager.NameNotFoundException()
i.addCategory(Intent.CATEGORY_LAUNCHER)
binding.btnSpotify.setOnClickListener { startActivity(i) }
} catch (e: PackageManager.NameNotFoundException) {
val uri: Uri =
Uri.parse("http://open.spotify.com")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnSpotify.setOnClickListener {
startActivity(intent)
}
}
}
private fun openYTButton() {
val manager: PackageManager = requireActivity().packageManager
try {
val i = manager.getLaunchIntentForPackage("com.google.android.youtube")
?: throw PackageManager.NameNotFoundException()
i.addCategory(Intent.CATEGORY_LAUNCHER)
binding.btnYoutube.setOnClickListener { startActivity(i) }
} catch (e: PackageManager.NameNotFoundException) {
val uri: Uri =
Uri.parse("http://m.youtube.com")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnYoutube.setOnClickListener {
startActivity(intent)
}
}
}
private fun openGithubButton() {
val uri: Uri =
Uri.parse("http://github.com/Shabinder/SpotiFlyer")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnGithubSpotify.setOnClickListener {
startActivity(intent)
}
}
private fun openLinkedInButton() {
val uri: Uri =
Uri.parse("https://in.linkedin.com/in/shabinder")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.btnLinkedin.setOnClickListener {
startActivity(intent)
}
}
private fun openInstaButton() {
val uri: Uri =
Uri.parse("http://www.instagram.com/mr.shabinder")
val intent = Intent(Intent.ACTION_VIEW, uri)
binding.developerInstaSpotify.setOnClickListener {
startActivity(intent)
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2020 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.mainfragment
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View File

@ -18,7 +18,10 @@
package com.shabinder.spotiflyer.ui.spotify
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Environment
@ -47,6 +50,7 @@ import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter
import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.copyTo
import com.shabinder.spotiflyer.utils.rotateAnim
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -60,6 +64,8 @@ class SpotifyFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapterSpotify:SpotifyTrackListAdapter
private var webView: WebView? = null
private var intentFilter:IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null
@SuppressLint("SetJavaScriptEnabled")
@ -71,6 +77,7 @@ class SpotifyFragment : Fragment() {
adapterSpotify = SpotifyTrackListAdapter()
initializeAll()
initializeLiveDataObservers()
initializeBroadcast()
val args = SpotifyFragmentArgs.fromBundle(requireArguments())
val spotifyLink = args.link
@ -95,6 +102,19 @@ class SpotifyFragment : Fragment() {
spotifyViewModel.spotifySearch(type,link)
if(type=="album")adapterSpotify.isAlbum = true
binding.btnDownloadAllSpotify.setOnClickListener {
for (track in spotifyViewModel.trackList.value!!){
if(track.downloaded != "Downloaded"){
track.downloaded = "Downloading"
}
}
binding.btnDownloadAllSpotify.visibility = View.GONE
binding.downloadingFabSpotify.visibility = View.VISIBLE
rotateAnim(binding.downloadingFabSpotify)
for (track in spotifyViewModel.trackList.value!!){
if(track.downloaded != "Downloaded"){
adapterSpotify.notifyItemChanged(spotifyViewModel.trackList.value!!.indexOf(track))
}
}
showToast("Starting Download in Few Seconds")
loadAllImages(spotifyViewModel.trackList.value!!)
spotifyViewModel.uiScope.launch {
@ -111,6 +131,41 @@ class SpotifyFragment : Fragment() {
return binding.root
}
override fun onResume() {
super.onResume()
initializeBroadcast()
}
private fun initializeBroadcast() {
intentFilter = IntentFilter()
intentFilter?.addAction("track_download_completed")
updateUIReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//UI update here
if (intent != null){
val track = intent.getParcelableExtra<Track?>("track")
track?.let {
val position: Int = spotifyViewModel.trackList.value?.indexOf(track)!!
Log.i("Track","Download Completed Intent :$position")
track.downloaded = "Downloaded"
if(position != -1) {
spotifyViewModel.trackList.value?.set(position, track)
adapterSpotify.notifyItemChanged(position)
checkIfAllDownloaded()
}
}
}
}
}
requireActivity().registerReceiver(updateUIReceiver, intentFilter)
}
override fun onPause() {
super.onPause()
requireActivity().unregisterReceiver(updateUIReceiver)
}
/**
*Live Data Observers
**/
@ -127,8 +182,9 @@ class SpotifyFragment : Fragment() {
**/
spotifyViewModel.trackList.observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()){
Log.i("SpotifyFragment","TrackList Fetched!")
Log.i("SpotifyFragment","TrackList Updated")
adapterConfig(it)
checkIfAllDownloaded()
}
})
@ -140,6 +196,19 @@ class SpotifyFragment : Fragment() {
})
}
private fun checkIfAllDownloaded() {
var allDownloaded = true
for (track in spotifyViewModel.trackList.value!!){
if (track.downloaded != "Downloaded")allDownloaded = false
}
if(allDownloaded){
binding.downloadingFabSpotify.setImageResource(R.drawable.ic_tick)
binding.btnDownloadAllSpotify.visibility = View.GONE
binding.downloadingFabSpotify.visibility = View.VISIBLE
binding.downloadingFabSpotify.clearAnimation()
}
}
/**
* Basic Initialization
**/
@ -155,7 +224,7 @@ class SpotifyFragment : Fragment() {
})
SpotifyDownloadHelper.webView = binding.webViewSpotify
SpotifyDownloadHelper.context = requireContext()
SpotifyDownloadHelper.sharedViewModel = sharedViewModel
SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel
SpotifyDownloadHelper.statusBar = binding.StatusBarSpotify
binding.trackListSpotify.adapter = adapterSpotify
}
@ -214,11 +283,9 @@ class SpotifyFragment : Fragment() {
* Configure Recycler View Adapter
**/
private fun adapterConfig(trackList: List<Track>){
adapterSpotify.trackList = trackList
adapterSpotify.totalItems = trackList.size
adapterSpotify.spotifyFragment = this
adapterSpotify.sharedViewModel = sharedViewModel
adapterSpotify.notifyDataSetChanged()
adapterSpotify.spotifyViewModel = spotifyViewModel
adapterSpotify.submitList(trackList)
}

View File

@ -25,16 +25,18 @@ import com.shabinder.spotiflyer.models.Album
import com.shabinder.spotiflyer.models.Playlist
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.utils.SpotifyService
import com.shabinder.spotiflyer.utils.finalOutputDir
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.io.File
class SpotifyViewModel: ViewModel() {
var folderType:String = ""
var subFolder:String = ""
var trackList = MutableLiveData<List<Track>>()
var trackList = MutableLiveData<MutableList<Track>>()
private val loading = "Loading"
var title = MutableLiveData<String>().apply { value = loading }
var coverUrl = MutableLiveData<String>().apply { value = loading }
@ -50,42 +52,57 @@ class SpotifyViewModel: ViewModel() {
"track" -> {
uiScope.launch {
val trackObject = getTrackDetails(link)
folderType = "Tracks"
val tempTrackList = mutableListOf<Track>()
tempTrackList.add(trackObject!!)
if(File(finalOutputDir(trackObject?.name!!,folderType,subFolder)).exists()){//Download Already Present!!
trackObject.downloaded = "Downloaded"
}
tempTrackList.add(trackObject)
trackList.value = tempTrackList
title.value = trackObject.name
coverUrl.value = trackObject.album!!.images?.get(0)!!.url!!
folderType = "Tracks"
}
}
"album" -> {
uiScope.launch {
val albumObject = getAlbumDetails(link)
folderType = "Albums"
subFolder = albumObject?.name!!
val tempTrackList = mutableListOf<Track>()
albumObject!!.tracks?.items?.forEach { tempTrackList.add(it) }
albumObject.tracks?.items?.forEach {
if(File(finalOutputDir(it.name!!,folderType,subFolder)).exists()){//Download Already Present!!
it.downloaded = "Downloaded"
}
tempTrackList.add(it)
}
trackList.value = tempTrackList
title.value = albumObject.name
coverUrl.value = albumObject.images?.get(0)!!.url!!
folderType = "Albums"
subFolder = albumObject.name!!
}
}
"playlist" -> {
uiScope.launch {
val playlistObject = getPlaylistDetails(link)
folderType = "Playlists"
subFolder = playlistObject?.name!!
val tempTrackList = mutableListOf<Track>()
playlistObject!!.tracks?.items?.forEach {
it.track?.let { it1 -> tempTrackList.add(it1) }
playlistObject.tracks?.items?.forEach {
it.track?.let {
it1 -> if(File(finalOutputDir(it1.name!!,folderType,subFolder)).exists()){//Download Already Present!!
it1.downloaded = "Downloaded"
Log.i("ViewModel123","${it1.name} Downloaded")
}
tempTrackList.add(it1)
}
}
Log.i("ViewModel",tempTrackList.size.toString())
Log.i("ViewModel",playlistObject.tracks?.items?.size.toString())
trackList.value = tempTrackList
Log.i("VIEW MODEL",playlistObject.tracks?.items!!.toString())
Log.i("VIEW MODEL",trackList.value?.size.toString())
title.value = playlistObject.name
coverUrl.value = playlistObject.images?.get(0)!!.url!!
folderType = "Playlists"
subFolder = playlistObject.name!!
}
}
"episode" -> {//TODO

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2020 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.youtube
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.shabinder.spotiflyer.R
import com.shabinder.spotiflyer.SharedViewModel
import com.shabinder.spotiflyer.databinding.YoutubeFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.YTDownloadHelper
import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.YoutubeTrackListAdapter
import com.shabinder.spotiflyer.utils.bindImage
class YoutubeFragment : Fragment() {
private lateinit var binding:YoutubeFragmentBinding
private lateinit var youtubeViewModel: YoutubeViewModel
private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapter : YoutubeTrackListAdapter
private val sampleDomain1 = "youtube.com"
private val sampleDomain2 = "youtu.be"
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater,R.layout.youtube_fragment,container,false)
youtubeViewModel = ViewModelProvider(this).get(YoutubeViewModel::class.java)
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
adapter = YoutubeTrackListAdapter()
YTDownloadHelper.context = requireContext()
YTDownloadHelper.statusBar = binding.StatusBarYoutube
binding.trackListYoutube.adapter = adapter
sharedViewModel.ytDownloader.observe(viewLifecycleOwner, Observer {
youtubeViewModel.ytDownloader = it
})
initializeLiveDataObservers()
val args = YoutubeFragmentArgs.fromBundle(requireArguments())
val link = args.link
youtubeSearch(link)
return binding.root
}
private fun youtubeSearch(linkSearch:String) {
val link = linkSearch.removePrefix("https://").removePrefix("http://")
if(!link.contains("playlist",true)){
var searchId = "error"
if(link.contains(sampleDomain1,true) ){
searchId = link.substringAfterLast("=","error")
}
if(link.contains(sampleDomain2,true) && !link.contains("playlist",true) ){
searchId = link.substringAfterLast("/","error")
}
if(searchId != "error") {
// val coverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
youtubeViewModel.getYTTrack(searchId)
binding.btnDownloadAllYoutube.setOnClickListener {
//TODO
}
}else{showToast("Your Youtube Link is not of a Video!!")}
}else(showToast("Your Youtube Link is not of a Video!!"))
}
private fun initializeLiveDataObservers() {
/**
* CoverUrl Binding Observer!
**/
youtubeViewModel.coverUrl.observe(viewLifecycleOwner, Observer {
if(it!="Loading") bindImage(binding.youtubeCoverImage,it)
})
/**
* TrackList Binding Observer!
**/
youtubeViewModel.ytTrack.observe(viewLifecycleOwner, Observer {
val list = mutableListOf<Track>()
list.add(it)
adapterConfig(list)
})
youtubeViewModel.format.observe(viewLifecycleOwner, Observer {
adapter.format = it
})
/**
* Title Binding Observer!
**/
youtubeViewModel.title.observe(viewLifecycleOwner, Observer {
binding.titleViewYoutube.text = it
})
}
/**
* Configure Recycler View Adapter
**/
private fun adapterConfig(list:List<Track>){
adapter.sharedViewModel = sharedViewModel
adapter.submitList(list)
}
/**
* Util. Function to create toasts!
**/
private fun showToast(message:String){
Toast.makeText(context,message, Toast.LENGTH_SHORT).show()
}
/**
* Util. Function To Check Connection Status
**/
private fun isNotOnline(): Boolean {
val cm =
requireActivity().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 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.youtube
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.spotiflyer.models.Artist
import com.shabinder.spotiflyer.models.Track
import kotlinx.coroutines.*
class YoutubeViewModel : ViewModel() {
val ytTrack = MutableLiveData<Track>()
val format = MutableLiveData<Format>()
private val loading = "Loading"
var title = MutableLiveData<String>().apply { value = "\"Loading!\"" }
var coverUrl = MutableLiveData<String>().apply { value = loading }
var ytDownloader: YoutubeDownloader? = null
private var viewModelJob = Job()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun getYTTrack(searchId:String) {
uiScope.launch {
withContext(Dispatchers.IO){
Log.i("YT View Model",searchId)
val video = ytDownloader?.getVideo(searchId)
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(),"",true) ?: detail?.title()
Log.i("YT View Model",detail.toString())
ytTrack.postValue(
Track(
id = searchId,
name = name,
artists = listOf<Artist>(Artist(name = detail?.author())),
duration_ms = detail?.lengthSeconds()?.times(1000)?.toLong()?:0,
ytCoverUrl = "https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
))
coverUrl.postValue("https://i.ytimg.com/vi/$searchId/maxresdefault.jpg")
title.postValue(
if(name?.length!! > 17){"${name.subSequence(0,16)}..."}else{name}
)
format.postValue(try {
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
} catch (e: IndexOutOfBoundsException) {
try {
video?.findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
} catch (e: IndexOutOfBoundsException) {
try {
video?.findAudioWithQuality(AudioQuality.low)?.get(0) as Format
} catch (e: IndexOutOfBoundsException) {
Log.i("YTDownloader", e.toString())
null
}
}
})
}
}
}
}

View File

@ -21,6 +21,10 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Environment
import android.util.Log
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation
import android.widget.ImageView
import androidx.core.net.toUri
import androidx.databinding.BindingAdapter
@ -39,6 +43,25 @@ import java.io.File
import java.io.FileInputStream
import java.io.IOException
fun finalOutputDir(itemName:String,type:String, subFolder:String?=null): String{
return Environment.getExternalStorageDirectory().toString() + File.separator +
SpotifyDownloadHelper.defaultDir + SpotifyDownloadHelper.removeIllegalChars(type) + File.separator +
(if(subFolder == null){""}else{ SpotifyDownloadHelper.removeIllegalChars(subFolder) + File.separator}
+ SpotifyDownloadHelper.removeIllegalChars(itemName) +".mp3")
}
fun rotateAnim(view: View){
val rotate = RotateAnimation(
0F, 360F,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f
)
rotate.duration = 1000
rotate.repeatCount = Animation.INFINITE
rotate.repeatMode = Animation.INFINITE
rotate.interpolator = LinearInterpolator()
view.animation = rotate
}
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

View File

@ -98,9 +98,15 @@ class ForegroundService : Service(){
.setDownloadConcurrentLimit(4)
.build()
fetch = Fetch.Impl.getInstance(fetchConfiguration)
Fetch.setDefaultInstanceConfiguration(fetchConfiguration)
fetch = Fetch.getDefaultInstance()
// fetch?.enableLogging(true)
fetch?.addListener(fetchListener)
//clearing all not completed Downloads
//Starting fresh
fetch?.removeAll()
startForeground()
}
@ -218,11 +224,11 @@ class ForegroundService : Service(){
super.onTaskRemoved(rootIntent)
if(downloadMap.isEmpty() && converted == total ){
Log.i(tag,"Service Removed.")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true)
} else {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// stopForeground(true)
// } else {
stopSelf()//System will automatically close it
}
// }
}
}
@ -307,12 +313,17 @@ class ForegroundService : Service(){
override fun onCompleted(download: Download) {
val track = requestMap[download.request]
for (message in messageList){
if( message == "Downloading ${track?.name}"){
messageList[messageList.indexOf(message)] = ""
}
}
//Notify Download Completed
val intent = Intent()
.setAction("track_download_completed")
.putExtra("track",track)
this@ForegroundService.sendBroadcast(intent)
serviceScope.launch {
try{

View File

@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="90"
@ -6,5 +23,4 @@
android:endColor="#0374FFF4"
android:startColor="#45F700FF" />
<corners android:radius="0dp" />
</shape>

View File

@ -1,10 +1,27 @@
<!--
~ Copyright (C) 2020 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="26dp"
android:height="22dp" android:viewportWidth="512" android:viewportHeight="512">
<path android:fillColor="#0AFF02" android:pathData="m296,288 l60,-60c7.73,-7.73 17.86,-11.6 28,-11.6 10.055,0 20.101,3.806 27.806,11.407 15.612,15.402 15.207,41.18 -0.3,56.687l-134.293,134.293c-11.716,11.716 -30.711,11.716 -42.426,0l-134.787,-134.787c-7.73,-7.73 -11.6,-17.86 -11.6,-28 0,-10.055 3.806,-20.101 11.407,-27.806 15.402,-15.612 41.18,-15.207 56.687,0.3l59.506,59.506v-232c0,-22.091 17.909,-40 40,-40 22.091,0 40,17.909 40,40z"/>
<path android:fillColor="#218F02" android:pathData="m411.51,284.49 l-134.3,134.3c-11.71,11.71 -30.71,11.71 -42.42,0l-12.74,-12.74c10.69,4.06 23.23,1.77 31.84,-6.84l134.29,-134.29c12.51,-12.51 15.19,-31.7 7.57,-46.74 5.86,1.81 11.39,5.03 16.06,9.63 15.61,15.4 15.2,41.18 -0.3,56.68z"/>
<path android:fillColor="#218F02" android:pathData="m251.88,27.72c-3.46,-3.46 -7.55,-6.29 -12.08,-8.3 4.95,-2.2 10.43,-3.42 16.2,-3.42 11.04,0 21.04,4.48 28.28,11.72s11.72,17.24 11.72,28.28v232l-15.329,15.329c-6.3,6.3 -17.071,1.838 -17.071,-7.071v-240.258c0,-11.04 -4.48,-21.04 -11.72,-28.28z"/>
<path android:fillColor="#41B632" android:pathData="m496,512h-24c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h24c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
<path android:fillColor="#41B632" android:pathData="m40,512h-24c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h24c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
<path android:fillColor="#41B632" android:pathData="m416,512h-320c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h320c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
<path android:fillColor="#000000" android:pathData="m296,288 l60,-60c7.73,-7.73 17.86,-11.6 28,-11.6 10.055,0 20.101,3.806 27.806,11.407 15.612,15.402 15.207,41.18 -0.3,56.687l-134.293,134.293c-11.716,11.716 -30.711,11.716 -42.426,0l-134.787,-134.787c-7.73,-7.73 -11.6,-17.86 -11.6,-28 0,-10.055 3.806,-20.101 11.407,-27.806 15.402,-15.612 41.18,-15.207 56.687,0.3l59.506,59.506v-232c0,-22.091 17.909,-40 40,-40 22.091,0 40,17.909 40,40z"/>
<path android:fillColor="#000000" android:pathData="m411.51,284.49 l-134.3,134.3c-11.71,11.71 -30.71,11.71 -42.42,0l-12.74,-12.74c10.69,4.06 23.23,1.77 31.84,-6.84l134.29,-134.29c12.51,-12.51 15.19,-31.7 7.57,-46.74 5.86,1.81 11.39,5.03 16.06,9.63 15.61,15.4 15.2,41.18 -0.3,56.68z"/>
<path android:fillColor="#000000" android:pathData="m251.88,27.72c-3.46,-3.46 -7.55,-6.29 -12.08,-8.3 4.95,-2.2 10.43,-3.42 16.2,-3.42 11.04,0 21.04,4.48 28.28,11.72s11.72,17.24 11.72,28.28v232l-15.329,15.329c-6.3,6.3 -17.071,1.838 -17.071,-7.071v-240.258c0,-11.04 -4.48,-21.04 -11.72,-28.28z"/>
<path android:fillColor="#000000" android:pathData="m496,512h-24c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h24c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
<path android:fillColor="#000000" android:pathData="m40,512h-24c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h24c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
<path android:fillColor="#000000" android:pathData="m416,512h-320c-8.836,0 -16,-7.164 -16,-16s7.164,-16 16,-16h320c8.836,0 16,7.164 16,16s-7.164,16 -16,16z"/>
</vector>

View File

@ -0,0 +1,34 @@
<!--
~ Copyright (C) 2020 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="52dp" android:height="52dp"
android:viewportWidth="512" android:viewportHeight="512">
<path android:pathData="m140.008,423h-30c-11.047,0 -20,-8.953 -20,-20v-186c0,-11.047 8.953,-20 20,-20h30c11.047,0 20,8.953 20,20v186c0,11.047 -8.953,20 -20,20zM166.992,124.996c0,-22.629 -18.359,-40.996 -40.977,-40.996 -22.703,0 -41.016,18.367 -41.016,40.996 0,22.637 18.313,41.004 41.016,41.004 22.617,0 40.977,-18.367 40.977,-41.004zM422,403v-104.336c0,-60.668 -12.816,-105.664 -83.688,-105.664 -34.055,0 -56.914,17.031 -66.246,34.742h-0.066v-10.742c0,-11.047 -8.953,-20 -20,-20h-28c-11.047,0 -20,8.953 -20,20v186c0,11.047 8.953,20 20,20h28c11.047,0 20,-8.953 20,-20v-92.211c0,-29.387 7.48,-57.855 43.906,-57.855 35.93,0 37.094,33.605 37.094,59.723v90.344c0,11.047 8.953,20 20,20h29c11.047,0 20,-8.953 20,-20zM512,432c0,-11.047 -8.953,-20 -20,-20s-20,8.953 -20,20c0,22.055 -17.945,40 -40,40h-352c-22.055,0 -40,-17.945 -40,-40v-352c0,-22.055 17.945,-40 40,-40h352c22.055,0 40,17.945 40,40v251c0,11.047 8.953,20 20,20s20,-8.953 20,-20v-251c0,-44.113 -35.887,-80 -80,-80h-352c-44.113,0 -80,35.887 -80,80v352c0,44.113 35.887,80 80,80h352c44.113,0 80,-35.887 80,-80zM512,432">
<aapt:attr name="android:fillColor">
<gradient android:endX="512" android:endY="256"
android:startX="0" android:startY="256" android:type="linear">
<item android:color="#FF00F2FE" android:offset="0"/>
<item android:color="#FF03EFFE" android:offset="0.0208"/>
<item android:color="#FF24D2FE" android:offset="0.2931"/>
<item android:color="#FF3CBDFE" android:offset="0.5538"/>
<item android:color="#FF4AB0FE" android:offset="0.7956"/>
<item android:color="#FF4FACFE" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,30 @@
<!--
~ Copyright (C) 2020 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="42dp" android:height="42dp"
android:viewportWidth="512" android:viewportHeight="512">
<path android:pathData="m45,300.008c-24.813,0 -45,-20.188 -45,-45s20.188,-45 45,-45c24.816,0 45.004,20.188 45.004,45s-20.188,45 -45.004,45zM237.008,100.004c-27.57,0 -50.004,-22.43 -50.004,-50.004 0,-27.57 22.434,-50 50.004,-50 27.57,0 50,22.43 50,50 0,27.574 -22.43,50.004 -50,50.004zM237.008,40c-5.516,0 -10,4.488 -10,10 0,5.516 4.484,10 10,10 5.512,0 10,-4.484 10,-10 0,-5.512 -4.488,-10 -10,-10zM100.504,162.004c-12.957,0 -25.137,-5.043 -34.297,-14.207 -9.16,-9.16 -14.207,-21.34 -14.207,-34.293 0,-12.957 5.047,-25.137 14.207,-34.297 9.16,-9.16 21.34,-14.207 34.297,-14.207 12.953,0 25.133,5.047 34.297,14.207h-0.004,0.004c9.16,9.16 14.203,21.34 14.203,34.297 0,12.953 -5.043,25.133 -14.207,34.297 -9.16,9.16 -21.34,14.203 -34.293,14.203zM100.504,105.004c-1.316,0 -3.844,0.32 -6.012,2.488 -2.168,2.168 -2.488,4.695 -2.488,6.012s0.32,3.844 2.488,6.008c2.168,2.168 4.695,2.492 6.012,2.492 1.313,0 3.844,-0.324 6.008,-2.488 2.168,-2.168 2.492,-4.695 2.492,-6.012s-0.324,-3.844 -2.488,-6.012h-0.004c-2.164,-2.168 -4.695,-2.488 -6.008,-2.488zM382.512,164.004c-14.824,0 -28.762,-5.773 -39.246,-16.254 -10.484,-10.484 -16.258,-24.422 -16.258,-39.246 0,-14.824 5.773,-28.762 16.258,-39.246 10.484,-10.484 24.422,-16.258 39.246,-16.258s28.762,5.773 39.246,16.258c10.48,10.484 16.254,24.422 16.254,39.246 0,14.824 -5.773,28.762 -16.254,39.246 -10.484,10.48 -24.422,16.254 -39.246,16.254zM382.512,93.004c-4.141,0 -8.031,1.609 -10.961,4.539 -2.926,2.93 -4.539,6.82 -4.539,10.961s1.609,8.031 4.539,10.957c2.93,2.93 6.82,4.543 10.961,4.543s8.031,-1.613 10.957,-4.539c2.93,-2.93 4.543,-6.82 4.543,-10.961s-1.613,-8.031 -4.543,-10.961c-2.926,-2.93 -6.816,-4.539 -10.957,-4.539zM447.012,318.98c-16.645,0 -33.289,-6.336 -45.961,-19.008 -25.348,-25.344 -25.348,-66.586 0,-91.93 25.344,-25.344 66.582,-25.344 91.926,0s25.344,66.586 0,91.93c-12.672,12.672 -29.316,19.008 -45.965,19.008zM447.012,229.02c-6.402,0 -12.805,2.438 -17.676,7.309 -9.75,9.75 -9.75,25.609 0,35.359 9.746,9.746 25.605,9.746 35.355,0 9.746,-9.75 9.746,-25.609 0,-35.359 -4.875,-4.871 -11.277,-7.309 -17.68,-7.309zM383.012,484.98c-17.672,0 -35.34,-6.723 -48.793,-20.176 -26.902,-26.902 -26.902,-70.68 0,-97.582 26.902,-26.906 70.68,-26.906 97.582,0 26.906,26.902 26.906,70.68 0,97.582 -13.449,13.453 -31.121,20.176 -48.789,20.176zM383.012,387.023c-7.426,0 -14.852,2.828 -20.508,8.48 -11.305,11.309 -11.305,29.707 0,41.016 11.309,11.309 29.707,11.309 41.016,0 11.305,-11.309 11.305,-29.707 0,-41.016 -5.656,-5.652 -13.082,-8.48 -20.508,-8.48zM92.5,438.992c-11.137,0 -22.277,-4.242 -30.758,-12.719 -16.961,-16.965 -16.961,-44.563 0,-61.523 16.961,-16.961 44.559,-16.961 61.52,0 16.961,16.961 16.961,44.559 0,61.52 -8.48,8.48 -19.621,12.723 -30.762,12.723zM210.004,512c-7.684,0 -15.363,-2.922 -21.211,-8.77 -11.699,-11.699 -11.699,-30.734 -0.004,-42.43l0.004,-0.004c11.695,-11.695 30.73,-11.695 42.426,0 11.699,11.699 11.699,30.734 0.004,42.43 -5.852,5.848 -13.535,8.773 -21.219,8.773zM210.004,512">
<aapt:attr name="android:fillColor">
<gradient android:endX="255.99213" android:endY="512.00024"
android:startX="255.99213" android:startY="0" android:type="linear">
<item android:color="#FF7490" android:offset="0"/>
<item android:color="#4563FF" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,32 @@
<!--
~ Copyright (C) 2020 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="42dp"
android:height="42dp" android:viewportWidth="496" android:viewportHeight="496">
<path android:fillColor="#6C9DFF" android:pathData="M248,92c-13.6,0 -24,-10.4 -24,-24V24c0,-13.6 10.4,-24 24,-24s24,10.4 24,24v44C272,80.8 261.6,92 248,92z"/>
<path android:fillColor="#DA3B7A" android:pathData="M248,496c-13.6,0 -24,-10.4 -24,-24v-44c0,-13.6 10.4,-24 24,-24s24,10.4 24,24v44C272,485.6 261.6,496 248,496z"/>
<path android:fillColor="#63BBFF" android:pathData="M157.6,116c-8,0 -16,-4 -20.8,-12l-21.6,-37.6c-6.4,-11.2 -2.4,-26.4 8.8,-32.8s26.4,-2.4 32.8,8.8L178.4,80c6.4,11.2 2.4,26.4 -8.8,32.8C166.4,114.4 161.6,116 157.6,116z"/>
<path android:fillColor="#E542A9" android:pathData="M360,465.6c-8,0 -16,-4 -20.8,-12L317.6,416c-6.4,-11.2 -2.4,-26.4 8.8,-32.8c11.2,-6.4 26.4,-2.4 32.8,8.8l21.6,37.6c6.4,11.2 2.4,26.4 -8.8,32.8C368,464.8 364,465.6 360,465.6z"/>
<path android:fillColor="#A1DCEC" android:pathData="M92,181.6c-4,0 -8,-0.8 -12,-3.2l-37.6,-21.6c-11.2,-6.4 -15.2,-21.6 -8.8,-32.8s21.6,-15.2 32.8,-8.8l37.6,21.6c11.2,6.4 15.2,21.6 8.8,32.8C108,177.6 100,181.6 92,181.6z"/>
<path android:fillColor="#B135FF" android:pathData="M442.4,384c-4,0 -8,-0.8 -12,-3.2L392,359.2c-11.2,-6.4 -15.2,-21.6 -8.8,-32.8c6.4,-11.2 21.6,-15.2 32.8,-8.8l37.6,21.6c11.2,6.4 15.2,21.6 8.8,32.8C458.4,380 450.4,384 442.4,384z"/>
<path android:fillColor="#F3FFFD" android:pathData="M68,272H24c-13.6,0 -24,-10.4 -24,-24s10.4,-24 24,-24h44c13.6,0 24,10.4 24,24S80.8,272 68,272z"/>
<path android:fillColor="#9254C8" android:pathData="M472,272h-44c-13.6,0 -24,-10.4 -24,-24s10.4,-24 24,-24h44c13.6,0 24,10.4 24,24S485.6,272 472,272z"/>
<path android:fillColor="#CE1CFF" android:pathData="M53.6,384c-8,0 -16,-4 -20.8,-12c-6.4,-11.2 -2.4,-26.4 8.8,-32.8l37.6,-21.6c11.2,-6.4 26.4,-2.4 32.8,8.8c6.4,11.2 2.4,26.4 -8.8,32.8l-37.6,21.6C62.4,383.2 58.4,384 53.6,384z"/>
<path android:fillColor="#6953E5" android:pathData="M404,181.6c-8,0 -16,-4 -20.8,-12c-6.4,-11.2 -2.4,-26.4 8.8,-32.8l37.6,-21.6c11.2,-6.4 26.4,-2.4 32.8,8.8s2.4,26.4 -8.8,32.8L416,178.4C412,180.8 408,181.6 404,181.6z"/>
<path android:fillColor="#DE339F" android:pathData="M136,465.6c-4,0 -8,-0.8 -12,-3.2c-11.2,-6.4 -15.2,-21.6 -8.8,-32.8l21.6,-37.6c6.4,-11.2 21.6,-15.2 32.8,-8.8c11.2,6.4 15.2,21.6 8.8,32.8l-21.6,37.6C152,461.6 144,465.6 136,465.6z"/>
<path android:fillColor="#5681FF" android:pathData="M338.4,116c-4,0 -8,-0.8 -12,-3.2c-11.2,-6.4 -15.2,-21.6 -8.8,-32.8l21.6,-37.6c6.4,-11.2 21.6,-15.2 32.8,-8.8c11.2,6.4 15.2,21.6 8.8,32.8L359.2,104C354.4,111.2 346.4,116 338.4,116z"/>
</vector>

View File

@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2020 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="42dp"
android:height="42dp" android:viewportWidth="427.652" android:viewportHeight="427.652">
<path android:fillColor="#00D95F" android:pathData="M213.826,0C95.733,0 0,95.733 0,213.826s95.733,213.826 213.826,213.826s213.826,-95.733 213.826,-213.826S331.919,0 213.826,0zM306.886,310.32c-2.719,4.652 -7.612,7.246 -12.638,7.247c-2.506,0 -5.044,-0.645 -7.364,-2c-38.425,-22.456 -82.815,-26.065 -113.295,-25.138c-33.763,1.027 -58.523,7.692 -58.769,7.76c-7.783,2.126 -15.826,-2.454 -17.961,-10.236c-2.134,-7.781 2.43,-15.819 10.209,-17.962c1.116,-0.307 27.76,-7.544 64.811,-8.766c21.824,-0.72 42.834,0.801 62.438,4.52c24.83,4.71 47.48,12.978 67.322,24.574C308.612,294.393 310.96,303.349 306.886,310.32zM334.07,253.861c-3.22,5.511 -9.016,8.583 -14.97,8.584c-2.968,0 -5.975,-0.763 -8.723,-2.369c-45.514,-26.6 -98.097,-30.873 -134.2,-29.776c-39.994,1.217 -69.323,9.112 -69.614,9.192c-9.217,2.515 -18.746,-2.906 -21.275,-12.124c-2.528,-9.218 2.879,-18.738 12.093,-21.277c1.322,-0.364 32.882,-8.937 76.77,-10.384c25.853,-0.852 50.739,0.949 73.96,5.354c29.412,5.58 56.241,15.373 79.744,29.108C336.115,234.995 338.897,245.603 334.07,253.861zM350.781,202.526c-3.641,0 -7.329,-0.936 -10.7,-2.906c-108.207,-63.238 -248.572,-25.643 -249.977,-25.255c-11.313,3.117 -23.008,-3.527 -26.124,-14.839c-3.117,-11.312 3.527,-23.008 14.839,-26.124c1.621,-0.447 40.333,-10.962 94.166,-12.737c31.713,-1.044 62.237,1.164 90.72,6.567c36.077,6.844 68.987,18.856 97.815,35.704c10.13,5.92 13.543,18.931 7.623,29.061C365.193,198.757 358.084,202.526 350.781,202.526z"/>
</vector>

View File

@ -0,0 +1,31 @@
<!--
~ Copyright (C) 2020 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="38dp" android:height="38dp"
android:viewportWidth="512" android:viewportHeight="512">
<path android:pathData="m512,256c0,141.387 -114.613,256 -256,256s-256,-114.613 -256,-256 114.613,-256 256,-256 256,114.613 256,256zM512,256">
<aapt:attr name="android:fillColor">
<gradient android:endX="512" android:endY="256"
android:startX="0" android:startY="256" android:type="linear">
<item android:color="#748AFF" android:offset="0"/>
<item android:color="#FF3C64" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path android:fillColor="#000000" android:pathData="m175,395.246c-4.035,0 -7.902,-1.629 -10.727,-4.512l-81,-82.832c-5.789,-5.922 -5.684,-15.418 0.238,-21.211 5.922,-5.793 15.418,-5.688 21.211,0.238l70.273,71.859 232.277,-237.523c5.793,-5.922 15.289,-6.027 21.211,-0.234 5.926,5.789 6.031,15.289 0.238,21.211l-243,248.492c-2.82,2.883 -6.688,4.512 -10.723,4.512zM175,395.246"/>
</vector>

View File

@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2020 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="42dp"
android:height="42dp" android:viewportWidth="512.0006" android:viewportHeight="512">
<path android:fillColor="#fff" android:fillType="evenOdd" android:pathData="m271.277,218.113 l-61.887,-38.234v76.469l0.004,76.469 61.883,-38.234 61.891,-38.234zM271.277,218.113"/>
<path android:fillColor="#f00" android:fillType="evenOdd" android:pathData="m209.391,179.879 l61.887,38.234 61.891,38.234 -61.891,38.234 -61.883,38.234 -0.004,-76.469zM13.816,343.254c5.242,50.121 25.367,70.117 66.094,76.469 76.73,11.961 275.449,11.961 352.184,0 40.723,-6.352 60.848,-26.344 66.09,-76.469 5.129,-48.996 5.129,-124.816 0,-173.813 -5.242,-50.121 -25.367,-70.117 -66.09,-76.469 -76.734,-11.961 -275.453,-11.961 -352.184,0 -40.727,6.352 -60.852,26.348 -66.094,76.469 -5.125,48.996 -5.125,124.816 0,173.813zM13.816,343.254"/>
<path android:fillColor="#FF000000" android:pathData="m329.848,417.492c-23.492,0.797 -48.336,1.199 -73.848,1.199 -73.746,0 -138.996,-3.309 -174.551,-8.852 -37.988,-5.922 -53.082,-23.621 -57.688,-67.629 -5.035,-48.148 -5.035,-123.582 0,-171.73 4.605,-44.008 19.699,-61.703 57.688,-67.625 6.168,-0.965 13.414,-1.879 21.543,-2.723 5.496,-0.57 9.488,-5.484 8.918,-10.98 -0.57,-5.492 -5.492,-9.488 -10.98,-8.914 -8.469,0.879 -16.059,1.84 -22.563,2.852 -23.695,3.695 -40.305,11.652 -52.266,25.039 -12.309,13.766 -19.371,32.918 -22.234,60.273 -5.156,49.316 -5.156,126.574 0,175.891 2.863,27.355 9.926,46.504 22.234,60.273 11.961,13.387 28.57,21.344 52.266,25.035 36.512,5.695 102.918,9.094 177.633,9.094 25.734,0 50.809,-0.406 74.523,-1.211 5.52,-0.188 9.844,-4.813 9.656,-10.332 -0.188,-5.52 -4.789,-9.844 -10.332,-9.66zM329.848,417.492"/>
<path android:fillColor="#FF000000" android:pathData="m508.133,168.402c-2.863,-27.352 -9.926,-46.504 -22.23,-60.273 -11.965,-13.387 -28.574,-21.344 -52.27,-25.035 -36.516,-5.695 -102.922,-9.094 -177.633,-9.094 -25.734,0 -50.805,0.41 -74.52,1.211 -5.523,0.188 -9.848,4.813 -9.66,10.332 0.188,5.523 4.793,9.855 10.336,9.66 23.488,-0.797 48.336,-1.199 73.848,-1.199 73.738,0 138.992,3.309 174.551,8.852 37.984,5.926 53.082,23.621 57.684,67.629 5.039,48.148 5.039,123.582 0,171.73 -4.602,44.008 -19.699,61.703 -57.684,67.629 -6.168,0.961 -13.418,1.875 -21.547,2.719 -5.492,0.57 -9.484,5.484 -8.914,10.98 0.535,5.145 4.875,8.969 9.934,8.969 0.348,0 0.695,-0.02 1.047,-0.055 8.465,-0.879 16.055,-1.836 22.559,-2.852 23.695,-3.695 40.305,-11.648 52.27,-25.035 12.305,-13.77 19.367,-32.922 22.23,-60.273 5.156,-49.316 5.156,-126.578 0,-175.895zM508.133,168.402"/>
<path android:fillColor="#FF000000" android:pathData="m338.422,247.84 l-123.773,-76.473c-3.086,-1.906 -6.961,-1.992 -10.129,-0.227 -3.164,1.766 -5.129,5.109 -5.129,8.734v152.941c0,3.625 1.965,6.969 5.129,8.734 1.52,0.844 3.195,1.266 4.871,1.266 1.828,0 3.648,-0.5 5.258,-1.492l123.773,-76.469c2.949,-1.824 4.746,-5.043 4.746,-8.508 0,-3.469 -1.797,-6.688 -4.746,-8.508zM219.395,314.883v-117.07l94.742,58.535zM219.395,314.883"/>
<path android:fillColor="#FF000000" android:pathData="m141.844,97.035c0.223,0 0.449,-0.008 0.676,-0.02 5.512,-0.367 9.68,-5.133 9.313,-10.645 -0.367,-5.512 -5.141,-9.688 -10.645,-9.316h-0.008c-5.512,0.367 -9.676,5.133 -9.309,10.645 0.352,5.285 4.75,9.336 9.973,9.336zM141.844,97.035"/>
<path android:fillColor="#FF000000" android:pathData="m369.484,415.684c-5.512,0.363 -9.684,5.129 -9.316,10.641 0.352,5.285 4.75,9.336 9.969,9.336 0.227,0 0.449,-0.008 0.676,-0.02h0.008c5.512,-0.367 9.676,-5.133 9.309,-10.645 -0.367,-5.512 -5.125,-9.676 -10.645,-9.313zM369.484,415.684"/>
</vector>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring">
<solid android:color="#00000000"/>
<corners
android:bottomLeftRadius="180dp"
android:bottomRightRadius="180dp"
android:topLeftRadius="180dp"
android:topRightRadius="180dp" />
</shape>

View File

@ -0,0 +1,274 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/linkSearch"
android:layout_width="wrap_content"
android:layout_height="46dp"
android:layout_marginTop="24dp"
android:background="@drawable/text_background_accented"
android:ems="10"
android:hint="Paste Link here"
android:inputType="text"
android:padding="8dp"
android:textAlignment="center"
android:textColor="@color/white"
android:textColorHint="@color/grey"
android:textSize="19sp"
app:layout_constraintEnd_toStartOf="@+id/btn_search"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:background="@drawable/btn_design"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:text="Search"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/linkSearch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/linkSearch"
app:layout_constraintTop_toTopOf="@+id/linkSearch" />
<ImageView
android:id="@+id/appLogo"
android:layout_width="0dp"
android:layout_height="200dp"
android:contentDescription="App Logo"
android:foreground="@drawable/gradient"
android:padding="20dp"
android:paddingBottom="10dp"
android:src="@drawable/spotify_download"
android:visibility="visible"
app:layout_collapseMode="parallax"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkSearch" />
<TextView
android:id="@+id/appName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:fontFamily="@font/raleway_semibold"
android:gravity="end"
android:text='"SpotiFlyer"'
android:textAlignment="viewEnd"
android:textColor="#9AB3FF"
android:textSize="40sp"
android:typeface="normal"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appLogo" />
<TextView
android:id="@+id/appSubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:fontFamily="@font/raleway_semibold"
android:text=" -Music Downloader"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="17sp"
app:layout_constraintEnd_toEndOf="@+id/appName"
app:layout_constraintStart_toStartOf="@+id/appName"
app:layout_constraintTop_toBottomOf="@+id/appName" />
<TextView
android:id="@+id/platforms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="2dp"
android:fontFamily="@font/raleway_semibold"
android:text="Supports: "
android:textAlignment="center"
android:textColor="#9AB3FF"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintEnd_toStartOf="@+id/btn_spotify"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appSubTitle" />
<ImageButton
android:id="@+id/btn_spotify"
android:layout_width="46dp"
android:layout_height="46dp"
android:layout_marginEnd="2dp"
android:background="@color/black"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_spotify_logo"
app:layout_constraintBottom_toBottomOf="@+id/platforms"
app:layout_constraintEnd_toStartOf="@+id/btn_youtube"
app:layout_constraintStart_toEndOf="@+id/platforms"
app:layout_constraintTop_toTopOf="@+id/platforms" />
<ImageButton
android:id="@+id/btn_youtube"
android:layout_width="52dp"
android:layout_height="52dp"
android:background="@color/black"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_youtube"
app:layout_constraintBottom_toBottomOf="@+id/btn_spotify"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btn_spotify"
app:layout_constraintTop_toTopOf="@+id/btn_spotify" />
<TextView
android:id="@+id/usage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:text="Usage Instructions!"
android:textAlignment="center"
android:textColor="#D0838383"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/developer_insta_spotify"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btn_linkedin"
app:layout_constraintTop_toTopOf="@+id/btn_github_spotify" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_donate"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:layout_marginBottom="32dp"
android:background="@drawable/text_background_accented"
android:drawableEnd="@drawable/ic_mug"
android:drawablePadding="5dp"
android:fontFamily="@font/capriola"
android:foreground="@drawable/rounded_gradient"
android:gravity="end|center_vertical"
android:padding="5dp"
android:text="Buy Me a Coffee"
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@+id/heart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/spotify_donate_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="Like This App?"
android:textColor="@color/colorPrimary"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/btn_donate"
app:layout_constraintEnd_toEndOf="@+id/btn_donate"
app:layout_constraintStart_toStartOf="@+id/btn_donate" />
<ImageButton
android:id="@+id/btn_github_spotify"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:background="@color/black"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_github"
app:layout_constraintBottom_toTopOf="@+id/btn_linkedin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_youtube"
app:layout_constraintVertical_chainStyle="packed" />
<ImageButton
android:id="@+id/btn_linkedin"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:background="@color/black"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_linkedin"
app:layout_constraintBottom_toTopOf="@+id/developer_insta_spotify"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_github_spotify" />
<ImageButton
android:id="@+id/developer_insta_spotify"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:background="@color/black"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_instagram"
app:layout_constraintBottom_toTopOf="@+id/btn_donate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_linkedin" />
<TextView
android:id="@+id/tagLine1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text="Made with "
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/heart"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:contentDescription="Made With Love In India"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tagline2"
app:layout_constraintStart_toEndOf="@+id/tagLine1"
app:srcCompat="@drawable/ic_heart" />
<TextView
android:id="@+id/tagline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text=" in India"
android:textAlignment="center"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/heart" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -34,7 +34,6 @@
android:background="@drawable/btn_design"
android:drawableEnd="@drawable/ic_arrow_slim"
android:drawablePadding="4dp"
android:drawableTint="@color/black"
android:padding="12dp"
android:text="Download All |"
android:textColor="@color/black"
@ -43,6 +42,21 @@
app:layout_anchor="@+id/appbar_spotify"
app:layout_anchorGravity="bottom|center" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/downloading_fab_spotify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:scaleType="fitCenter"
android:visibility="gone"
app:borderWidth="0dp"
app:layout_anchor="@+id/appbar_spotify"
app:layout_anchorGravity="bottom|center"
app:maxImageSize="38dp"
app:rippleColor="@color/colorPrimaryDark"
app:srcCompat="@drawable/ic_refresh"
app:tint="@null" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_spotify"
android:layout_width="match_parent"
@ -105,7 +119,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="20dp"
android:layout_marginBottom="22dp"
android:background="#00000000"
android:fontFamily="@font/raleway_semibold"
android:gravity="end"
@ -144,5 +158,6 @@
android:layout_height="0dp"
android:visibility="gone"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -20,6 +20,12 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="track"
type="com.shabinder.spotiflyer.models.Track" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="80dp"

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_youtube"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="25dp"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_download_all_youtube"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:background="@drawable/btn_design"
android:drawableEnd="@drawable/ic_arrow_slim"
android:drawablePadding="4dp"
android:drawableTint="@color/black"
android:padding="12dp"
android:text="Download All |"
android:textColor="@color/black"
android:textSize="16sp"
android:visibility="visible"
app:layout_anchor="@+id/appbar_youtube"
app:layout_anchorGravity="bottom|center" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/downloading_fab_youtube"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/black"
android:scaleType="fitCenter"
android:visibility="gone"
app:borderWidth="0dp"
app:layout_anchor="@+id/appbar_youtube"
app:layout_anchorGravity="bottom|center"
app:maxImageSize="38dp"
app:rippleColor="@color/colorPrimaryDark"
app:srcCompat="@drawable/ic_refresh"
app:tint="@null" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_youtube"
android:layout_width="match_parent"
android:layout_height="230dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="#F2C102B7"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:layout_scrollInterpolator="@android:anim/decelerate_interpolator"
app:toolbarId="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/TopLayout_youtube"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="@drawable/gradient"
>
<ImageView
android:id="@+id/youtube_cover_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="23dp"
android:layout_marginBottom="3dp"
android:contentDescription="Album Cover"
android:padding="15dp"
android:src="@drawable/spotify_download"
android:visibility="visible"
app:layout_collapseMode="parallax"
app:layout_constraintBottom_toTopOf="@id/title_view_youtube"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/StatusBar_youtube"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="@drawable/text_background_accented"
android:fontFamily="@font/raleway_semibold"
android:foreground="@drawable/rounded_gradient"
android:gravity="center"
android:paddingLeft="12dp"
android:paddingTop="1dp"
android:paddingRight="12dp"
android:paddingBottom="1dp"
android:text="Total: 100 Processed: 50"
android:textAlignment="center"
android:textColor="@color/grey"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/youtube_cover_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/title_view_youtube"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="20dp"
android:background="#00000000"
android:fontFamily="@font/raleway_semibold"
android:gravity="end"
android:text='"SpotiFlyer"'
android:textAlignment="viewEnd"
android:textColor="#9AB3FF"
android:textSize="28sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_list_youtube"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="26dp"
android:visibility="visible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar_youtube" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 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/>.
*/
configurations.maybeCreate("default")
artifacts.add("default", file('mobile-ffmpeg.aar'))

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg-runtime

View File

@ -0,0 +1 @@
o/com.arthenica.mobileffmpeg-r.txt

View File

@ -0,0 +1 @@
com.arthenica.mobileffmpeg

View File

@ -0,0 +1 @@
o/com.arthenica.mobileffmpeg

View File

@ -0,0 +1 @@
i/jars/classes.jar

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg.aar

View File

@ -0,0 +1 @@
i/AndroidManifest.xml

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg-runtime

View File

@ -0,0 +1 @@
i/jni

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg-runtime

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arthenica.mobileffmpeg"
android:versionCode="160440"
android:versionName="4.4.LTS" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="29" />
</manifest>

View File

@ -0,0 +1,16 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
-keep class com.arthenica.mobileffmpeg.Config {
native <methods>;
void log(int, byte[]);
void statistics(int, float, float, long, int, double, double);
}
-keep class com.arthenica.mobileffmpeg.AbiDetect {
native <methods>;
}

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg-api.jar

View File

@ -0,0 +1 @@
o/jetified-mobile-ffmpeg-runtime.jar

Binary file not shown.