initial commit

This commit is contained in:
yausername
2020-05-22 19:26:58 +05:30
commit 8586c5f91a
92 changed files with 3055 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

122
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,122 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

20
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

30
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state />
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

12
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

101
app/build.gradle Normal file
View File

@@ -0,0 +1,101 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.yausername.dvd"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
ndk {
abiFilters 'x86', 'armeabi-v7a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
splits {
abi {
enable true
reset()
include 'x86', 'armeabi-v7a'
universalApk true
}
}
}
dependencies {
def nav_version = "2.3.0-alpha06"
def material_version = "1.2.0-alpha06"
def room_version = "2.2.5"
def arch_lifecycle_version = "2.2.0"
def coroutines_version = "1.3.4"
def work_version = "2.3.4"
def picassoVersion = "2.71828"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Kotlin components
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "com.google.android.material:material:$material_version"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-common-java8:$arch_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$arch_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$arch_lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$arch_lifecycle_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "com.github.yausername.youtubedl-android:library:0.9.0"
implementation "com.github.yausername.youtubedl-android:ffmpeg:0.9.0"
implementation "com.squareup.picasso:picasso:$picassoVersion"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# 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
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,76 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "4c10f0bc9b2e7a9b22a720c77489bd70",
"entities": [
{
"tableName": "downloads_table",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `downloaded_percent` REAL NOT NULL, `downloaded_size` INTEGER NOT NULL, `downloaded_path` TEXT NOT NULL, `media_type` TEXT NOT NULL, `name` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `total_size` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadedPercent",
"columnName": "downloaded_percent",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "downloadedSize",
"columnName": "downloaded_size",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadedPath",
"columnName": "downloaded_path",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "mediaType",
"columnName": "media_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "totalSize",
"columnName": "total_size",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4c10f0bc9b2e7a9b22a720c77489bd70')"
]
}
}

View File

@@ -0,0 +1,24 @@
package com.yausername.dvd
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yausername.dvd", appContext.packageName)
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yausername.dvd">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:name=".App"
android:theme="@style/AppTheme">
<activity android:name=".ui.MainActivity"
android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,33 @@
package com.yausername.dvd
import android.app.Application
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class App: Application() {
override fun onCreate() {
super.onCreate()
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
AppCompatDelegate.setDefaultNightMode(preferences.getString("Theme", AppCompatDelegate.MODE_NIGHT_YES.toString())!!.toInt())
val application = this
GlobalScope.launch {
try{
withContext(Dispatchers.IO){
YoutubeDL.getInstance().init(application)
FFmpeg.getInstance().init(application)
}
} catch (e: Exception){
Toast.makeText(applicationContext, "initialization failed", Toast.LENGTH_LONG).show()
}
}
}
}

View File

@@ -0,0 +1,64 @@
package com.yausername.dvd.adapters
import android.content.Intent
import android.net.Uri
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.RecyclerView
import com.yausername.dvd.R
import com.yausername.dvd.database.Download
import kotlinx.android.synthetic.main.fragment_downloads.view.*
class DownloadsAdapter : RecyclerView.Adapter<DownloadsAdapter.ViewHolder>() {
private var mValues: List<Download> = emptyList()
fun addItems(items: List<Download>){
mValues = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_downloads, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
with(holder.itemView) {
title_tv.text = item.name
download_pb.progress = item.downloadedPercent.toInt()
download_percent_tv.text = "${item.downloadedPercent} %"
val totalSize = Formatter.formatShortFileSize(context, item.totalSize)
val downloadedSize = Formatter.formatShortFileSize(context, item.downloadedSize)
download_size_tv.text = "${downloadedSize}/${totalSize}"
if(item.mediaType == "audio"){
format_ic.setImageResource(R.drawable.ic_audio_24dp)
}else{
format_ic.setImageResource(R.drawable.ic_video_24dp)
}
setOnClickListener(View.OnClickListener {
openFolder(item.downloadedPath, it)
})
}
}
override fun getItemCount(): Int = mValues.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
private fun openFolder(path: String, view: View) {
val intent = Intent(Intent.ACTION_VIEW)
val uri: Uri = Uri.parse(path + "/")
intent.setDataAndType(uri, "*/*")
startActivity(view.context, Intent.createChooser(intent, "Open folder"), null)
}
}

View File

@@ -0,0 +1,161 @@
package com.yausername.dvd.adapters
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.yausername.dvd.R
import com.yausername.dvd.model.VidInfoItem
import com.yausername.dvd.utils.NumberUtils
import com.yausername.youtubedl_android.mapper.VideoInfo
import kotlinx.android.synthetic.main.vid_format.view.*
import kotlinx.android.synthetic.main.vid_header.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
private const val ITEM_VIEW_TYPE_HEADER = 0
private const val ITEM_VIEW_TYPE_ITEM = 1
class VidInfoAdapter(private val clickListener: VidInfoListener) :
ListAdapter<VidInfoItem, RecyclerView.ViewHolder>(
VidInfoDiffCallback()
) {
private val adapterScope = CoroutineScope(Dispatchers.Default)
fun fill(vidInfo: VideoInfo?) {
adapterScope.launch {
if (vidInfo == null) {
submitList(emptyList())
return@launch
}
val items = mutableListOf<VidInfoItem>()
withContext(Dispatchers.Default) {
items.add(VidInfoItem.VidHeaderItem(vidInfo))
vidInfo.formats?.forEach { format ->
items.add(
VidInfoItem.VidFormatItem(
vidInfo,
format.formatId
)
)
}
}
withContext(Dispatchers.Main) {
submitList(items.toList())
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val vidItem = getItem(position) as VidInfoItem.VidFormatItem
val vidFormat = vidItem.vidFormat
with(holder.itemView) {
format_tv.text = vidFormat.format
ext_tv.text = vidFormat.ext
size_tv.text = Formatter.formatShortFileSize(context, vidFormat.filesize)
fps_tv.text = "${vidFormat.fps.toString()} fps "
abr_tv.text = "${vidFormat.abr.toString()} abr "
if (vidFormat.acodec != "none" && vidFormat.vcodec == "none") {
format_ic.setImageResource(R.drawable.ic_audio_24dp)
} else {
format_ic.setImageResource(R.drawable.ic_video_24dp)
}
setOnClickListener { clickListener.onClick(vidItem) }
}
}
else -> {
val vidItem = getItem(position) as VidInfoItem.VidHeaderItem
val vidInfo = vidItem.vidInfo
with(holder.itemView) {
title_tv.text = vidInfo.title
uploader_tv.text = vidInfo.uploader
uploader_tv.isSelected = true
views_tv.text = vidInfo.viewCount?.toLongOrNull()?.let {
NumberUtils.format(it)
} ?: vidInfo.viewCount
likes_tv.text = vidInfo.likeCount?.toLongOrNull()?.let {
NumberUtils.format(it)
} ?: vidInfo.likeCount
dislikes_tv.text = vidInfo.dislikeCount?.toLongOrNull()?.let {
NumberUtils.format(it)
} ?: vidInfo.dislikeCount
upload_date_tv.text = vidInfo.uploadDate
vidInfo.duration.toLong().apply {
val minutes = TimeUnit.SECONDS.toMinutes(this)
val seconds = this - TimeUnit.MINUTES.toSeconds(minutes)
duration_tv.text = "$minutes min, $seconds sec"
}
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_HEADER -> HeaderViewHolder.from(
parent
)
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(
parent
)
else -> throw ClassCastException("Unknown viewType ${viewType}")
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is VidInfoItem.VidHeaderItem -> ITEM_VIEW_TYPE_HEADER
is VidInfoItem.VidFormatItem -> ITEM_VIEW_TYPE_ITEM
}
}
class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
companion object {
fun from(parent: ViewGroup): HeaderViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.vid_header, parent, false)
return HeaderViewHolder(
view
)
}
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.vid_format, parent, false)
return ViewHolder(view)
}
}
}
}
class VidInfoDiffCallback : DiffUtil.ItemCallback<VidInfoItem>() {
override fun areItemsTheSame(oldItem: VidInfoItem, newItem: VidInfoItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: VidInfoItem, newItem: VidInfoItem): Boolean {
return oldItem == newItem
}
}
class VidInfoListener(val clickListener: (VidInfoItem.VidFormatItem) -> Unit) {
fun onClick(vidInfo: VidInfoItem.VidFormatItem) = clickListener(vidInfo)
}

View File

@@ -0,0 +1,35 @@
package com.yausername.dvd.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = arrayOf(Download::class), version = 1, exportSchema = true)
public abstract class AppDatabase : RoomDatabase() {
abstract fun downloadsDao(): DownloadsDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"dvd_db"
).build()
INSTANCE = instance
return instance
}
}
}
}

View File

@@ -0,0 +1,32 @@
package com.yausername.dvd.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "downloads_table")
data class Download(
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "timestamp")
val timestamp: Long,
@ColumnInfo(name = "total_size")
var totalSize: Long
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "downloaded_percent")
var downloadedPercent: Double = 0.0
@ColumnInfo(name = "downloaded_size")
var downloadedSize: Long = 0L
@ColumnInfo(name = "downloaded_path")
lateinit var downloadedPath: String
@ColumnInfo(name = "media_type")
lateinit var mediaType: String
}

View File

@@ -0,0 +1,20 @@
package com.yausername.dvd.database
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface DownloadsDao {
@Insert
suspend fun insert(item: Download)
@Update
suspend fun update(item: Download)
@Delete
suspend fun delete(item: Download)
@Query("SELECT * from downloads_table ORDER BY timestamp DESC")
fun getAllDownloads(): LiveData<List<Download>>
}

View File

@@ -0,0 +1,19 @@
package com.yausername.dvd.database
import androidx.lifecycle.LiveData
class DownloadsRepository(private val downloadsDao: DownloadsDao) {
val allDownloads: LiveData<List<Download>> = downloadsDao.getAllDownloads()
suspend fun insert(download: Download) {
downloadsDao.insert(download)
}
suspend fun update(download: Download) {
downloadsDao.update(download)
}
suspend fun delete(download: Download) {
downloadsDao.delete(download)
}
}

View File

@@ -0,0 +1,18 @@
package com.yausername.dvd.model
import com.yausername.youtubedl_android.mapper.VideoFormat
import com.yausername.youtubedl_android.mapper.VideoInfo
sealed class VidInfoItem {
abstract val id: String
data class VidFormatItem(val vidInfo: VideoInfo, val formatId: String) : VidInfoItem() {
override val id = vidInfo.id + "_" + formatId
val vidFormat: VideoFormat = vidInfo.formats.find { f -> f.formatId == formatId }!!
}
data class VidHeaderItem(val vidInfo: VideoInfo) : VidInfoItem() {
override val id = vidInfo.id
}
}

View File

@@ -0,0 +1,58 @@
package com.yausername.dvd.ui
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.yausername.dvd.R
import kotlinx.android.synthetic.main.dialog_fragment_download_path.view.*
class DownloadPathDialogFragment: DialogFragment() {
private lateinit var listener: DialogListener
interface DialogListener {
fun onOk(dialog: DownloadPathDialogFragment)
fun onFilePicker(dialog: DownloadPathDialogFragment)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.dialog_fragment_download_path, null)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
view.download_path_tv.text = sharedPrefs.getString("downloadLocation", "Not set")
builder.setView(view)
.setIcon(R.drawable.ic_folder_24dp)
.setTitle("Download location")
.setNegativeButton("Pick folder",
DialogInterface.OnClickListener { dialog, id ->
listener.onFilePicker(this)
})
.setPositiveButton("ok",
DialogInterface.OnClickListener { dialog, id ->
listener.onOk(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = parentFragment as DialogListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement DialogListener"))
}
}
}

View File

@@ -0,0 +1,40 @@
package com.yausername.dvd.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.yausername.dvd.adapters.DownloadsAdapter
import com.yausername.dvd.R
import com.yausername.dvd.vm.DownloadsViewModel
class DownloadsFragment : Fragment() {
private lateinit var downloadsViewModel: DownloadsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_downloads_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
adapter = DownloadsAdapter()
}
downloadsViewModel = ViewModelProvider(this).get(DownloadsViewModel::class.java)
downloadsViewModel.allDownloads.observe(viewLifecycleOwner, Observer { downloads ->
downloads?.let { (view.adapter as DownloadsAdapter).addItems(downloads)}
})
}
return view
}
}

View File

@@ -0,0 +1,232 @@
package com.yausername.dvd.ui
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.work.*
import com.squareup.picasso.Picasso
import com.yausername.dvd.R
import com.yausername.dvd.adapters.VidInfoAdapter
import com.yausername.dvd.adapters.VidInfoListener
import com.yausername.dvd.model.VidInfoItem
import com.yausername.dvd.vm.LoadState
import com.yausername.dvd.vm.VidInfoViewModel
import com.yausername.dvd.work.DownloadWorker
import getPathFromUri
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
class HomeFragment : Fragment(), SearchView.OnQueryTextListener,
DownloadPathDialogFragment.DialogListener {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
initViews(view)
}
private fun initViews(view: View) {
val vidFormatsVm =
ViewModelProvider(activity as MainActivity).get(VidInfoViewModel::class.java)
with(view.recyclerview) {
adapter =
VidInfoAdapter(VidInfoListener listener@{
vidFormatsVm.selectedItem = it
if (!isStoragePermissionGranted()) {
return@listener
}
DownloadPathDialogFragment().show(
childFragmentManager,
"choose download location"
)
})
}
vidFormatsVm.vidFormats.observe(viewLifecycleOwner, Observer { t ->
(recyclerview.adapter as VidInfoAdapter).fill(t)
})
vidFormatsVm.loadState.observe(viewLifecycleOwner, Observer { t ->
when (t) {
LoadState.INITIAL -> {
loading_pb.visibility = GONE
}
LoadState.LOADING -> {
loading_pb.visibility = VISIBLE
start_tv.visibility = GONE
}
LoadState.LOADED -> {
loading_pb.visibility = GONE
start_tv.visibility = GONE
}
}
})
vidFormatsVm.thumbnail.observe(viewLifecycleOwner, Observer {
it?.apply {
val picasso = Picasso.get()
picasso.load(this)
.into(toolbar_image)
}
})
}
override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.search).isVisible = true
(activity as MainActivity).supportActionBar?.themedContext?.let {
val searchView = SearchView(context)
menu.findItem(R.id.search).actionView = searchView
searchView.setOnQueryTextListener(this)
}
super.onPrepareOptionsMenu(menu)
}
override fun onQueryTextSubmit(query: String?): Boolean {
processSearch(query!!)
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
private fun processSearch(url: String) {
val vidFormatsVm =
ViewModelProvider(activity as MainActivity).get(VidInfoViewModel::class.java)
vidFormatsVm.fetchInfo(url)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
42069 -> {
data?.data?.let { uri ->
getPathFromUri(uri)
}?.let {
val vidFormatsVm =
ViewModelProvider(activity as MainActivity).get(VidInfoViewModel::class.java)
startDownload(vidFormatsVm.selectedItem, it)
}
}
}
}
private fun startDownload(vidFormatItem: VidInfoItem.VidFormatItem, downloadDir: String) {
val vidInfo = vidFormatItem.vidInfo
val vidFormat = vidFormatItem.vidFormat
val workTag = downloadDir + "/" + vidInfo.id
val workManager = WorkManager.getInstance(activity?.applicationContext!!)
val state =
workManager.getWorkInfosByTag(workTag).get()?.getOrNull(0)?.state
val running = state === WorkInfo.State.RUNNING || state === WorkInfo.State.ENQUEUED
if (running) {
Toast.makeText(
context,
"A download is already running in this directory. Please wait or choose a different directory",
Toast.LENGTH_LONG
).show()
return
}
val workData = workDataOf(
"url" to vidInfo.webpageUrl,
"name" to vidInfo.title,
"formatId" to vidFormat.formatId,
"acodec" to vidFormat.acodec,
"vcodec" to vidFormat.vcodec,
"downloadDir" to downloadDir,
"size" to vidFormat.filesize
)
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.addTag(workTag)
.setInputData(workData)
.build()
workManager.enqueueUniqueWork(
workTag,
ExistingWorkPolicy.KEEP,
workRequest
)
Toast.makeText(
context,
"Download queued. Check notification for progress",
Toast.LENGTH_LONG
).show()
}
override fun onOk(dialog: DownloadPathDialogFragment) {
val vidFormatsVm =
ViewModelProvider(activity as MainActivity).get(VidInfoViewModel::class.java)
val path = PreferenceManager.getDefaultSharedPreferences(context)
.getString("downloadLocation", null)
if (path == null) {
Toast.makeText(context, "invalid download location", Toast.LENGTH_SHORT).show()
return
}
startDownload(vidFormatsVm.selectedItem, path)
}
override fun onFilePicker(dialog: DownloadPathDialogFragment) {
val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
i.addCategory(Intent.CATEGORY_DEFAULT)
startActivityForResult(Intent.createChooser(i, "Choose directory"), 42069)
}
private fun isStoragePermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
true
} else {
requestPermissions(
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
1
)
false
}
} else { //permission is automatically granted on sdk<23 upon installation
true
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1 && grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
DownloadPathDialogFragment().show(
childFragmentManager,
"choose download location"
)
}
}
}

View File

@@ -0,0 +1,98 @@
package com.yausername.dvd.ui
import android.app.SearchManager
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.iterator
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.Navigation
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.setupWithNavController
import com.yausername.dvd.R
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(), NavActivity {
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(setOf(
R.id.home_fragment,
R.id.downloads_fragment,
R.id.youtube_dl_fragment
), drawer_layout)
toolbar.setupWithNavController(navController, appBarConfiguration)
supportActionBar?.title = navController.currentDestination?.label
bottom_view?.setupWithNavController(navController)
nav_view?.setupWithNavController(navController)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleIntent(intent!!)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_toolbar, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val navController = Navigation.findNavController(this,
R.id.nav_host_fragment
)
val navigated = NavigationUI.onNavDestinationSelected(item!!, navController)
return navigated || super.onOptionsItemSelected(item)
}
override fun hideNav() {
drawer_layout?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
bottom_view?.visibility = View.GONE
}
override fun showNav() {
drawer_layout?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
bottom_view?.visibility = View.VISIBLE
}
override fun showOptions() {
toolbar.menu.iterator().forEach { it.isVisible = true }
}
override fun hideOptions() {
toolbar.menu.iterator().forEach { it.isVisible = false }
}
private fun handleIntent(intent: Intent) {
if (Intent.ACTION_SEARCH == intent.action) {
val query = intent.getStringExtra(SearchManager.QUERY)
//use the query to search your data somehow
}
}
}
interface NavActivity {
fun hideNav()
fun showNav()
fun showOptions()
fun hideOptions()
}

View File

@@ -0,0 +1,78 @@
package com.yausername.dvd.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.yausername.dvd.R
import getPathFromUri
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
val themePreference: ListPreference? = findPreference("Theme")
themePreference?.let {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
it.entries = arrayOf("Light", "Dark", "Set by Battery Saver");
it.entryValues = arrayOf(AppCompatDelegate.MODE_NIGHT_NO.toString(), AppCompatDelegate.MODE_NIGHT_YES.toString(), AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY.toString());
} else{
it.entries = arrayOf("Light", "Dark", "System Default");
it.entryValues = arrayOf(AppCompatDelegate.MODE_NIGHT_NO.toString(), AppCompatDelegate.MODE_NIGHT_YES.toString(), AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM.toString());
}
}
themePreference?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt())
true
}
val downloadLocationPref: Preference? = findPreference("downloadLocation")
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
downloadLocationPref?.setSummary(sharedPrefs.getString("downloadLocation", "Set default download location"))
downloadLocationPref?.let {
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
i.addCategory(Intent.CATEGORY_DEFAULT)
startActivityForResult(Intent.createChooser(i, "Choose directory"), 6969)
true
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? NavActivity)?.hideNav()
setHasOptionsMenu(true)
}
override fun onDestroyView() {
super.onDestroyView()
(activity as? NavActivity)?.showNav()
(activity as? NavActivity)?.showOptions()
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
(activity as? NavActivity)?.hideOptions()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
6969 -> {
val path = getPathFromUri(data!!.data!!)
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
editor.putString("downloadLocation", path).apply()
findPreference<Preference>("downloadLocation")?.setSummary(path)
}
}
}
}

View File

@@ -0,0 +1,113 @@
package com.yausername.dvd.ui
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.fragment.app.Fragment
import androidx.work.*
import com.yausername.dvd.R
import com.yausername.dvd.work.CommandWorker
import kotlinx.android.synthetic.main.fragment_youtube_dl.*
class YoutubeDlFragment : Fragment(), View.OnClickListener {
var command: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_youtube_dl, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews(view)
}
private fun initViews(view: View){
command_btn.setOnClickListener(this)
}
override fun onClick(view: View?) {
when(view?.id){
R.id.command_btn -> {
command = command_et.text.toString()
if(isStoragePermissionGranted() && !command.isNullOrBlank()){
startCommand(command!!)
}
}
}
}
private fun startCommand(command: String) {
val workTag = "youtube-dl-custom-command"
val workManager = WorkManager.getInstance(activity?.applicationContext!!)
val state =
workManager.getWorkInfosByTag(workTag).get()?.getOrNull(0)?.state
val running = state === WorkInfo.State.RUNNING || state === WorkInfo.State.ENQUEUED
if (running) {
Toast.makeText(
context,
"A command is already running. Please wait for it to finish.",
Toast.LENGTH_LONG
).show()
return
}
val workData = workDataOf(
"command" to command
)
val workRequest = OneTimeWorkRequestBuilder<CommandWorker>()
.addTag(workTag)
.setInputData(workData)
.build()
workManager.enqueueUniqueWork(
workTag,
ExistingWorkPolicy.KEEP,
workRequest
)
Toast.makeText(
context,
"Command queued. Check notification for progress",
Toast.LENGTH_LONG
).show()
}
private fun isStoragePermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
true
} else {
requestPermissions(
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
1
)
false
}
} else { //permission is automatically granted on sdk<23 upon installation
true
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if(requestCode == 1 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
startCommand(command!!)
}
}
}

View File

@@ -0,0 +1,16 @@
@file:JvmName("PathUtils")
import android.net.Uri
import android.provider.DocumentsContract
fun getPathFromUri(uri: Uri): String? {
val docId = DocumentsContract.getTreeDocumentId(uri)
val split: Array<String?> = docId.split(":").toTypedArray()
val res: String?
if (split.size >= 2 && split[1] != null) {
res = split[1]!!
} else {
res = null
}
return res
}

View File

@@ -0,0 +1,35 @@
package com.yausername.dvd.utils;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
public class NumberUtils {
private static final NavigableMap<Long, String> suffixes = new TreeMap<>();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Map.Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
}

View File

@@ -0,0 +1,33 @@
package com.yausername.dvd.vm
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.yausername.dvd.database.AppDatabase
import com.yausername.dvd.database.Download
import com.yausername.dvd.database.DownloadsRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class DownloadsViewModel(application: Application) : AndroidViewModel(application) {
private val repository: DownloadsRepository
val allDownloads: LiveData<List<Download>>
init {
val downloadsDao = AppDatabase.getDatabase(application).downloadsDao()
repository = DownloadsRepository(downloadsDao)
allDownloads = repository.allDownloads
}
fun insert(word: Download) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}
fun update(word: Download) = viewModelScope.launch(Dispatchers.IO) {
repository.update(word)
}
fun delete(word: Download) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(word)
}
}

View File

@@ -0,0 +1,56 @@
package com.yausername.dvd.vm
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.yausername.dvd.model.VidInfoItem
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.mapper.VideoInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class VidInfoViewModel : ViewModel() {
val vidFormats: MutableLiveData<VideoInfo> = MutableLiveData()
val loadState: MutableLiveData<LoadState> = MutableLiveData(LoadState.INITIAL)
val thumbnail: MutableLiveData<String> = MutableLiveData()
lateinit var selectedItem: VidInfoItem.VidFormatItem
private fun submit(vidInfoItems: VideoInfo?) {
vidFormats.postValue(vidInfoItems)
}
private fun updateLoading(loadState: LoadState) {
this.loadState.postValue(loadState)
}
private fun updateThumbnail(thumbnail: String) {
this.thumbnail.postValue(thumbnail)
}
fun fetchInfo(url: String) {
viewModelScope.launch {
updateLoading(LoadState.LOADING)
submit(null)
lateinit var vidInfo: VideoInfo
try {
withContext(Dispatchers.IO) {
vidInfo = YoutubeDL.getInstance().getInfo(url)
}
} catch (e: Exception) {
updateLoading(LoadState.LOADED)
return@launch
}
updateLoading(LoadState.LOADED)
updateThumbnail(vidInfo.thumbnail)
submit(vidInfo)
}
}
}
enum class LoadState {
INITIAL, LOADING, LOADED
}

View File

@@ -0,0 +1,101 @@
package com.yausername.dvd.work
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.yausername.dvd.R
import com.yausername.youtubedl_android.DownloadProgressCallback
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
class CommandWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
private val notificationManager =
appContext.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val command = inputData.getString("command")!!
createNotificationChannel()
val notificationId = id.hashCode()
val notification = NotificationCompat.Builder(applicationContext,
channelId
)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("youtube-dl command")
.setContentText("Running command")
.build()
val foregroundInfo = ForegroundInfo(notificationId, notification)
setForeground(foregroundInfo)
// this is not the recommended way to add options/flags/url and might break in future
// use the constructor for url, addOption(key) for flags, addOption(key, value) for options
val request = YoutubeDLRequest(Collections.emptyList())
val commandRegex = "\"([^\"]*)\"|(\\S+)"
val m: Matcher = Pattern.compile(commandRegex).matcher(command)
while (m.find()) {
if (m.group(1) != null) {
request.addOption(m.group(1))
} else {
request.addOption(m.group(2))
}
}
YoutubeDL.getInstance()
.execute(request, DownloadProgressCallback { progress, etaInSeconds ->
showProgress(id.hashCode(), "youtube-dl command", progress.toInt(), etaInSeconds)
})
return Result.success()
}
private fun showProgress(id: Int, name: String, progress: Int, etaInSeconds: Long) {
val notification = NotificationCompat.Builder(applicationContext,
channelId
)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(name)
.setStyle(NotificationCompat.BigTextStyle().bigText("Task ?/n (ETA $etaInSeconds seconds)"))
.setProgress(100, progress, false)
.build()
notificationManager?.notify(id, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var notificationChannel =
notificationManager?.getNotificationChannel(channelId)
if (notificationChannel == null) {
notificationChannel = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_LOW
)
notificationChannel.description =
channelDescription
notificationManager?.createNotificationChannel(notificationChannel)
}
}
}
companion object {
const val channelName = "dvd download"
const val channelDescription = "dvd download"
const val channelId = "dvd download"
}
}

View File

@@ -0,0 +1,118 @@
package com.yausername.dvd.work
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.yausername.dvd.R
import com.yausername.dvd.database.AppDatabase
import com.yausername.dvd.database.Download
import com.yausername.dvd.database.DownloadsRepository
import com.yausername.youtubedl_android.DownloadProgressCallback
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import java.util.*
class DownloadWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
private val notificationManager =
appContext.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val url = inputData.getString("url")!!
val name = inputData.getString("name")!!
val formatId = inputData.getString("formatId")!!
val acodec = inputData.getString("acodec")
val vcodec = inputData.getString ("vcodec")
val downloadDir = inputData.getString("downloadDir")!!
val size = inputData.getLong("size", 0L)
createNotificationChannel()
val notificationId = id.hashCode()
val notification = NotificationCompat.Builder(applicationContext,
channelId
)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(name)
.setContentText("Starting download")
.build()
val foregroundInfo = ForegroundInfo(notificationId, notification)
setForeground(foregroundInfo)
val request = YoutubeDLRequest(url)
request.addOption("-o", "$downloadDir/%(title)s.%(ext)s")
val videoOnly = vcodec != "none" && acodec == "none"
if (videoOnly) {
request.addOption("-f", "${formatId}+bestaudio")
} else {
request.addOption("-f", formatId)
}
YoutubeDL.getInstance()
.execute(request, DownloadProgressCallback { progress, etaInSeconds ->
showProgress(id.hashCode(), name, progress.toInt(), etaInSeconds)
})
val downloadsDao = AppDatabase.getDatabase(
applicationContext
).downloadsDao()
val repository =
DownloadsRepository(downloadsDao)
val download =
Download(name, Date().time, size)
download.downloadedPath = downloadDir
download.downloadedPercent = 100.00
download.downloadedSize = size
download.mediaType = if(vcodec == "none" && acodec != "none") "audio" else "video"
repository.insert(download)
return Result.success()
}
private fun showProgress(id: Int, name: String, progress: Int, etaInSeconds: Long) {
val notification = NotificationCompat.Builder(applicationContext,
channelId
)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(name)
.setStyle(NotificationCompat.BigTextStyle().bigText("Task ?/n (ETA $etaInSeconds seconds)"))
.setProgress(100, progress, false)
.build()
notificationManager?.notify(id, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
var notificationChannel =
notificationManager?.getNotificationChannel(channelId)
if (notificationChannel == null) {
notificationChannel = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_LOW
)
notificationChannel.description =
channelDescription
notificationManager?.createNotificationChannel(notificationChannel)
}
}
}
companion object {
const val channelName = "dvd download"
const val channelDescription = "dvd download"
const val channelId = "dvd download"
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M15,3L6,3c-0.83,0 -1.54,0.5 -1.84,1.22l-3.02,7.05c-0.09,0.23 -0.14,0.47 -0.14,0.73v1.91l0.01,0.01L1,14c0,1.1 0.9,2 2,2h6.31l-0.95,4.57 -0.03,0.32c0,0.41 0.17,0.79 0.44,1.06L9.83,23l6.59,-6.59c0.36,-0.36 0.58,-0.86 0.58,-1.41L17,5c0,-1.1 -0.9,-2 -2,-2zM19,3v12h4L23,3h-4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:attr/colorBackground"/>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
app:menu="@menu/menu_toolbar"
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/menu_nav" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_toolbar"
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottom_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:navGraph="@navigation/nav_graph"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<TextView
android:id="@+id/download_path_tv"
android:text="Not set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="10dp"
/>
</RelativeLayout>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Never gonna give you up"
android:maxLines="2"
android:ellipsize="marquee"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/download_pb"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/title_tv" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/download_pb">
<TextView
android:id="@+id/download_percent_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="5dp"
android:text="69%"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/download_size_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/download_percent_tv"
android:paddingHorizontal="5dp"
android:text="69M/420M"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<ImageView
android:id="@+id/format_ic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/download_size_tv"
android:src="@drawable/ic_audio_24dp"
/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:name="com.yausername.dvd.DownloadsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="10dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ui.DownloadsFragment"
tools:listitem="@layout/fragment_downloads" />

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity"
app:layoutDescription="@xml/collapsing_toolbar"
tools:showPaths="true">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_image" />
<TextView
android:id="@+id/start_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/toolbar_image"
android:layout_marginTop="20dp"
android:layout_marginHorizontal="10dp"
android:text="Paste a video link in the search bar"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
/>
<ProgressBar
android:id="@+id/loading_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:elevation="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<ImageView
android:id="@+id/toolbar_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:contentDescription="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:fitsSystemWindows="true"
android:scaleType="centerInside"
android:src="@drawable/toolbar"
android:background="@color/colorPrimary" />
</androidx.constraintlayout.motion.widget.MotionLayout>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/collapsing_view"
tools:context=".ui.MainActivity"
tools:showPaths="true">
<ImageView
android:id="@+id/toolbar_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="@color/colorPrimary"
android:contentDescription="@null"
android:fitsSystemWindows="true"
android:scaleType="centerInside"
android:src="@drawable/toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/start_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/toolbar_image"
android:layout_marginTop="20dp"
android:layout_marginHorizontal="10dp"
android:text="youtube-dl commands"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
/>
<RelativeLayout
android:id="@+id/youtube_dl_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/start_tv">
<EditText
android:id="@+id/command_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:layout_marginTop="20dp"
android:hint="--extract-audio --audio-format mp3 -o /sdcard/Download/youtubedl-android/%(title)s.%(ext)s https://www.youtube.com/watch?v=dQw4w9WgXcQ"
android:inputType="textMultiLine" />
<Button
android:id="@+id/command_btn"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_below="@id/command_et"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:text="execute" />
</RelativeLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<RelativeLayout
android:id="@+id/vid_details1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/format_ic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:src="@drawable/ic_video_24dp" />
<TextView
android:id="@+id/format_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_toEndOf="@id/format_ic"
android:ellipsize="marquee"
android:singleLine="true"
android:text="302 - 1280x720 (720p60)"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/vid_details2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vid_details1">
<TextView
android:id="@+id/ext_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_eye_24dp"
android:text="mp4" />
<TextView
android:id="@+id/size_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/ext_tv"
android:paddingHorizontal="7dp"
android:text="69MB" />
<TextView
android:id="@+id/fps_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/size_tv"
android:paddingHorizontal="7dp"
android:text="60 fps" />
<TextView
android:id="@+id/abr_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/fps_tv"
android:paddingHorizontal="7dp"
android:text="60 abr" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp">
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Never gonna give you up"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/vid_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/title_tv">
<TextView
android:id="@+id/uploader_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/views_ic"
android:text="Rick Ashley"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<ImageView
android:id="@+id/views_ic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/views_tv"
android:paddingHorizontal="7dp"
android:src="@drawable/ic_eye_24dp" />
<TextView
android:id="@+id/views_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/thumbs_up_ic"
android:text="69" />
<ImageView
android:id="@+id/thumbs_up_ic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/likes_tv"
android:paddingHorizontal="7dp"
android:src="@drawable/ic_thumb_up_24dp" />
<TextView
android:id="@+id/likes_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/thumbs_down_ic"
android:text="69" />
<ImageView
android:id="@+id/thumbs_down_ic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/dislikes_tv"
android:paddingHorizontal="7dp"
android:src="@drawable/ic_thumb_down_24dp" />
<TextView
android:id="@+id/dislikes_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="69" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vid_details">
<TextView
android:id="@+id/upload_date_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2020-69-69"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/duration_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:paddingHorizontal="5dp"
android:text="69 seconds"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/home_fragment"
android:icon="@drawable/ic_home_24dp"
android:title="Home" />
<item
android:id="@+id/downloads_fragment"
android:icon="@drawable/ic_cloud_download_24dp"
android:title="Downloads" />
<item
android:id="@+id/youtube_dl_fragment"
android:icon="@drawable/ic_code_24dp"
android:title="youtube-dl" />
</menu>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/settings_fragment"
android:icon="@android:drawable/ic_menu_save"
android:menuCategory="secondary"
android:title="Settings" />
<item
android:id="@+id/search"
android:icon="@drawable/ic_search_24dp"
android:title="Search"
android:visible="false"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="collapseActionView|ifRoom" />
</menu>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="com.yausername.dvd.ui.HomeFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/downloads_fragment"
android:name="com.yausername.dvd.ui.DownloadsFragment"
android:label="Downloads"
tools:layout="@layout/fragment_downloads_list" />
<fragment
android:id="@+id/settings_fragment"
android:name="com.yausername.dvd.ui.SettingsFragment"
android:label="Settings" />
<fragment
android:id="@+id/youtube_dl_fragment"
android:name="com.yausername.dvd.ui.YoutubeDlFragment"
android:label="youtube-dl" />
</navigation>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#4fc3f7</color>
<color name="colorPrimaryDark">#0093c4</color>
<color name="colorPrimaryLight">#8bf6ff</color>
<color name="colorAccent">#ce93d8</color>
<color name="colorAccentDark">#883997</color>
<color name="colorAccentLight">#ee98fb</color>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<string name="app_name">dvd</string>
<string name="theme_dark">Dark</string>
<string name="theme_light">Light</string>
</resources>

View File

@@ -0,0 +1,17 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- The launcher theme. It sets the main window background to the launch_screen drawable -->
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/launch_screen</item>
<!-- Optional, on Android 5+ you can modify the colorPrimaryDark color to match the windowBackground color for further branding-->
<!--<item name="colorPrimaryDark">@android:color/white</item>-->
</style>
</resources>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:motion="http://schemas.android.com/tools">
<Transition
app:constraintSetEnd="@id/collapsed"
app:constraintSetStart="@id/expanded">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@id/recyclerview"
app:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="@+id/expanded">
<Constraint
android:id="@id/toolbar_image"
android:layout_height="250dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="imageAlpha"
app:customIntegerValue="255" />
</Constraint>
<Constraint android:id="@+id/loading_pb">
<PropertySet app:visibilityMode="ignore" />
</Constraint>
<Constraint android:id="@+id/start_tv">
<PropertySet app:visibilityMode="ignore" />
</Constraint>
</ConstraintSet>
<ConstraintSet
android:id="@+id/collapsed"
motion:deriveConstraintsFrom="@id/expanded">
<Constraint
android:id="@id/toolbar_image"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="imageAlpha"
app:customIntegerValue="0" />
</Constraint>
</ConstraintSet>
</MotionScene>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@id/collapsed"
app:constraintSetStart="@id/expanded">
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@id/youtube_dl_rl"
app:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="@+id/expanded">
<Constraint
android:id="@id/toolbar_image"
android:layout_height="250dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="imageAlpha"
app:customIntegerValue="255" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/collapsed">
<Constraint
android:id="@id/toolbar_image"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<CustomAttribute
app:attributeName="imageAlpha"
app:customIntegerValue="0" />
</Constraint>
</ConstraintSet>
</MotionScene>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
app:key="Theme"
app:title="Theme"
app:useSimpleSummaryProvider="true"
app:iconSpaceReserved="false"/>
<Preference
android:title="Download Location"
android:key="downloadLocation"
app:summary="Default download location"
app:iconSpaceReserved="false">
</Preference>
<Preference
android:title="About"
app:summary="https://github.com/yausername/dvd"
app:persistent="false"
app:iconSpaceReserved="false">
<intent android:action="android.intent.action.VIEW"
android:data="https://github.com/yausername/dvd" />
</Preference>
</PreferenceScreen>

View File

@@ -0,0 +1,17 @@
package com.yausername.dvd
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

29
build.gradle Normal file
View File

@@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

21
gradle.properties Normal file
View File

@@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Mon May 11 21:03:29 IST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

172
gradlew vendored Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name='dvd'
include ':app'