- added extractor args lang when searching in yt-dlp
- removed webarchive search engine since its not supported anymore
- fixed terminal prematurely closing
- made format auto-update on by default for new users
- fixed main activity getting removed from recents when using the app with rvx
- added ability to have the last used command template for the next download
- fixed app crashing in landscape logs
- fixed app constantly going back to home in landscape or config change. now it keeps state
- add subtitle language suggestions in the subtitle dialog
- made command template scroll state hold even if fragment is destroyed
- added slovak sk language
- fixed terminal icon being blank in white mode, and now its red
- fixed share files from notification showing 2 files even though its 1
- fixed history item not being deleted from the bottom sheet
- cleared outdated player urls for stale result items
- added export cookies as file
- added export command templates for selected templates
- added icons for history details sheet chips
- added markdown in the update dialog
This commit is contained in:
deniscerri 2023-10-29 00:45:42 +02:00
parent 9353e08ee9
commit f335bbddf5
No known key found for this signature in database
GPG key ID: 95C43D517D830350
111 changed files with 1811 additions and 682 deletions

View file

@ -4,7 +4,7 @@
</h1>
<div align="center">
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.MD">English</a>
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.md">English</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
Azərbaycanca
</div>

View file

@ -4,7 +4,7 @@
</h1>
<div align="center">
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.MD">English</a>
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.md">English</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
Indonesia
</div>

View file

@ -4,7 +4,7 @@
</h1>
<div align="center">
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.MD">English</a>
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.md">English</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
Português
</div>

View file

@ -4,7 +4,7 @@
</h1>
<div align="center">
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.MD">English</a>
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README.md">English</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
Shqip
</div>

View file

@ -10,7 +10,7 @@
&nbsp;&nbsp;| &nbsp;&nbsp;
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-az.md">Azərbaycanca</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-id.MD">Indonesia</a>
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-id.md">Indonesia</a>
&nbsp;&nbsp;| &nbsp;&nbsp;
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-pt.md">Português</a>
</div>
@ -34,7 +34,7 @@
[![community](https://img.shields.io/badge/Telegram-Updates-red?style=flat-square&logo=telegram)](https://t.me/ytdlnisupdates)
### The links above are the only trusted sources of YTDLnis. Everything else is not related to me.
</div>

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
def versionPatch = 8
def versionBuild = 3 // bump for dogfood builds, public betas, etc.
def versionBuild = 4 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){
@ -188,5 +188,6 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha09'
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha10'
implementation "io.noties.markwon:core:4.6.2"
}

View file

@ -0,0 +1,558 @@
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "09798517d2b7ce96999c879bcd381c7f",
"entities": [
{
"tableName": "results",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `creationTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formats",
"columnName": "formats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "urls",
"columnName": "urls",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "chapters",
"columnName": "chapters",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTime",
"columnName": "creationTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadId",
"columnName": "downloadId",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "container",
"columnName": "container",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Default'"
},
{
"fieldPath": "downloadSections",
"columnName": "downloadSections",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "allFormats",
"columnName": "allFormats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadSize",
"columnName": "downloadSize",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "audioPreferences",
"columnName": "audioPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "videoPreferences",
"columnName": "videoPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "extraCommands",
"columnName": "extraCommands",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "customFileNameTemplate",
"columnName": "customFileNameTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "SaveThumb",
"columnName": "SaveThumb",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Queued'"
},
{
"fieldPath": "downloadStartTime",
"columnName": "downloadStartTime",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "logID",
"columnName": "logID",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "commandTemplates",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "templateShortcuts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cookies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "logs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadType",
"columnName": "downloadType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadTime",
"columnName": "downloadTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "terminalDownloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"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, '09798517d2b7ce96999c879bcd381c7f')"
]
}
}

View file

@ -43,7 +43,7 @@
<activity
android:name=".MainActivity"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:configChanges="locale"
android:exported="true"
android:windowSoftInputMode="adjustPan">
@ -53,7 +53,6 @@
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:excludeFromRecents="true"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.BottomSheet">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@ -125,7 +124,7 @@
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:configChanges="locale"
android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan"
android:exported="true">
@ -147,7 +146,7 @@
android:exported="true"
android:icon="@mipmap/ic_launcher_dark"
android:roundIcon="@mipmap/ic_launcher_round_dark"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:configChanges="locale"
android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan">
<intent-filter>
@ -167,7 +166,7 @@
android:exported="true"
android:icon="@mipmap/ic_launcher_light"
android:roundIcon="@mipmap/ic_launcher_round_light"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:configChanges="locale"
android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan">
<intent-filter>
@ -183,7 +182,7 @@
<activity
android:name=".ui.more.WebViewActivity"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:configChanges="locale"
android:exported="true"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustResize">

View file

@ -45,11 +45,9 @@ import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigation.NavigationView
import com.google.android.material.navigationrail.NavigationRailView
@ -65,7 +63,6 @@ import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.io.Reader
import java.net.URI
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import kotlin.system.exitProcess
@ -81,7 +78,6 @@ class MainActivity : BaseActivity() {
private lateinit var navigationView: View
private lateinit var navHostFragment : NavHostFragment
private lateinit var navController : NavController
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -93,9 +89,7 @@ class MainActivity : BaseActivity() {
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (preferences.getBoolean("incognito", false)){
resultViewModel.deleteAll()
}
if (preferences.getBoolean("incognito", false)) resultViewModel.deleteAll()
askPermissions()
checkUpdate()
@ -120,17 +114,17 @@ class MainActivity : BaseActivity() {
}
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (savedInstanceState == null){
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
graph.setStartDestination(R.id.homeFragment)
when(preferences.getString("start_destination", "")) {
"History" -> graph.setStartDestination(R.id.historyFragment)
"More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
}
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
graph.setStartDestination(R.id.homeFragment)
when(sharedPreferences.getString("start_destination", "")) {
"History" -> graph.setStartDestination(R.id.historyFragment)
"More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
navController.graph = graph
}
navController.graph = graph
if (navigationView is NavigationBarView){
(navigationView as NavigationBarView).setupWithNavController(navController)
(navigationView as NavigationBarView).setOnItemReselectedListener {
@ -164,7 +158,7 @@ class MainActivity : BaseActivity() {
(navigationView as NavigationView).setupWithNavController(navController)
//terminate button
(navigationView as NavigationView).menu.getItem(7).setOnMenuItemClickListener {
if (sharedPreferences.getBoolean("ask_terminate_app", true)){
if (preferences.getBoolean("ask_terminate_app", true)){
var doNotShowAgain = false
val terminateDialog = MaterialAlertDialogBuilder(this)
terminateDialog.setTitle(getString(R.string.confirm_delete_history))
@ -188,7 +182,7 @@ class MainActivity : BaseActivity() {
runBlocking {
job.join()
if (doNotShowAgain){
sharedPreferences.edit().putBoolean("ask_terminate_app", false).apply()
preferences.edit().putBoolean("ask_terminate_app", false).apply()
}
finishAndRemoveTask()
finishAffinity()
@ -212,9 +206,6 @@ class MainActivity : BaseActivity() {
(navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this)
}
cookieViewModel.updateCookiesFile()
val intent = intent
handleIntents(intent)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (navigationView is NavigationBarView){
@ -233,12 +224,15 @@ class MainActivity : BaseActivity() {
}
}
when(sharedPreferences.getString("start_destination", "")) {
"Queue" -> navController.navigate(R.id.downloadQueueMainFragment)
when(preferences.getString("start_destination", "")) {
"Queue" -> if (savedInstanceState == null) navController.navigate(R.id.downloadQueueMainFragment)
}
if (sharedPreferences.getBoolean("auto_update_ytdlp", false)){
cookieViewModel.updateCookiesFile()
val intent = intent
handleIntents(intent)
if (preferences.getBoolean("auto_update_ytdlp", false)){
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
@ -251,8 +245,8 @@ class MainActivity : BaseActivity() {
}
}
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBundle("nav_state", navController.saveState())
@ -489,10 +483,6 @@ class MainActivity : BaseActivity() {
}
dialog.show()
}
override fun onConfigurationChanged(newConfig: Configuration) {
startActivity(Intent(this, MainActivity::class.java))
super.onConfigurationChanged(newConfig)
}
companion object {
private const val TAG = "MainActivity"

View file

@ -7,11 +7,6 @@ import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.VideoPreferences
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class Converters {

View file

@ -1,14 +1,9 @@
package com.deniscerri.ytdlnis.database
import android.content.Context
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.room.*
import com.deniscerri.ytdlnis.database.dao.*
import com.deniscerri.ytdlnis.database.models.*
import kotlinx.coroutines.flow.Flow
@TypeConverters(Converters::class)
@Database(

View file

@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.database.dao
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import kotlinx.coroutines.flow.Flow
@ -48,10 +47,13 @@ interface CommandTemplateDao {
fun getTotalShortcutNumber() : Int
@Query("SELECT * FROM commandTemplates WHERE id=:id LIMIT 1")
fun getTemplate(id: Long) : CommandTemplate
fun getTemplate(id: Long) : CommandTemplate?
@Query("SELECT * FROM commandTemplates WHERE content=:content LIMIT 1")
fun getTemplateByContent(content: String) : CommandTemplate?
@Query("SELECT * FROM commandTemplates ORDER BY id DESC LIMIT 1")
fun getFirst() : CommandTemplate
fun getFirst() : CommandTemplate?
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: CommandTemplate)

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.LogItem
import kotlinx.coroutines.flow.Flow

View file

@ -1,10 +1,6 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.paging.PagingSource
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.TerminalItem
import kotlinx.coroutines.flow.Flow
@ -34,11 +30,12 @@ interface TerminalDao {
val log = t.log ?: ""
val lines = log.split("\n")
//clean dublicate progress + add newline
val l = if (listOf(line, lines.last()).all { it.contains("[download")}){
lines.dropLast(1).joinToString("\n")
}else{
log
} + "\n${line}"
var newLine = line
if (newLine.contains("[download")){
newLine = "[download]" + line.split("[download]").last()
}
val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
updateTerminalLog(l, id)
}

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@Entity(tableName = "downloads")
data class DownloadItemSimple(

View file

@ -3,6 +3,8 @@ package com.deniscerri.ytdlnis.database.models
import com.google.gson.annotations.SerializedName
data class GithubRelease(
@SerializedName(value = "html_url")
val html_url: String,
@SerializedName(value = "tag_name")
var tag_name: String,
@SerializedName(value = "body")

View file

@ -1,6 +1,5 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@Entity(tableName = "terminalDownloads")
data class TerminalItem(

View file

@ -1,10 +1,8 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import kotlinx.coroutines.flow.Flow
@ -37,7 +35,7 @@ class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
}
fun getItem(id: Long) : CommandTemplate {
return commandDao.getTemplate(id)
return commandDao.getTemplate(id)!!
}
suspend fun insert(item: CommandTemplate){

View file

@ -1,6 +1,5 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
import com.deniscerri.ytdlnis.database.dao.CookieDao
import com.deniscerri.ytdlnis.database.models.CookieItem
import kotlinx.coroutines.flow.Flow

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.database.repository
import com.deniscerri.ytdlnis.database.dao.LogDao
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.LogItem
import kotlinx.coroutines.flow.Flow
@ -16,6 +15,10 @@ class LogRepository(private val logDao: LogDao) {
return logDao.getLogFlowByID(id)
}
fun getLogByID(id: Long) : LogItem? {
return logDao.getByID(id)
}
suspend fun insert(item: LogItem) : Long{
return logDao.insert(item)
@ -31,13 +34,17 @@ class LogRepository(private val logDao: LogDao) {
}
fun getItem(id: Long) : LogItem{
return logDao.getByID(id);
return logDao.getByID(id)
}
suspend fun update(newLine: String, id: Long){
val item = getItem(id)
item.content += "${newLine}\n"
logDao.update(item)
suspend fun update(line: String, id: Long){
runCatching {
val item = getItem(id)
val log = item.content
//clean duplicate progress + add newline
//item.content = log.replace("(?s:.*\\n)?\\K\\[download\\]( *?)(\\d)(.*?)\\n(?!.*\\[download\\]( *?)(\\d)(.*?)\\n)".toRegex()).replac { it.contains("[download") }.joinToString("\n") + "\n${line}"
logDao.update(item)
}
}
}

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.database.repository
import android.content.Context
import android.util.Log
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.InfoUtil

View file

@ -12,10 +12,8 @@ import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -28,8 +26,9 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
val sortOrder = MutableLiveData(DBManager.SORTING.DESC)
val sortType = MutableLiveData(CommandTemplateRepository.CommandTemplateSortType.DATE)
private val queryFilter = MutableLiveData("")
private var _items = MediatorLiveData<List<CommandTemplate>>()
val allItems: LiveData<List<CommandTemplate>>
private var _items = MediatorLiveData<List<CommandTemplate>>()
val shortcuts : LiveData<List<TemplateShortcut>>
private val jsonFormat = Json { prettyPrint = true }

View file

@ -6,6 +6,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase.OPEN_READONLY
import android.os.Environment
import android.util.Log
import android.webkit.CookieManager
import androidx.lifecycle.AndroidViewModel
@ -17,6 +18,7 @@ import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.repository.CookieRepository
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
@ -169,4 +171,18 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
}
}
fun exportToFile(exported: (File?) -> Unit) {
try{
val cookieFile = File(application.cacheDir, "cookies.txt")
if (!cookieFile.exists()) updateCookiesFile()
val downloads = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis_Cookies.txt")
val file = cookieFile.copyTo(downloads, true)
exported(file)
}catch (e: Exception){
e.printStackTrace()
exported(null)
}
}
}

View file

@ -6,13 +6,11 @@ import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Looper
import android.util.DisplayMetrics
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
@ -29,7 +27,6 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.HistoryDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.AudioPreferences
import com.deniscerri.ytdlnis.database.models.CommandTemplate
@ -272,7 +269,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
if (preferredAudioFormats.isEmpty()){
preferredAudioFormats.add(getFormat(resultItem.formats, Type.audio).format_id)
val audioF = getFormat(resultItem.formats, Type.audio)
if (!infoUtil.getGenericAudioFormats(resources).contains(audioF)){
preferredAudioFormats.add(audioF.format_id)
}
}
val videoPreferences = VideoPreferences(
@ -465,7 +465,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
)
}
else -> {
val c = commandTemplateDao.getFirst()
val lastUsedCommandTemplate = sharedPreferences.getString("lastCommandTemplateUsed", "")!!
val c = if (lastUsedCommandTemplate.isBlank()){
commandTemplateDao.getFirst() ?: CommandTemplate(0,"","", false)
}else{
commandTemplateDao.getTemplateByContent(lastUsedCommandTemplate) ?: CommandTemplate(0, "", lastUsedCommandTemplate, false)
}
return generateCommandFormat(c)
}
}
@ -474,7 +479,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun generateCommandFormat(c: CommandTemplate) : Format {
return Format(
c.title,
"",
c.id.toString(),
"",
"",
"",
@ -486,7 +491,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun getLatestCommandTemplateAsFormat() : Format {
val t = commandTemplateDao.getFirst()
val t = commandTemplateDao.getFirst()!!
return Format(t.title, "", "", "", "", 0, t.content)
}
@ -591,7 +596,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
startDownloadWorker(emptyList())
}
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicate : Boolean = false) {
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicate : Boolean = false) = CoroutineScope(Dispatchers.IO).launch {
val context = App.instance
val alarmScheduler = AlarmScheduler(context)
val activeAndQueuedDownloads = withContext(Dispatchers.IO){

View file

@ -12,7 +12,6 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.DBManager.SORTING
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -23,6 +22,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
val websiteFilter = MutableLiveData("")
private val queryFilter = MutableLiveData("")
private val formatFilter = MutableLiveData("")
val allItems : LiveData<List<HistoryItem>>
private var _items = MediatorLiveData<List<HistoryItem>>()

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
@ -11,7 +10,6 @@ import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.LogRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class LogViewModel(private val application: Application) : AndroidViewModel(application) {

View file

@ -6,7 +6,6 @@ import android.util.Log
import android.util.Patterns
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
@ -17,10 +16,8 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
import com.deniscerri.ytdlnis.util.UiUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

View file

@ -1,62 +1,21 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.preference.PreferenceManager
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.await
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.dao.TerminalDao
import com.deniscerri.ytdlnis.database.models.AudioPreferences
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.TerminalItem
import com.deniscerri.ytdlnis.database.models.VideoPreferences
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.work.AlarmScheduler
import com.deniscerri.ytdlnis.work.DownloadWorker
import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.Locale
import java.util.concurrent.TimeUnit
class TerminalViewModel(private val application: Application) : AndroidViewModel(application) {

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
@ -13,9 +12,7 @@ import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class AlarmStopReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {

View file

@ -4,7 +4,6 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.NotificationUtil
@ -12,7 +11,6 @@ import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class CancelDownloadNotificationReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {

View file

@ -3,16 +3,7 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class CancelWorkReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.NotificationUtil

View file

@ -4,9 +4,11 @@ import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
@ -36,10 +38,13 @@ class ShareFileActivity : BaseActivity() {
}
}
setContentView(R.layout.activity_share)
val paths = intent.getStringArrayExtra("path")
val notificationId = intent.getIntExtra("notificationID", 0)
NotificationUtil(this).cancelDownloadNotification(notificationId)
UiUtil.shareFileIntent(this, paths!!.toList())
Log.e("SHAREE", paths.toString())
UiUtil.shareFileIntent(this, paths!!.toList().filter { it.isNotBlank() }.distinct())
finishAffinity()
}
}

View file

@ -11,6 +11,7 @@ import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.util.Log
import android.util.Patterns
import android.view.*
import android.view.View.*
import android.widget.*
@ -30,7 +31,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.HomeAdapter
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -227,7 +228,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
launch{
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res) }
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed ={}) }
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
}
@ -557,7 +558,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
lifecycleScope.launch(Dispatchers.IO){
Thread.sleep(300)
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
if (queryList.size == 1 && queryList.first().matches("(https?://(?:www\\.|(?!www))[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|www\\.[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z\\d]+\\.\\S{2,}|www\\.[a-zA-Z\\d]+\\.\\S{2,})".toRegex())){
if (queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()){
if (sharedPreferences!!.getBoolean("download_card", true)) {
showSingleDownloadSheet(
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),

View file

@ -1,11 +1,10 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -26,12 +25,11 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.squareup.picasso.Picasso
import java.lang.StringBuilder
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
@ -19,18 +19,16 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.squareup.picasso.Picasso
import java.lang.StringBuilder
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences
@ -132,13 +130,15 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
pauseButton.setOnClickListener {
if (pauseButton.tag == ActiveDownloadAdapter.ActiveDownloadAction.Pause){
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
onItemClickListener.onPauseClick(item.id,
ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
if (progressBar.progress == 0) progressBar.isIndeterminate = false
cancelButton.visibility = View.VISIBLE
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
}else{
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
onItemClickListener.onPauseClick(item.id,
ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
progressBar.isIndeterminate = true
cancelButton.visibility = View.GONE

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
@ -24,7 +24,9 @@ import com.google.android.material.button.MaterialButton
import com.squareup.picasso.Picasso
import java.util.Locale
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences : SharedPreferences
@ -35,7 +37,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val cardView: FrameLayout
init {
@ -46,7 +48,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.download_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
return ViewHolder(cardView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -136,11 +138,16 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
card.setOnClickListener {
onItemClickListener.onCardClick(item.url)
}
card.setOnLongClickListener {
onItemClickListener.onDelete(item.url); true
}
}
interface OnItemClickListener {
fun onButtonClick(itemURL: String)
fun onCardClick(itemURL: String)
fun onDelete(itemURL: String)
}
companion object {

View file

@ -1,11 +1,10 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -14,7 +13,9 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.google.android.material.card.MaterialCardView
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
@ -6,25 +6,23 @@ import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<LogItem?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<LogItem?, DownloadLogsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val checkedItems: ArrayList<Long>

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
@ -10,13 +10,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil
@ -24,7 +22,9 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItemSimple, GenericDownloadAdapter.ViewHolder>(DIFF_CALLBACK) {
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItemSimple, GenericDownloadAdapter.ViewHolder>(
DIFF_CALLBACK
) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
val checkedItems: ArrayList<Long>

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
@ -24,13 +24,16 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.squareup.picasso.Picasso
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<HistoryItem?, HistoryAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<HistoryItem?, HistoryAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val checkedItems: ArrayList<Long>
private val onItemClickListener: OnItemClickListener
private val activity: Activity
@ -105,7 +108,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
datetime.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
// BUTTON ----------------------------------
val btn = card.findViewById<MaterialButton>(R.id.downloads_download_button_type)
val btn = card.findViewById<FloatingActionButton>(R.id.downloads_download_button_type)
var filePresent = true
//IS IN THE FILE SYSTEM?
@ -121,11 +124,11 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
thumbnail.alpha = 0.7f
}
if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
if (filePresent) btn.setImageResource(R.drawable.ic_music_downloaded) else btn.setImageResource(R.drawable.ic_music)
} else if (item.type == DownloadViewModel.Type.video) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_terminal)
btn.setImageResource(R.drawable.ic_terminal)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filePresent

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
@ -9,9 +9,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
@ -25,7 +23,9 @@ import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val checkedVideos: ArrayList<String>
private val onItemClickListener: OnItemClickListener
private val activity: Activity

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
@ -21,11 +21,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.util.*
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val checkedItems: ArrayList<String>
private val onItemClickListener: OnItemClickListener
private val activity: Activity

View file

@ -1,23 +1,26 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.opengl.Visibility
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.LogItem
import com.google.android.material.card.MaterialCardView
class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CommandTemplate?, TemplatesAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CommandTemplate?, TemplatesAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val checkedItems: ArrayList<Long> = ArrayList()
init {
this.onItemClickListener = onItemClickListener
@ -52,19 +55,74 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
if (item!!.useAsExtraCommand) useInExtraCommands.visibility = View.VISIBLE
else useInExtraCommands.visibility = View.GONE
if (checkedItems.contains(item.id)) {
card.isChecked = true
card.strokeWidth = 5
} else {
card.isChecked = false
card.strokeWidth = 0
}
card.setOnClickListener {
onItemClickListener.onItemClick(item, position)
if (checkedItems.size > 0) {
checkCard(card, item.id)
} else {
onItemClickListener.onItemClick(item, position)
}
}
card.setOnLongClickListener {
onItemClickListener.onDelete(item); true
checkCard(card, item.id)
true
}
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckeditems() {
for (i in 0 until itemCount){
val item = getItem(i)
if (checkedItems.find { it == item?.id } != null){
checkedItems.remove(item?.id)
notifyItemChanged(i)
}
}
checkedItems.clear()
}
@SuppressLint("NotifyDataSetChanged")
fun checkAll(items: List<CommandTemplate?>?){
checkedItems.clear()
checkedItems.addAll(items!!.map { it!!.id })
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun invertSelected(items: List<CommandTemplate?>?){
val invertedList = mutableListOf<Long>()
items?.forEach {
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
}
checkedItems.clear()
checkedItems.addAll(invertedList)
notifyDataSetChanged()
}
private fun checkCard(card: MaterialCardView, itemID: Long) {
if (card.isChecked) {
card.strokeWidth = 0
checkedItems.remove(itemID)
} else {
card.strokeWidth = 5
checkedItems.add(itemID)
}
card.isChecked = !card.isChecked
onItemClickListener.onCardSelect(itemID, card.isChecked)
}
interface OnItemClickListener {
fun onItemClick(commandTemplate: CommandTemplate, index: Int)
fun onSelected(commandTemplate: CommandTemplate)
fun onDelete(commandTemplate: CommandTemplate)
fun onCardSelect(itemID: Long, isChecked: Boolean)
}
companion object {

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.adapter
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
@ -17,7 +17,9 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
class TerminalDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<TerminalItem?, TerminalDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
class TerminalDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<TerminalItem?, TerminalDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences

View file

@ -4,11 +4,8 @@ import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -25,17 +22,12 @@ import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import java.util.regex.Pattern
class AddExtraCommandsDialog(private val item: DownloadItem?, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {

View file

@ -12,7 +12,6 @@ import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.core.content.edit
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@ -179,10 +178,10 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = result.url
link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), result.url, null)
UiUtil.openLinkIntent(requireContext(), result.url)
}
link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null)
UiUtil.copyLinkToClipBoard(requireContext(), result.url)
true
}

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri

View file

@ -14,15 +14,14 @@ import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.isEmpty
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
@ -35,6 +34,7 @@ import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -69,7 +69,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
return fragmentView
}
@ -92,6 +92,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
if (title.editText?.text?.isEmpty() == true){
title.editText!!.setText(downloadItem.title)
title.endIconMode = END_ICON_NONE
}
title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
@ -100,17 +101,12 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
downloadItem.title = p0.toString()
}
})
title.setEndIconOnClickListener {
if (resultItem != null){
title.editText?.setText(resultItem?.title)
}
title.endIconDrawable = null
}
author = view.findViewById(R.id.author_textinput)
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
if (author.editText?.text?.isEmpty() == true){
author.editText!!.setText(downloadItem.author)
author.endIconMode = END_ICON_NONE
}
author.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
@ -119,25 +115,35 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
downloadItem.author = p0.toString()
}
})
author.setEndIconOnClickListener {
if (resultItem != null){
author.editText?.setText(resultItem?.author)
}
author.endIconDrawable = null
}
if (savedInstanceState?.containsKey("updated") == true){
if (!listOf(resultItem?.title, downloadItem.title).contains(title.editText?.text.toString())){
title.endIconMode = TextInputLayout.END_ICON_CUSTOM
title.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
downloadItem.title = title.editText?.text.toString()
}
if (!listOf(resultItem?.author, downloadItem.author).contains(author.editText?.text.toString())){
author.endIconMode = TextInputLayout.END_ICON_CUSTOM
author.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
downloadItem.author = author.editText?.text.toString()
}
}
title.setEndIconOnClickListener {
if (resultItem != null){
title.editText?.setText(resultItem?.title)
}
title.endIconMode = END_ICON_NONE
}
author.setEndIconOnClickListener {
if (resultItem != null){
author.editText?.setText(resultItem?.author)
}
author.endIconMode = END_ICON_NONE
}
saveDir = view.findViewById(R.id.outputPath)
saveDir.editText!!.setText(
@ -295,9 +301,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
downloadItem.title = t
downloadItem.author = a
title.editText?.setText(t)
title.endIconDrawable = null
title.endIconMode = END_ICON_NONE
author.editText?.setText(a)
title.endIconDrawable = null
author.endIconMode = END_ICON_NONE
}
override fun updateUI(res: ResultItem?){

View file

@ -22,6 +22,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
@ -31,6 +32,7 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.InfoUtil
@ -44,6 +46,7 @@ import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import java.text.SimpleDateFormat
import java.util.*
@ -53,16 +56,19 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
private lateinit var viewPager2: ViewPager2
private lateinit var fragmentAdapter : DownloadFragmentAdapter
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var historyViewModel: HistoryViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var commandTemplateDao : CommandTemplateDao
private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences : SharedPreferences
private lateinit var updateItem : Button
private lateinit var view: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
infoUtil = InfoUtil(requireContext())
@ -87,6 +93,7 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
tabLayout = view.findViewById(R.id.download_tablayout)
viewPager2 = view.findViewById(R.id.download_viewpager)
updateItem = view.findViewById(R.id.update_item)
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
isNestedScrollingEnabled = false
@ -121,8 +128,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.isClickable = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
updateItem.visibility = View.GONE
}
//remove outdated player url of 1hr so it can refetch it in the cut player
if (result.creationTime > System.currentTimeMillis() - 3600000) result.urls = ""
val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter(
@ -200,8 +211,10 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
val shownFields = sharedPreferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.visibility = if(sharedPreferences.getStringSet("modify_download_card", setOf())!!.contains("schedule")){
scheduleBtn.visibility = if(shownFields.contains("schedule")){
View.VISIBLE
}else{
View.GONE
@ -250,19 +263,19 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.visibility = if(sharedPreferences.getStringSet("modify_download_card", setOf())!!.contains("url")){
link.visibility = if(shownFields.contains("url")){
View.VISIBLE
}else{
View.GONE
}
val updateItem = view.findViewById<Button>(R.id.update_item)
if (Patterns.WEB_URL.matcher(result.url).matches()){
link.text = result.url
link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), result.url, null)
UiUtil.openLinkIntent(requireContext(), result.url)
}
link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null)
UiUtil.copyLinkToClipBoard(requireContext(), result.url)
true
}
@ -281,12 +294,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}else{
link.visibility = View.GONE
updateItem.visibility = View.GONE
(updateItem.parent as LinearLayout).visibility = View.GONE
}
//update in the background if there is no data
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false)){
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
initUpdateData(view)
}else {
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
@ -295,10 +308,10 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
}
lifecycleScope.launch {
CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) {
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel)
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel, historyViewModel)
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null,
actions = null
@ -306,6 +319,17 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
}
}
lifecycleScope.launch {
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed = {
dismiss()
}) }
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
}
}
}
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){
@ -344,12 +368,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
private fun initUpdateData(v: View){
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
val shimmerLoading = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_title)
val title = v.findViewById<View>(R.id.bottom_sheet_title)
val shimmerLoading = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_title)
val title = v.findViewById<View>(R.id.bottom_sheet_title)
val shimmerLoadingSubtitle = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_subtitle)
val subtitle = v.findViewById<View>(R.id.bottom_sheet_subtitle)
val shimmerLoadingSubtitle = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_subtitle)
val subtitle = v.findViewById<View>(R.id.bottom_sheet_subtitle)
val updateJob = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
withContext(Dispatchers.Main){
title.visibility = View.GONE
subtitle.visibility = View.GONE
@ -367,12 +391,6 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
val result = resultViewModel.parseQueries(listOf(result.url))
if (result.isEmpty()){
withContext(Dispatchers.Main){
Looper.prepare().run {
Toast.makeText(requireContext(), "No Results Found!", Toast.LENGTH_SHORT).show()
}
dismiss()
}
return@launch
}
@ -421,6 +439,21 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
}
}
shimmerLoading.setOnClickListener {
updateJob.cancel()
}
updateJob.invokeOnCompletion {
requireActivity().runOnUiThread {
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
}
}
updateJob.start()
}
private fun initUpdateFormats(url: String){
@ -436,7 +469,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}
}
val formats = infoUtil.getFormats(url).toMutableList()
val formats = runCatching {
infoUtil.getFormats(url).toMutableList()
}.getOrElse {
mutableListOf()
}
withContext(Dispatchers.Main){
runCatching {

View file

@ -8,10 +8,8 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.util.Patterns
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
@ -23,6 +21,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -30,7 +29,6 @@ import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
@ -39,7 +37,9 @@ import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@ -66,7 +66,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
return fragmentView
}
@ -84,6 +84,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
}
}
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
if (!Patterns.WEB_URL.matcher(downloadItem.url).matches()){
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", false))
downloadItem.url = ""
@ -117,6 +119,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0,
p0.toString()
)
preferences.edit().putString("lastCommandTemplateUsed", p0.toString()).apply()
}
})
@ -132,7 +135,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
runCatching {
populateCommandCard(commandTemplateCard, templates.first())
commandTemplateCard.setOnClickListener {
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
@ -141,6 +143,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
chosenCommandView.editText!!.text.insert(chosenCommandView.editText!!.selectionStart, "${c.content} ")
}
populateCommandCard(commandTemplateCard, it.first())
downloadItem.format = Format(
it.first().title,
"",
@ -148,15 +151,20 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
"",
"",
0,
it.map { c -> c.content }.joinToString { " " }
it.joinToString(" ") { c -> c.content }
)
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
}
}
}
if (downloadItem.url.isEmpty()){
view.findViewById<MaterialCardView>(R.id.command_card).alpha = 0.3f
val existingTemplate = withContext(Dispatchers.IO){
templates.firstOrNull { it.content.replace("^ +|\n| +$".toRegex(), " ") == downloadItem.format.format_note.trim() }
}
populateCommandCard(commandTemplateCard,existingTemplate ?: templates.first())
if (downloadItem.url.isEmpty() || existingTemplate == null){
commandTemplateCard.alpha = 0.3f
}
}.onFailure {
commandTemplateCard.visibility = View.GONE
@ -189,7 +197,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
view.findViewById<LinearLayout>(R.id.adjust).apply {
visibility = if (shownFields.contains("adjust_command")) View.VISIBLE else View.GONE
visibility = if (shownFields.contains("adjust_templates")) View.VISIBLE else View.GONE
if (isVisible){
UiUtil.configureCommand(
view,
@ -208,6 +216,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0,
it.content
)
preferences.edit().putString("lastCommandTemplateUsed", it.content).apply()
commandTemplateCard.visibility = View.VISIBLE
view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE
view.findViewById<Chip>(R.id.editSelected).isEnabled = true
@ -237,6 +246,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0,
it.content
)
preferences.edit().putString("lastCommandTemplateUsed", it.content).apply()
}
},
shortcutClicked = {
@ -244,6 +254,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
itemSelected = {
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
},
itemRemoved = {removed ->
chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${
@ -252,6 +263,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
)
})(?!.*\\1)".toRegex(), "").trimEnd())
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString().trimEnd()
preferences.edit().putString("lastCommandTemplateUsed",
chosenCommandView.editText!!.text.toString().trimEnd()).apply()
})
}
)

View file

@ -34,12 +34,13 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.ui.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil
@ -67,6 +68,7 @@ import java.util.Locale
class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var historyViewModel: HistoryViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
@ -81,6 +83,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -472,10 +475,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
saveSubtitlesClicked = {checked ->
items.forEach { it.videoPreferences.writeSubs = checked }
},
subtitleLanguagesClicked = {
UiUtil.showSubtitleLanguagesDialog(requireActivity(), ""){ value ->
items.forEach { it.videoPreferences.subsLanguages = value }
}
subtitleLanguagesSet = {value ->
items.forEach { it.videoPreferences.subsLanguages = value }
},
removeAudioClicked = {checked ->
items.forEach { it.videoPreferences.removeAudio = checked }
@ -509,7 +510,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
lifecycleScope.launch {
downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) {
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel)
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel, historyViewModel)
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null,
actions = null
@ -638,6 +639,24 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
}
}
override fun onDelete(itemURL: String) {
val deletedItem = items.find { it.url == itemURL } ?: return
val position = items.indexOf(deletedItem)
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
items.remove(deletedItem)
listAdapter.submitList(items.toList())
if (items.isNotEmpty()){
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
items.add(position, deletedItem)
listAdapter.submitList(items.toList())
}.show()
}else{
dismiss()
}
}
}
override fun onClick(p0: View?) {
}

View file

@ -7,7 +7,6 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -23,6 +22,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
@ -35,6 +35,9 @@ import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.textfield.TextInputLayout.EndIconMode
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -71,7 +74,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
infoUtil = InfoUtil(requireContext())
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
return fragmentView
}
@ -95,6 +98,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
if (title.editText?.text?.isEmpty() == true){
title.editText!!.setText(downloadItem.title)
title.endIconMode = END_ICON_NONE
}
title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
@ -108,6 +112,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
if (author.editText?.text?.isEmpty() == true){
author.editText!!.setText(downloadItem.author)
author.endIconMode = END_ICON_NONE
}
author.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
@ -119,16 +124,32 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
if (savedInstanceState?.containsKey("updated") == true){
if (!listOf(resultItem?.title, downloadItem.title).contains(title.editText?.text.toString())){
title.endIconMode = END_ICON_CUSTOM
title.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
downloadItem.title = title.editText?.text.toString()
}
if (!listOf(resultItem?.author, downloadItem.author).contains(author.editText?.text.toString())){
author.endIconMode = END_ICON_CUSTOM
author.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
downloadItem.author = author.editText?.text.toString()
}
}
title.setEndIconOnClickListener {
if (resultItem != null){
title.editText?.setText(resultItem?.title)
}
title.endIconMode = END_ICON_NONE
}
author.setEndIconOnClickListener {
if (resultItem != null){
author.editText?.setText(resultItem?.author)
}
author.endIconMode = END_ICON_NONE
}
saveDir = view.findViewById(R.id.outputPath)
saveDir.editText!!.setText(
FileUtil.formatPath(downloadItem.downloadPath)
@ -282,10 +303,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
saveSubtitlesClicked = {
downloadItem.videoPreferences.writeSubs = it
},
subtitleLanguagesClicked = {
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
downloadItem.videoPreferences.subsLanguages = it
}
subtitleLanguagesSet = {
downloadItem.videoPreferences.subsLanguages = it
},
removeAudioClicked = {
downloadItem.videoPreferences.removeAudio = it
@ -313,9 +332,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
downloadItem.title = t
downloadItem.author = a
title.editText?.setText(t)
title.endIconDrawable = null
title.endIconMode = END_ICON_NONE
author.editText?.setText(a)
title.endIconDrawable = null
title.endIconMode = END_ICON_NONE
}
override fun updateUI(res: ResultItem?) {

View file

@ -8,7 +8,6 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.compose.runtime.invalidateGroupsWithKey
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
@ -27,7 +26,6 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.internal.format
import java.util.*
@ -217,9 +215,9 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
dismiss()
}
if (sharedPreferences.getBoolean("update_formats", false) && refreshBtn.isVisible && items.size == 1){
refreshBtn.performClick()
}
// if (sharedPreferences.getBoolean("update_formats", false) && refreshBtn.isVisible && items.size == 1){
// refreshBtn.performClick()
// }
}
private fun returnFormats(){

View file

@ -37,9 +37,9 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -136,6 +136,9 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//remove outdated player url of 1hr so it can refetch it in the player
if (item.creationTime > System.currentTimeMillis() - 3600000) item.urls = ""
activeAdapter = ActiveDownloadMinifiedAdapter(this,requireActivity())
queuedAdapter = GenericDownloadAdapter(this,requireActivity())
@ -220,10 +223,10 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
bottomSheetLink.text = item.url
bottomSheetLink.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), item.url, null)
UiUtil.openLinkIntent(requireContext(), item.url)
}
bottomSheetLink.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), item.url, null)
UiUtil.copyLinkToClipBoard(requireContext(), item.url)
true
}
@ -505,10 +508,12 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
link!!.text = url
link.tag = itemID
link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
bottomSheet.dismiss()
UiUtil.openLinkIntent(requireContext(), item.url)
}
link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
bottomSheet.dismiss()
UiUtil.copyLinkToClipBoard(requireContext(), item.url)
true
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)

View file

@ -1,18 +1,14 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.ActionBar.LayoutParams
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
@ -20,7 +16,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.PlaylistAdapter
import com.deniscerri.ytdlnis.ui.adapter.PlaylistAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -30,14 +26,10 @@ import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
class SelectPlaylistItemsDialog(private val items: List<ResultItem?>, private val type: DownloadViewModel.Type) : DialogFragment(), PlaylistAdapter.OnItemClickListener {
private lateinit var downloadViewModel: DownloadViewModel

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -17,7 +18,7 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -41,6 +42,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
lateinit var downloadItem: DownloadItem
private lateinit var notificationUtil: NotificationUtil
private lateinit var pauseResume: MaterialButton
private lateinit var noResults: RelativeLayout
private lateinit var workManager: WorkManager
@ -71,6 +73,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
activeRecyclerView.adapter = activeDownloads
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
pauseResume = view.findViewById(R.id.pause_resume)
noResults = view.findViewById(R.id.no_results)
pauseResume.setOnClickListener {
if (pauseResume.text == requireContext().getString(R.string.pause)){
@ -168,6 +171,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
activeDownloads.notifyDataSetChanged()
pauseResume.visibility = View.GONE
}
noResults.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE
}
}

View file

@ -10,6 +10,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
@ -21,7 +22,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -47,6 +48,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var adapter : GenericDownloadAdapter
private lateinit var noResults : RelativeLayout
private var actionMode : ActionMode? = null
private var totalSize = 0
@ -55,7 +57,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return fragmentView
@ -70,6 +72,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
requireActivity()
)
noResults = view.findViewById(R.id.no_results)
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
cancelledRecyclerView.forceFastScrollMode()
cancelledRecyclerView.adapter = adapter
@ -90,6 +93,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){
totalSize = it
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}

View file

@ -15,10 +15,8 @@ import androidx.viewpager2.widget.ViewPager2
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout

View file

@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.Fragment
@ -23,7 +24,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -48,6 +49,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var erroredRecyclerView : RecyclerView
private lateinit var noResults : RelativeLayout
private lateinit var adapter : GenericDownloadAdapter
private var actionMode : ActionMode? = null
private var totalSize: Int = 0
@ -56,7 +58,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return fragmentView
@ -71,6 +73,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
requireActivity()
)
noResults = view.findViewById(R.id.no_results)
erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
erroredRecyclerView.forceFastScrollMode()
erroredRecyclerView.adapter = adapter
@ -90,6 +93,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){
totalSize = it
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}

View file

@ -37,7 +37,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.HistoryAdapter
import com.deniscerri.ytdlnis.ui.adapter.HistoryAdapter
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.DBManager.SORTING
@ -72,7 +72,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
private var topAppBar: MaterialToolbar? = null
private var recyclerView: RecyclerView? = null
private var historyAdapter: HistoryAdapter? = null
private var bottomSheet: BottomSheetDialog? = null
private var sortSheet: BottomSheetDialog? = null
private var uiHandler: Handler? = null
private var noResults: RelativeLayout? = null
@ -401,20 +400,24 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onCardClick(itemID: Long, isPresent: Boolean) {
val item = findItem(itemID)
UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent,
removeItem = { it, deleteFile ->
historyViewModel.delete(it, deleteFile)
},
redownloadItem = {
val downloadItem = downloadViewModel.createDownloadItemFromHistory(it)
runBlocking{
downloadViewModel.queueDownloads(listOf(downloadItem))
}
historyViewModel.delete(it, false)
},
redownloadShowDownloadCard = {
val sheet = DownloadBottomSheetDialog(
result = downloadViewModel.createResultItemFromHistory(it),
type = it.type
)
sheet.show(parentFragmentManager, "downloadSingleSheet")
}
) {
val sheet = DownloadBottomSheetDialog(
result = downloadViewModel.createResultItemFromHistory(it),
type = it.type
)
sheet.show(parentFragmentManager, "downloadSingleSheet")
}
)
}
override fun onCardSelect(itemID: Long, isChecked: Boolean) {

View file

@ -11,6 +11,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@ -24,7 +25,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -53,6 +54,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var queuedRecyclerView : RecyclerView
private lateinit var adapter : GenericDownloadAdapter
private lateinit var noResults : RelativeLayout
private lateinit var notificationUtil: NotificationUtil
private lateinit var fileSize: TextView
private var totalSize: Int = 0
@ -80,6 +82,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
requireActivity()
)
noResults = view.findViewById(R.id.no_results)
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
queuedRecyclerView.forceFastScrollMode()
queuedRecyclerView.adapter = adapter
@ -120,6 +123,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)).observe(viewLifecycleOwner){
totalSize = it
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}

View file

@ -10,6 +10,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
@ -21,7 +22,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -47,6 +48,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var savedRecyclerView: RecyclerView
private lateinit var adapter: GenericDownloadAdapter
private lateinit var noResults: RelativeLayout
private var actionMode : ActionMode? = null
private var totalSize : Int = 0
override fun onCreateView(
@ -54,7 +56,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false)
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return fragmentView
@ -69,6 +71,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
requireActivity()
)
noResults = view.findViewById(R.id.no_results)
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
savedRecyclerView.forceFastScrollMode()
savedRecyclerView.enableFastScroll()
@ -89,6 +92,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Saved)).observe(viewLifecycleOwner){
totalSize = it
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
}

View file

@ -1,5 +1,7 @@
package com.deniscerri.ytdlnis.ui.more
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.graphics.Canvas
import android.graphics.Color
@ -9,12 +11,15 @@ import android.os.Looper
import android.text.InputType
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
@ -26,11 +31,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.TemplatesAdapter
import com.deniscerri.ytdlnis.ui.adapter.TemplatesAdapter
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
import com.deniscerri.ytdlnis.database.DBManager.SORTING
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
@ -42,6 +49,8 @@ import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListener {
@ -53,6 +62,9 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
private lateinit var noResults: RelativeLayout
private lateinit var mainActivity: MainActivity
private lateinit var sortChip: Chip
private var selectedObjects: ArrayList<CommandTemplate>? = null
private var actionMode : ActionMode? = null
private val jsonFormat = Json { prettyPrint = true }
override fun onCreateView(
inflater: LayoutInflater,
@ -60,6 +72,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
savedInstanceState: Bundle?
): View? {
mainActivity = activity as MainActivity
selectedObjects = arrayListOf()
return inflater.inflate(R.layout.fragment_command_templates, container, false)
}
@ -87,7 +100,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
}
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
commandTemplateViewModel.allItems.observe(viewLifecycleOwner) {
if (it.isEmpty()) noResults.visibility = View.VISIBLE
else noResults.visibility = View.GONE
@ -259,14 +272,109 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
override fun onSelected(commandTemplate: CommandTemplate) {
}
override fun onDelete(commandTemplate: CommandTemplate) {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + commandTemplate.title + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
commandTemplateViewModel.delete(commandTemplate)
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
val item = templatesList.find { it.id == itemID }
if (isChecked) {
selectedObjects!!.add(item!!)
if (actionMode == null){
actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
}
}
deleteDialog.show()
else {
selectedObjects!!.remove(item)
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
if (selectedObjects!!.isEmpty()){
actionMode?.finish()
}
}
}
private val contextualActionBar = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode!!.menuInflater.inflate(R.menu.templates_menu_context, menu)
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
return true
}
override fun onPrepareActionMode(
mode: ActionMode?,
menu: Menu?
): Boolean {
return false
}
override fun onActionItemClicked(
mode: ActionMode?,
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
for (obj in selectedObjects!!){
commandTemplateViewModel.delete(obj)
}
clearCheckedItems()
actionMode?.finish()
}
deleteDialog.show()
true
}
R.id.select_all -> {
templatesAdapter.checkAll(templatesList)
selectedObjects?.clear()
templatesList.forEach { selectedObjects?.add(it) }
mode?.title = getString(R.string.all_items_selected)
true
}
R.id.invert_selected -> {
templatesAdapter.invertSelected(templatesList)
val invertedList = arrayListOf<CommandTemplate>()
templatesList.forEach {
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
}
selectedObjects?.clear()
selectedObjects?.addAll(invertedList)
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
if (invertedList.isEmpty()) actionMode?.finish()
true
}
R.id.export_clipboard -> {
lifecycleScope.launch{
val output = jsonFormat.encodeToString(
CommandTemplateExport(
templates = selectedObjects!!.toList(),
shortcuts = listOf()
)
)
val clipboard: ClipboardManager =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(output)
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
clearCheckedItems()
actionMode?.finish()
}
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
clearCheckedItems()
}
}
private fun clearCheckedItems(){
templatesAdapter.clearCheckeditems()
selectedObjects?.clear()
}
private var simpleCallback: ItemTouchHelper.SimpleCallback =
@ -282,7 +390,9 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
ItemTouchHelper.LEFT -> {
val deletedItem = templatesList[position]
templatesAdapter.notifyItemChanged(position)
onDelete(deletedItem)
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
commandTemplateViewModel.delete(deletedItem)
}
}
}

View file

@ -15,7 +15,6 @@ import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.RelativeLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.view.children
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -26,10 +25,10 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.CookieAdapter
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -130,6 +129,10 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
}
deleteDialog.show()
}
R.id.use_header -> {
m.isChecked = !m.isChecked
preferences.edit().putBoolean("use_header", m.isChecked).apply()
}
R.id.import_clipboard -> {
lifecycleScope.launch(Dispatchers.IO){
cookiesViewModel.importFromClipboard()
@ -139,9 +142,18 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
cookiesViewModel.exportToClipboard()
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
}
R.id.use_header -> {
m.isChecked = !m.isChecked
preferences.edit().putBoolean("use_header", m.isChecked).apply()
R.id.export_file -> {
cookiesViewModel.exportToFile {f ->
if (f == null){
Snackbar.make(recyclerView, getString(R.string.couldnt_parse_file), Snackbar.LENGTH_LONG).show()
}else{
val snack = Snackbar.make(recyclerView, getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
snack.setAction(R.string.share) {
UiUtil.shareFileIntent(requireContext(), listOf(f.absolutePath))
}
snack.show()
}
}
}
}
true

View file

@ -48,7 +48,6 @@ class MoreFragment : Fragment() {
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return inflater.inflate(R.layout.fragment_more, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

View file

@ -1,25 +1,30 @@
package com.deniscerri.ytdlnis.ui.more.downloadLogs
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color
import android.os.Bundle
import android.os.FileObserver
import android.text.method.LinkMovementMethod
import android.text.method.MovementMethod
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.core.view.children
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
@ -30,11 +35,9 @@ import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
import kotlin.math.log
class DownloadLogFragment : Fragment() {
@ -56,6 +59,7 @@ class DownloadLogFragment : Fragment() {
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -65,11 +69,17 @@ class DownloadLogFragment : Fragment() {
}
content = view.findViewById(R.id.content)
content.movementMethod = ScrollingMovementMethod()
content.setTextIsSelectable(true)
contentScrollView = view.findViewById(R.id.content_scrollview)
val bottomAppBar = view.findViewById<BottomAppBar>(R.id.bottomAppBar)
topAppBar.setOnClickListener {
contentScrollView.scrollTo(0,0)
bottomAppBar?.menu?.get(1)?.isVisible = true
}
copyLog = view.findViewById(R.id.copy_log)
copyLog.setOnClickListener {
val clipboard: ClipboardManager =
@ -81,22 +91,13 @@ class DownloadLogFragment : Fragment() {
val id = arguments?.getLong("logID")
if (id == null) {
mainActivity.onBackPressedDispatcher.onBackPressed()
}else{
arguments?.remove("logID")
}
logViewModel = ViewModelProvider(this)[LogViewModel::class.java]
logViewModel.getLogFlowByID(id!!).observe(viewLifecycleOwner){logItem ->
mainActivity.runOnUiThread{
content.text = logItem?.content
content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
CoroutineScope(Dispatchers.IO).launch {
val logItem = logViewModel.getItemById(id)
val logItem = logViewModel.getItemById(id!!)
topAppBar.title = logItem.title
}
@ -118,6 +119,7 @@ class DownloadLogFragment : Fragment() {
*schemes.map { it }.toTypedArray()
)
highlight.setSpan(content)
content.isFocusable = true
content.addTextChangedListener(highlightWatcher)
content.setHorizontallyScrolling(false)
@ -144,9 +146,37 @@ class DownloadLogFragment : Fragment() {
}
content.setHorizontallyScrolling(!content.canScrollHorizontally(1))
}
R.id.scroll_down -> {
m.isVisible = false
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
true
}
// contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
// if (sy < osy){
// if (contentScrollView.canScrollVertically(1)){
// shouldScroll = false
// content.movementMethod = LinkMovementMethod.getInstance()
// bottomAppBar?.menu?.get(1)?.isVisible = true
// }else{
// shouldScroll = true
// content.movementMethod = ScrollingMovementMethod()
// bottomAppBar?.menu?.get(1)?.isVisible = false
// }
// }
// }
logViewModel.getLogFlowByID(id!!).observe(viewLifecycleOwner){logItem ->
lifecycleScope.launch {
content.text = logItem.content
content.scrollTo(content.scrollX, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
}
}
companion object {

View file

@ -23,7 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
import com.deniscerri.ytdlnis.ui.adapter.DownloadLogsAdapter
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll

View file

@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.more.settings
import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build.VERSION
import android.os.Bundle
@ -35,14 +36,16 @@ class FolderSettingsFragment : BaseSettingsFragment() {
private var videoFilenameTemplate : Preference? = null
private var clearCache: Preference? = null
private var moveCache: Preference? = null
private lateinit var preferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private var activeDownloadCount = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.folders_preference, rootKey)
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = preferences.edit()
musicPath = findPreference("music_path")
videoPath = findPreference("video_path")
@ -110,7 +113,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
val uri = Uri.parse("package:" + requireContext().packageName)
intent.data = uri
accessAllFilesLauncher.launch(intent)
startActivity(intent)
true
}
@ -180,6 +183,18 @@ class FolderSettingsFragment : BaseSettingsFragment() {
}
override fun onResume() {
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
VERSION.SDK_INT < 30) {
accessAllFiles!!.isVisible = false
cacheDownloads!!.isEnabled = true
}else{
editor.putBoolean("cache_downloads", true).apply()
cacheDownloads!!.isEnabled = false
}
super.onResume()
}
private var musicPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
@ -223,15 +238,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
}
}
private var accessAllFilesLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
accessAllFiles?.isVisible = false
cacheDownloads?.isEnabled = true
}
}
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
val path = data!!.data.toString()
p!!.summary = FileUtil.formatPath(data.data.toString())

View file

@ -12,6 +12,7 @@ import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference
import androidx.preference.Preference
@ -130,29 +131,24 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
true
}
ignoreBatteryOptimization = findPreference<Preference>("ignore_battery")
val packageName: String = requireContext().packageName
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization!!.isVisible = false
}
ignoreBatteryOptimization = findPreference("ignore_battery")
ignoreBatteryOptimization!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + requireContext().packageName)
ignoreBatteryLauncher.launch(intent)
startActivity(intent)
true
}
}
private var ignoreBatteryLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
ignoreBatteryOptimization?.isVisible = false
override fun onResume() {
val packageName: String = requireContext().packageName
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization!!.isVisible = false
}
super.onResume()
}
}

View file

@ -43,7 +43,7 @@ class SettingsActivity : BaseActivity() {
}
}
navController.navigate(R.id.mainSettingsFragment)
if (savedInstanceState == null) navController.navigate(R.id.mainSettingsFragment)
}
fun changeTopAppbarTitle(text: String) {

View file

@ -1,28 +1,21 @@
package com.deniscerri.ytdlnis.ui.more.terminal
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavArgument
import androidx.navigation.NavGraph
import androidx.navigation.NavType
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgument
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

View file

@ -1,20 +1,12 @@
package com.deniscerri.ytdlnis.ui.more.terminal
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.activity.addCallback
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.get
import androidx.fragment.app.Fragment
@ -23,17 +15,14 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.TerminalDownloadsAdapter
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
import com.deniscerri.ytdlnis.database.models.TerminalItem
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

View file

@ -8,9 +8,8 @@ import android.content.Context.INPUT_METHOD_SERVICE
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.os.PersistableBundle
import android.text.method.ScrollingMovementMethod
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
@ -23,19 +22,14 @@ import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.TerminalItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
@ -47,13 +41,12 @@ import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.StringBuilder
import java.util.concurrent.Executor
import kotlin.properties.Delegates
@ -87,7 +80,7 @@ class TerminalFragment : Fragment() {
super.onSaveInstanceState(outState)
outState.putString("input", input?.text.toString())
outState.putString("output", output?.text.toString())
outState.putBoolean("run", fab!!.text == getString(R.string.run_command))
outState.putBoolean("run", fab!!.text == requireActivity().getString(R.string.run_command))
outState.putLong("downloadID", downloadID)
}
@ -103,6 +96,7 @@ class TerminalFragment : Fragment() {
scrollView = view.findViewById(R.id.custom_command_scrollview)
topAppBar = requireActivity().findViewById(R.id.custom_command_toolbar)
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
topAppBar!!.setOnClickListener { scrollView?.scrollTo(0,0) }
input = view.findViewById(R.id.command_edittext)
fab = view.findViewById(R.id.command_fab)
@ -156,7 +150,7 @@ class TerminalFragment : Fragment() {
when(it.itemId){
R.id.command_templates -> {
if (templateCount == 0){
Toast.makeText(requireContext(), getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), requireActivity().getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ templates ->
@ -198,11 +192,12 @@ class TerminalFragment : Fragment() {
}
output = view.findViewById(R.id.custom_command_output)
output!!.movementMethod = ScrollingMovementMethod()
output!!.setTextIsSelectable(true)
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
input!!.requestFocus()
fab!!.setOnClickListener {
if (fab!!.text == getString(R.string.run_command)){
if (fab!!.text == requireActivity().getString(R.string.run_command)){
input!!.visibility = View.GONE
output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n"
showCancelFab()
@ -213,6 +208,8 @@ class TerminalFragment : Fragment() {
terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString()))
}
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
input!!.visibility = View.GONE
showCancelFab()
runWorkerListener()
}
}else {
@ -274,12 +271,16 @@ class TerminalFragment : Fragment() {
}
}
private fun hideCancelFab() {
fab!!.text = getString(R.string.run_command)
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
kotlin.runCatching {
fab!!.text = getString(R.string.run_command)
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
}
}
private fun showCancelFab() {
fab!!.text = getString(R.string.cancel_download)
fab!!.setIconResource(R.drawable.ic_cancel)
kotlin.runCatching {
fab!!.text = getString(R.string.cancel_download)
fab!!.setIconResource(R.drawable.ic_cancel)
}
}
private var commandPathResultLauncher = registerForActivityResult(
@ -307,7 +308,11 @@ class TerminalFragment : Fragment() {
output?.text = it.log
}
output?.scrollTo(0, output!!.height)
scrollView?.fullScroll(View.FOCUS_DOWN)
if (!scrollView!!.canScrollVertically(1) && !scrollView!!.canScrollHorizontally(-1)){
scrollView?.fullScroll(View.FOCUS_DOWN)
}
input!!.visibility = View.GONE
showCancelFab()
}
}
}
@ -316,24 +321,34 @@ class TerminalFragment : Fragment() {
}
WorkManager.getInstance(requireContext())
.getWorkInfosByTagLiveData(downloadID.toString())
.observe(viewLifecycleOwner){ list ->
list.forEach {work ->
if (listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED).contains(work.state)) {
requireActivity().runOnUiThread {
kotlin.runCatching {
input!!.setText("yt-dlp ")
input!!.visibility = View.VISIBLE
input!!.requestFocus()
input!!.setSelection(input!!.text.length)
hideCancelFab()
}
.getWorkInfosForUniqueWorkLiveData(downloadID.toString())
.removeObserver(workerObserver)
WorkManager.getInstance(requireContext())
.getWorkInfosForUniqueWorkLiveData(downloadID.toString())
.observe(viewLifecycleOwner, workerObserver)
}
private val workerObserver = object: Observer<MutableList<WorkInfo>> {
override fun onChanged(value: MutableList<WorkInfo>) {
value.forEach { work ->
if (listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED).contains(work.state)) {
requireActivity().runOnUiThread {
kotlin.runCatching {
input!!.setText("yt-dlp ")
input!!.visibility = View.VISIBLE
input!!.requestFocus()
input!!.setSelection(input!!.text.length)
hideCancelFab()
}
return@forEach
}
return@forEach
}
}
}
}
}

View file

@ -1,32 +1,21 @@
package com.deniscerri.ytdlnis.util
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.webkit.MimeTypeMap
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -64,6 +53,7 @@ object FileUtil {
var dataValue = path
if (dataValue.startsWith("/storage/")) return dataValue
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
dataValue = dataValue.replace("raw:/storage/", "")
dataValue = dataValue.replace("^/document/".toRegex(), "")
dataValue = dataValue.replace("^primary:".toRegex(), "primary/")
dataValue = dataValue.replace("%3A".toRegex(), "/")
@ -296,6 +286,10 @@ object FileUtil {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command"
}
fun getDefaultTerminalPath() : String {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/TERMINAL_CACHE"
}
fun getCookieFile(context : Context, path: (path: String) -> Unit){
val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()) path(cookiesFile.absolutePath)

View file

@ -9,6 +9,7 @@ import android.util.Log
import android.util.Patterns
import android.widget.Toast
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -553,13 +554,20 @@ class InfoUtil(private val context: Context) {
request = YoutubeDLRequest(query)
}else{
request = YoutubeDLRequest(emptyList())
if (searchEngine == "ytsearchmusic"){
request.addOption("--default-search", "https://music.youtube.com/search?q=")
request.addOption("ytsearch25:\"${query}\"")
}else{
request.addOption("${searchEngine}25:\"${query}\"")
when (searchEngine){
"ytsearchmusic" -> {
request.addOption("--default-search", "https://music.youtube.com/search?q=")
request.addOption("ytsearch25:\"${query}\"")
}
else -> {
request.addOption("${searchEngine}25:\"${query}\"")
}
}
}
val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" && context.getStringArray(R.array.subtitle_langs).contains(lang)){
request.addOption("--extractor-args", "youtube:lang=$lang")
}
request.addOption("--flat-playlist")
request.addOption("-j")
@ -954,8 +962,8 @@ class InfoUtil(private val context: Context) {
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
request.addOption("--no-quiet")
request.addOption("-no-simulate")
request.addOption("--print", "after_video:'%(filename)s'")
request.addOption("--no-simulate")
request.addOption("--print", "after_move:'%(filepath,_filename)s'")
}else{
val cacheDir = FileUtil.getCachePath(context)
downDir = File(cacheDir, downloadItem.id.toString())
@ -1072,7 +1080,9 @@ class InfoUtil(private val context: Context) {
}
if (downloadItem.extraCommands.isNotBlank()){
val conf = File(FileUtil.getCachePath(context) + "/${System.currentTimeMillis()}.txt")
val cache = File(FileUtil.getCachePath(context))
cache.mkdirs()
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}.txt")
conf.createNewFile()
conf.writeText(downloadItem.extraCommands)
request.addOption(
@ -1135,7 +1145,7 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("embed_metadata", true)){
request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
request.addOption("--parse-metadata", "%(release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")

View file

@ -11,15 +11,16 @@ import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavDeepLinkBuilder
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.App.Companion.instance
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
@ -195,7 +196,8 @@ class NotificationUtil(var context: Context) {
notificationManager.notify(DOWNLOAD_UPDATING_NOTIFICATION_ID, notificationBuilder.build())
}
fun createDownloadFinished(title: String?,
fun createDownloadFinished(
title: String?,
filepath: List<String>?
) {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
@ -215,24 +217,36 @@ class NotificationUtil(var context: Context) {
.clearActions()
if (filepath != null){
try{
val file = File(filepath.first())
val uri = FileProvider.getUriForFile(
context,
"com.deniscerri.ytdl.fileprovider",
file
)
val uri = filepath.first().runCatching {
DocumentFile.fromSingleUri(context, Uri.parse(filepath.first())).run{
if (this?.exists() == true){
this.uri
}else if (File(this@runCatching).exists()){
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
File(this@runCatching))
}else null
}
}.getOrNull()
val openFileIntent = Intent()
if (uri != null){
openFileIntent.apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_VIEW
data = uri
}
}
//open intent
val intent = Intent()
intent.action = Intent.ACTION_VIEW
intent.setDataAndType(uri, "*/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val openNotificationPendingIntent: PendingIntent = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(intent)
addNextIntentWithParentStack(openFileIntent)
getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
//share intent
val shareIntent = Intent(context, ShareFileActivity::class.java)
shareIntent.putExtra("path", filepath.toTypedArray())

View file

@ -13,7 +13,6 @@ import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.net.Uri
import android.os.Build
import android.os.Looper
import android.text.Editable
import android.text.TextWatcher
import android.text.format.DateFormat
@ -34,13 +33,9 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.allViews
import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
@ -50,7 +45,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
@ -59,9 +53,9 @@ import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -285,19 +279,17 @@ object UiUtil {
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun copyLinkToClipBoard(context: Context, url: String, bottomSheet: BottomSheetDialog?) {
fun copyLinkToClipBoard(context: Context, url: String) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(context.getString(R.string.url), url)
clipboard.setPrimaryClip(clip)
bottomSheet?.hide()
Toast.makeText(context, context.getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
fun openLinkIntent(context: Context, url: String, bottomSheet: BottomSheetDialog?) {
fun openLinkIntent(context: Context, url: String) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
bottomSheet?.hide()
context.startActivity(i)
}
@ -432,18 +424,19 @@ object UiUtil {
// BUTTON ----------------------------------
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
when (item.type) {
DownloadViewModel.Type.audio -> {
btn?.setImageResource(R.drawable.ic_music)
val typeImageResource: Int =
when (item.type) {
DownloadViewModel.Type.audio -> {
R.drawable.ic_music
}
DownloadViewModel.Type.video -> {
R.drawable.ic_video
}
else -> {
R.drawable.ic_terminal
}
}
DownloadViewModel.Type.video -> {
btn?.setImageResource(R.drawable.ic_video)
}
else -> {
btn?.setImageResource(R.drawable.ic_terminal)
}
}
btn?.setImageResource(typeImageResource)
val time = bottomSheet.findViewById<Chip>(R.id.time)
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
@ -474,8 +467,12 @@ object UiUtil {
View.GONE
else formatNote!!.text = item.format.format_note
if (item.format.container != "") container!!.text = item.format.container.uppercase()
else container!!.visibility = View.GONE
if (item.format.container != "") {
container!!.text = item.format.container.uppercase()
container.setChipIconResource(typeImageResource)
}else {
container!!.visibility = View.GONE
}
val codecText =
if (item.format.encoding != "") {
@ -501,10 +498,12 @@ object UiUtil {
link!!.text = url
link.tag = item.id
link.setOnClickListener{
openLinkIntent(context, item.url, bottomSheet)
bottomSheet.dismiss()
openLinkIntent(context, item.url)
}
link.setOnLongClickListener{
copyLinkToClipBoard(context, item.url, bottomSheet)
bottomSheet.dismiss()
copyLinkToClipBoard(context, item.url)
true
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
@ -561,6 +560,7 @@ object UiUtil {
item: HistoryItem?,
context: Activity,
isPresent: Boolean,
removeItem: (item:HistoryItem, removeFiles: Boolean) -> Unit,
redownloadItem: (HistoryItem) -> Unit,
redownloadShowDownloadCard: (HistoryItem) -> Unit,
){
@ -575,21 +575,23 @@ object UiUtil {
// BUTTON ----------------------------------
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
val typeImageResource: Int =
if (item.type == DownloadViewModel.Type.audio) {
if (isPresent) {
btn?.setImageResource(R.drawable.ic_music_downloaded)
R.drawable.ic_music_downloaded
} else {
btn?.setImageResource(R.drawable.ic_music)
R.drawable.ic_music
}
} else if (item.type == DownloadViewModel.Type.video) {
if (isPresent) {
btn?.setImageResource(R.drawable.ic_video_downloaded)
R.drawable.ic_video_downloaded
} else {
btn?.setImageResource(R.drawable.ic_video)
R.drawable.ic_video
}
}else{
btn?.setImageResource(R.drawable.ic_terminal)
R.drawable.ic_terminal
}
btn?.setImageResource(typeImageResource)
if (isPresent){
btn?.setOnClickListener {
@ -599,9 +601,10 @@ object UiUtil {
val time = bottomSheet.findViewById<TextView>(R.id.time)
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
val container = bottomSheet.findViewById<TextView>(R.id.container_chip)
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val file = File(item.downloadPath)
val calendar = Calendar.getInstance()
calendar.timeInMillis = item.time * 1000L
@ -610,10 +613,14 @@ object UiUtil {
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
View.GONE
else formatNote!!.text = item.format.format_note
else formatNote!!.text = item.format.format_note.uppercase()
if (item.format.container != "") container!!.text = item.format.container.uppercase()
else container!!.visibility = View.GONE
if (item.format.container != "") {
container!!.text = if (file.exists()) file.extension.uppercase() else item.format.container.uppercase()
container.setChipIconResource(typeImageResource)
}else {
container!!.visibility = View.GONE
}
val codecText =
if (item.format.encoding != "") {
@ -630,7 +637,6 @@ object UiUtil {
codec.text = codecText
}
val file = File(item.downloadPath)
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
@ -640,18 +646,18 @@ object UiUtil {
link!!.text = url
link.tag = item.id
link.setOnClickListener{
openLinkIntent(context, item.url, bottomSheet)
bottomSheet.dismiss()
openLinkIntent(context, item.url)
}
link.setOnLongClickListener{
copyLinkToClipBoard(context, item.url, bottomSheet)
bottomSheet.dismiss()
copyLinkToClipBoard(context, item.url)
true
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = item.id
remove.setOnClickListener{
showRemoveHistoryItemDialog(item, context, delete = { item, deleteFile ->
})
showRemoveHistoryItemDialog(item, context, delete = removeItem)
bottomSheet.dismiss()
}
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
@ -786,15 +792,15 @@ object UiUtil {
)
}
fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
private fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.subtitle_languages))
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = context.getString(R.string.subtitle_languages)
val view = context.layoutInflater.inflate(R.layout.subtitle_dialog, null)
val editText = view.findViewById<EditText>(R.id.subtitle_edittext)
view.findViewById<TextInputLayout>(R.id.subtitle).hint = context.getString(R.string.subtitle_languages)
editText.setText(currentValue)
editText.setSelection(editText.text.length)
builder.setView(inputLayout)
builder.setView(view)
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
@ -812,19 +818,46 @@ object UiUtil {
context.startActivity(browserIntent)
}
view.findViewById<View>(R.id.suggested).visibility = View.GONE
val dialog = builder.create()
editText.doOnTextChanged { _, _, _, _ ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText.setSelection(editText.text!!.length)
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
//handle suggestion chips
CoroutineScope(Dispatchers.IO).launch {
val chipGroup = view.findViewById<ChipGroup>(R.id.subtitle_suggested_chipgroup)
val chips = mutableListOf<Chip>()
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = s
tmp.id = index
tmp.setOnClickListener {
val c = it as Chip
if(!c.isChecked){
editText.setText(editText.text.toString().replace(c.text.toString(), ""))
editText.setSelection(editText.text.length)
}else{
editText.append(c.text)
}
}
chips.add(tmp)
}
withContext(Dispatchers.Main){
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach {
it.isChecked = editText.text.contains(it.text)
chipGroup!!.addView(it)
}
}
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
@ -934,7 +967,7 @@ object UiUtil {
cutClicked: (VideoCutListener) -> Unit,
filenameTemplateSet: (String) -> Unit,
saveSubtitlesClicked: (Boolean) -> Unit,
subtitleLanguagesClicked: () -> Unit,
subtitleLanguagesSet: (String) -> Unit,
removeAudioClicked: (Boolean) -> Unit,
extraCommandsClicked: () -> Unit
){
@ -1093,7 +1126,14 @@ object UiUtil {
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
subtitleLanguages.setOnClickListener {
subtitleLanguagesClicked()
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
items[0].videoPreferences.subsLanguages
}else {
""
}
showSubtitleLanguagesDialog(context, currentSubtitleLang){
subtitleLanguagesSet(it)
}
}
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
@ -1301,7 +1341,7 @@ object UiUtil {
private var textHighLightSchemes = listOf(
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#FF0000")),
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
)
@ -1339,7 +1379,7 @@ object UiUtil {
this.requestLayout()
}
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState){
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
val title = context.getString(it.errorMessage!!.first)
val message = it.errorMessage!!.second
@ -1358,11 +1398,16 @@ object UiUtil {
}
}
}
errDialog.setOnCancelListener {
closed()
}
errDialog.show()
}
@SuppressLint("SetTextI18n")
fun handleDownloadsResponse(context: MainActivity, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel){
fun handleDownloadsResponse(context: MainActivity, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY}
if (downloadAnywayAction != null){
if (downloadAnywayAction.third == null) return
@ -1425,14 +1470,11 @@ object UiUtil {
if (historyItem != null){
ttle.setOnClickListener {
showHistoryItemDetailsCard(historyItem, context, isPresent = true,
removeItem = { item, deleteFile ->
historyViewModel.delete(item, deleteFile)
},
redownloadItem = { },
redownloadShowDownloadCard = {
val sheet = DownloadBottomSheetDialog(
result = downloadViewModel.createResultItemFromHistory(it),
type = it.type
)
sheet.show(context.supportFragmentManager, "downloadSingleSheet")
}
redownloadShowDownloadCard = {}
)
}
}
@ -1455,7 +1497,7 @@ object UiUtil {
errDialog.setPositiveButton(downloadAnywayAction.first) { d:DialogInterface?, _:Int ->
CoroutineScope(Dispatchers.IO).launch {
linearLayout.allViews.forEach {view ->
linearLayout.children.forEach {view ->
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
downloadItem.id = 0
downloadViewModel.queueDownloads(listOf(downloadItem), true)
@ -1547,6 +1589,12 @@ object UiUtil {
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/yt-dlp/yt-dlp#user-content-outtmpl-postprocess-note"))
context.startActivity(browserIntent)
}
view.findViewById<View>(R.id.suggested).visibility = View.GONE
val dialog = builder.create()

View file

@ -1,37 +1,51 @@
package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.text.method.LinkMovementMethod
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.GithubRelease
import com.deniscerri.ytdlnis.database.models.GithubReleaseAsset
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.MarkwonSpansFactory
import io.noties.markwon.core.MarkwonTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class UpdateUtil(var context: Context) {
private val tag = "UpdateUtil"
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val downloadManager: DownloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun updateApp(result: (result: String) -> Unit) {
try {
if (updatingApp) {
@ -99,7 +113,7 @@ class UpdateUtil(var context: Context) {
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs()
downloadManager.enqueue(
val downloadID = downloadManager.enqueue(
DownloadManager.Request(uri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
@ -110,10 +124,34 @@ class UpdateUtil(var context: Context) {
.setTitle(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
)
val onDownloadComplete: BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
context?.unregisterReceiver(this)
UiUtil.openFileIntent(context!!,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath +
File.separator + releaseVersion.name)
}
}
context.registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
}
updateDialog.show()
val view = updateDialog.show()
val textView = view.findViewById<TextView>(android.R.id.message)
textView!!.movementMethod = LinkMovementMethod.getInstance()
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(context, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(textView, v.body)
}
return
}catch (e: Exception){

View file

@ -27,14 +27,12 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.LogRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@ -122,6 +120,7 @@ class DownloadWorker(
val commandString = infoUtil.parseYTDLRequestString(request)
val logString = StringBuilder("\n ${commandString}\n\n")
val logItem = LogItem(
0,
downloadItem.title.ifEmpty { downloadItem.url },
@ -129,7 +128,7 @@ class DownloadWorker(
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Command:\n ${commandString}\n\n",
"Command: $logString",
downloadItem.format,
downloadItem.type,
System.currentTimeMillis(),
@ -137,7 +136,7 @@ class DownloadWorker(
runBlocking {
logItem.id = logRepo.insert(logItem)
if (logDownloads) logItem.id = logRepo.insert(logItem)
downloadItem.logID = logItem.id
dao.update(downloadItem)
}
@ -154,7 +153,10 @@ class DownloadWorker(
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(line, logItem.id)
if (logDownloads) {
logRepo.update(line, logItem.id)
logString.append("$line\n")
}
}
}
}.onSuccess {
@ -164,16 +166,18 @@ class DownloadWorker(
if (noCache){
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
val p = it.out.split("\n")
finalPaths = it.out.split("\n")
.filter { it.startsWith("'/storage") }
.map { it.removePrefix("'") }
.map { it.removeSuffix("'\n") }
finalPaths = File(FileUtil.formatPath(downloadLocation))
.walkTopDown()
.filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
.sortedByDescending { it.length() }
.map { it.absolutePath }
.map { it.removeSuffix("\n") }
.map { it.removeSuffix("'") }
.toList()
// finalPaths = File(FileUtil.formatPath(downloadLocation))
// .walkTopDown()
// .filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
// .sortedByDescending { it.length() }
// .map { it.absolutePath }
// .toList()
FileUtil.scanMedia(finalPaths, context)
if (finalPaths.isEmpty()){
finalPaths = listOf(context.getString(R.string.unfound_file))
@ -236,8 +240,6 @@ class DownloadWorker(
if (logDownloads){
logRepo.update(it.out, logItem.id)
}else{
logRepo.delete(logItem)
}
}
@ -250,9 +252,13 @@ class DownloadWorker(
if (it is YoutubeDL.CanceledException) {
}else{
if (logDownloads){
if(it.message != null){
if(it.message != null){
if (logDownloads){
logRepo.update(it.message!!, logItem.id)
}else{
logString.append("${it.message}\n")
logItem.content = logString.toString()
logRepo.insert(logItem)
}
}

View file

@ -7,33 +7,14 @@ import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption

View file

@ -24,7 +24,6 @@ import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -45,10 +44,8 @@ class TerminalDownloadWorker(
val dbManager = DBManager.getInstance(context)
val logRepo = LogRepository(dbManager.logDao)
val notificationUtil = NotificationUtil(context)
val handler = Handler(Looper.getMainLooper())
val infoUtil = InfoUtil(context)
val intent = Intent(context, TerminalActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
@ -95,7 +92,7 @@ class TerminalDownloadWorker(
System.currentTimeMillis(),
)
runCatching {
kotlin.runCatching {
if (logDownloads){
runBlocking {
logItem.id = logRepo.insert(logItem)
@ -137,12 +134,10 @@ class TerminalDownloadWorker(
}
return runBlocking {
CoroutineScope(Dispatchers.IO).launch {
if (logDownloads) logRepo.update(it.out, logItem.id)
dao.updateLog(it.out, itemId.toLong())
Thread.sleep(1000)
dao.delete(itemId.toLong())
}
if (logDownloads) logRepo.update(it.out, logItem.id)
dao.updateLog(it.out, itemId.toLong())
Thread.sleep(1000)
dao.delete(itemId.toLong())
notificationUtil.cancelDownloadNotification(itemId)
Result.success()

View file

@ -3,7 +3,7 @@
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#fff">
android:tint="@color/icon_fg">
<group android:scaleX="0.58"
android:scaleY="0.58"
android:translateX="5.04"

View file

@ -133,7 +133,7 @@
android:orientation="vertical"
android:layout_height="wrap_content">
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />
<TextView

View file

@ -62,6 +62,7 @@
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="fill"
app:tabMinWidth="0dp"
app:tabMode="scrollable">
<com.google.android.material.tabs.TabItem

View file

@ -128,6 +128,7 @@
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="start"
app:tabMinWidth="0dp"
app:tabMode="scrollable">
<com.google.android.material.tabs.TabItem

View file

@ -1,35 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="100dp"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.button.MaterialButton
android:id="@+id/pause_resume"
android:visibility="gone"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:layout_gravity="end"
android:paddingVertical="10dp"
android:text="@string/resume"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
android:layout_height="match_parent"
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.button.MaterialButton
android:id="@+id/pause_resume"
android:visibility="gone"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:layout_gravity="end"
android:paddingVertical="10dp"
android:text="@string/resume"
android:textStyle="bold"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
<include layout="@layout/no_results"
android:visibility="gone"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -31,23 +31,15 @@
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout
android:id="@+id/history_selection_chips"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/sortChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by"
app:chipIcon="@drawable/ic_down" />
<View style="@style/Divider.Vertical"/>
<com.google.android.material.chip.ChipGroup
android:layout_width="match_parent"
app:selectionRequired="false"
@ -69,9 +61,19 @@
android:layout_height="wrap_content"
android:text="@string/shortcuts"
app:chipIcon="@drawable/ic_shortcut" />
</com.google.android.material.chip.ChipGroup>
<View style="@style/Divider.Vertical"/>
<com.google.android.material.chip.Chip
android:id="@+id/sortChip"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sort_by"
app:chipIcon="@drawable/ic_down" />
</LinearLayout>
</HorizontalScrollView>
@ -92,7 +94,7 @@
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />

View file

@ -66,7 +66,7 @@
</RelativeLayout>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />

View file

@ -21,7 +21,6 @@
android:paddingHorizontal="10dp"
android:paddingBottom="10dp"
android:hint="@string/title"
app:endIconMode="custom"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
@ -44,7 +43,6 @@
android:layout_height="wrap_content"
android:layout_weight="40"
android:hint="@string/author"
app:endIconMode="custom"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText

View file

@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:paddingTop="10dp"
android:text="@string/command" />
android:text="@string/command_templates" />
<include layout="@layout/command_template_item" />

View file

@ -52,7 +52,7 @@
</LinearLayout>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />
<com.google.android.material.bottomappbar.BottomAppBar

View file

@ -37,7 +37,7 @@
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />

View file

@ -21,7 +21,6 @@
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
@ -36,7 +35,8 @@
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="fill"
app:tabGravity="start"
app:tabMinWidth="0dp"
app:tabMode="scrollable" />
<androidx.viewpager2.widget.ViewPager2
@ -44,7 +44,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />

View file

@ -118,7 +118,7 @@
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />

View file

@ -1,36 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/filesize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:paddingVertical="10dp"
android:visibility="gone"
android:text="@string/file_size"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filesize">
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
<TextView
android:id="@+id/filesize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:paddingVertical="10dp"
android:visibility="gone"
android:text="@string/file_size"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filesize">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
<include layout="@layout/no_results"
android:visibility="gone"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -18,7 +18,7 @@
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results"
<include layout="@layout/no_results"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView android:id="@+id/download_recyclerview"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/no_results"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView android:id="@+id/download_recyclerview"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
</androidx.recyclerview.widget.RecyclerView>

Some files were not shown because too many files have changed in this diff Show more