initial commit
14
.gitignore
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
/build
|
||||
101
app/build.gradle
Normal 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
@@ -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
|
||||
76
app/schemas/com.yausername.dvd.database.AppDatabase/1.json
Normal 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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
29
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
33
app/src/main/java/com/yausername/dvd/App.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
161
app/src/main/java/com/yausername/dvd/adapters/VidInfoAdapter.kt
Normal 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)
|
||||
}
|
||||
|
||||
35
app/src/main/java/com/yausername/dvd/database/AppDatabase.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/com/yausername/dvd/database/Download.kt
Normal 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
|
||||
}
|
||||
@@ -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>>
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
18
app/src/main/java/com/yausername/dvd/model/VidInfoItem.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
40
app/src/main/java/com/yausername/dvd/ui/DownloadsFragment.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
232
app/src/main/java/com/yausername/dvd/ui/HomeFragment.kt
Normal 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
98
app/src/main/java/com/yausername/dvd/ui/MainActivity.kt
Normal 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()
|
||||
}
|
||||
78
app/src/main/java/com/yausername/dvd/ui/SettingsFragment.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
113
app/src/main/java/com/yausername/dvd/ui/YoutubeDlFragment.kt
Normal 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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/src/main/java/com/yausername/dvd/utils/FileUtils.kt
Normal 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
|
||||
}
|
||||
35
app/src/main/java/com/yausername/dvd/utils/NumberUtils.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
56
app/src/main/java/com/yausername/dvd/vm/VidInfoViewModel.kt
Normal 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
|
||||
}
|
||||
101
app/src/main/java/com/yausername/dvd/work/CommandWorker.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
|
||||
118
app/src/main/java/com/yausername/dvd/work/DownloadWorker.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
|
||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_audio_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_cloud_download_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_code_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_eye_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_folder_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_home_24dp.xml
Normal 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>
|
||||
74
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_search_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_thumb_down_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_thumb_up_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_video_24dp.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/launch_screen.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/drawable/toolbar.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
42
app/src/main/res/layout-land/activity_main.xml
Normal 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>
|
||||
39
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
13
app/src/main/res/layout/dialog_fragment_download_path.xml
Normal 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>
|
||||
67
app/src/main/res/layout/fragment_downloads.xml
Normal 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>
|
||||
12
app/src/main/res/layout/fragment_downloads_list.xml
Normal 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" />
|
||||
58
app/src/main/res/layout/fragment_home.xml
Normal 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>
|
||||
62
app/src/main/res/layout/fragment_youtube_dl.xml
Normal 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>
|
||||
80
app/src/main/res/layout/vid_format.xml
Normal 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>
|
||||
107
app/src/main/res/layout/vid_header.xml
Normal 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>
|
||||
18
app/src/main/res/menu/menu_nav.xml
Normal 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>
|
||||
16
app/src/main/res/menu/menu_toolbar.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
29
app/src/main/res/navigation/nav_graph.xml
Normal 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>
|
||||
9
app/src/main/res/values/colors.xml
Normal 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>
|
||||
4
app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
</resources>
|
||||
5
app/src/main/res/values/strings.xml
Normal 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>
|
||||
17
app/src/main/res/values/styles.xml
Normal 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>
|
||||
50
app/src/main/res/xml/collapsing_toolbar.xml
Normal 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>
|
||||
41
app/src/main/res/xml/collapsing_view.xml
Normal 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>
|
||||
25
app/src/main/res/xml/preferences.xml
Normal 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>
|
||||
17
app/src/test/java/com/yausername/dvd/ExampleUnitTest.kt
Normal 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
@@ -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
@@ -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
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
rootProject.name='dvd'
|
||||
include ':app'
|
||||