added formats

pull/44/head
Denis Çerri 4 years ago
parent 09bddccb61
commit c4c4e7eab1
No known key found for this signature in database
GPG Key ID: 96B3554AF5B193EE

@ -9,9 +9,10 @@
<div align="center"> <div align="center">
| Build | Latest Release | Downloads | Contribute | ![CI](https://github.com/deniscerri/ytdlnis/actions/workflows/android.yml/badge.svg?branch=main&event=pull) [![stable release](https://img.shields.io/github/release/deniscerri/ytdlnis.svg?maxAge=3600&label=download)](https://github.com/deniscerri/ytdlnis/releases/latest) [![downloads](https://img.shields.io/github/downloads/deniscerri/ytdlnis/total?style=flat-square)](https://github.com/deniscerri/ytdlnis/releases) [![Translation status](https://hosted.weblate.org/widgets/ytdlnis/-/svg-badge.svg)](https://hosted.weblate.org/engage/ytdlnis/?utm_source=widget) [![community](https://img.shields.io/badge/Discord-YTDLnis-blueviolet?style=flat-square&logo=discord)](https://discord.gg/WW3KYWxAPm)
|-------|----------|------------|------------|
| ![CI](https://github.com/deniscerri/ytdlnis/actions/workflows/android.yml/badge.svg?branch=main&event=pull) | [![stable release](https://img.shields.io/github/release/deniscerri/ytdlnis.svg?maxAge=3600&label=download)](https://github.com/deniscerri/ytdlnis/releases/latest) | [![downloads](https://img.shields.io/github/downloads/deniscerri/ytdlnis/total?style=flat-square)](https://github.com/deniscerri/ytdlnis/releases) | [![Translation status](https://hosted.weblate.org/widgets/ytdlnis/-/svg-badge.svg)](https://hosted.weblate.org/engage/ytdlnis/?utm_source=widget) |
</div> </div>

@ -89,9 +89,6 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
// implementation project(":library")
// implementation project(":ffmpeg")
implementation "com.github.junkfood02.youtubedl-android:library:$youtubedlAndroidVer" implementation "com.github.junkfood02.youtubedl-android:library:$youtubedlAndroidVer"
implementation "com.github.junkfood02.youtubedl-android:ffmpeg:$youtubedlAndroidVer" implementation "com.github.junkfood02.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
implementation "com.github.junkfood02.youtubedl-android:aria2c:$youtubedlAndroidVer" implementation "com.github.junkfood02.youtubedl-android:aria2c:$youtubedlAndroidVer"
@ -122,10 +119,10 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$workVer" implementation "androidx.work:work-runtime-ktx:$workVer"
implementation "androidx.room:room-runtime:2.4.3" implementation "androidx.room:room-runtime:2.5.0"
implementation "androidx.room:room-ktx:2.4.3" implementation "androidx.room:room-ktx:2.5.0"
kapt "androidx.room:room-compiler:2.4.3" kapt "androidx.room:room-compiler:2.5.0"
androidTestImplementation "androidx.room:room-testing:2.4.3" androidTestImplementation "androidx.room:room-testing:2.5.0"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation "androidx.compose.runtime:runtime:$composeVer" implementation "androidx.compose.runtime:runtime:$composeVer"
androidTestImplementation "com.google.truth:truth:1.1.3" androidTestImplementation "com.google.truth:truth:1.1.3"
@ -137,4 +134,6 @@ dependencies {
implementation "com.squareup.retrofit2:converter-gson:$retrofitVer" implementation "com.squareup.retrofit2:converter-gson:$retrofitVer"
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10" implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
implementation "com.squareup.okhttp3:logging-interceptor:4.7.2" implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
} }

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "1391e7ec82bbacc310cf6f23e3d1f85a", "identityHash": "6c48acc2155f9a895971bd84c84b7d4e",
"entities": [ "entities": [
{ {
"tableName": "results", "tableName": "results",
@ -58,10 +58,66 @@
} }
], ],
"primaryKey": { "primaryKey": {
"autoGenerate": true,
"columnNames": [ "columnNames": [
"id" "id"
], ]
"autoGenerate": true },
"indices": [],
"foreignKeys": []
},
{
"tableName": "formats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` INTEGER NOT NULL, `format` TEXT NOT NULL, `format_id` TEXT NOT NULL, `ext` TEXT NOT NULL, `filesize` INTEGER NOT NULL, `format_note` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "itemId",
"columnName": "itemId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format_id",
"columnName": "format_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ext",
"columnName": "ext",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "filesize",
"columnName": "filesize",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "format_note",
"columnName": "format_note",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}, },
"indices": [], "indices": [],
"foreignKeys": [] "foreignKeys": []
@ -132,17 +188,17 @@
} }
], ],
"primaryKey": { "primaryKey": {
"autoGenerate": true,
"columnNames": [ "columnNames": [
"id" "id"
], ]
"autoGenerate": true
}, },
"indices": [], "indices": [],
"foreignKeys": [] "foreignKeys": []
}, },
{ {
"tableName": "downloads", "tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `id` INTEGER PRIMARY KEY AUTOINCREMENT)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `workID` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [ "fields": [
{ {
"fieldPath": "url", "fieldPath": "url",
@ -254,6 +310,12 @@
"notNull": true, "notNull": true,
"defaultValue": "'Queued'" "defaultValue": "'Queued'"
}, },
{
"fieldPath": "workID",
"columnName": "workID",
"affinity": "INTEGER",
"notNull": true
},
{ {
"fieldPath": "id", "fieldPath": "id",
"columnName": "id", "columnName": "id",
@ -262,10 +324,10 @@
} }
], ],
"primaryKey": { "primaryKey": {
"autoGenerate": true,
"columnNames": [ "columnNames": [
"id" "id"
], ]
"autoGenerate": true
}, },
"indices": [], "indices": [],
"foreignKeys": [] "foreignKeys": []
@ -294,10 +356,10 @@
} }
], ],
"primaryKey": { "primaryKey": {
"autoGenerate": true,
"columnNames": [ "columnNames": [
"id" "id"
], ]
"autoGenerate": true
}, },
"indices": [], "indices": [],
"foreignKeys": [] "foreignKeys": []
@ -306,7 +368,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, '1391e7ec82bbacc310cf6f23e3d1f85a')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6c48acc2155f9a895971bd84c84b7d4e')"
] ]
} }
} }

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis package com.deniscerri.ytdlnis
import android.app.Activity import android.app.Activity
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -10,7 +9,6 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
@ -19,9 +17,7 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.service.DownloadInfo import com.deniscerri.ytdlnis.service.DownloadInfo
import com.deniscerri.ytdlnis.service.IDownloaderListener import com.deniscerri.ytdlnis.service.IDownloaderListener
import com.deniscerri.ytdlnis.service.IDownloaderService import com.deniscerri.ytdlnis.service.IDownloaderService
import com.deniscerri.ytdlnis.ui.CustomCommandActivity
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.work.FileTransferWorker import com.deniscerri.ytdlnis.work.FileTransferWorker
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.downLocation import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.downLocation
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.originDir import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.originDir
@ -34,7 +30,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern import java.util.regex.Pattern

@ -18,7 +18,6 @@ import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.deniscerri.ytdlnis.DownloaderService.LocalBinder import com.deniscerri.ytdlnis.DownloaderService.LocalBinder
import com.deniscerri.ytdlnis.database.DatabaseManager
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.databinding.ActivityMainBinding import com.deniscerri.ytdlnis.databinding.ActivityMainBinding
import com.deniscerri.ytdlnis.ui.DownloadsFragment import com.deniscerri.ytdlnis.ui.DownloadsFragment
@ -331,7 +330,6 @@ class MainActivity : AppCompatActivity() {
grantResults: IntArray grantResults: IntArray
) { ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Log.e(TAG, grantResults[0].toString())
for (i in permissions.indices) { for (i in permissions.indices) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
createPermissionRequestDialog() createPermissionRequestDialog()

@ -4,7 +4,6 @@ import android.app.Activity;
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;
@ -36,19 +35,16 @@ public class HomeAdapter extends ListAdapter<ResultItem, HomeAdapter.ViewHolder>
this.checkedVideos = new ArrayList<>(); this.checkedVideos = new ArrayList<>();
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
this.activity = activity; this.activity = activity;
Log.e("TAG", "adapter");
} }
private static final DiffUtil.ItemCallback<ResultItem> DIFF_CALLBACK = new DiffUtil.ItemCallback<ResultItem>() { private static final DiffUtil.ItemCallback<ResultItem> DIFF_CALLBACK = new DiffUtil.ItemCallback<ResultItem>() {
@Override @Override
public boolean areItemsTheSame(@NonNull ResultItem oldItem, @NonNull ResultItem newItem) { public boolean areItemsTheSame(@NonNull ResultItem oldItem, @NonNull ResultItem newItem) {
Log.e("TAG", "adapter2");
return oldItem.getId() == newItem.getId(); return oldItem.getId() == newItem.getId();
} }
@Override @Override
public boolean areContentsTheSame(@NonNull ResultItem oldItem, @NonNull ResultItem newItem) { public boolean areContentsTheSame(@NonNull ResultItem oldItem, @NonNull ResultItem newItem) {
Log.e("TAG", "adapter3");
return oldItem.getUrl().equals(newItem.getUrl()); return oldItem.getUrl().equals(newItem.getUrl());
} }
}; };
@ -79,7 +75,6 @@ public class HomeAdapter extends ListAdapter<ResultItem, HomeAdapter.ViewHolder>
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ResultItem video = getItem(position); ResultItem video = getItem(position);
Log.e("TAG", "adapter4");
MaterialCardView card = holder.cardView; MaterialCardView card = holder.cardView;
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
ImageView thumbnail = card.findViewById(R.id.result_image_view); ImageView thumbnail = card.findViewById(R.id.result_image_view);

@ -4,23 +4,18 @@ import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.dao.*
import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.*
import com.deniscerri.ytdlnis.database.dao.HistoryDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem
@Database( @Database(
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class], entities = [ResultItem::class, Format::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class],
version = 1, version = 1,
autoMigrations = [] autoMigrations = []
) )
abstract class DBManager : RoomDatabase(){ abstract class DBManager : RoomDatabase(){
abstract val resultDao : ResultDao abstract val resultDao : ResultDao
abstract val historyDao : HistoryDao abstract val historyDao : HistoryDao
abstract val formatDao : FormatDao
abstract val downloadDao : DownloadDao abstract val downloadDao : DownloadDao
abstract val commandTemplateDao : CommandTemplateDao abstract val commandTemplateDao : CommandTemplateDao

@ -6,9 +6,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.database.models.HistoryItem; import com.deniscerri.ytdlnis.database.models.HistoryItem;
import com.deniscerri.ytdlnis.database.models.ResultItem; import com.deniscerri.ytdlnis.database.models.ResultItem;

@ -12,8 +12,11 @@ interface DownloadDao {
@Query("SELECT * FROM downloads") @Query("SELECT * FROM downloads")
fun getAllDownloads() : LiveData<List<DownloadItem>> fun getAllDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Queued'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getQueuedDownloads() : List<DownloadItem> fun getActiveDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE workID=:workID")
fun getDownloadsByWorkId(workID: Int) : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
fun getDownloadById(id: Int) : DownloadItem fun getDownloadById(id: Int) : DownloadItem

@ -0,0 +1,25 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
@Dao
interface FormatDao {
@Query("SELECT * FROM formats")
fun getFormatsByItemId() : LiveData<List<Format>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: Format)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: Format)
@Query("DELETE FROM formats")
suspend fun deleteAll()
@Query("DELETE FROM formats WHERE itemId=:itemId")
suspend fun deleteFromatsByItemId(itemId: Int)
}

@ -3,19 +3,25 @@ package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.FileUtil
@Dao @Dao
interface ResultDao { interface ResultDao {
@Query("SELECT * FROM results") @Query("SELECT * FROM results")
fun getResults() : LiveData<List<ResultItem>> fun getResults() : LiveData<List<ResultItem>>
@Query("SELECT * FROM results LIMIT 1")
fun getFirstResult() : ResultItem
@Query("SELECT * FROM results ORDER BY id DESC LIMIT 1")
fun getLastResult(): ResultItem
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: ResultItem) suspend fun insert(item: ResultItem) : Long
@Update(onConflict = OnConflictStrategy.REPLACE) @Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: ResultItem) suspend fun update(item: ResultItem)
@Query("DELETE FROM results") @Query("DELETE FROM results")
suspend fun deleteAll() suspend fun deleteAll()
} }

@ -25,7 +25,8 @@ data class DownloadItem(
val downloadSize: String, val downloadSize: String,
val playlistTitle: String, val playlistTitle: String,
@ColumnInfo(defaultValue = "Queued") @ColumnInfo(defaultValue = "Queued")
var status: String val status: String,
val workID: Int
){ ){
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Int? = null var id: Int? = null

@ -0,0 +1,17 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "formats")
data class Format(
var itemId: Int,
val format: String,
val format_id: String,
val ext: String,
val filesize: Long,
val format_note: String
){
@PrimaryKey(autoGenerate = true)
var id: Int? = null
}

@ -2,9 +2,6 @@ package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import com.yausername.youtubedl_android.mapper.VideoFormat
import com.yausername.youtubedl_android.mapper.VideoInfo
@Entity(tableName = "results") @Entity(tableName = "results")
data class ResultItem( data class ResultItem(

@ -0,0 +1,6 @@
package com.deniscerri.ytdlnis.database.models
data class ResultItemWithFormats(
val resultItem: ResultItem,
var formats: ArrayList<Format>
)

@ -6,6 +6,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
class DownloadRepository(private val downloadDao: DownloadDao) { class DownloadRepository(private val downloadDao: DownloadDao) {
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads() val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads()
val activeDownloads : LiveData<List<DownloadItem>> = downloadDao.getActiveDownloads()
suspend fun insert(item: DownloadItem){ suspend fun insert(item: DownloadItem){
downloadDao.insert(item) downloadDao.insert(item)
@ -19,13 +20,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.update(item) downloadDao.update(item)
} }
suspend fun getQueuedDownloads() : List<DownloadItem>{ fun getDownloadsByWorkID(workID: Int) : List<DownloadItem>{
val items = downloadDao.getQueuedDownloads() return downloadDao.getDownloadsByWorkId(workID)
for (i in items){
i.status = "Downloading"
downloadDao.update(i)
}
return items
} }

@ -1,31 +1,118 @@
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 androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.dao.FormatDao
import com.deniscerri.ytdlnis.database.dao.ResultDao import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.DownloadItem 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.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
class ResultRepository(private val resultDao: ResultDao) { class ResultRepository(private val resultDao: ResultDao, private val formatDao: FormatDao, private val context: Context) {
private val tag: String = "ResultRepository"
val allResults : LiveData<List<ResultItem>> = resultDao.getResults() val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
suspend fun insert(it: ResultItem){ suspend fun insert(it: ResultItem){
resultDao.insert(it) resultDao.insert(it)
} }
suspend fun updateTrending(context: Context){ fun getFirstResult() : ResultItem{
resultDao.deleteAll(); return resultDao.getFirstResult()
}
suspend fun updateTrending(){
deleteAll()
val infoUtil = InfoUtil(context) val infoUtil = InfoUtil(context)
val items = infoUtil.getTrending(context) val items = infoUtil.getTrending(context)
for (i in items){ for (i in items){
resultDao.insert(i!!); val id = resultDao.insert(i!!.resultItem)
addFormats(i.formats,id)
}
}
suspend fun search(inputQuery: String, resetResults: Boolean){
val infoUtil = InfoUtil(context)
try{
if (resetResults) deleteAll()
val res = infoUtil.search(inputQuery)
res.forEach {
val id = resultDao.insert(it!!.resultItem)
addFormats(it.formats,id)
}
}catch (ignored: Exception){}
}
suspend fun getOne(inputQuery: String, resetResults: Boolean){
var el: Array<String?> =
inputQuery.split("/".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
var query = el[el.size - 1]
if (query!!.contains("watch?v=")) {
query = query.substring(8)
}
el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
query = el[0]
el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
query = el[0]
val infoUtil = InfoUtil(context)
try {
val v = infoUtil.getVideo(query!!)
if (resetResults) deleteAll()
val id = resultDao.insert(v!!.resultItem)
addFormats(v.formats,id)
} catch (e: Exception) {
Log.e(tag, e.toString())
}
}
suspend fun getPlaylist(inputQuery: String, resetResults: Boolean){
val query = inputQuery.split("list=".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()[1]
var nextPageToken = ""
if (resetResults) deleteAll()
val infoUtil = InfoUtil(context)
do {
val tmp = infoUtil.getPlaylist(query, nextPageToken)
val tmpVids = tmp.videos
val tmpToken = tmp.nextPageToken
tmpVids.forEach {
val id = resultDao.insert(it!!.resultItem);
addFormats(it.formats,id)
}
if (tmpToken.isEmpty()) break
if (tmpToken == nextPageToken) break
nextPageToken = tmpToken
} while (true)
}
suspend fun getDefault(inputQuery: String, resetResults: Boolean){
val infoUtil = InfoUtil(context)
try {
if (resetResults) deleteAll()
val items = infoUtil.getFromYTDL(inputQuery)
items.forEach {
val id = resultDao.insert(it!!.resultItem);
addFormats(it.formats,id)
}
} catch (e: Exception) {
Log.e(tag, e.toString())
}
}
private suspend fun addFormats(formats : ArrayList<Format>, id: Long){
formats.forEach { f ->
if (f.filesize == 0.toLong()) return@forEach
f.itemId = id.toInt()
formatDao.insert(f)
} }
} }
suspend fun deleteAll(){ suspend fun deleteAll(){
resultDao.deleteAll() resultDao.deleteAll()
formatDao.deleteAll()
} }
suspend fun update(item: ResultItem){ suspend fun update(item: ResultItem){

@ -20,6 +20,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
allDownloads = repository.allDownloads allDownloads = repository.allDownloads
} }
fun startWork(items: List<DownloadItem>){
items.forEach {
insertDownload(it)
}
}
fun insertDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){ fun insertDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
repository.insert(item) repository.insert(item)
} }

@ -1,33 +1,76 @@
package com.deniscerri.ytdlnis.database.viewmodel package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.*
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.database.repository.ResultRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.regex.Pattern
class ResultViewModel(application: Application) : AndroidViewModel(application) { class ResultViewModel(application: Application) : AndroidViewModel(application) {
private val tag: String = "ResultViewModel"
private val repository : ResultRepository private val repository : ResultRepository
val items : LiveData<List<ResultItem>> val items : LiveData<List<ResultItem>>
val loadingItems = MutableLiveData<Boolean>()
init { init {
val dao = DBManager.getInstance(application).resultDao val dao = DBManager.getInstance(application).resultDao
repository = ResultRepository(dao) val formatDao = DBManager.getInstance(application).formatDao
repository = ResultRepository(dao, formatDao, getApplication<Application>().applicationContext)
items = repository.allResults items = repository.allResults
loadingItems.postValue(false)
} }
fun checkTrending() : Boolean{ fun checkTrending() = viewModelScope.launch(Dispatchers.IO){
return items.value?.get(0)?.playlistTitle.equals("ytdlnis-TRENDING"); try {
val item = repository.getFirstResult()
if (item.playlistTitle == "ytdlnis-TRENDING") getTrending()
}catch (e : Exception){
getTrending()
}
} }
fun getTrending() = viewModelScope.launch(Dispatchers.IO){ fun getTrending() = viewModelScope.launch(Dispatchers.IO){
repository.updateTrending(getApplication<Application>().applicationContext); loadingItems.postValue(true)
repository.updateTrending();
loadingItems.postValue(false)
}
fun parseQuery(inputQuery: String, resetResults: Boolean) = viewModelScope.launch(Dispatchers.IO){
if (resetResults) loadingItems.postValue(true)
var type = "Search"
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
val m = p.matcher(inputQuery!!)
if (m.find()) {
type = "Video"
if (inputQuery!!.contains("playlist?list=")) {
type = "Playlist"
}
} else if (inputQuery!!.contains("http")) {
type = "Default"
}
Log.e(tag, "$inputQuery $type")
try {
when (type) {
"Search" -> {
repository.search(inputQuery, resetResults);
}
"Video" -> {
repository.getOne(inputQuery, resetResults);
}
"Playlist" -> {
repository.getPlaylist(inputQuery, resetResults);
}
"Default" -> {
repository.getDefault(inputQuery, resetResults);
}
}
} catch (e: Exception) {
Log.e(tag, e.toString())
}
loadingItems.postValue(false)
} }
fun insert(items: ArrayList<ResultItem?>) = viewModelScope.launch(Dispatchers.IO){ fun insert(items: ArrayList<ResultItem?>) = viewModelScope.launch(Dispatchers.IO){

@ -1,7 +1,5 @@
package com.deniscerri.ytdlnis.service package com.deniscerri.ytdlnis.service
import androidx.room.Room
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject

@ -1,10 +1,5 @@
package com.deniscerri.ytdlnis.service package com.deniscerri.ytdlnis.service
import androidx.room.Query
import androidx.room.Room
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.InfoUtil
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import retrofit2.Response import retrofit2.Response

@ -1,23 +1,19 @@
package com.deniscerri.ytdlnis.ui package com.deniscerri.ytdlnis.ui
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.InputType import android.text.InputType
import android.util.Log
import android.view.* import android.view.*
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.widget.* import android.widget.*
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -27,7 +23,6 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.HomeAdapter import com.deniscerri.ytdlnis.adapter.HomeAdapter
import com.deniscerri.ytdlnis.database.DatabaseManager import com.deniscerri.ytdlnis.database.DatabaseManager
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
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.databinding.FragmentDownloadsBinding import com.deniscerri.ytdlnis.databinding.FragmentDownloadsBinding
import com.deniscerri.ytdlnis.service.DownloadInfo import com.deniscerri.ytdlnis.service.DownloadInfo
@ -44,8 +39,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import java.util.* import java.util.*
import java.util.regex.Pattern
import kotlin.collections.ArrayList
class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickListener { class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickListener {
private var progressBar: LinearProgressIndicator? = null private var progressBar: LinearProgressIndicator? = null
@ -75,7 +68,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
private var topAppBar: MaterialToolbar? = null private var topAppBar: MaterialToolbar? = null
private var recyclerView: RecyclerView? = null private var recyclerView: RecyclerView? = null
private var bottomSheet: BottomSheetDialog? = null private var bottomSheet: 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
private var selectionChips: LinearLayout? = null private var selectionChips: LinearLayout? = null
@ -116,7 +108,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
selectedObjects = ArrayList() selectedObjects = ArrayList()
downloading = mainActivity!!.isDownloadServiceRunning() downloading = mainActivity!!.isDownloadServiceRunning()
downloadQueue = ArrayList() downloadQueue = ArrayList()
resultsList = mutableListOf() resultsList = mutableListOf()
selectedObjects = ArrayList() selectedObjects = ArrayList()
@ -140,8 +131,23 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
resultViewModel.items.observe(viewLifecycleOwner) { resultViewModel.items.observe(viewLifecycleOwner) {
homeAdapter!!.submitList(it) homeAdapter!!.submitList(it)
resultsList = it resultsList = it
shimmerCards!!.stopShimmer() if(it.size > 1){
shimmerCards!!.visibility = GONE if (it[0].playlistTitle.isNotEmpty() || it[0].playlistTitle != "ytdlnis-TRENDING"){
downloadAllFabCoordinator!!.visibility = VISIBLE
}
}
}
resultViewModel.loadingItems.observe(viewLifecycleOwner){
if (it){
recyclerView?.setPadding(0,0,0,0)
shimmerCards!!.startShimmer()
shimmerCards!!.visibility = VISIBLE
}else{
recyclerView?.setPadding(0,0,0,100)
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
}
} }
initMenu() initMenu()
@ -163,12 +169,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
if (inputQueries != null) { if (inputQueries != null) {
resultViewModel.deleteAll() resultViewModel.deleteAll()
inputQueriesLength = inputQueries!!.size inputQueriesLength = inputQueries!!.size
shimmerCards!!.startShimmer() Handler(Looper.getMainLooper()).post { scrollToTop() }
shimmerCards!!.visibility = VISIBLE
val thread = Thread { val thread = Thread {
while (!inputQueries!!.isEmpty()) { while (!inputQueries!!.isEmpty()) {
inputQuery = inputQueries!!.pop() inputQuery = inputQueries!!.pop()
parseQuery(false) resultViewModel.parseQuery(inputQuery!!, false)
} }
try { try {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
@ -187,11 +192,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
} }
thread.start() thread.start()
} else { } else {
if (resultViewModel.checkTrending()){ resultViewModel.checkTrending()
shimmerCards!!.startShimmer()
shimmerCards!!.visibility = VISIBLE
resultViewModel.getTrending()
}
} }
} }
@ -261,16 +262,16 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
val onActionExpandListener: MenuItem.OnActionExpandListener = val onActionExpandListener: MenuItem.OnActionExpandListener =
object : MenuItem.OnActionExpandListener { object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean { override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean {
homeFabs!!.visibility = View.GONE homeFabs!!.visibility = GONE
recyclerView!!.visibility = View.GONE recyclerView!!.visibility = GONE
searchSuggestions!!.visibility = View.VISIBLE searchSuggestions!!.visibility = VISIBLE
return true return true
} }
override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean { override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean {
homeFabs!!.visibility = View.VISIBLE homeFabs!!.visibility = VISIBLE
recyclerView!!.visibility = View.VISIBLE recyclerView!!.visibility = VISIBLE
searchSuggestions!!.visibility = View.GONE searchSuggestions!!.visibility = GONE
return true return true
} }
} }
@ -287,20 +288,12 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
topAppBar!!.menu.findItem(R.id.search).collapseActionView() topAppBar!!.menu.findItem(R.id.search).collapseActionView()
downloadAllFabCoordinator!!.visibility = GONE downloadAllFabCoordinator!!.visibility = GONE
downloadFabs!!.visibility = View.GONE downloadFabs!!.visibility = GONE
selectedObjects = ArrayList() selectedObjects = ArrayList()
inputQuery = query.trim { it <= ' ' } inputQuery = query.trim { it <= ' ' }
shimmerCards!!.startShimmer()
shimmerCards!!.visibility = View.VISIBLE
resultViewModel.deleteAll() resultViewModel.deleteAll()
val thread = Thread { val thread = Thread {
parseQuery(true) resultViewModel.parseQuery(inputQuery!!, true)
// DOWNLOAD ALL BUTTON
if (resultsList!!.size > 1 && resultsList!![1]!!.playlistTitle.isNotEmpty()) {
Handler(Looper.getMainLooper()).post {
downloadAllFabCoordinator!!.visibility = View.VISIBLE
}
}
} }
thread.start() thread.start()
return true return true
@ -335,14 +328,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
return false return false
} }
}) })
topAppBar!!.setOnClickListener { view: View? -> scrollToTop() } topAppBar!!.setOnClickListener { scrollToTop() }
topAppBar!!.setOnMenuItemClickListener { m: MenuItem -> topAppBar!!.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId val itemId = m.itemId
if (itemId == R.id.delete_results) { if (itemId == R.id.delete_results) {
resultViewModel.getTrending() resultViewModel.getTrending()
selectedObjects = ArrayList() selectedObjects = ArrayList()
downloadAllFabCoordinator!!.visibility = View.GONE downloadAllFabCoordinator!!.visibility = GONE
downloadFabs!!.visibility = View.GONE downloadFabs!!.visibility = GONE
} }
// } else if (itemId == R.id.cancel_download) { // } else if (itemId == R.id.cancel_download) {
// try { // try {
@ -376,114 +369,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
(topAppBar!!.parent as AppBarLayout).setExpanded(true, true) (topAppBar!!.parent as AppBarLayout).setExpanded(true, true)
} }
private fun parseQuery(resetResults: Boolean) { private fun findVideo(url: String): ResultItem? {
databaseManager = DatabaseManager(context)
infoUtil = InfoUtil(requireContext())
Handler(Looper.getMainLooper()).post { scrollToTop() }
var type = "Search"
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
val m = p.matcher(inputQuery!!)
if (m.find()) {
type = "Video"
if (inputQuery!!.contains("playlist?list=")) {
type = "Playlist"
}
} else if (inputQuery!!.contains("http")) {
type = "Default"
}
Log.e(TAG, "$inputQuery $type")
try {
when (type) {
"Search" -> {
try {
val res = infoUtil!!.search(
inputQuery!!
)
Handler(Looper.getMainLooper()).post {
if (resetResults) resultViewModel.deleteAll()
resultViewModel.insert(res)
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
Handler(Looper.getMainLooper()).post {
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = View.GONE
}
}
}
"Video" -> {
var el: Array<String?> =
inputQuery!!.split("/".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
inputQuery = el[el.size - 1]
if (inputQuery!!.contains("watch?v=")) {
inputQuery = inputQuery!!.substring(8)
}
el = inputQuery!!.split("&".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
inputQuery = el[0]
el = inputQuery!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
inputQuery = el[0]
try {
val v = infoUtil!!.getVideo(inputQuery!!)
val res = ArrayList<ResultItem?>()
res.add(v)
Handler(Looper.getMainLooper()).post {
if (resetResults) resultViewModel.deleteAll()
resultViewModel.insert(res)
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
Handler(Looper.getMainLooper()).post {
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = View.GONE
}
}
}
"Playlist" -> {
inputQuery =
inputQuery!!.split("list=".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()[1]
var nextPageToken = ""
if (resetResults) resultViewModel.deleteAll()
do {
val tmp = infoUtil!!.getPlaylist(inputQuery!!, nextPageToken)
val tmp_vids = tmp.videos
val tmp_token = tmp.nextPageToken
Handler(Looper.getMainLooper()).post {
resultViewModel.insert(tmp_vids)
}
if (tmp_token.isEmpty()) break
if (tmp_token == nextPageToken) break
nextPageToken = tmp_token
} while (true)
}
"Default" -> {
try {
val video = infoUtil!!.getFromYTDL(
inputQuery!!
)
Handler(Looper.getMainLooper()).post {
if (resetResults) resultViewModel.deleteAll()
resultViewModel.insert(video)
}
Handler(Looper.getMainLooper()).post {
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = View.GONE
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
databaseManager!!.close()
}
fun findVideo(url: String): ResultItem? {
for (i in resultsList!!.indices) { for (i in resultsList!!.indices) {
val v = resultsList!![i] val v = resultsList!![i]
if (v!!.url == url) { if (v!!.url == url) {
@ -524,23 +410,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
// } // }
} }
private val isStoragePermissionGranted: Boolean
get() = if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED
) {
true
} else {
downloadQueue = ArrayList()
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
1
)
false
}
fun updateDownloadStatusOnResult(v: ResultItem?, type: String?, downloaded: Boolean) { fun updateDownloadStatusOnResult(v: ResultItem?, type: String?, downloaded: Boolean) {
// if (v != null) { // if (v != null) {
@ -571,19 +440,18 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
val item = resultsList!![position] val item = resultsList!![position]
if (add) selectedObjects!!.add(item!!) else selectedObjects!!.remove(item) if (add) selectedObjects!!.add(item!!) else selectedObjects!!.remove(item)
if (selectedObjects!!.size > 1) { if (selectedObjects!!.size > 1) {
downloadAllFabCoordinator!!.visibility = View.GONE downloadAllFabCoordinator!!.visibility = GONE
downloadFabs!!.visibility = View.VISIBLE downloadFabs!!.visibility = VISIBLE
} else { } else {
downloadFabs!!.visibility = View.GONE downloadFabs!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![1]!!.playlistTitle.isNotEmpty()) { if (resultsList!!.size > 1 && resultsList!![1]!!.playlistTitle.isNotEmpty()) {
downloadAllFabCoordinator!!.visibility = View.VISIBLE downloadAllFabCoordinator!!.visibility = VISIBLE
} }
} }
} }
override fun onClick(v: View) { override fun onClick(v: View) {
val viewIdName: String val viewIdName: String = try {
viewIdName = try {
v.tag.toString() v.tag.toString()
} catch (e: Exception) { } catch (e: Exception) {
"" ""
@ -600,12 +468,12 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
//remove previously selected //remove previously selected
for (i in selectedObjects!!.indices) { for (i in selectedObjects!!.indices) {
val vid = findVideo( val vid = findVideo(
selectedObjects!![i]!!.url selectedObjects!![i].url
) )
homeAdapter!!.notifyItemChanged(resultsList!!.indexOf(vid)) homeAdapter!!.notifyItemChanged(resultsList!!.indexOf(vid))
} }
selectedObjects = ArrayList() selectedObjects = ArrayList()
downloadFabs!!.visibility = View.GONE downloadFabs!!.visibility = GONE
bottomSheet = BottomSheetDialog(fragmentContext!!) bottomSheet = BottomSheetDialog(fragmentContext!!)
bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet!!.setContentView(R.layout.home_download_all_bottom_sheet) bottomSheet!!.setContentView(R.layout.home_download_all_bottom_sheet)
@ -614,13 +482,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
val last = bottomSheet!!.findViewById<TextInputLayout>(R.id.last_textinput) val last = bottomSheet!!.findViewById<TextInputLayout>(R.id.last_textinput)
last!!.editText!!.setText(resultsList!!.size.toString()) last!!.editText!!.setText(resultsList!!.size.toString())
val audio = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_audio_button) val audio = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_audio_button)
audio!!.setOnClickListener { view: View? -> audio!!.setOnClickListener {
val start = first.editText!!.text.toString().toInt() val start = first.editText!!.text.toString().toInt()
val end = last.editText!!.text.toString().toInt() val end = last.editText!!.text.toString().toInt()
initDownloadAll(bottomSheet!!, start, end, "audio") initDownloadAll(bottomSheet!!, start, end, "audio")
} }
val video = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_video_button) val video = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_video_button)
video!!.setOnClickListener { view: View? -> video!!.setOnClickListener {
val start = first.editText!!.text.toString().toInt() val start = first.editText!!.text.toString().toInt()
val end = last.editText!!.text.toString().toInt() val end = last.editText!!.text.toString().toInt()
initDownloadAll(bottomSheet!!, start, end, "video") initDownloadAll(bottomSheet!!, start, end, "video")

@ -8,7 +8,6 @@ import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
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.ResultItem
import java.io.File import java.io.File
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets

@ -5,9 +5,13 @@ import android.content.Context
import android.util.Log import android.util.Log
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DatabaseManager import com.deniscerri.ytdlnis.database.DatabaseManager
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.models.ResultItemWithFormats
import com.google.gson.Gson
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.serialization.json.Json
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -19,7 +23,7 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class InfoUtil(context: Context) { class InfoUtil(context: Context) {
private var items: ArrayList<ResultItem?> private var items: ArrayList<ResultItemWithFormats?>
private var key: String? = null private var key: String? = null
private var useInvidous = false private var useInvidous = false
private var databaseManager: DatabaseManager? = null private var databaseManager: DatabaseManager? = null
@ -52,7 +56,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun search(query: String): ArrayList<ResultItem?> { fun search(query: String): ArrayList<ResultItemWithFormats?> {
init() init()
items = ArrayList() items = ArrayList()
return if (key!!.isEmpty()) { return if (key!!.isEmpty()) {
@ -61,7 +65,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun searchFromKey(query: String): ArrayList<ResultItem?> { fun searchFromKey(query: String): ArrayList<ResultItemWithFormats?> {
//short data //short data
val res = val res =
genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q=$query&maxResults=25&regionCode=$countryCODE&key=$key") genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q=$query&maxResults=25&regionCode=$countryCODE&key=$key")
@ -100,7 +104,7 @@ class InfoUtil(context: Context) {
snippet.put("duration", duration) snippet.put("duration", duration)
fixThumbnail(snippet) fixThumbnail(snippet)
val v = createVideofromJSON(snippet) val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) { if (v == null || v.resultItem.thumb.isEmpty()) {
continue continue
} }
items.add(createVideofromJSON(snippet)) items.add(createVideofromJSON(snippet))
@ -110,7 +114,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun searchFromInvidous(query: String): ArrayList<ResultItem?> { fun searchFromInvidous(query: String): ArrayList<ResultItemWithFormats?> {
val dataArray = genericArrayRequest(invidousURL + "search?q=" + query + "?type=video") val dataArray = genericArrayRequest(invidousURL + "search?q=" + query + "?type=video")
if (dataArray.length() == 0) return getFromYTDL(query) if (dataArray.length() == 0) return getFromYTDL(query)
for (i in 0 until dataArray.length()) { for (i in 0 until dataArray.length()) {
@ -126,7 +130,7 @@ class InfoUtil(context: Context) {
element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url") element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url")
) )
val v = createVideofromInvidiousJSON(element) val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) { if (v == null || v.resultItem.thumb.isEmpty()) {
continue continue
} }
items.add(v) items.add(v)
@ -181,13 +185,13 @@ class InfoUtil(context: Context) {
snippet.put("duration", duration) snippet.put("duration", duration)
fixThumbnail(snippet) fixThumbnail(snippet)
val v = createVideofromJSON(snippet) val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) { if (v == null || v.resultItem.thumb.isEmpty()) {
i++ i++
continue continue
} else { } else {
j++ j++
} }
v.playlistTitle = "YTDLnis" v.resultItem.playlistTitle = "YTDLnis"
items.add(v) items.add(v)
i++ i++
} }
@ -207,8 +211,8 @@ class InfoUtil(context: Context) {
for (i in 0 until vids.length()) { for (i in 0 until vids.length()) {
val element = vids.getJSONObject(i) val element = vids.getJSONObject(i)
val v = createVideofromInvidiousJSON(element) val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue if (v == null || v.resultItem.thumb.isEmpty()) continue
v.playlistTitle = res.getString("title") v.resultItem.playlistTitle = res.getString("title")
items.add(v) items.add(v)
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -218,7 +222,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun getVideo(id: String): ResultItem? { fun getVideo(id: String): ResultItemWithFormats? {
init() init()
if (key!!.isEmpty()) { if (key!!.isEmpty()) {
return if (useInvidous) { return if (useInvidous) {
@ -245,8 +249,8 @@ class InfoUtil(context: Context) {
return createVideofromJSON(res) return createVideofromJSON(res)
} }
private fun createVideofromJSON(obj: JSONObject): ResultItem? { private fun createVideofromJSON(obj: JSONObject): ResultItemWithFormats? {
var video: ResultItem? = null var video: ResultItemWithFormats? = null
try { try {
val id = obj.getString("videoID") val id = obj.getString("videoID")
val title = obj.getString("title").toString() val title = obj.getString("title").toString()
@ -257,23 +261,26 @@ class InfoUtil(context: Context) {
val downloadedAudio = databaseManager!!.checkDownloaded(url, "audio") val downloadedAudio = databaseManager!!.checkDownloaded(url, "audio")
val downloadedVideo = databaseManager!!.checkDownloaded(url, "video") val downloadedVideo = databaseManager!!.checkDownloaded(url, "video")
val isPlaylist = 0 val isPlaylist = 0
video = ResultItem( video = ResultItemWithFormats(
id, ResultItem(
url, url,
title, title,
author, author,
duration, duration,
thumb, thumb,
"youtube" "youtube",
) "",
),
ArrayList()
)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
} }
return video return video
} }
private fun createVideofromInvidiousJSON(obj: JSONObject): ResultItem? { private fun createVideofromInvidiousJSON(obj: JSONObject): ResultItemWithFormats? {
var video: ResultItem? = null var video: ResultItemWithFormats? = null
try { try {
val id = obj.getString("videoId") val id = obj.getString("videoId")
val title = obj.getString("title").toString() val title = obj.getString("title").toString()
@ -281,17 +288,17 @@ class InfoUtil(context: Context) {
val duration = formatIntegerDuration(obj.getInt("lengthSeconds")) val duration = formatIntegerDuration(obj.getInt("lengthSeconds"))
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg" val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
val url = "https://www.youtube.com/watch?v=$id" val url = "https://www.youtube.com/watch?v=$id"
val downloadedAudio = databaseManager!!.checkDownloaded(url, "audio") video = ResultItemWithFormats(
val downloadedVideo = databaseManager!!.checkDownloaded(url, "video") ResultItem(
val isPlaylist = 0 url,
video = ResultItem( title,
id, author,
url, duration,
title, thumb,
author, "youtube",
duration, "",
thumb, ),
"youtube" ArrayList()
) )
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
@ -383,7 +390,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun getTrending(context: Context): ArrayList<ResultItem?> { fun getTrending(context: Context): ArrayList<ResultItemWithFormats?> {
init() init()
items = ArrayList() items = ArrayList()
return if (key!!.isEmpty()) { return if (key!!.isEmpty()) {
@ -392,7 +399,7 @@ class InfoUtil(context: Context) {
} }
@Throws(JSONException::class) @Throws(JSONException::class)
fun getTrendingFromKey(context: Context): ArrayList<ResultItem?> { fun getTrendingFromKey(context: Context): ArrayList<ResultItemWithFormats?> {
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key" val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key"
//short data //short data
val res = genericRequest(url) val res = genericRequest(url)
@ -412,16 +419,16 @@ class InfoUtil(context: Context) {
snippet.put("duration", duration) snippet.put("duration", duration)
fixThumbnail(snippet) fixThumbnail(snippet)
val v = createVideofromJSON(snippet) val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) { if (v == null || v.resultItem.thumb.isEmpty()) {
continue continue
} }
v.playlistTitle = context.getString(R.string.trendingPlaylist) v.resultItem.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v) items.add(v)
} }
return items return items
} }
private fun getTrendingFromInvidous(context: Context): ArrayList<ResultItem?> { private fun getTrendingFromInvidous(context: Context): ArrayList<ResultItemWithFormats?> {
val url = invidousURL + "trending?type=music&region=" + countryCODE val url = invidousURL + "trending?type=music&region=" + countryCODE
val res = genericArrayRequest(url) val res = genericArrayRequest(url)
try { try {
@ -429,8 +436,8 @@ class InfoUtil(context: Context) {
val element = res.getJSONObject(i) val element = res.getJSONObject(i)
if (element.getString("type") != "video") continue if (element.getString("type") != "video") continue
val v = createVideofromInvidiousJSON(element) val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue if (v == null || v.resultItem.thumb.isEmpty()) continue
v.playlistTitle = context.getString(R.string.trendingPlaylist) v.resultItem.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v) items.add(v)
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -439,7 +446,7 @@ class InfoUtil(context: Context) {
return items return items
} }
fun getFromYTDL(query: String): ArrayList<ResultItem?> { fun getFromYTDL(query: String): ArrayList<ResultItemWithFormats?> {
init() init()
items = ArrayList() items = ArrayList()
try { try {
@ -488,8 +495,16 @@ class InfoUtil(context: Context) {
} }
val website = if (jsonObject.has("ie_key")) jsonObject.getString("ie_key") else jsonObject.getString("extractor") val website = if (jsonObject.has("ie_key")) jsonObject.getString("ie_key") else jsonObject.getString("extractor")
var playlistTitle: String? = "" var playlistTitle: String? = ""
if (jsonObject.has("playlist")) playlistTitle = jsonObject.getString("playlist") if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
items.add( val formatsInJSON = if (jsonObject.has("formats")) jsonObject.getJSONArray("formats") else null
val formats : ArrayList<Format> = ArrayList()
if (formatsInJSON != null) {
for (f in 0 until formatsInJSON.length()){
val format = formatsInJSON.getJSONObject(f).toString()
formats.add(Gson().fromJson(format, Format::class.java))
}
}
items.add(ResultItemWithFormats(
ResultItem( ResultItem(
url, url,
title, title,
@ -498,6 +513,8 @@ class InfoUtil(context: Context) {
thumb!!, thumb!!,
website, website,
playlistTitle!! playlistTitle!!
),
formats
) )
) )
} }
@ -507,6 +524,10 @@ class InfoUtil(context: Context) {
return items return items
} }
private val jsonFormat = Json {
ignoreUnknownKeys = true
}
fun getSearchSuggestions(query: String): ArrayList<String> { fun getSearchSuggestions(query: String): ArrayList<String> {
if (!useInvidous) return ArrayList() if (!useInvidous) return ArrayList()
val url = invidousURL + "search/suggestions?q=" + query val url = invidousURL + "search/suggestions?q=" + query
@ -580,12 +601,12 @@ class InfoUtil(context: Context) {
class PlaylistTuple internal constructor( class PlaylistTuple internal constructor(
var nextPageToken: String, var nextPageToken: String,
var videos: ArrayList<ResultItem?> var videos: ArrayList<ResultItemWithFormats?>
) )
companion object { companion object {
private const val TAG = "API MANAGER" private const val TAG = "API MANAGER"
private const val invidousURL = "https://invidious.baczek.me/api/v1/" private const val invidousURL = "https://invidious.baczek.me/api/vvvvvv/"
private var countryCODE: String? = null private var countryCODE: String? = null
} }
} }

@ -41,7 +41,7 @@ class DownloadWorker(
val commandTemplateDao = dbManager.commandTemplateDao val commandTemplateDao = dbManager.commandTemplateDao
val historyDao = dbManager.historyDao val historyDao = dbManager.historyDao
queue = repository.getQueuedDownloads() as MutableList<DownloadItem> queue = repository.getDownloadsByWorkID(workID) as MutableList<DownloadItem>
val intent = Intent(context, MainActivity::class.java) val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)

@ -15,8 +15,8 @@
tools:ignore="MissingConstraints"> tools:ignore="MissingConstraints">
<ImageView <ImageView
android:layout_width="200sp" android:layout_width="150sp"
android:layout_height="150sp" android:layout_height="100sp"
android:layout_gravity="center" android:layout_gravity="center"
android:scaleType="centerInside" android:scaleType="centerInside"
android:background="@drawable/ic_no_results" /> android:background="@drawable/ic_no_results" />

@ -43,51 +43,56 @@
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<androidx.recyclerview.widget.RecyclerView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/recyclerViewHome"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:clipToPadding="false"
android:paddingBottom="150dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_results_framelayout"
android:layout_marginTop="?attr/actionBarSize" android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical"> android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
<LinearLayout
android:id="@+id/shimmer_linear_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/recyclerViewHome"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:scrollbars="vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:clipToPadding="false"
android:paddingBottom="100dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_results_framelayout"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/result_card_shimmer" /> <LinearLayout
android:id="@+id/shimmer_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<include layout="@layout/result_card_shimmer" /> <include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" /> <include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" /> <include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" /> <include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout> </LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_fabs" android:id="@+id/home_fabs"

@ -6,36 +6,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/preference_coordinatorLayout"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/more"
app:menu="@menu/empty_menu"
/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp" android:layout_height="200dp"
android:layout_marginTop="?attr/actionBarSize"
android:gravity="top" android:gravity="top"
android:orientation="vertical" android:orientation="vertical"
tools:ignore="MissingConstraints"> tools:ignore="MissingConstraints">

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/history_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/result_card_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/result_relative_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:paddingBottom="10dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/result_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="10dp"
app:cardElevation="10dp"
app:cardMaxElevation="12dp"
app:cardBackgroundColor="@color/black"
app:cardPreventCornerOverlap="true"
android:checkable="true"
app:strokeWidth="0dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/result_image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:dividerPadding="5dp"
android:orientation="vertical">
<TextView
android:id="@+id/result_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:textSize="17sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="2"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
<TextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:paddingStart="10dp"
android:textSize="12sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
</LinearLayout>
<TextView
android:id="@+id/duration"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:paddingEnd="10dp"
android:paddingBottom="20dp"
android:paddingStart="10dp"
android:textSize="12sp"
android:textColor="@color/white"
android:textStyle="bold"
android:shadowRadius="1.5"
android:shadowDx="4"
android:shadowDy="4"
android:shadowColor="@color/black" />
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<com.google.android.material.tabs.TabItem
android:icon="@drawable/ic_music"
android:text="@string/audio"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
<com.google.android.material.tabs.TabItem
android:icon="@drawable/ic_video"
android:text="@string/video"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
<com.google.android.material.tabs.TabItem
android:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
android:text="@string/run_command"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager.widget.ViewPager
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
</FrameLayout>

@ -2,6 +2,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference <Preference
app:enabled="false"
android:layout="@layout/fragment_more"/> android:layout="@layout/fragment_more"/>
<Preference <Preference

@ -12,10 +12,10 @@ buildscript {
versionCode = versionMajor * 100000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionCode = versionMajor * 100000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}" versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
// dependency versions // dependency versions
appCompatVer = '1.5.1' appCompatVer = '1.6.0'
junitVer = '4.13.2' junitVer = '4.13.2'
androidJunitVer = '1.1.1' androidJunitVer = '1.1.5'
espressoVer = '3.2.0' espressoVer = '3.5.1'
jacksonVer = '2.9.8' jacksonVer = '2.9.8'
// supports java 1.6 // supports java 1.6
commonsIoVer = '2.5' commonsIoVer = '2.5'

Loading…
Cancel
Save