This commit is contained in:
Denis Çerri 2023-01-08 16:50:57 +01:00
parent 51ff22abcd
commit b330a5ba08
No known key found for this signature in database
GPG key ID: 96B3554AF5B193EE
30 changed files with 1016 additions and 762 deletions

View file

@ -113,7 +113,7 @@ dependencies {
implementation "com.devbrackets.android:exomedia:4.3.0"
implementation("androidx.cardview:cardview:1.0.0")
implementation("com.squareup.picasso:picasso:2.8")
implementation("com.squareup.picasso:picasso:2.71828")
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
@ -131,4 +131,10 @@ dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer"
implementation "com.squareup.retrofit2:retrofit:$retrofitVer"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVer"
implementation "org.kodein.di:kodein-di-generic-jvm:6.4.0"
implementation "org.kodein.di:kodein-di-framework-android-x:$kodeinVer"
}

View file

@ -39,7 +39,7 @@
</intent-filter>
</activity>
<activity
android:name=".page.settings.SettingsActivity"
android:name=".ui.settings.SettingsActivity"
android:parentActivityName=".MainActivity"
android:exported="true">
<intent-filter>
@ -48,7 +48,7 @@
</intent-filter>
</activity>
<activity
android:name=".page.CustomCommandActivity"
android:name=".ui.CustomCommandActivity"
android:theme="@style/AppDefaultTheme"
android:parentActivityName=".MainActivity"
android:label="@string/run_command"

View file

@ -5,13 +5,10 @@ import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.SystemClock
import android.provider.DocumentsContract
import android.provider.Settings.Global
import android.util.Log
import android.widget.Toast
import androidx.work.ExistingWorkPolicy
@ -19,7 +16,7 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.database.Video
import com.deniscerri.ytdlnis.page.CustomCommandActivity
import com.deniscerri.ytdlnis.ui.CustomCommandActivity
import com.deniscerri.ytdlnis.service.DownloadInfo
import com.deniscerri.ytdlnis.service.IDownloaderListener
import com.deniscerri.ytdlnis.service.IDownloaderService
@ -29,7 +26,6 @@ import com.deniscerri.ytdlnis.work.FileTransferWorker
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.originDir
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.title
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.downLocation
import com.yausername.youtubedl_android.DownloadProgressCallback
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import com.yausername.youtubedl_android.YoutubeDLResponse
@ -37,13 +33,11 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern
import kotlin.jvm.functions.Function3
class DownloaderService : Service() {

View file

@ -21,10 +21,10 @@ import com.deniscerri.ytdlnis.DownloaderService.LocalBinder
import com.deniscerri.ytdlnis.database.DatabaseManager
import com.deniscerri.ytdlnis.database.Video
import com.deniscerri.ytdlnis.databinding.ActivityMainBinding
import com.deniscerri.ytdlnis.page.DownloadsFragment
import com.deniscerri.ytdlnis.page.HomeFragment
import com.deniscerri.ytdlnis.page.MoreFragment
import com.deniscerri.ytdlnis.page.settings.SettingsActivity
import com.deniscerri.ytdlnis.ui.DownloadsFragment
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.MoreFragment
import com.deniscerri.ytdlnis.ui.settings.SettingsActivity
import com.deniscerri.ytdlnis.service.IDownloaderListener
import com.deniscerri.ytdlnis.service.IDownloaderService
import com.deniscerri.ytdlnis.util.UpdateUtil

View file

@ -0,0 +1,42 @@
package com.deniscerri.ytdlnis.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.HistoryDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem
@Database(
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class],
version = 1,
autoMigrations = []
)
abstract class DBManager : RoomDatabase(){
abstract val resultDao : ResultDao
abstract val historyDao : HistoryDao
abstract val downloadDao : DownloadDao
companion object {
//prevents multiple instances of db getting created at the same time
@Volatile
private var instance : DBManager? = null
//if its not null return it, otherwise create db
fun getInstance(context: Context) : DBManager {
return instance ?: synchronized(this){
val dbInstance = Room.databaseBuilder(
context.applicationContext,
DBManager::class.java,
"YTDLnisDatabase"
).build()
instance = dbInstance
dbInstance
}
}
}
}

View file

@ -0,0 +1,29 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.util.FileUtil
@Dao
interface DownloadDao {
@Query("SELECT * FROM downloads")
fun getAllDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
suspend fun getDownloadById(id: Int) : DownloadItem
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: DownloadItem)
@Query("DELETE FROM downloads")
suspend fun deleteAll()
@Delete
suspend fun delete(item: DownloadItem)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: DownloadItem)
}

View file

@ -0,0 +1,37 @@
package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.util.FileUtil
@Dao
interface HistoryDao {
@Query("SELECT * FROM history WHERE title LIKE '%'||:query||'%' AND type LIKE '%'||:format||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN id END ASC," +
"CASE WHEN :sort = 'DESC' THEN id END DESC," +
"CASE WHEN :sort = '' THEN id END DESC ")
suspend fun getHistory(query : String, format : String, site : String, sort : String) : LiveData<List<HistoryItem>>
@Query("SELECT * FROM history")
fun getAllHistory() : LiveData<List<HistoryItem>>
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
suspend fun getHistoryItem(id: Int) : HistoryItem
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: HistoryItem)
@Delete
suspend fun delete(item: HistoryItem)
@Query("DELETE FROM history")
suspend fun deleteAll()
@Query("DELETE FROM history WHERE id > (SELECT MIN(h.id) FROM history h WHERE h.url = history.url AND h.type = history.type)")
suspend fun deleteDuplicates()
@Update
suspend fun update(item: HistoryItem)
}

View file

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

View file

@ -0,0 +1,18 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "downloads")
data class DownloadItem(
@PrimaryKey(autoGenerate = true)
val id: Int,
val url: String,
val title: String,
val author: String,
val duration: String,
val thumb: String,
val type: String,
val downloadPath: String,
val website: String
)

View file

@ -0,0 +1,20 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "history")
data class HistoryItem(
@PrimaryKey(autoGenerate = true)
val id: Int,
val url: String,
val title: String,
val author: String,
val duration: String,
val thumb: String,
val type: String,
val time: String,
val downloadPath: String,
val website: String
//val isQueuedDownload: Int
)

View file

@ -0,0 +1,17 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "results")
data class ResultItem(
@PrimaryKey(autoGenerate = true)
val id: Int,
val url: String,
val title: String,
val author: String,
val duration: String,
val thumb: String,
val website: String,
val playlistTitle: String
)

View file

@ -0,0 +1,23 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
class DownloadRepository(private val downloadDao: DownloadDao) {
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads()
suspend fun insert(item: DownloadItem){
downloadDao.insert(item)
}
suspend fun delete(item: DownloadItem){
downloadDao.delete(item)
}
suspend fun update(item: DownloadItem){
downloadDao.update(item)
}
}

View file

@ -0,0 +1,50 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
import androidx.room.Transaction
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.HistoryDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.util.FileUtil
class HistoryRepository(private val historyDao: HistoryDao) {
val allHistory : LiveData<List<HistoryItem>> = historyDao.getAllHistory()
suspend fun getItem(id: Int) : HistoryItem?{
return historyDao.getHistoryItem(id)
}
suspend fun insert(item: HistoryItem){
historyDao.insert(item)
}
suspend fun delete(item: HistoryItem){
historyDao.delete(item)
}
suspend fun deleteAll(){
historyDao.deleteAll()
}
suspend fun deleteDuplicates(){
historyDao.deleteDuplicates()
}
suspend fun update(item: HistoryItem){
historyDao.update(item)
}
suspend fun clearDeletedHistory(){
val fileUtil = FileUtil()
allHistory.value?.forEach { item ->
if (!fileUtil.exists(item.downloadPath)){
delete(item)
}
}
}
suspend fun checkDownloaded(){
}
}

View file

@ -0,0 +1,27 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
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.ResultItem
class ResultRepository(private val resultDao: ResultDao) {
val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
suspend fun insert(items: List<ResultItem>){
items.forEach {
resultDao.insert(it)
}
}
suspend fun deleteAll(){
resultDao.deleteAll()
}
suspend fun update(item: ResultItem){
resultDao.update(item)
}
}

View file

@ -0,0 +1,34 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
private val repository : DownloadRepository
private val allDownloads : LiveData<List<DownloadItem>>
init {
val dao = DBManager.getInstance(application).downloadDao
repository = DownloadRepository(dao)
allDownloads = repository.allDownloads
}
fun insertDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
repository.insert(item)
}
fun deleteDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO) {
repository.delete(item)
}
fun updateDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
repository.update(item);
}
}

View file

@ -0,0 +1,48 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
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.repository.HistoryRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
private val repository : HistoryRepository
private val allHistory : LiveData<List<HistoryItem>>
init {
val dao = DBManager.getInstance(application).historyDao
repository = HistoryRepository(dao)
allHistory = repository.allHistory
}
fun insert(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){
repository.insert(item)
}
fun delete(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){
repository.delete(item)
}
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
}
fun deleteDuplicates() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteDuplicates()
}
fun update(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){
repository.update(item);
}
fun clearDeleted() = viewModelScope.launch(Dispatchers.IO) {
repository.clearDeletedHistory()
}
}

View file

@ -0,0 +1,36 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.ResultRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ResultViewModel(application: Application) : AndroidViewModel(application) {
private val repository : ResultRepository
private val allResults : LiveData<List<ResultItem>>
init {
val dao = DBManager.getInstance(application).resultDao
repository = ResultRepository(dao)
allResults = repository.allResults
}
fun insert(items: List<ResultItem>) = viewModelScope.launch(Dispatchers.IO){
repository.insert(items)
}
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll()
}
fun update(item: ResultItem) = viewModelScope.launch(Dispatchers.IO){
repository.update(item);
}
}

View file

@ -1,738 +0,0 @@
package com.deniscerri.ytdlnis.page;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.deniscerri.ytdlnis.MainActivity;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.adapter.DownloadsRecyclerViewAdapter;
import com.deniscerri.ytdlnis.database.DatabaseManager;
import com.deniscerri.ytdlnis.database.Video;
import com.deniscerri.ytdlnis.service.DownloadInfo;
import com.deniscerri.ytdlnis.service.IDownloaderListener;
import com.deniscerri.ytdlnis.util.FileUtil;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* A fragment representing a list of Items.
*/
public class DownloadsFragment extends Fragment implements DownloadsRecyclerViewAdapter.OnItemClickListener, View.OnClickListener, View.OnLongClickListener{
private boolean downloading = false;
private View fragmentView;
private DatabaseManager databaseManager;
Context context;
Activity activity;
MainActivity mainActivity;
Context fragmentContext;
private LayoutInflater layoutinflater;
private ShimmerFrameLayout shimmerCards;
private MaterialToolbar topAppBar;
private RecyclerView recyclerView;
private DownloadsRecyclerViewAdapter downloadsRecyclerViewAdapter;
private BottomSheetDialog bottomSheet;
private BottomSheetDialog sortSheet;
private Handler uiHandler;
private RelativeLayout no_results;
private LinearLayout selectionChips;
private ChipGroup websiteGroup;
private ArrayList<Video> downloadsObjects;
public ArrayList<Video> selectedObjects;
private LinearProgressIndicator progressBar;
private ExtendedFloatingActionButton deleteFab;
private FileUtil fileUtil;
private String format = "";
private String website = "";
private String sort = "DESC";
private String searchQuery = "";
private static final String TAG = "downloadsFragment";
public IDownloaderListener listener = new IDownloaderListener() {
public void onDownloadStart(DownloadInfo downloadInfo) {
try{
if(downloadInfo != null){
Video v = downloadInfo.getVideo();
progressBar = fragmentView.findViewWithTag(v.getURL()+v.getDownloadedType()+"##progress");
}
}catch(Exception ignored){}
downloading = true;
}
public void onDownloadProgress(DownloadInfo info) {
activity.runOnUiThread(() -> {
try{
int progress = info.getProgress();
Video v = info.getVideo();
if (progress > 0) {
progressBar = fragmentView.findViewWithTag(v.getURL()+v.getDownloadedType()+"##progress");
progressBar.setProgressCompat(progress, true);
}
}catch(Exception ignored){
}
});
}
public void onDownloadError(DownloadInfo info){
try{
Video item = info.getVideo();
Log.e(TAG, item.toString());
String url = item.getURL();
String type = info.getDownloadType();
Video v = findVideo(url, type);
Log.e(TAG, v.toString());
databaseManager = new DatabaseManager(context);
databaseManager.clearHistoryItem(v, false);
int position = downloadsObjects.indexOf(v);
downloadsRecyclerViewAdapter.notifyItemRemoved(position);
downloadsObjects.remove(v);
if (downloadsObjects.isEmpty()) initCards();
downloading = false;
}catch(Exception e){
e.printStackTrace();
}
}
public void onDownloadEnd(DownloadInfo downloadInfo) {
Video item = downloadInfo.getVideo();
String url = item.getURL();
String type = downloadInfo.getDownloadType();
String path = downloadInfo.getDownloadPath();
item = findVideo(url, type);
int index = downloadsObjects.indexOf(item);
try{
item.setDownloadedType(type);
item.setDownloadPath(path);
Calendar cal = Calendar.getInstance();
Date date = new Date();
cal.setTime(date);
int day = cal.get(Calendar.DAY_OF_MONTH);
String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
int year = cal.get(Calendar.YEAR);
DateFormat formatter = new SimpleDateFormat("HH:mm", Locale.getDefault());
String time = formatter.format(date);
String downloadedTime = day + " " + month + " " + year + " " + time;
String downloadPath = "";
try{
downloadPath = fileUtil.scanMedia(item, context);
}catch(Exception e){
e.printStackTrace();
}
databaseManager = new DatabaseManager(context);
//TODO observe file transfer worker here and then update, but first you need to turn this to kotlin and implement proper mvvm
try {
item.setDownloadedTime(downloadedTime);
item.setDownloadPath(downloadPath);
item.setQueuedDownload(false);
databaseManager.updateHistoryItem(item);
databaseManager.close();
downloadsRecyclerViewAdapter.notifyItemChanged(index);
} catch (Exception ignored) {}
downloading = false;
}catch(Exception ignored){}
}
@Override
public void onDownloadCancel(DownloadInfo info) {
Video v = info.getVideo();
v = findVideo(v.getURL(), v.getDownloadedType());
try {
databaseManager = new DatabaseManager(context);
databaseManager.clearHistoryItem(v,false);
databaseManager.close();
downloadsRecyclerViewAdapter.notifyItemRemoved(downloadsObjects.indexOf(v));
downloadsObjects.remove(v);
if (downloadsObjects.isEmpty()) initCards();
downloading = false;
}catch (Exception ignored){}
}
@Override
public void onDownloadCancelAll(DownloadInfo downloadInfo){
try {
databaseManager = new DatabaseManager(context);
databaseManager.clearDownloadingHistory();
databaseManager.close();
initCards();
downloading = false;
}catch (Exception e){
e.printStackTrace();
}
}
public void onDownloadServiceEnd() {}
};
public void setDownloading(boolean d){
downloading = d;
}
public DownloadsFragment() {
}
@SuppressWarnings("unused")
public static DownloadsFragment newInstance() {
DownloadsFragment fragment = new DownloadsFragment();
Bundle args = new Bundle();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentView = inflater.inflate(R.layout.fragment_downloads, container, false);
context = fragmentView.getContext().getApplicationContext();
activity = getActivity();
mainActivity = (MainActivity) activity;
fragmentContext = fragmentView.getContext();
layoutinflater = LayoutInflater.from(context);
shimmerCards = fragmentView.findViewById(R.id.shimmer_downloads_framelayout);
topAppBar = fragmentView.findViewById(R.id.downloads_toolbar);
no_results = fragmentView.findViewById(R.id.downloads_no_results);
selectionChips = fragmentView.findViewById(R.id.downloads_selection_chips);
websiteGroup = fragmentView.findViewById(R.id.website_chip_group);
deleteFab = fragmentView.findViewById(R.id.delete_selected_fab);
fileUtil = new FileUtil();
deleteFab.setTag("deleteSelected");
deleteFab.setOnClickListener(this);
uiHandler = new Handler(Looper.getMainLooper());
downloadsObjects = new ArrayList<>();
selectedObjects = new ArrayList<>();
downloading = mainActivity.isDownloadServiceRunning();
recyclerView = fragmentView.findViewById(R.id.recycler_view_downloads);
downloadsRecyclerViewAdapter = new DownloadsRecyclerViewAdapter(downloadsObjects, this, activity);
recyclerView.setAdapter(downloadsRecyclerViewAdapter);
recyclerView.setNestedScrollingEnabled(false);
initMenu();
initChips();
initCards();
return fragmentView;
}
public void initCards(){
shimmerCards.startShimmer();
shimmerCards.setVisibility(View.VISIBLE);
downloadsRecyclerViewAdapter.clear();
no_results.setVisibility(View.GONE);
selectionChips.setVisibility(View.VISIBLE);
databaseManager = new DatabaseManager(context);
try{
Thread thread = new Thread(() -> {
if (!downloading) databaseManager.clearDownloadingHistory();
downloadsObjects = databaseManager.getHistory("", format,website,sort);
uiHandler.post(() -> {
downloadsRecyclerViewAdapter.add(downloadsObjects);
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
updateWebsiteChips();
});
if(downloadsObjects.size() == 0){
uiHandler.post(() -> {
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
websiteGroup.removeAllViews();
});
}
databaseManager.close();
});
thread.start();
}catch(Exception e){
Log.e(TAG, e.toString());
}
}
public void scrollToTop(){
recyclerView.scrollToPosition(0);
new Handler(Looper.getMainLooper()).post(() -> ((AppBarLayout) topAppBar.getParent()).setExpanded(true, true));
}
private void initMenu(){
MenuItem.OnActionExpandListener onActionExpandListener = new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
return true;
}
};
MaterialToolbar toolbar = fragmentView.findViewById(R.id.downloads_toolbar);
topAppBar.getMenu().findItem(R.id.search_downloads).setOnActionExpandListener(onActionExpandListener);
SearchView searchView = (SearchView) topAppBar.getMenu().findItem(R.id.search_downloads).getActionView();
searchView.setInputType(InputType.TYPE_CLASS_TEXT);
searchView.setQueryHint(getString(R.string.search_history_hint));
databaseManager = new DatabaseManager(context);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchQuery = query;
topAppBar.getMenu().findItem(R.id.search_downloads).collapseActionView();
downloadsObjects = databaseManager.getHistory(query, format,website,sort);
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
if(downloadsObjects.size() == 0) {
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
websiteGroup.removeAllViews();
}
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
searchQuery = newText;
downloadsObjects = databaseManager.getHistory(newText, format,website,sort);
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
if (downloadsObjects.size() == 0) {
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
}else{
no_results.setVisibility(View.GONE);
selectionChips.setVisibility(View.VISIBLE);
}
return true;
}
});
topAppBar.setOnClickListener(view -> scrollToTop());
topAppBar.setOnMenuItemClickListener((MenuItem m) -> {
int itemID = m.getItemId();
if(itemID == R.id.remove_downloads) {
if (downloadsObjects.size() == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show();
return true;
}
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage(getString(R.string.confirm_delete_history_desc));
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
databaseManager.clearHistory();
downloadsRecyclerViewAdapter.clear();
downloadsObjects.clear();
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
websiteGroup.removeAllViews();
});
delete_dialog.show();
}else if(itemID == R.id.remove_deleted_downloads){
if (downloadsObjects.size() == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show();
return true;
}
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage(getString(R.string.confirm_delete_history_deleted_desc));
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
databaseManager.clearDeletedHistory();
initCards();
});
delete_dialog.show();
}else if (itemID == R.id.remove_duplicates){
if (downloadsObjects.size() == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show();
return true;
}
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage(getString(R.string.confirm_delete_history_duplicates_desc));
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
databaseManager.clearDuplicateHistory();
initCards();
});
delete_dialog.show();
}else if (itemID == R.id.remove_downloading){
if (downloadsObjects.size() == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show();
return true;
}
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage(getString(R.string.confirm_delete_downloading_desc));
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
databaseManager.clearDownloadingHistory();
mainActivity.cancelDownloadService();
initCards();
});
delete_dialog.show();
}
return true;
});
}
private void initChips(){
//sort and history/downloading switch
Chip sortChip = fragmentView.findViewById(R.id.sort_chip);
sortChip.setOnClickListener(view -> {
sortSheet = new BottomSheetDialog(fragmentContext);
sortSheet.requestWindowFeature(Window.FEATURE_NO_TITLE);
sortSheet.setContentView(R.layout.downloads_sort_sheet);
TextView newest = sortSheet.findViewById(R.id.newest);
TextView oldest = sortSheet.findViewById(R.id.oldest);
newest.setOnClickListener(view1 -> {
sort = "DESC";
downloadsObjects = databaseManager.getHistory(searchQuery, format,website,sort);
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
sortSheet.cancel();
});
oldest.setOnClickListener(view1 -> {
sort = "ASC";
downloadsObjects = databaseManager.getHistory(searchQuery, format,website,sort);
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
sortSheet.cancel();
});
TextView cancel = sortSheet.findViewById(R.id.cancel);
cancel.setOnClickListener(view1 -> {
sortSheet.cancel();
});
sortSheet.show();
sortSheet.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
});
//format
Chip audio = fragmentView.findViewById(R.id.audio_chip);
audio.setOnClickListener(view -> {
if (audio.isChecked()) {
format = "audio";
if (recyclerView.getVisibility() == View.GONE){
}
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
audio.setChecked(true);
}else {
format = "";
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
audio.setChecked(false);
}
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
});
Chip video = fragmentView.findViewById(R.id.video_chip);
video.setOnClickListener(view -> {
if (video.isChecked()) {
format = "video";
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
video.setChecked(true);
}else {
format = "";
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
video.setChecked(false);
}
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
});
}
private void updateWebsiteChips(){
websiteGroup.removeAllViews();
ArrayList<String> websites = downloadsRecyclerViewAdapter.getWebsites();
for (int i = 0; i < websites.size(); i++){
String w = websites.get(i);
Chip tmp = (Chip) layoutinflater.inflate(R.layout.filter_chip, websiteGroup, false);
tmp.setText(w);
tmp.setId(i);
tmp.setOnClickListener(view -> {
if (tmp.isChecked()){
website = (String) tmp.getText();
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
websiteGroup.check(view.getId());
}else{
website = "";
downloadsObjects = databaseManager.getHistory(searchQuery,format,website,sort);
websiteGroup.clearCheck();
}
downloadsRecyclerViewAdapter.clear();
downloadsRecyclerViewAdapter.add(downloadsObjects);
});
websiteGroup.addView(tmp);
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if(id == R.id.bottomsheet_remove_button){
removedownloadsItem((Integer) v.getTag());
}else if(id == R.id.bottom_sheet_link){
openLinkIntent((Integer) v.getTag());
}else if(id == R.id.bottomsheet_open_file_button){
openFileIntent((Integer) v.getTag());
}else if (id == R.id.delete_selected_fab){
removeSelectedItems();
}
}
@Override
public boolean onLongClick(View v) {
int id = v.getId();
if(id == R.id.bottom_sheet_link){
copyLinkToClipBoard((Integer) v.getTag());
return true;
}
return false;
}
private void removeSelectedItems(){
if(bottomSheet != null) bottomSheet.hide();
final boolean[] delete_file = {false};
databaseManager = new DatabaseManager(context);
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items));
delete_dialog.setMultiChoiceItems(new String[]{getString(R.string.delete_files_too)}, new boolean[]{false}, (dialogInterface, i, b) -> delete_file[0] = b);
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
for (int j = 0; j < selectedObjects.size(); j++){
Video v = selectedObjects.get(j);
int position = downloadsObjects.indexOf(v);
downloadsObjects.remove(v);
downloadsRecyclerViewAdapter.remove(position);
databaseManager.clearHistoryItem(v, delete_file[0]);
}
updateWebsiteChips();
databaseManager.close();
selectedObjects = new ArrayList<>();
downloadsRecyclerViewAdapter.clearCheckedVideos();
deleteFab.setVisibility(View.GONE);
if(downloadsObjects.size() == 0){
uiHandler.post(() -> {
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
websiteGroup.removeAllViews();
});
}
});
delete_dialog.show();
}
private void removedownloadsItem(int position){
if(bottomSheet != null) bottomSheet.hide();
final boolean[] delete_file = {false};
databaseManager = new DatabaseManager(context);
Video v = downloadsObjects.get(position);
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(fragmentContext);
delete_dialog.setTitle(getString(R.string.you_are_going_to_delete) + " \""+v.getTitle()+"\"!");
delete_dialog.setMultiChoiceItems(new String[]{getString(R.string.delete_file_too)}, new boolean[]{false}, (dialogInterface, i, b) -> delete_file[0] = b);
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
downloadsObjects.remove(position);
downloadsRecyclerViewAdapter.remove(position);
updateWebsiteChips();
databaseManager.clearHistoryItem(v, delete_file[0]);
databaseManager.close();
if(downloadsObjects.size() == 0){
uiHandler.post(() -> {
no_results.setVisibility(View.VISIBLE);
selectionChips.setVisibility(View.GONE);
websiteGroup.removeAllViews();
});
}
});
delete_dialog.show();
}
private void copyLinkToClipBoard(int position){
String url = downloadsObjects.get(position).getURL();
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(getString(R.string.url), url);
clipboard.setPrimaryClip(clip);
if(bottomSheet != null) bottomSheet.hide();
Toast.makeText(context, getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT).show();
}
private void openLinkIntent(int position){
String url =downloadsObjects.get(position).getURL();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
if(bottomSheet != null) bottomSheet.hide();
startActivity(i);
}
private void openFileIntent(int position){
String downloadPath =downloadsObjects.get(position).getDownloadPath();
File file = new File(downloadPath);
Uri uri = FileProvider.getUriForFile(fragmentContext, fragmentContext.getPackageName() + ".fileprovider", file);
String mime = mainActivity.getContentResolver().getType(uri);
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(uri, mime);
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
}
@Override
public void onCardClick(int position, boolean isFilePresent) {
bottomSheet = new BottomSheetDialog(fragmentContext);
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE);
bottomSheet.setContentView(R.layout.downloads_bottom_sheet);
Video video = downloadsObjects.get(position);
TextView title = bottomSheet.findViewById(R.id.bottom_sheet_title);
title.setText(video.getTitle());
TextView author = bottomSheet.findViewById(R.id.bottom_sheet_author);
author.setText(video.getAuthor());
Button link = bottomSheet.findViewById(R.id.bottom_sheet_link);
String url = video.getURL();
link.setText(url);
link.setTag(position);
link.setOnClickListener(this);
link.setOnLongClickListener(this);
Button remove = bottomSheet.findViewById(R.id.bottomsheet_remove_button);
remove.setTag(position);
remove.setOnClickListener(this);
Button openFile = bottomSheet.findViewById(R.id.bottomsheet_open_file_button);
openFile.setTag(position);
openFile.setOnClickListener(this);
if (!isFilePresent) openFile.setVisibility(View.GONE);
bottomSheet.show();
bottomSheet.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
public void onCardSelect(int position, boolean add) {
Video video = downloadsObjects.get(position);
if (add) selectedObjects.add(video); else selectedObjects.remove(video);
if(selectedObjects.size() > 1){
deleteFab.setVisibility(View.VISIBLE);
} else {
deleteFab.setVisibility(View.GONE);
}
}
@Override
public void onButtonClick(int position) {
Video vid = downloadsObjects.get(position);
try {
mainActivity.removeItemFromDownloadQueue(vid, vid.getDownloadedType());
}catch (Exception e){
DownloadInfo info = new DownloadInfo();
info.setVideo(vid);
info.setDownloadType(vid.getDownloadedType());
listener.onDownloadCancel(info);
}
}
public Video findVideo(String url, String type) {
for (int i = 0; i < downloadsObjects.size(); i++) {
Video v = downloadsObjects.get(i);
if ((v.getURL()).equals(url) && v.getDownloadedType().equals(type) && v.isQueuedDownload()) {
return v;
}
}
return null;
}
}

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.page;
package com.deniscerri.ytdlnis.ui;
import android.Manifest;
import android.content.ComponentName;
@ -19,7 +19,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.DownloaderService;
import com.deniscerri.ytdlnis.database.Video;
import com.deniscerri.ytdlnis.service.DownloadInfo;
import com.deniscerri.ytdlnis.service.IDownloaderListener;
import com.deniscerri.ytdlnis.service.IDownloaderService;
@ -27,7 +26,6 @@ import com.deniscerri.ytdlnis.util.NotificationUtil;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import java.lang.reflect.Array;
import java.util.ArrayList;
public class CustomCommandActivity extends AppCompatActivity {

View file

@ -0,0 +1,554 @@
package com.deniscerri.ytdlnis.ui
import android.app.Activity
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.InputType
import android.util.Log
import android.view.*
import android.view.View.OnLongClickListener
import android.widget.*
import androidx.appcompat.widget.SearchView
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.DownloadsRecyclerViewAdapter
import com.deniscerri.ytdlnis.database.DatabaseManager
import com.deniscerri.ytdlnis.database.Video
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.service.DownloadInfo
import com.deniscerri.ytdlnis.util.FileUtil
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import java.io.File
/**
* A fragment representing a list of Items.
*/
class DownloadsFragment : Fragment(), DownloadsRecyclerViewAdapter.OnItemClickListener,
View.OnClickListener, OnLongClickListener {
private lateinit var historyViewModel : HistoryViewModel
private var downloading = false
private var fragmentView: View? = null
private var databaseManager: DatabaseManager? = null
var activity: Activity? = null
var mainActivity: MainActivity? = null
var fragmentContext: Context? = null
private var layoutinflater: LayoutInflater? = null
private var shimmerCards: ShimmerFrameLayout? = null
private var topAppBar: MaterialToolbar? = null
private var recyclerView: RecyclerView? = null
private var downloadsRecyclerViewAdapter: DownloadsRecyclerViewAdapter? = null
private var bottomSheet: BottomSheetDialog? = null
private var sortSheet: BottomSheetDialog? = null
private var uiHandler: Handler? = null
private var no_results: RelativeLayout? = null
private var selectionChips: LinearLayout? = null
private var websiteGroup: ChipGroup? = null
private var downloadsObjects: ArrayList<Video?>? = null
var selectedObjects: ArrayList<Video?>? = null
private var progressBar: LinearProgressIndicator? = null
private var deleteFab: ExtendedFloatingActionButton? = null
private var fileUtil: FileUtil? = null
private var format = ""
private var website = ""
private var sort = "DESC"
private var searchQuery = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.fragment_downloads, container, false)
activity = getActivity()
mainActivity = activity as MainActivity?
fragmentContext = context?.applicationContext
layoutinflater = LayoutInflater.from(context)
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
shimmerCards = this.view?.findViewById(R.id.shimmer_downloads_framelayout)
topAppBar = this.view?.findViewById(R.id.downloads_toolbar)
no_results = this.view?.findViewById(R.id.downloads_no_results)
selectionChips = this.view?.findViewById(R.id.downloads_selection_chips)
websiteGroup = this.view?.findViewById(R.id.website_chip_group)
deleteFab = this.view?.findViewById(R.id.delete_selected_fab)
fileUtil = FileUtil()
deleteFab?.tag = "deleteSelected"
deleteFab?.setOnClickListener(this)
uiHandler = Handler(Looper.getMainLooper())
downloadsObjects = ArrayList()
selectedObjects = ArrayList()
downloading = mainActivity!!.isDownloadServiceRunning()
recyclerView = this.view?.findViewById(R.id.recycler_view_downloads)
downloadsRecyclerViewAdapter =
DownloadsRecyclerViewAdapter(downloadsObjects, this, activity)
recyclerView?.adapter = downloadsRecyclerViewAdapter
recyclerView?.isNestedScrollingEnabled = false
initMenu()
initChips()
initCards()
return fragmentView
}
fun initCards() {
shimmerCards!!.startShimmer()
shimmerCards!!.visibility = View.VISIBLE
downloadsRecyclerViewAdapter!!.clear()
no_results!!.visibility = View.GONE
selectionChips!!.visibility = View.VISIBLE
databaseManager = DatabaseManager(context)
try {
val thread = Thread {
if (!downloading) databaseManager!!.clearDownloadingHistory()
downloadsObjects = databaseManager!!.getHistory("", format, website, sort)
uiHandler!!.post {
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = View.GONE
updateWebsiteChips()
}
if ((downloadsObjects as ArrayList<Video>?)?.size == 0) {
uiHandler!!.post {
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
websiteGroup!!.removeAllViews()
}
}
databaseManager!!.close()
}
thread.start()
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
fun scrollToTop() {
recyclerView!!.scrollToPosition(0)
Handler(Looper.getMainLooper()).post {
(topAppBar!!.parent as AppBarLayout).setExpanded(
true,
true
)
}
}
private fun initMenu() {
val onActionExpandListener: MenuItem.OnActionExpandListener =
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean {
return true
}
override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean {
return true
}
}
val toolbar = fragmentView!!.findViewById<MaterialToolbar>(R.id.downloads_toolbar)
topAppBar!!.menu.findItem(R.id.search_downloads)
.setOnActionExpandListener(onActionExpandListener)
val searchView = topAppBar!!.menu.findItem(R.id.search_downloads).actionView as SearchView?
searchView!!.inputType = InputType.TYPE_CLASS_TEXT
searchView.queryHint = getString(R.string.search_history_hint)
databaseManager = DatabaseManager(context)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
searchQuery = query
topAppBar!!.menu.findItem(R.id.search_downloads).collapseActionView()
downloadsObjects = databaseManager!!.getHistory(query, format, website, sort)
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
if ((downloadsObjects as ArrayList<Video>?)?.size == 0) {
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
websiteGroup!!.removeAllViews()
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
searchQuery = newText
downloadsObjects = databaseManager!!.getHistory(newText, format, website, sort)
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
if ((downloadsObjects as ArrayList<Video>?)?.size == 0) {
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
} else {
no_results!!.visibility = View.GONE
selectionChips!!.visibility = View.VISIBLE
}
return true
}
})
topAppBar!!.setOnClickListener { view: View? -> scrollToTop() }
topAppBar!!.setOnMenuItemClickListener { m: MenuItem ->
val itemID = m.itemId
if (itemID == R.id.remove_downloads) {
if (downloadsObjects!!.size == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.confirm_delete_history))
delete_dialog.setMessage(getString(R.string.confirm_delete_history_desc))
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
databaseManager!!.clearHistory()
downloadsRecyclerViewAdapter!!.clear()
downloadsObjects!!.clear()
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
websiteGroup!!.removeAllViews()
}
delete_dialog.show()
} else if (itemID == R.id.remove_deleted_downloads) {
if (downloadsObjects!!.size == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.confirm_delete_history))
delete_dialog.setMessage(getString(R.string.confirm_delete_history_deleted_desc))
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
databaseManager!!.clearDeletedHistory()
initCards()
}
delete_dialog.show()
} else if (itemID == R.id.remove_duplicates) {
if (downloadsObjects!!.size == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.confirm_delete_history))
delete_dialog.setMessage(getString(R.string.confirm_delete_history_duplicates_desc))
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
databaseManager!!.clearDuplicateHistory()
initCards()
}
delete_dialog.show()
} else if (itemID == R.id.remove_downloading) {
if (downloadsObjects!!.size == 0) {
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.confirm_delete_history))
delete_dialog.setMessage(getString(R.string.confirm_delete_downloading_desc))
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
databaseManager!!.clearDownloadingHistory()
mainActivity!!.cancelDownloadService()
initCards()
}
delete_dialog.show()
}
true
}
}
private fun initChips() {
//sort and history/downloading switch
val sortChip = fragmentView!!.findViewById<Chip>(R.id.sort_chip)
sortChip.setOnClickListener { view: View? ->
sortSheet = BottomSheetDialog(fragmentContext!!)
sortSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
sortSheet!!.setContentView(R.layout.downloads_sort_sheet)
val newest = sortSheet!!.findViewById<TextView>(R.id.newest)
val oldest = sortSheet!!.findViewById<TextView>(R.id.oldest)
newest!!.setOnClickListener { view1: View? ->
sort = "DESC"
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
sortSheet!!.cancel()
}
oldest!!.setOnClickListener { view1: View? ->
sort = "ASC"
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
sortSheet!!.cancel()
}
val cancel = sortSheet!!.findViewById<TextView>(R.id.cancel)
cancel!!.setOnClickListener { view1: View? -> sortSheet!!.cancel() }
sortSheet!!.show()
sortSheet!!.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
//format
val audio = fragmentView!!.findViewById<Chip>(R.id.audio_chip)
audio.setOnClickListener { view: View? ->
if (audio.isChecked) {
format = "audio"
if (recyclerView!!.visibility == View.GONE) {
}
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
audio.isChecked = true
} else {
format = ""
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
audio.isChecked = false
}
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
}
val video = fragmentView!!.findViewById<Chip>(R.id.video_chip)
video.setOnClickListener { view: View? ->
if (video.isChecked) {
format = "video"
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
video.isChecked = true
} else {
format = ""
downloadsObjects = databaseManager!!.getHistory(searchQuery, format, website, sort)
video.isChecked = false
}
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
}
}
private fun updateWebsiteChips() {
websiteGroup!!.removeAllViews()
val websites = downloadsRecyclerViewAdapter!!.websites
for (i in websites.indices) {
val w = websites[i]
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
tmp.text = w
tmp.id = i
tmp.setOnClickListener { view: View ->
if (tmp.isChecked) {
website = tmp.text as String
downloadsObjects =
databaseManager!!.getHistory(searchQuery, format, website, sort)
websiteGroup!!.check(view.id)
} else {
website = ""
downloadsObjects =
databaseManager!!.getHistory(searchQuery, format, website, sort)
websiteGroup!!.clearCheck()
}
downloadsRecyclerViewAdapter!!.clear()
downloadsRecyclerViewAdapter!!.add(downloadsObjects)
}
websiteGroup!!.addView(tmp)
}
}
override fun onClick(v: View) {
val id = v.id
if (id == R.id.bottomsheet_remove_button) {
removedownloadsItem(v.tag as Int)
} else if (id == R.id.bottom_sheet_link) {
openLinkIntent(v.tag as Int)
} else if (id == R.id.bottomsheet_open_file_button) {
openFileIntent(v.tag as Int)
} else if (id == R.id.delete_selected_fab) {
removeSelectedItems()
}
}
override fun onLongClick(v: View): Boolean {
val id = v.id
if (id == R.id.bottom_sheet_link) {
copyLinkToClipBoard(v.tag as Int)
return true
}
return false
}
private fun removeSelectedItems() {
if (bottomSheet != null) bottomSheet!!.hide()
val delete_file = booleanArrayOf(false)
databaseManager = DatabaseManager(context)
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
delete_dialog.setMultiChoiceItems(
arrayOf(getString(R.string.delete_files_too)),
booleanArrayOf(false)
) { dialogInterface: DialogInterface?, i: Int, b: Boolean -> delete_file[0] = b }
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
for (j in selectedObjects!!.indices) {
val v = selectedObjects!![j]
val position = downloadsObjects!!.indexOf(v)
downloadsObjects!!.remove(v)
downloadsRecyclerViewAdapter!!.remove(position)
databaseManager!!.clearHistoryItem(v, delete_file[0])
}
updateWebsiteChips()
databaseManager!!.close()
selectedObjects = ArrayList()
downloadsRecyclerViewAdapter!!.clearCheckedVideos()
deleteFab!!.visibility = View.GONE
if (downloadsObjects!!.size == 0) {
uiHandler!!.post {
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
websiteGroup!!.removeAllViews()
}
}
}
delete_dialog.show()
}
private fun removedownloadsItem(position: Int) {
if (bottomSheet != null) bottomSheet!!.hide()
val delete_file = booleanArrayOf(false)
databaseManager = DatabaseManager(context)
val v = downloadsObjects!![position]
val delete_dialog = MaterialAlertDialogBuilder(fragmentContext!!)
delete_dialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + v!!.title + "\"!")
delete_dialog.setMultiChoiceItems(
arrayOf(getString(R.string.delete_file_too)),
booleanArrayOf(false)
) { dialogInterface: DialogInterface?, i: Int, b: Boolean -> delete_file[0] = b }
delete_dialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, i: Int -> dialogInterface.cancel() }
delete_dialog.setPositiveButton(getString(R.string.ok)) { dialogInterface: DialogInterface?, i: Int ->
downloadsObjects!!.removeAt(position)
downloadsRecyclerViewAdapter!!.remove(position)
updateWebsiteChips()
databaseManager!!.clearHistoryItem(v, delete_file[0])
databaseManager!!.close()
if (downloadsObjects!!.size == 0) {
uiHandler!!.post {
no_results!!.visibility = View.VISIBLE
selectionChips!!.visibility = View.GONE
websiteGroup!!.removeAllViews()
}
}
}
delete_dialog.show()
}
private fun copyLinkToClipBoard(position: Int) {
val url = downloadsObjects!![position]!!.getURL()
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.url), url)
clipboard.setPrimaryClip(clip)
if (bottomSheet != null) bottomSheet!!.hide()
Toast.makeText(context, getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
private fun openLinkIntent(position: Int) {
val url = downloadsObjects!![position]!!.getURL()
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
if (bottomSheet != null) bottomSheet!!.hide()
startActivity(i)
}
private fun openFileIntent(position: Int) {
val downloadPath = downloadsObjects!![position]!!.downloadPath
val file = File(downloadPath)
val uri = FileProvider.getUriForFile(
fragmentContext!!,
fragmentContext!!.packageName + ".fileprovider",
file
)
val mime = mainActivity!!.contentResolver.getType(uri)
val i = Intent(Intent.ACTION_VIEW)
i.setDataAndType(uri, mime)
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(i)
}
override fun onCardClick(position: Int, isFilePresent: Boolean) {
bottomSheet = BottomSheetDialog(fragmentContext!!)
bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet!!.setContentView(R.layout.downloads_bottom_sheet)
val video = downloadsObjects!![position]
val title = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = video!!.title
val author = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_author)
author!!.text = video.author
val link = bottomSheet!!.findViewById<Button>(R.id.bottom_sheet_link)
val url = video.getURL()
link!!.text = url
link.tag = position
link.setOnClickListener(this)
link.setOnLongClickListener(this)
val remove = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = position
remove.setOnClickListener(this)
val openFile = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.tag = position
openFile.setOnClickListener(this)
if (!isFilePresent) openFile.visibility = View.GONE
bottomSheet!!.show()
bottomSheet!!.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
override fun onCardSelect(position: Int, add: Boolean) {
val video = downloadsObjects!![position]
if (add) selectedObjects!!.add(video) else selectedObjects!!.remove(video)
if (selectedObjects!!.size > 1) {
deleteFab!!.visibility = View.VISIBLE
} else {
deleteFab!!.visibility = View.GONE
}
}
override fun onButtonClick(position: Int) {
val vid = downloadsObjects!![position]
try {
mainActivity!!.removeItemFromDownloadQueue(vid, vid!!.downloadedType)
} catch (e: Exception) {
val info = DownloadInfo()
info.video = vid
info.downloadType = vid!!.downloadedType
}
}
fun findVideo(url: String, type: String): Video? {
for (i in downloadsObjects!!.indices) {
val v = downloadsObjects!![i]
if (v!!.getURL() == url && v.downloadedType == type && v.isQueuedDownload) {
return v
}
}
return null
}
companion object {
private const val TAG = "downloadsFragment"
fun newInstance(): DownloadsFragment {
val fragment = DownloadsFragment()
val args = Bundle()
return fragment
}
}
}

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.page;
package com.deniscerri.ytdlnis.ui;
import android.Manifest;
import android.annotation.SuppressLint;

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.page;
package com.deniscerri.ytdlnis.ui;
import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.page.settings;
package com.deniscerri.ytdlnis.ui.settings;
import android.content.Context;
import android.os.Bundle;

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdlnis.page.settings
package com.deniscerri.ytdlnis.ui.settings
import android.app.Activity
import android.content.Intent

View file

@ -0,0 +1,36 @@
package com.deniscerri.ytdlnis.work;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.work.ForegroundInfo;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.deniscerri.ytdlnis.MainActivity;
import com.deniscerri.ytdlnis.database.Video;
import java.util.ArrayList;
import kotlin.random.Random;
public class DownloadWorker extends Worker {
private Context context;
private WorkerParameters workerParams;
public DownloadWorker(
@NonNull Context appContext,
@NonNull WorkerParameters workerParams) {
super(appContext, workerParams);
this.context = appContext;
this.workerParams = workerParams;
}
@NonNull
@Override
public Result doWork() {
//TODO
return Result.success();
}
}

View file

@ -10,7 +10,6 @@ import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.page.CustomCommandActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import java.io.File

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdlnis.page.CustomCommandActivity">
tools:context="com.deniscerri.ytdlnis.ui.CustomCommandActivity">
<FrameLayout
android:id="@+id/custom_command_frame_layout"

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdlnis.page.settings.SettingsActivity">
tools:context="com.deniscerri.ytdlnis.ui.settings.SettingsActivity">
<FrameLayout
android:id="@+id/settings_frame_layout"

View file

@ -26,6 +26,8 @@ buildscript {
composeVer = "1.3.2"
kotlinVer = "1.7.20"
coroutineVer = "1.6.4"
retrofitVer = "2.9.0"
kodeinVer = "7.16.0"
}
repositories {
mavenCentral()

View file

@ -13,6 +13,7 @@ dependencyResolutionManagement {
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://dl.bintray.com/kodein-framework/Kodein-DI" }
}
}
rootProject.name = "YTDLnis"