- 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> </h1>
<div align="center"> <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; &nbsp;&nbsp;| &nbsp;&nbsp;
Azərbaycanca Azərbaycanca
</div> </div>

View file

@ -4,7 +4,7 @@
</h1> </h1>
<div align="center"> <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; &nbsp;&nbsp;| &nbsp;&nbsp;
Indonesia Indonesia
</div> </div>

View file

@ -4,7 +4,7 @@
</h1> </h1>
<div align="center"> <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; &nbsp;&nbsp;| &nbsp;&nbsp;
Português Português
</div> </div>

View file

@ -4,7 +4,7 @@
</h1> </h1>
<div align="center"> <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; &nbsp;&nbsp;| &nbsp;&nbsp;
Shqip Shqip
</div> </div>

View file

@ -10,7 +10,7 @@
&nbsp;&nbsp;| &nbsp;&nbsp; &nbsp;&nbsp;| &nbsp;&nbsp;
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-az.md">Azərbaycanca</a> <a href="https://github.com/deniscerri/ytdlnis/blob/main/README-az.md">Azərbaycanca</a>
&nbsp;&nbsp;| &nbsp;&nbsp; &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; &nbsp;&nbsp;| &nbsp;&nbsp;
<a href="https://github.com/deniscerri/ytdlnis/blob/main/README-pt.md">Português</a> <a href="https://github.com/deniscerri/ytdlnis/blob/main/README-pt.md">Português</a>
</div> </div>
@ -34,7 +34,7 @@
[![community](https://img.shields.io/badge/Telegram-Updates-red?style=flat-square&logo=telegram)](https://t.me/ytdlnisupdates) [![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> </div>

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 8 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 = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){
@ -188,5 +188,6 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5" implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0' implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'com.google.accompanist:accompanist-webview:0.30.1' 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 <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="locale"
android:exported="true" android:exported="true"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
@ -53,7 +53,6 @@
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="true" android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.BottomSheet"> android:theme="@style/Theme.BottomSheet">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -125,7 +124,7 @@
android:enabled="true" android:enabled="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="locale"
android:targetActivity=".MainActivity" android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan" android:windowSoftInputMode="adjustPan"
android:exported="true"> android:exported="true">
@ -147,7 +146,7 @@
android:exported="true" android:exported="true"
android:icon="@mipmap/ic_launcher_dark" android:icon="@mipmap/ic_launcher_dark"
android:roundIcon="@mipmap/ic_launcher_round_dark" android:roundIcon="@mipmap/ic_launcher_round_dark"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="locale"
android:targetActivity=".MainActivity" android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
@ -167,7 +166,7 @@
android:exported="true" android:exported="true"
android:icon="@mipmap/ic_launcher_light" android:icon="@mipmap/ic_launcher_light"
android:roundIcon="@mipmap/ic_launcher_round_light" android:roundIcon="@mipmap/ic_launcher_round_light"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="locale"
android:targetActivity=".MainActivity" android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
@ -183,7 +182,7 @@
<activity <activity
android:name=".ui.more.WebViewActivity" android:name=".ui.more.WebViewActivity"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="locale"
android:exported="true" android:exported="true"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustResize"> 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.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UpdateUtil import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.NavigationBarView
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
@ -65,7 +63,6 @@ import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.Reader import java.io.Reader
import java.net.URI
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -81,7 +78,6 @@ class MainActivity : BaseActivity() {
private lateinit var navigationView: View private lateinit var navigationView: View
private lateinit var navHostFragment : NavHostFragment private lateinit var navHostFragment : NavHostFragment
private lateinit var navController : NavController private lateinit var navController : NavController
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -93,9 +89,7 @@ class MainActivity : BaseActivity() {
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(context) preferences = PreferenceManager.getDefaultSharedPreferences(context)
if (preferences.getBoolean("incognito", false)){ if (preferences.getBoolean("incognito", false)) resultViewModel.deleteAll()
resultViewModel.deleteAll()
}
askPermissions() askPermissions()
checkUpdate() 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) navController.graph = 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
if (navigationView is NavigationBarView){ if (navigationView is NavigationBarView){
(navigationView as NavigationBarView).setupWithNavController(navController) (navigationView as NavigationBarView).setupWithNavController(navController)
(navigationView as NavigationBarView).setOnItemReselectedListener { (navigationView as NavigationBarView).setOnItemReselectedListener {
@ -164,7 +158,7 @@ class MainActivity : BaseActivity() {
(navigationView as NavigationView).setupWithNavController(navController) (navigationView as NavigationView).setupWithNavController(navController)
//terminate button //terminate button
(navigationView as NavigationView).menu.getItem(7).setOnMenuItemClickListener { (navigationView as NavigationView).menu.getItem(7).setOnMenuItemClickListener {
if (sharedPreferences.getBoolean("ask_terminate_app", true)){ if (preferences.getBoolean("ask_terminate_app", true)){
var doNotShowAgain = false var doNotShowAgain = false
val terminateDialog = MaterialAlertDialogBuilder(this) val terminateDialog = MaterialAlertDialogBuilder(this)
terminateDialog.setTitle(getString(R.string.confirm_delete_history)) terminateDialog.setTitle(getString(R.string.confirm_delete_history))
@ -188,7 +182,7 @@ class MainActivity : BaseActivity() {
runBlocking { runBlocking {
job.join() job.join()
if (doNotShowAgain){ if (doNotShowAgain){
sharedPreferences.edit().putBoolean("ask_terminate_app", false).apply() preferences.edit().putBoolean("ask_terminate_app", false).apply()
} }
finishAndRemoveTask() finishAndRemoveTask()
finishAffinity() finishAffinity()
@ -212,9 +206,6 @@ class MainActivity : BaseActivity() {
(navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this) (navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this)
} }
cookieViewModel.updateCookiesFile()
val intent = intent
handleIntents(intent)
navController.addOnDestinationChangedListener { _, destination, _ -> navController.addOnDestinationChangedListener { _, destination, _ ->
if (navigationView is NavigationBarView){ if (navigationView is NavigationBarView){
@ -233,12 +224,15 @@ class MainActivity : BaseActivity() {
} }
} }
when(preferences.getString("start_destination", "")) {
when(sharedPreferences.getString("start_destination", "")) { "Queue" -> if (savedInstanceState == null) navController.navigate(R.id.downloadQueueMainFragment)
"Queue" -> 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) { CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){ if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) { if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
@ -251,8 +245,8 @@ class MainActivity : BaseActivity() {
} }
} }
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) { override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState) super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBundle("nav_state", navController.saveState()) savedInstanceState.putBundle("nav_state", navController.saveState())
@ -489,10 +483,6 @@ class MainActivity : BaseActivity() {
} }
dialog.show() dialog.show()
} }
override fun onConfigurationChanged(newConfig: Configuration) {
startActivity(Intent(this, MainActivity::class.java))
super.onConfigurationChanged(newConfig)
}
companion object { companion object {
private const val TAG = "MainActivity" 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.models.VideoPreferences
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.gson.Gson 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 { class Converters {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
package com.deniscerri.ytdlnis.database.models package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@Entity(tableName = "terminalDownloads") @Entity(tableName = "terminalDownloads")
data class TerminalItem( data class TerminalItem(

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis.database.repository package com.deniscerri.ytdlnis.database.repository
import com.deniscerri.ytdlnis.database.dao.LogDao import com.deniscerri.ytdlnis.database.dao.LogDao
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.LogItem import com.deniscerri.ytdlnis.database.models.LogItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -16,6 +15,10 @@ class LogRepository(private val logDao: LogDao) {
return logDao.getLogFlowByID(id) return logDao.getLogFlowByID(id)
} }
fun getLogByID(id: Long) : LogItem? {
return logDao.getByID(id)
}
suspend fun insert(item: LogItem) : Long{ suspend fun insert(item: LogItem) : Long{
return logDao.insert(item) return logDao.insert(item)
@ -31,13 +34,17 @@ class LogRepository(private val logDao: LogDao) {
} }
fun getItem(id: Long) : LogItem{ fun getItem(id: Long) : LogItem{
return logDao.getByID(id); return logDao.getByID(id)
} }
suspend fun update(newLine: String, id: Long){ suspend fun update(line: String, id: Long){
val item = getItem(id) runCatching {
item.content += "${newLine}\n" val item = getItem(id)
logDao.update(item) 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 package com.deniscerri.ytdlnis.database.repository
import android.content.Context import android.content.Context
import android.util.Log
import com.deniscerri.ytdlnis.database.dao.ResultDao import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.InfoUtil 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.DBManager
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport 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.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -28,8 +26,9 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
val sortOrder = MutableLiveData(DBManager.SORTING.DESC) val sortOrder = MutableLiveData(DBManager.SORTING.DESC)
val sortType = MutableLiveData(CommandTemplateRepository.CommandTemplateSortType.DATE) val sortType = MutableLiveData(CommandTemplateRepository.CommandTemplateSortType.DATE)
private val queryFilter = MutableLiveData("") private val queryFilter = MutableLiveData("")
private var _items = MediatorLiveData<List<CommandTemplate>>()
val allItems: LiveData<List<CommandTemplate>> val allItems: LiveData<List<CommandTemplate>>
private var _items = MediatorLiveData<List<CommandTemplate>>()
val shortcuts : LiveData<List<TemplateShortcut>> val shortcuts : LiveData<List<TemplateShortcut>>
private val jsonFormat = Json { prettyPrint = true } private val jsonFormat = Json { prettyPrint = true }

View file

@ -6,6 +6,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase.OPEN_READONLY import android.database.sqlite.SQLiteDatabase.OPEN_READONLY
import android.os.Environment
import android.util.Log import android.util.Log
import android.webkit.CookieManager import android.webkit.CookieManager
import androidx.lifecycle.AndroidViewModel 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.models.CookieItem
import com.deniscerri.ytdlnis.database.repository.CookieRepository import com.deniscerri.ytdlnis.database.repository.CookieRepository
import com.deniscerri.ytdlnis.ui.more.WebViewActivity import com.deniscerri.ytdlnis.ui.more.WebViewActivity
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File 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.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
@ -29,7 +27,6 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.dao.DownloadDao 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.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.AudioPreferences import com.deniscerri.ytdlnis.database.models.AudioPreferences
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
@ -272,7 +269,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
} }
if (preferredAudioFormats.isEmpty()){ 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( val videoPreferences = VideoPreferences(
@ -465,7 +465,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
) )
} }
else -> { 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) return generateCommandFormat(c)
} }
} }
@ -474,7 +479,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun generateCommandFormat(c: CommandTemplate) : Format { fun generateCommandFormat(c: CommandTemplate) : Format {
return Format( return Format(
c.title, c.title,
"", c.id.toString(),
"", "",
"", "",
"", "",
@ -486,7 +491,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun getLatestCommandTemplateAsFormat() : Format { fun getLatestCommandTemplateAsFormat() : Format {
val t = commandTemplateDao.getFirst() val t = commandTemplateDao.getFirst()!!
return Format(t.title, "", "", "", "", 0, t.content) return Format(t.title, "", "", "", "", 0, t.content)
} }
@ -591,7 +596,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
startDownloadWorker(emptyList()) 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 context = App.instance
val alarmScheduler = AlarmScheduler(context) val alarmScheduler = AlarmScheduler(context)
val activeAndQueuedDownloads = withContext(Dispatchers.IO){ 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.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.DBManager.SORTING import com.deniscerri.ytdlnis.database.DBManager.SORTING
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -23,6 +22,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
val websiteFilter = MutableLiveData("") val websiteFilter = MutableLiveData("")
private val queryFilter = MutableLiveData("") private val queryFilter = MutableLiveData("")
private val formatFilter = MutableLiveData("") private val formatFilter = MutableLiveData("")
val allItems : LiveData<List<HistoryItem>> val allItems : LiveData<List<HistoryItem>>
private var _items = MediatorLiveData<List<HistoryItem>>() private var _items = MediatorLiveData<List<HistoryItem>>()

View file

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

View file

@ -6,7 +6,6 @@ import android.util.Log
import android.util.Patterns import android.util.Patterns
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager 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.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.database.repository.ResultRepository
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
import com.deniscerri.ytdlnis.util.UiUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View file

@ -1,62 +1,21 @@
package com.deniscerri.ytdlnis.database.viewmodel package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application 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.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.Data
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager 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.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.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.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.util.NotificationUtil
import com.deniscerri.ytdlnis.work.AlarmScheduler
import com.deniscerri.ytdlnis.work.DownloadWorker
import com.deniscerri.ytdlnis.work.TerminalDownloadWorker import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch 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) { 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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
@ -13,9 +12,7 @@ import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale
class AlarmStopReceiver : BroadcastReceiver() { class AlarmStopReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) { override fun onReceive(p0: Context?, p1: Intent?) {

View file

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

View file

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

View file

@ -11,6 +11,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.Editable import android.text.Editable
import android.util.Log import android.util.Log
import android.util.Patterns
import android.view.* import android.view.*
import android.view.View.* import android.view.View.*
import android.widget.* import android.widget.*
@ -30,7 +31,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -227,7 +228,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
launch{ launch{
resultViewModel.uiState.collectLatest { res -> resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){ 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) } resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
} }
@ -557,7 +558,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
lifecycleScope.launch(Dispatchers.IO){ lifecycleScope.launch(Dispatchers.IO){
Thread.sleep(300) Thread.sleep(300)
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){ 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)) { if (sharedPreferences!!.getBoolean("download_card", true)) {
showSingleDownloadSheet( showSingleDownloadSheet(
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()), 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.app.Activity
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.card.MaterialCardView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.progressindicator.LinearProgressIndicator 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 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val sharedPreferences: SharedPreferences 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.app.Activity
import android.content.SharedPreferences import android.content.SharedPreferences
@ -19,18 +19,16 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository 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.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView 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.progressindicator.LinearProgressIndicator
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.lang.StringBuilder 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val sharedPreferences: SharedPreferences private val sharedPreferences: SharedPreferences
@ -132,13 +130,15 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
pauseButton.setOnClickListener { pauseButton.setOnClickListener {
if (pauseButton.tag == ActiveDownloadAdapter.ActiveDownloadAction.Pause){ 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) pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
if (progressBar.progress == 0) progressBar.isIndeterminate = false if (progressBar.progress == 0) progressBar.isIndeterminate = false
cancelButton.visibility = View.VISIBLE cancelButton.visibility = View.VISIBLE
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
}else{ }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) pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
progressBar.isIndeterminate = true progressBar.isIndeterminate = true
cancelButton.visibility = View.GONE 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.app.Activity
import android.content.SharedPreferences import android.content.SharedPreferences
@ -24,7 +24,9 @@ import com.google.android.material.button.MaterialButton
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.util.Locale 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val sharedPreferences : SharedPreferences private val sharedPreferences : SharedPreferences
@ -35,7 +37,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
} }
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val cardView: FrameLayout val cardView: FrameLayout
init { init {
@ -46,7 +48,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context) val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.download_card, parent, false) .inflate(R.layout.download_card, parent, false)
return ViewHolder(cardView, onItemClickListener) return ViewHolder(cardView)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -136,11 +138,16 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
card.setOnClickListener { card.setOnClickListener {
onItemClickListener.onCardClick(item.url) onItemClickListener.onCardClick(item.url)
} }
card.setOnLongClickListener {
onItemClickListener.onDelete(item.url); true
}
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onButtonClick(itemURL: String) fun onButtonClick(itemURL: String)
fun onCardClick(itemURL: String) fun onCardClick(itemURL: String)
fun onDelete(itemURL: String)
} }
companion object { 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.app.Activity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
@ -14,7 +13,9 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CookieItem import com.deniscerri.ytdlnis.database.models.CookieItem
import com.google.android.material.card.MaterialCardView 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 onItemClickListener: OnItemClickListener
private val activity: Activity 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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -6,25 +6,23 @@ import android.text.format.DateFormat
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.LogItem import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val checkedItems: ArrayList<Long> 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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -10,13 +10,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil 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.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
val checkedItems: ArrayList<Long> 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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -24,13 +24,16 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* 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 checkedItems: ArrayList<Long>
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity 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) datetime.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
// BUTTON ---------------------------------- // 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 var filePresent = true
//IS IN THE FILE SYSTEM? //IS IN THE FILE SYSTEM?
@ -121,11 +124,11 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
thumbnail.alpha = 0.7f thumbnail.alpha = 0.7f
} }
if (item.type == DownloadViewModel.Type.audio) { 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) { } 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{ }else{
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_terminal) btn.setImageResource(R.drawable.ic_terminal)
} }
if (btn.hasOnClickListeners()) btn.setOnClickListener(null) if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filePresent 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.app.Activity
import android.content.SharedPreferences import android.content.SharedPreferences
@ -9,9 +9,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil 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.google.android.material.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso 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 checkedVideos: ArrayList<String>
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity 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.annotation.SuppressLint
import android.app.Activity import android.app.Activity
@ -21,11 +21,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.util.* 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 checkedItems: ArrayList<String>
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity 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.app.Activity
import android.opengl.Visibility
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.LogItem
import com.google.android.material.card.MaterialCardView 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val checkedItems: ArrayList<Long> = ArrayList()
init { init {
this.onItemClickListener = onItemClickListener this.onItemClickListener = onItemClickListener
@ -52,19 +55,74 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
if (item!!.useAsExtraCommand) useInExtraCommands.visibility = View.VISIBLE if (item!!.useAsExtraCommand) useInExtraCommands.visibility = View.VISIBLE
else useInExtraCommands.visibility = View.GONE 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 { card.setOnClickListener {
onItemClickListener.onItemClick(item, position) if (checkedItems.size > 0) {
checkCard(card, item.id)
} else {
onItemClickListener.onItemClick(item, position)
}
} }
card.setOnLongClickListener { 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 { interface OnItemClickListener {
fun onItemClick(commandTemplate: CommandTemplate, index: Int) fun onItemClick(commandTemplate: CommandTemplate, index: Int)
fun onSelected(commandTemplate: CommandTemplate) fun onSelected(commandTemplate: CommandTemplate)
fun onDelete(commandTemplate: CommandTemplate) fun onCardSelect(itemID: Long, isChecked: Boolean)
} }
companion object { 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.app.Activity
import android.content.SharedPreferences 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.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator 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 onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val sharedPreferences: SharedPreferences private val sharedPreferences: SharedPreferences

View file

@ -4,11 +4,8 @@ import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight 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.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import java.util.regex.Pattern
class AddExtraCommandsDialog(private val item: DownloadItem?, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() { 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.Button
import android.widget.Toast import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager 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) val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = result.url link.text = result.url
link.setOnClickListener{ link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), result.url, null) UiUtil.openLinkIntent(requireContext(), result.url)
} }
link.setOnLongClickListener{ link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null) UiUtil.copyLinkToClipBoard(requireContext(), result.url)
true true
} }

View file

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

View file

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

View file

@ -22,6 +22,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager 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.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type 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.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.InfoUtil 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 com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -53,16 +56,19 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
private lateinit var viewPager2: ViewPager2 private lateinit var viewPager2: ViewPager2
private lateinit var fragmentAdapter : DownloadFragmentAdapter private lateinit var fragmentAdapter : DownloadFragmentAdapter
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var historyViewModel: HistoryViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var commandTemplateDao : CommandTemplateDao private lateinit var commandTemplateDao : CommandTemplateDao
private lateinit var infoUtil: InfoUtil private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences : SharedPreferences private lateinit var sharedPreferences : SharedPreferences
private lateinit var updateItem : Button
private lateinit var view: View private lateinit var view: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
@ -87,6 +93,7 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
tabLayout = view.findViewById(R.id.download_tablayout) tabLayout = view.findViewById(R.id.download_tablayout)
viewPager2 = view.findViewById(R.id.download_viewpager) viewPager2 = view.findViewById(R.id.download_viewpager)
updateItem = view.findViewById(R.id.update_item)
(viewPager2.getChildAt(0) as? RecyclerView)?.apply { (viewPager2.getChildAt(0) as? RecyclerView)?.apply {
isNestedScrollingEnabled = false 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)?.isClickable = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f (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 val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter( fragmentAdapter = DownloadFragmentAdapter(
@ -200,8 +211,10 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer()) 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) 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 View.VISIBLE
}else{ }else{
View.GONE 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) 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 View.VISIBLE
}else{ }else{
View.GONE View.GONE
} }
val updateItem = view.findViewById<Button>(R.id.update_item)
if (Patterns.WEB_URL.matcher(result.url).matches()){ if (Patterns.WEB_URL.matcher(result.url).matches()){
link.text = result.url link.text = result.url
link.setOnClickListener{ link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), result.url, null) UiUtil.openLinkIntent(requireContext(), result.url)
} }
link.setOnLongClickListener{ link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null) UiUtil.copyLinkToClipBoard(requireContext(), result.url)
true true
} }
@ -281,12 +294,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
}else{ }else{
link.visibility = View.GONE link.visibility = View.GONE
updateItem.visibility = View.GONE (updateItem.parent as LinearLayout).visibility = View.GONE
} }
//update in the background if there is no data //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) initUpdateData(view)
}else { }else {
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") } 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 -> downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) { if (res.errorMessage != null) {
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel) UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel, historyViewModel)
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState( downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null, errorMessage = null,
actions = 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{ private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){ return when(selectedTabPosition){
@ -344,12 +368,12 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
} }
private fun initUpdateData(v: View){ private fun initUpdateData(v: View){
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){ val shimmerLoading = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_title)
val shimmerLoading = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_title) val title = v.findViewById<View>(R.id.bottom_sheet_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 updateJob = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
val subtitle = v.findViewById<View>(R.id.bottom_sheet_subtitle)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
title.visibility = View.GONE title.visibility = View.GONE
subtitle.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)) val result = resultViewModel.parseQueries(listOf(result.url))
if (result.isEmpty()){ if (result.isEmpty()){
withContext(Dispatchers.Main){
Looper.prepare().run {
Toast.makeText(requireContext(), "No Results Found!", Toast.LENGTH_SHORT).show()
}
dismiss()
}
return@launch 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){ 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){ withContext(Dispatchers.Main){
runCatching { runCatching {

View file

@ -8,10 +8,8 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
@ -23,6 +21,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem 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.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight 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.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -66,7 +66,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) 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 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()){ if (!Patterns.WEB_URL.matcher(downloadItem.url).matches()){
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", false)) downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", false))
downloadItem.url = "" downloadItem.url = ""
@ -117,6 +119,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0, 0,
p0.toString() 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) val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
runCatching { runCatching {
populateCommandCard(commandTemplateCard, templates.first())
commandTemplateCard.setOnClickListener { commandTemplateCard.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ 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} ") chosenCommandView.editText!!.text.insert(chosenCommandView.editText!!.selectionStart, "${c.content} ")
} }
populateCommandCard(commandTemplateCard, it.first()) populateCommandCard(commandTemplateCard, it.first())
downloadItem.format = Format( downloadItem.format = Format(
it.first().title, it.first().title,
"", "",
@ -148,15 +151,20 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
"", "",
"", "",
0, 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()){ val existingTemplate = withContext(Dispatchers.IO){
view.findViewById<MaterialCardView>(R.id.command_card).alpha = 0.3f 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 { }.onFailure {
commandTemplateCard.visibility = View.GONE commandTemplateCard.visibility = View.GONE
@ -189,7 +197,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
view.findViewById<LinearLayout>(R.id.adjust).apply { 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){ if (isVisible){
UiUtil.configureCommand( UiUtil.configureCommand(
view, view,
@ -208,6 +216,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0, 0,
it.content it.content
) )
preferences.edit().putString("lastCommandTemplateUsed", it.content).apply()
commandTemplateCard.visibility = View.VISIBLE commandTemplateCard.visibility = View.VISIBLE
view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE
view.findViewById<Chip>(R.id.editSelected).isEnabled = true view.findViewById<Chip>(R.id.editSelected).isEnabled = true
@ -237,6 +246,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0, 0,
it.content it.content
) )
preferences.edit().putString("lastCommandTemplateUsed", it.content).apply()
} }
}, },
shortcutClicked = { shortcutClicked = {
@ -244,6 +254,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
itemSelected = { itemSelected = {
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it") chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString() downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
}, },
itemRemoved = {removed -> itemRemoved = {removed ->
chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${ chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${
@ -252,6 +263,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
) )
})(?!.*\\1)".toRegex(), "").trimEnd()) })(?!.*\\1)".toRegex(), "").trimEnd())
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString().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 androidx.work.WorkManager
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil 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, class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener { ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var historyViewModel: HistoryViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
@ -81,6 +83,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -472,10 +475,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
saveSubtitlesClicked = {checked -> saveSubtitlesClicked = {checked ->
items.forEach { it.videoPreferences.writeSubs = checked } items.forEach { it.videoPreferences.writeSubs = checked }
}, },
subtitleLanguagesClicked = { subtitleLanguagesSet = {value ->
UiUtil.showSubtitleLanguagesDialog(requireActivity(), ""){ value -> items.forEach { it.videoPreferences.subsLanguages = value }
items.forEach { it.videoPreferences.subsLanguages = value }
}
}, },
removeAudioClicked = {checked -> removeAudioClicked = {checked ->
items.forEach { it.videoPreferences.removeAudio = checked } items.forEach { it.videoPreferences.removeAudio = checked }
@ -509,7 +510,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.uiState.collectLatest { res -> downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) { if (res.errorMessage != null) {
UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel) UiUtil.handleDownloadsResponse(requireActivity() as MainActivity, res, downloadViewModel, historyViewModel)
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState( downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null, errorMessage = null,
actions = 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?) { override fun onClick(p0: View?) {
} }

View file

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

View file

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

View file

@ -37,9 +37,9 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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()) activeAdapter = ActiveDownloadMinifiedAdapter(this,requireActivity())
queuedAdapter = GenericDownloadAdapter(this,requireActivity()) queuedAdapter = GenericDownloadAdapter(this,requireActivity())
@ -220,10 +223,10 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
bottomSheetLink.text = item.url bottomSheetLink.text = item.url
bottomSheetLink.setOnClickListener{ bottomSheetLink.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), item.url, null) UiUtil.openLinkIntent(requireContext(), item.url)
} }
bottomSheetLink.setOnLongClickListener{ bottomSheetLink.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), item.url, null) UiUtil.copyLinkToClipBoard(requireContext(), item.url)
true true
} }
@ -505,10 +508,12 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
link!!.text = url link!!.text = url
link.tag = itemID link.tag = itemID
link.setOnClickListener{ link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet) bottomSheet.dismiss()
UiUtil.openLinkIntent(requireContext(), item.url)
} }
link.setOnLongClickListener{ link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet) bottomSheet.dismiss()
UiUtil.copyLinkToClipBoard(requireContext(), item.url)
true true
} }
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button) val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)

View file

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

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -17,7 +18,7 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkQuery import androidx.work.WorkQuery
import com.deniscerri.ytdlnis.R 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.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -41,6 +42,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
lateinit var downloadItem: DownloadItem lateinit var downloadItem: DownloadItem
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var pauseResume: MaterialButton private lateinit var pauseResume: MaterialButton
private lateinit var noResults: RelativeLayout
private lateinit var workManager: WorkManager private lateinit var workManager: WorkManager
@ -71,6 +73,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
activeRecyclerView.adapter = activeDownloads activeRecyclerView.adapter = activeDownloads
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size)) activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
pauseResume = view.findViewById(R.id.pause_resume) pauseResume = view.findViewById(R.id.pause_resume)
noResults = view.findViewById(R.id.no_results)
pauseResume.setOnClickListener { pauseResume.setOnClickListener {
if (pauseResume.text == requireContext().getString(R.string.pause)){ if (pauseResume.text == requireContext().getString(R.string.pause)){
@ -168,6 +171,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
activeDownloads.notifyDataSetChanged() activeDownloads.notifyDataSetChanged()
pauseResume.visibility = View.GONE 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.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
@ -21,7 +22,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R 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.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -47,6 +48,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var cancelledRecyclerView : RecyclerView private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var adapter : GenericDownloadAdapter private lateinit var adapter : GenericDownloadAdapter
private lateinit var noResults : RelativeLayout
private var actionMode : ActionMode? = null private var actionMode : ActionMode? = null
private var totalSize = 0 private var totalSize = 0
@ -55,7 +57,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return fragmentView return fragmentView
@ -70,6 +72,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
requireActivity() requireActivity()
) )
noResults = view.findViewById(R.id.no_results)
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview) cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
cancelledRecyclerView.forceFastScrollMode() cancelledRecyclerView.forceFastScrollMode()
cancelledRecyclerView.adapter = adapter cancelledRecyclerView.adapter = adapter
@ -90,6 +93,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){ downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){
totalSize = it 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 androidx.work.WorkManager
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout

View file

@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener import android.widget.AdapterView.OnItemClickListener
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -23,7 +24,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R 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.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -48,6 +49,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var erroredRecyclerView : RecyclerView private lateinit var erroredRecyclerView : RecyclerView
private lateinit var noResults : RelativeLayout
private lateinit var adapter : GenericDownloadAdapter private lateinit var adapter : GenericDownloadAdapter
private var actionMode : ActionMode? = null private var actionMode : ActionMode? = null
private var totalSize: Int = 0 private var totalSize: Int = 0
@ -56,7 +58,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.generic_recyclerview, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return fragmentView return fragmentView
@ -71,6 +73,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
requireActivity() requireActivity()
) )
noResults = view.findViewById(R.id.no_results)
erroredRecyclerView = view.findViewById(R.id.download_recyclerview) erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
erroredRecyclerView.forceFastScrollMode() erroredRecyclerView.forceFastScrollMode()
erroredRecyclerView.adapter = adapter erroredRecyclerView.adapter = adapter
@ -90,6 +93,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){ downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){
totalSize = it 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 androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.HistoryRepository import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.DBManager.SORTING import com.deniscerri.ytdlnis.database.DBManager.SORTING
@ -72,7 +72,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
private var topAppBar: MaterialToolbar? = null private var topAppBar: MaterialToolbar? = null
private var recyclerView: RecyclerView? = null private var recyclerView: RecyclerView? = null
private var historyAdapter: HistoryAdapter? = null private var historyAdapter: HistoryAdapter? = null
private var bottomSheet: BottomSheetDialog? = null
private var sortSheet: BottomSheetDialog? = null private var sortSheet: BottomSheetDialog? = null
private var uiHandler: Handler? = null private var uiHandler: Handler? = null
private var noResults: RelativeLayout? = null private var noResults: RelativeLayout? = null
@ -401,20 +400,24 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onCardClick(itemID: Long, isPresent: Boolean) { override fun onCardClick(itemID: Long, isPresent: Boolean) {
val item = findItem(itemID) val item = findItem(itemID)
UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent, UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent,
removeItem = { it, deleteFile ->
historyViewModel.delete(it, deleteFile)
},
redownloadItem = { redownloadItem = {
val downloadItem = downloadViewModel.createDownloadItemFromHistory(it) val downloadItem = downloadViewModel.createDownloadItemFromHistory(it)
runBlocking{ runBlocking{
downloadViewModel.queueDownloads(listOf(downloadItem)) downloadViewModel.queueDownloads(listOf(downloadItem))
} }
historyViewModel.delete(it, false) 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) { override fun onCardSelect(itemID: Long, isChecked: Boolean) {

View file

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

View file

@ -1,5 +1,7 @@
package com.deniscerri.ytdlnis.ui.more package com.deniscerri.ytdlnis.ui.more
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
@ -9,12 +11,15 @@ import android.os.Looper
import android.text.InputType import android.text.InputType
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -26,11 +31,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.models.CommandTemplate
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
import com.deniscerri.ytdlnis.database.DBManager.SORTING 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.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListener { class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListener {
@ -53,6 +62,9 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
private lateinit var noResults: RelativeLayout private lateinit var noResults: RelativeLayout
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var sortChip: Chip 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( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -60,6 +72,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
mainActivity = activity as MainActivity mainActivity = activity as MainActivity
selectedObjects = arrayListOf()
return inflater.inflate(R.layout.fragment_command_templates, container, false) 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) { commandTemplateViewModel.allItems.observe(viewLifecycleOwner) {
if (it.isEmpty()) noResults.visibility = View.VISIBLE if (it.isEmpty()) noResults.visibility = View.VISIBLE
else noResults.visibility = View.GONE else noResults.visibility = View.GONE
@ -259,14 +272,109 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
override fun onSelected(commandTemplate: CommandTemplate) { override fun onSelected(commandTemplate: CommandTemplate) {
} }
override fun onDelete(commandTemplate: CommandTemplate) { override fun onCardSelect(itemID: Long, isChecked: Boolean) {
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val item = templatesList.find { it.id == itemID }
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + commandTemplate.title + "\"!") if (isChecked) {
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } selectedObjects!!.add(item!!)
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> if (actionMode == null){
commandTemplateViewModel.delete(commandTemplate) 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 = private var simpleCallback: ItemTouchHelper.SimpleCallback =
@ -282,7 +390,9 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
ItemTouchHelper.LEFT -> { ItemTouchHelper.LEFT -> {
val deletedItem = templatesList[position] val deletedItem = templatesList[position]
templatesAdapter.notifyItemChanged(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.EditText
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.children
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -26,10 +25,10 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.models.CookieItem
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel 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.appbar.MaterialToolbar
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -130,6 +129,10 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
} }
deleteDialog.show() deleteDialog.show()
} }
R.id.use_header -> {
m.isChecked = !m.isChecked
preferences.edit().putBoolean("use_header", m.isChecked).apply()
}
R.id.import_clipboard -> { R.id.import_clipboard -> {
lifecycleScope.launch(Dispatchers.IO){ lifecycleScope.launch(Dispatchers.IO){
cookiesViewModel.importFromClipboard() cookiesViewModel.importFromClipboard()
@ -139,9 +142,18 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
cookiesViewModel.exportToClipboard() cookiesViewModel.exportToClipboard()
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show() Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
} }
R.id.use_header -> { R.id.export_file -> {
m.isChecked = !m.isChecked cookiesViewModel.exportToFile {f ->
preferences.edit().putBoolean("use_header", m.isChecked).apply() 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 true

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll 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.app.Activity
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.Bundle
@ -35,14 +36,16 @@ class FolderSettingsFragment : BaseSettingsFragment() {
private var videoFilenameTemplate : Preference? = null private var videoFilenameTemplate : Preference? = null
private var clearCache: Preference? = null private var clearCache: Preference? = null
private var moveCache: Preference? = null private var moveCache: Preference? = null
private lateinit var preferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private var activeDownloadCount = 0 private var activeDownloadCount = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.folders_preference, rootKey) setPreferencesFromResource(R.xml.folders_preference, rootKey)
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit() editor = preferences.edit()
musicPath = findPreference("music_path") musicPath = findPreference("music_path")
videoPath = findPreference("video_path") videoPath = findPreference("video_path")
@ -110,7 +113,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
val uri = Uri.parse("package:" + requireContext().packageName) val uri = Uri.parse("package:" + requireContext().packageName)
intent.data = uri intent.data = uri
accessAllFilesLauncher.launch(intent) startActivity(intent)
true 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( private var musicPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { 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) { private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
val path = data!!.data.toString() val path = data!!.data.toString()
p!!.summary = FileUtil.formatPath(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.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
@ -130,29 +131,24 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
true 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 = findPreference("ignore_battery")
ignoreBatteryOptimization!!.onPreferenceClickListener = ignoreBatteryOptimization!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + requireContext().packageName) intent.data = Uri.parse("package:" + requireContext().packageName)
ignoreBatteryLauncher.launch(intent) startActivity(intent)
true true
} }
} }
private var ignoreBatteryLauncher = registerForActivityResult( override fun onResume() {
ActivityResultContracts.StartActivityForResult() val packageName: String = requireContext().packageName
) { result -> val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
if (result.resultCode == Activity.RESULT_OK) { if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization?.isVisible = false 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) { fun changeTopAppbarTitle(text: String) {

View file

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

View file

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

View file

@ -8,9 +8,8 @@ import android.content.Context.INPUT_METHOD_SERVICE
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle import android.text.method.ScrollingMovementMethod
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -23,19 +22,14 @@ import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.get import androidx.core.view.get
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.TerminalItem import com.deniscerri.ytdlnis.database.models.TerminalItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel 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.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.lang.StringBuilder import java.util.concurrent.Executor
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -87,7 +80,7 @@ class TerminalFragment : Fragment() {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putString("input", input?.text.toString()) outState.putString("input", input?.text.toString())
outState.putString("output", output?.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) outState.putLong("downloadID", downloadID)
} }
@ -103,6 +96,7 @@ class TerminalFragment : Fragment() {
scrollView = view.findViewById(R.id.custom_command_scrollview) scrollView = view.findViewById(R.id.custom_command_scrollview)
topAppBar = requireActivity().findViewById(R.id.custom_command_toolbar) topAppBar = requireActivity().findViewById(R.id.custom_command_toolbar)
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() } topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
topAppBar!!.setOnClickListener { scrollView?.scrollTo(0,0) }
input = view.findViewById(R.id.command_edittext) input = view.findViewById(R.id.command_edittext)
fab = view.findViewById(R.id.command_fab) fab = view.findViewById(R.id.command_fab)
@ -156,7 +150,7 @@ class TerminalFragment : Fragment() {
when(it.itemId){ when(it.itemId){
R.id.command_templates -> { R.id.command_templates -> {
if (templateCount == 0){ 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{ }else{
lifecycleScope.launch { lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ templates -> UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ templates ->
@ -198,11 +192,12 @@ class TerminalFragment : Fragment() {
} }
output = view.findViewById(R.id.custom_command_output) output = view.findViewById(R.id.custom_command_output)
output!!.movementMethod = ScrollingMovementMethod()
output!!.setTextIsSelectable(true) output!!.setTextIsSelectable(true)
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
input!!.requestFocus() input!!.requestFocus()
fab!!.setOnClickListener { fab!!.setOnClickListener {
if (fab!!.text == getString(R.string.run_command)){ if (fab!!.text == requireActivity().getString(R.string.run_command)){
input!!.visibility = View.GONE input!!.visibility = View.GONE
output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n" output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n"
showCancelFab() showCancelFab()
@ -213,6 +208,8 @@ class TerminalFragment : Fragment() {
terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString())) terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString()))
} }
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command)) terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
input!!.visibility = View.GONE
showCancelFab()
runWorkerListener() runWorkerListener()
} }
}else { }else {
@ -274,12 +271,16 @@ class TerminalFragment : Fragment() {
} }
} }
private fun hideCancelFab() { private fun hideCancelFab() {
fab!!.text = getString(R.string.run_command) kotlin.runCatching {
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24) fab!!.text = getString(R.string.run_command)
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
}
} }
private fun showCancelFab() { private fun showCancelFab() {
fab!!.text = getString(R.string.cancel_download) kotlin.runCatching {
fab!!.setIconResource(R.drawable.ic_cancel) fab!!.text = getString(R.string.cancel_download)
fab!!.setIconResource(R.drawable.ic_cancel)
}
} }
private var commandPathResultLauncher = registerForActivityResult( private var commandPathResultLauncher = registerForActivityResult(
@ -307,7 +308,11 @@ class TerminalFragment : Fragment() {
output?.text = it.log output?.text = it.log
} }
output?.scrollTo(0, output!!.height) 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()) WorkManager.getInstance(requireContext())
.getWorkInfosByTagLiveData(downloadID.toString()) .getWorkInfosForUniqueWorkLiveData(downloadID.toString())
.observe(viewLifecycleOwner){ list -> .removeObserver(workerObserver)
list.forEach {work ->
if (listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED).contains(work.state)) { WorkManager.getInstance(requireContext())
requireActivity().runOnUiThread { .getWorkInfosForUniqueWorkLiveData(downloadID.toString())
kotlin.runCatching { .observe(viewLifecycleOwner, workerObserver)
input!!.setText("yt-dlp ") }
input!!.visibility = View.VISIBLE
input!!.requestFocus() private val workerObserver = object: Observer<MutableList<WorkInfo>> {
input!!.setSelection(input!!.text.length) override fun onChanged(value: MutableList<WorkInfo>) {
hideCancelFab() 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 package com.deniscerri.ytdlnis.util
import android.app.Activity
import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.View
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile 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.FileCallback
import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.copyFolderTo import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo import com.anggrayudi.storage.file.moveFileTo
import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.yausername.youtubedl_android.YoutubeDLRequest import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -64,6 +53,7 @@ object FileUtil {
var dataValue = path var dataValue = path
if (dataValue.startsWith("/storage/")) return dataValue if (dataValue.startsWith("/storage/")) return dataValue
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "") dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
dataValue = dataValue.replace("raw:/storage/", "")
dataValue = dataValue.replace("^/document/".toRegex(), "") dataValue = dataValue.replace("^/document/".toRegex(), "")
dataValue = dataValue.replace("^primary:".toRegex(), "primary/") dataValue = dataValue.replace("^primary:".toRegex(), "primary/")
dataValue = dataValue.replace("%3A".toRegex(), "/") dataValue = dataValue.replace("%3A".toRegex(), "/")
@ -296,6 +286,10 @@ object FileUtil {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command" 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){ fun getCookieFile(context : Context, path: (path: String) -> Unit){
val cookiesFile = File(context.cacheDir, "cookies.txt") val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()) path(cookiesFile.absolutePath) if (cookiesFile.exists()) path(cookiesFile.absolutePath)

View file

@ -9,6 +9,7 @@ import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -553,13 +554,20 @@ class InfoUtil(private val context: Context) {
request = YoutubeDLRequest(query) request = YoutubeDLRequest(query)
}else{ }else{
request = YoutubeDLRequest(emptyList()) request = YoutubeDLRequest(emptyList())
if (searchEngine == "ytsearchmusic"){ when (searchEngine){
request.addOption("--default-search", "https://music.youtube.com/search?q=") "ytsearchmusic" -> {
request.addOption("ytsearch25:\"${query}\"") request.addOption("--default-search", "https://music.youtube.com/search?q=")
}else{ request.addOption("ytsearch25:\"${query}\"")
request.addOption("${searchEngine}25:\"${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("--flat-playlist")
request.addOption("-j") 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()){ if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
downDir = File(FileUtil.formatPath(downloadItem.downloadPath)) downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
request.addOption("--no-quiet") request.addOption("--no-quiet")
request.addOption("-no-simulate") request.addOption("--no-simulate")
request.addOption("--print", "after_video:'%(filename)s'") request.addOption("--print", "after_move:'%(filepath,_filename)s'")
}else{ }else{
val cacheDir = FileUtil.getCachePath(context) val cacheDir = FileUtil.getCachePath(context)
downDir = File(cacheDir, downloadItem.id.toString()) downDir = File(cacheDir, downloadItem.id.toString())
@ -1072,7 +1080,9 @@ class InfoUtil(private val context: Context) {
} }
if (downloadItem.extraCommands.isNotBlank()){ 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.createNewFile()
conf.writeText(downloadItem.extraCommands) conf.writeText(downloadItem.extraCommands)
request.addOption( request.addOption(
@ -1135,7 +1145,7 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("embed_metadata", true)){ if (sharedPreferences.getBoolean("embed_metadata", true)){
request.addOption("--embed-metadata") 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()) { if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s") 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.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavDeepLinkBuilder import androidx.navigation.NavDeepLinkBuilder
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.App.Companion.instance
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
@ -195,7 +196,8 @@ class NotificationUtil(var context: Context) {
notificationManager.notify(DOWNLOAD_UPDATING_NOTIFICATION_ID, notificationBuilder.build()) notificationManager.notify(DOWNLOAD_UPDATING_NOTIFICATION_ID, notificationBuilder.build())
} }
fun createDownloadFinished(title: String?, fun createDownloadFinished(
title: String?,
filepath: List<String>? filepath: List<String>?
) { ) {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID) val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
@ -215,24 +217,36 @@ class NotificationUtil(var context: Context) {
.clearActions() .clearActions()
if (filepath != null){ if (filepath != null){
try{ try{
val file = File(filepath.first()) val uri = filepath.first().runCatching {
val uri = FileProvider.getUriForFile( DocumentFile.fromSingleUri(context, Uri.parse(filepath.first())).run{
context, if (this?.exists() == true){
"com.deniscerri.ytdl.fileprovider", this.uri
file }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 { val openNotificationPendingIntent: PendingIntent = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(intent) addNextIntentWithParentStack(openFileIntent)
getPendingIntent(0, getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} }
//share intent //share intent
val shareIntent = Intent(context, ShareFileActivity::class.java) val shareIntent = Intent(context, ShareFileActivity::class.java)
shareIntent.putExtra("path", filepath.toTypedArray()) shareIntent.putExtra("path", filepath.toTypedArray())

View file

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

View file

@ -1,37 +1,51 @@
package com.deniscerri.ytdlnis.util package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.method.LinkMovementMethod
import android.util.Log import android.util.Log
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.GithubRelease import com.deniscerri.ytdlnis.database.models.GithubRelease
import com.deniscerri.ytdlnis.database.models.GithubReleaseAsset
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus 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.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedReader import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
class UpdateUtil(var context: Context) { class UpdateUtil(var context: Context) {
private val tag = "UpdateUtil" private val tag = "UpdateUtil"
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val downloadManager: DownloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager private val downloadManager: DownloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun updateApp(result: (result: String) -> Unit) { fun updateApp(result: (result: String) -> Unit) {
try { try {
if (updatingApp) { if (updatingApp) {
@ -99,7 +113,7 @@ class UpdateUtil(var context: Context) {
Environment Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs() .mkdirs()
downloadManager.enqueue( val downloadID = downloadManager.enqueue(
DownloadManager.Request(uri) DownloadManager.Request(uri)
.setAllowedNetworkTypes( .setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_WIFI or
@ -110,10 +124,34 @@ class UpdateUtil(var context: Context) {
.setTitle(context.getString(R.string.downloading_update)) .setTitle(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name) .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 return
}catch (e: Exception){ }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.models.LogItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.LogRepository 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.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -122,6 +120,7 @@ class DownloadWorker(
val commandString = infoUtil.parseYTDLRequestString(request) val commandString = infoUtil.parseYTDLRequestString(request)
val logString = StringBuilder("\n ${commandString}\n\n")
val logItem = LogItem( val logItem = LogItem(
0, 0,
downloadItem.title.ifEmpty { downloadItem.url }, downloadItem.title.ifEmpty { downloadItem.url },
@ -129,7 +128,7 @@ class DownloadWorker(
"Title: ${downloadItem.title}\n" + "Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" + "URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" + "Type: ${downloadItem.type}\n" +
"Command:\n ${commandString}\n\n", "Command: $logString",
downloadItem.format, downloadItem.format,
downloadItem.type, downloadItem.type,
System.currentTimeMillis(), System.currentTimeMillis(),
@ -137,7 +136,7 @@ class DownloadWorker(
runBlocking { runBlocking {
logItem.id = logRepo.insert(logItem) if (logDownloads) logItem.id = logRepo.insert(logItem)
downloadItem.logID = logItem.id downloadItem.logID = logItem.id
dao.update(downloadItem) dao.update(downloadItem)
} }
@ -154,7 +153,10 @@ class DownloadWorker(
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
) )
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
logRepo.update(line, logItem.id) if (logDownloads) {
logRepo.update(line, logItem.id)
logString.append("$line\n")
}
} }
} }
}.onSuccess { }.onSuccess {
@ -164,16 +166,18 @@ class DownloadWorker(
if (noCache){ if (noCache){
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id)) 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") } .filter { it.startsWith("'/storage") }
.map { it.removePrefix("'") } .map { it.removePrefix("'") }
.map { it.removeSuffix("'\n") } .map { it.removeSuffix("\n") }
finalPaths = File(FileUtil.formatPath(downloadLocation)) .map { it.removeSuffix("'") }
.walkTopDown()
.filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
.sortedByDescending { it.length() }
.map { it.absolutePath }
.toList() .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) FileUtil.scanMedia(finalPaths, context)
if (finalPaths.isEmpty()){ if (finalPaths.isEmpty()){
finalPaths = listOf(context.getString(R.string.unfound_file)) finalPaths = listOf(context.getString(R.string.unfound_file))
@ -236,8 +240,6 @@ class DownloadWorker(
if (logDownloads){ if (logDownloads){
logRepo.update(it.out, logItem.id) logRepo.update(it.out, logItem.id)
}else{
logRepo.delete(logItem)
} }
} }
@ -250,9 +252,13 @@ class DownloadWorker(
if (it is YoutubeDL.CanceledException) { if (it is YoutubeDL.CanceledException) {
}else{ }else{
if (logDownloads){ if(it.message != null){
if(it.message != null){ if (logDownloads){
logRepo.update(it.message!!, logItem.id) 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.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.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.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption 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.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,10 +44,8 @@ class TerminalDownloadWorker(
val dbManager = DBManager.getInstance(context) val dbManager = DBManager.getInstance(context)
val logRepo = LogRepository(dbManager.logDao) val logRepo = LogRepository(dbManager.logDao)
val notificationUtil = NotificationUtil(context) val notificationUtil = NotificationUtil(context)
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
val infoUtil = InfoUtil(context)
val intent = Intent(context, TerminalActivity::class.java) val intent = Intent(context, TerminalActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
@ -95,7 +92,7 @@ class TerminalDownloadWorker(
System.currentTimeMillis(), System.currentTimeMillis(),
) )
runCatching { kotlin.runCatching {
if (logDownloads){ if (logDownloads){
runBlocking { runBlocking {
logItem.id = logRepo.insert(logItem) logItem.id = logRepo.insert(logItem)
@ -137,12 +134,10 @@ class TerminalDownloadWorker(
} }
return runBlocking { return runBlocking {
CoroutineScope(Dispatchers.IO).launch { if (logDownloads) logRepo.update(it.out, logItem.id)
if (logDownloads) logRepo.update(it.out, logItem.id) dao.updateLog(it.out, itemId.toLong())
dao.updateLog(it.out, itemId.toLong()) Thread.sleep(1000)
Thread.sleep(1000) dao.delete(itemId.toLong())
dao.delete(itemId.toLong())
}
notificationUtil.cancelDownloadNotification(itemId) notificationUtil.cancelDownloadNotification(itemId)
Result.success() Result.success()

View file

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

View file

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

View file

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

View file

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

View file

@ -1,35 +1,42 @@
<?xml version="1.0" encoding="utf-8"?> <?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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginBottom="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.button.MaterialButton <LinearLayout
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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" android:orientation="vertical">
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> <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 <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout <LinearLayout
android:id="@+id/history_selection_chips" android:id="@+id/history_selection_chips"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:orientation="horizontal"
android:layout_height="wrap_content"> 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 <com.google.android.material.chip.ChipGroup
android:layout_width="match_parent" android:layout_width="match_parent"
app:selectionRequired="false" app:selectionRequired="false"
@ -69,9 +61,19 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/shortcuts" android:text="@string/shortcuts"
app:chipIcon="@drawable/ic_shortcut" /> app:chipIcon="@drawable/ic_shortcut" />
</com.google.android.material.chip.ChipGroup> </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> </LinearLayout>
</HorizontalScrollView> </HorizontalScrollView>
@ -92,7 +94,7 @@
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results" <include layout="@layout/no_results"
android:visibility="gone" /> android:visibility="gone" />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,36 +1,46 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<TextView <LinearLayout
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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="none" android:orientation="vertical">
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> <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> </androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results" <include layout="@layout/no_results"
android:visibility="gone" /> android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout> </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