finish history pagination & other features

This commit is contained in:
zaednasr 2024-11-14 22:09:29 +01:00
parent f6c9b011f2
commit 448c4bc66d
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
16 changed files with 199 additions and 170 deletions

View file

@ -8,31 +8,12 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.HistoryItem import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.repository.HistoryRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface HistoryDao { interface HistoryDao {
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' 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 ")
fun getHistorySortedByID(query : String, type : String, site : String, sort : String) : List<HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN title END ASC," +
"CASE WHEN :sort = 'DESC' THEN title END DESC," +
"CASE WHEN :sort = '' THEN title END DESC ")
fun getHistorySortedByTitle(query : String, type : String, site : String, sort : String) : List<HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN author END ASC," +
"CASE WHEN :sort = 'DESC' THEN author END DESC," +
"CASE WHEN :sort = '' THEN author END DESC ")
fun getHistorySortedByAuthor(query : String, type : String, site : String, sort : String) : List<HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " + @Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN id END ASC," + "CASE WHEN :sort = 'ASC' THEN id END ASC," +
"CASE WHEN :sort = 'DESC' THEN id END DESC," + "CASE WHEN :sort = 'DESC' THEN id END DESC," +
@ -57,6 +38,29 @@ interface HistoryDao {
"CASE WHEN :sort = '' THEN filesize END DESC ") "CASE WHEN :sort = '' THEN filesize END DESC ")
fun getHistorySortedByFilesizePaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem> fun getHistorySortedByFilesizePaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem>
@Query("SELECT id, downloadPath FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' 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 ")
fun getHistoryIDsSortedByID(query : String, type : String, site : String, sort : String) : List<HistoryRepository.HistoryIDsAndPaths>
@Query("SELECT id, downloadPath FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN title END ASC," +
"CASE WHEN :sort = 'DESC' THEN title END DESC," +
"CASE WHEN :sort = '' THEN title END DESC ")
fun getHistoryIDsSortedByTitle(query : String, type : String, site : String, sort : String) : List<HistoryRepository.HistoryIDsAndPaths>
@Query("SELECT id, downloadPath FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN author END ASC," +
"CASE WHEN :sort = 'DESC' THEN author END DESC," +
"CASE WHEN :sort = '' THEN author END DESC ")
fun getHistoryIDsSortedByAuthor(query : String, type : String, site : String, sort : String) : List<HistoryRepository.HistoryIDsAndPaths>
@Query("SELECT id, downloadPath FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN filesize END ASC," +
"CASE WHEN :sort = 'DESC' THEN filesize END DESC," +
"CASE WHEN :sort = '' THEN filesize END DESC ")
fun getHistoryIDsSortedByFilesize(query : String, type : String, site : String, sort : String) : List<HistoryRepository.HistoryIDsAndPaths>
@Query("SELECT * FROM history") @Query("SELECT * FROM history")
fun getAllHistory() : Flow<List<HistoryItem>> fun getAllHistory() : Flow<List<HistoryItem>>
@ -79,6 +83,12 @@ interface HistoryDao {
@Query("SELECT * FROM history WHERE url=:url") @Query("SELECT * FROM history WHERE url=:url")
fun getAllHistoryByURL(url: String) : List<HistoryItem> fun getAllHistoryByURL(url: String) : List<HistoryItem>
@Query("SELECT * FROM history WHERE id in (:ids)")
fun getAllHistoryByIDs(ids: List<Long>) : List<HistoryItem>
@Query("SELECT downloadPath FROM history WHERE id in (:ids)")
fun getDownloadPathsFromIDs(ids: List<Long>) : List<HistoryRepository.HistoryItemDownloadPaths>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: HistoryItem) suspend fun insert(item: HistoryItem)
@ -88,6 +98,9 @@ interface HistoryDao {
@Query("DELETE FROM history") @Query("DELETE FROM history")
suspend fun deleteAll() suspend fun deleteAll()
@Query("DELETE FROM history where id in (:ids)")
suspend fun deleteAllByIDs(ids: List<Long>)
@Query("DELETE FROM history WHERE id > (SELECT MIN(h.id) FROM history h WHERE h.url = history.url AND h.type = history.type)") @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() suspend fun deleteDuplicates()

View file

@ -35,19 +35,17 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return historyDao.getAllHistoryByURL(url) return historyDao.getAllHistoryByURL(url)
} }
data class HistoryIDsAndPaths(
val id: Long,
val downloadPath: List<String>
)
fun getFiltered(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List<HistoryItem> { fun getFilteredIDs (query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List<Long> {
var filtered = when(sortType){ var filtered = when(sortType){
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, type, site, sort.toString()) HistorySortType.DATE -> historyDao.getHistoryIDsSortedByID(query, type, site, sort.toString())
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, type, site, sort.toString()) HistorySortType.TITLE -> historyDao.getHistoryIDsSortedByTitle(query, type, site, sort.toString())
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, type, site, sort.toString()) HistorySortType.AUTHOR -> historyDao.getHistoryIDsSortedByAuthor(query, type, site, sort.toString())
HistorySortType.FILESIZE -> { HistorySortType.FILESIZE -> historyDao.getHistoryIDsSortedByFilesize(query, type, site, sort.toString())
val items = historyDao.getHistorySortedByID(query, type, site, sort.toString())
when(sort){
SORTING.DESC -> items.sortedByDescending { it.format.filesize }
SORTING.ASC -> items.sortedBy { it.format.filesize }
}
}
} }
when(statusFilter) { when(statusFilter) {
@ -59,8 +57,7 @@ class HistoryRepository(private val historyDao: HistoryDao) {
} }
else -> {} else -> {}
} }
return filtered.map { it.id }
return filtered
} }
fun getPaginatedSource(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING) : PagingSource<Int, HistoryItem> { fun getPaginatedSource(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING) : PagingSource<Int, HistoryItem> {
@ -101,6 +98,26 @@ class HistoryRepository(private val historyDao: HistoryDao) {
historyDao.deleteAll() historyDao.deleteAll()
} }
suspend fun deleteAllWithIDs(ids: List<Long>, deleteFile: Boolean = false){
if (deleteFile){
historyDao.getAllHistoryByIDs(ids).forEach { item ->
item.downloadPath.forEach {
FileUtil.deleteFile(it)
}
}
}
historyDao.deleteAllByIDs(ids)
}
data class HistoryItemDownloadPaths(
val downloadPath: List<String>
)
fun getDownloadPathsFromIDs(ids: List<Long>) : List<List<String>> {
val res = historyDao.getDownloadPathsFromIDs(ids)
return res.map { it.downloadPath }
}
suspend fun deleteDuplicates(){ suspend fun deleteDuplicates(){
historyDao.deleteDuplicates() historyDao.deleteDuplicates()
} }

View file

@ -118,7 +118,6 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
if (resetResults) deleteAll() if (resetResults) deleteAll()
//throw YoutubeDLException("Youtube Watch Videos is not yet supported in data fetching. You can download it directly by clicking Continue Anyway or by Quick Downloading it!") //throw YoutubeDLException("Youtube Watch Videos is not yet supported in data fetching. You can download it directly by clicking Continue Anyway or by Quick Downloading it!")
//TODO use below code after youtubedl-android has fixed issue #295 in their repo
val items = mutableListOf<ResultItem>() val items = mutableListOf<ResultItem>()
val ytExtractorResult = newPipeUtil?.getPlaylistData(inputQuery) { val ytExtractorResult = newPipeUtil?.getPlaylistData(inputQuery) {
if (addToResults){ if (addToResults){

View file

@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class HistoryViewModel(application: Application) : AndroidViewModel(application) { class HistoryViewModel(application: Application) : AndroidViewModel(application) {
@ -52,7 +53,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
var paginatedItems : Flow<PagingData<HistoryItem>> var paginatedItems : Flow<PagingData<HistoryItem>>
var websites : Flow<List<String>> var websites : Flow<List<String>>
var totalCount : Flow<Int> var totalCount = MutableStateFlow(0)
data class HistoryFilters( data class HistoryFilters(
var type: String = "", var type: String = "",
@ -67,7 +68,6 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
val dao = DBManager.getInstance(application).historyDao val dao = DBManager.getInstance(application).historyDao
repository = HistoryRepository(dao) repository = HistoryRepository(dao)
websites = repository.websites websites = repository.websites
totalCount = repository.count
val filters = listOf(dao.getAllHistory(), sortOrder, sortType, websiteFilter, statusFilter, queryFilter, typeFilter) val filters = listOf(dao.getAllHistory(), sortOrder, sortType, websiteFilter, statusFilter, queryFilter, typeFilter)
paginatedItems = combine(filters) { f -> paginatedItems = combine(filters) { f ->
@ -107,6 +107,10 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
else -> {} else -> {}
} }
withContext(Dispatchers.IO) {
totalCount.value = repository.getFilteredIDs(query, type, website, sortType, sortOrder, status).count()
}
pager pager
}.flatMapLatest { it } }.flatMapLatest { it }
} }
@ -138,9 +142,16 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
statusFilter.value = status statusFilter.value = status
} }
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryStatus) = viewModelScope.launch(Dispatchers.IO){ fun getIDsBetweenTwoItems(firstID: Long, secondID: Long): List<Long> {
val ids = repository.getFilteredIDs(queryFilter.value, typeFilter.value, websiteFilter.value, sortType.value, sortOrder.value, statusFilter.value)
val firstIndex = ids.indexOf(firstID)
val secondIndex = ids.indexOf(secondID)
return ids.filterIndexed {index, _ -> index in (firstIndex + 1) until secondIndex }
}
fun getItemIDsNotPresentIn(not: List<Long>) : List<Long> {
val ids = repository.getFilteredIDs(queryFilter.value, typeFilter.value, websiteFilter.value, sortType.value, sortOrder.value, statusFilter.value)
return ids.filter { !not.contains(it) }
} }
fun getAll() : List<HistoryItem> { fun getAll() : List<HistoryItem> {
@ -159,6 +170,14 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
repository.delete(item, deleteFile) repository.delete(item, deleteFile)
} }
fun deleteAllWithIDs(ids: List<Long>, deleteFile: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAllWithIDs(ids, deleteFile)
}
fun getDownloadPathsFromIDs(ids: List<Long>) : List<List<String>> {
return repository.getDownloadPathsFromIDs(ids)
}
fun deleteAll(deleteFile: Boolean = false) = viewModelScope.launch(Dispatchers.IO) { fun deleteAll(deleteFile: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
repository.deleteAll(deleteFile) repository.deleteAll(deleteFile)
} }
@ -175,16 +194,4 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
repository.clearDeletedHistory() repository.clearDeletedHistory()
} }
fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List<HistoryItem> {
val filtered = repository.getFiltered(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
val firstIndex = filtered.indexOfFirst { it.id == item1 }
val secondIndex = filtered.indexOfFirst { it.id == item2 }
return if(firstIndex > secondIndex) {
filtered.filterIndexed { index, _ -> index in (secondIndex + 1) until firstIndex }
}else{
filtered.filterIndexed { index, _ -> index in (firstIndex + 1) until secondIndex }
}
}
} }

View file

@ -84,6 +84,7 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) ?: return val item = getItem(position) ?: return
val card = holder.cardView val card = holder.cardView
holder.itemView.tag = item.id.toString()
card.tag = item.id.toString() card.tag = item.id.toString()
card.popup() card.popup()
@ -147,7 +148,7 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity
if (btn.hasOnClickListeners()) btn.setOnClickListener(null) if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filesPresent btn.isClickable = filesPresent
if (checkedItems.contains(item.id)) { if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
card.isChecked = true card.isChecked = true
card.strokeWidth = 5 card.strokeWidth = 5
} else { } else {
@ -156,12 +157,12 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity
} }
val finalFilePresent = filesPresent val finalFilePresent = filesPresent
card.setOnLongClickListener { card.setOnLongClickListener {
checkCard(card, item.id) checkCard(card, item.id, position)
true true
} }
card.setOnClickListener { card.setOnClickListener {
if (checkedItems.size > 0) { if (checkedItems.size > 0) {
checkCard(card, item.id) checkCard(card, item.id, position)
} else { } else {
onItemClickListener.onCardClick(item.id, finalFilePresent) onItemClickListener.onCardClick(item.id, finalFilePresent)
} }
@ -208,7 +209,7 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity
} }
} }
private fun checkCard(card: MaterialCardView, itemID: Long) { private fun checkCard(card: MaterialCardView, itemID: Long, position: Int) {
if (card.isChecked) { if (card.isChecked) {
card.strokeWidth = 0 card.strokeWidth = 0
if (inverted) checkedItems.add(itemID) if (inverted) checkedItems.add(itemID)
@ -219,13 +220,13 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity
else checkedItems.add(itemID) else checkedItems.add(itemID)
} }
card.isChecked = !card.isChecked card.isChecked = !card.isChecked
onItemClickListener.onCardSelect(itemID, card.isChecked) onItemClickListener.onCardSelect(card.isChecked, position)
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean) fun onButtonClick(itemID: Long, filePresent: Boolean)
fun onButtonClick(itemID: Long, isPresent: Boolean) fun onCardClick(itemID: Long, filePresent: Boolean)
fun onCardSelect(itemID: Long, isChecked: Boolean) fun onCardSelect(isChecked: Boolean, position: Int)
} }
companion object { companion object {

View file

@ -309,7 +309,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
scheduleBtn.setOnClickListener{ scheduleBtn.setOnClickListener{
UiUtil.showDatePicker(fragmentManager) { UiUtil.showDatePicker(fragmentManager, sharedPreferences) {
lifecycleScope.launch { lifecycleScope.launch {
resultViewModel.cancelUpdateItemData() resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData() resultViewModel.cancelUpdateFormatsItemData()

View file

@ -220,7 +220,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
scheduleBtn.setOnClickListener{ scheduleBtn.setOnClickListener{
UiUtil.showDatePicker(parentFragmentManager) { cal -> UiUtil.showDatePicker(parentFragmentManager, preferences) { cal ->
toggleLoading(true) toggleLoading(true)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){

View file

@ -315,7 +315,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
isFocusable = false isFocusable = false
isClickable = false isClickable = false
setOnClickListener{ setOnClickListener{
UiUtil.showTimePicker(fragmentManager){ UiUtil.showTimePicker(fragmentManager, sharedPreferences){
everyTime.editText?.setText( everyTime.editText?.setText(
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(it.timeInMillis) SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(it.timeInMillis)
) )

View file

@ -228,7 +228,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
} }
override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
TODO("Not yet implemented")
} }

View file

@ -48,6 +48,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
@ -523,14 +524,10 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
} }
} }
override fun onCardSelect(itemID: Long, isChecked: Boolean) { override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch { lifecycleScope.launch {
val item = withContext(Dispatchers.IO) {
historyViewModel.getByID(itemID)
}
if (actionMode == null) actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
val selectedObjects = historyAdapter.getSelectedObjectsCount(totalCount) val selectedObjects = historyAdapter.getSelectedObjectsCount(totalCount)
if (actionMode == null) actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply { actionMode?.apply {
if (selectedObjects == 0){ if (selectedObjects == 0){
this.finish() this.finish()
@ -538,15 +535,28 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
this.menu.findItem(R.id.select_between).isVisible = false this.menu.findItem(R.id.select_between).isVisible = false
if(selectedObjects == 2){ if(selectedObjects == 2){
//TODO val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
// val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } val idsInMiddle = withContext(Dispatchers.IO){
// val resultsInMiddle = withContext(Dispatchers.IO){ historyViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id) }
// }.toMutableList() this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
// this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
} }
} }
} }
if (isChecked) {
if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if (selectedObjects == 0){
actionMode?.finish()
}
}
} }
} }
@ -573,66 +583,63 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
): Boolean { ): Boolean {
return when (item!!.itemId) { return when (item!!.itemId) {
R.id.select_between -> { R.id.select_between -> {
//TODO lifecycleScope.launch {
// lifecycleScope.launch { val selectedIDs = getSelectedIDs().sortedBy { it }
// val selectedIDs = selectedObjects.sortedBy { it.id } val idsInMiddle = withContext(Dispatchers.IO){
// val resultsInMiddle = withContext(Dispatchers.IO){ historyViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id) }.toMutableList()
// }.toMutableList() idsInMiddle.addAll(selectedIDs)
// if (resultsInMiddle.isNotEmpty()){ if (idsInMiddle.isNotEmpty()){
// selectedObjects.addAll(resultsInMiddle) historyAdapter.checkMultipleItems(idsInMiddle)
// historyAdapter.checkMultipleItems(selectedObjects.map { it.id }) actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
// actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}" }
// } mode?.menu?.findItem(R.id.select_between)?.isVisible = false
// mode?.menu?.findItem(R.id.select_between)?.isVisible = false }
// }
true true
} }
R.id.delete_results -> { R.id.delete_results -> {
//TODO val deleteFile = booleanArrayOf(false)
// val deleteFile = booleanArrayOf(false) val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setMultiChoiceItems(
// deleteDialog.setMultiChoiceItems( arrayOf(getString(R.string.delete_files_too)),
// arrayOf(getString(R.string.delete_files_too)), booleanArrayOf(false)
// booleanArrayOf(false) ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b } deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch {
// for (obj in selectedObjects){ val selectedObjects = getSelectedIDs()
// historyViewModel.delete(obj, deleteFile[0]) historyAdapter.clearCheckedItems()
// } historyViewModel.deleteAllWithIDs(selectedObjects, deleteFile[0])
// clearCheckedItems() actionMode?.finish()
// actionMode?.finish() }
// } }
// deleteDialog.show() deleteDialog.show()
true true
} }
R.id.share -> { R.id.share -> {
//TODO lifecycleScope.launch {
// FileUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten()) val selectedObjects = getSelectedIDs()
// clearCheckedItems() val paths = withContext(Dispatchers.IO){
// actionMode?.finish() historyViewModel.getDownloadPathsFromIDs(selectedObjects)
}
FileUtil.shareFileIntent(requireContext(), paths.flatten())
historyAdapter.clearCheckedItems()
actionMode?.finish()
}
true true
} }
R.id.select_all -> { R.id.select_all -> {
//TODO historyAdapter.checkAll()
// historyAdapter.checkAll() val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount)
// historyList?.forEach { selectedObjects.add(it!!) } mode?.title = "(${selectedCount}) ${resources.getString(R.string.all_items_selected)}"
// mode?.title = "(${selectedObjects.size}) ${resources.getString(R.string.all_items_selected)}"
true true
} }
R.id.invert_selected -> { R.id.invert_selected -> {
//TODO historyAdapter.invertSelected()
// historyAdapter.invertSelected() val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount)
// val invertedList = arrayListOf<HistoryItem>() actionMode?.title = "$selectedCount ${getString(R.string.selected)}"
// historyList?.forEach { if (selectedCount == 0) actionMode?.finish()
// if (!selectedObjects.contains(it)!!) invertedList.add(it!!)
// }
// selectedObjects.clear()
// selectedObjects.addAll(invertedList)
// actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
// if (invertedList.isEmpty()) actionMode?.finish()
true true
} }
else -> false else -> false
@ -647,15 +654,13 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
} }
suspend fun getSelectedIDs() : List<Long>{ suspend fun getSelectedIDs() : List<Long>{
//TODO return if (historyAdapter.inverted || historyAdapter.checkedItems.isEmpty()){
// return if (historyAdapter.inverted || historyAdapter.checkedItems.isEmpty()) { withContext(Dispatchers.IO){
// withContext(Dispatchers.IO){ historyViewModel.getItemIDsNotPresentIn(historyAdapter.checkedItems.toList())
// historyViewModel.getItemIDsNotPresentIn(historyAdapter.checkedItems.toList()) }
// } }else{
// }else{ historyAdapter.checkedItems.toList()
// historyAdapter.checkedItems.toList() }
// }
return listOf()
} }
} }

View file

@ -4,6 +4,7 @@ import android.animation.AnimatorSet
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
@ -66,6 +67,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var fileSize: TextView private lateinit var fileSize: TextView
private lateinit var dragHandleToggle: TextView private lateinit var dragHandleToggle: TextView
private lateinit var sharedPreferences: SharedPreferences
private var totalSize: Int = 0 private var totalSize: Int = 0
private var actionMode : ActionMode? = null private var actionMode : ActionMode? = null
@ -84,6 +86,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
@SuppressLint("SetTextI18n", "RestrictedApi") @SuppressLint("SetTextI18n", "RestrictedApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
fileSize = view.findViewById(R.id.filesize) fileSize = view.findViewById(R.id.filesize)
dragHandleToggle = view.findViewById(R.id.drag) dragHandleToggle = view.findViewById(R.id.drag)
val itemTouchHelper = ItemTouchHelper(queuedDragDropHelper) val itemTouchHelper = ItemTouchHelper(queuedDragDropHelper)
@ -473,7 +476,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
} }
}, },
scheduleButtonClick = {downloadItem -> scheduleButtonClick = {downloadItem ->
UiUtil.showDatePicker(parentFragmentManager) { UiUtil.showDatePicker(parentFragmentManager, sharedPreferences) {
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
downloadViewModel.deleteDownload(downloadItem.id) downloadViewModel.deleteDownload(downloadItem.id)
downloadItem.downloadStartTime = it.timeInMillis downloadItem.downloadStartTime = it.timeInMillis

View file

@ -198,7 +198,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
) )
}, },
scheduleButtonClick = {downloadItem -> scheduleButtonClick = {downloadItem ->
UiUtil.showDatePicker(parentFragmentManager) { UiUtil.showDatePicker(parentFragmentManager, preferences) {
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
downloadViewModel.deleteDownload(downloadItem.id) downloadViewModel.deleteDownload(downloadItem.id)
downloadItem.downloadStartTime = it.timeInMillis downloadItem.downloadStartTime = it.timeInMillis

View file

@ -35,17 +35,6 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.downloading_preferences, rootKey) setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
findPreference<Preference>("prevent_duplicate_downloads")?.apply {
//TODO transitioning code, delete after couple releases
if (preferences.getBoolean("download_archive", false)){
preferences.edit(commit = true){
putBoolean("download_archive", false).apply()
putString("prevent_duplicate_downloads", "download_archive")
}
}
}
val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type") val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type")
val downloadType = findPreference<ListPreference>("preferred_download_type") val downloadType = findPreference<ListPreference>("preferred_download_type")
downloadType?.isEnabled = rememberDownloadType?.isChecked == false downloadType?.isEnabled = rememberDownloadType?.isChecked == false
@ -163,7 +152,7 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
} }
scheduleStart?.setOnPreferenceClickListener { scheduleStart?.setOnPreferenceClickListener {
UiUtil.showTimePicker(parentFragmentManager){ UiUtil.showTimePicker(parentFragmentManager, preferences){
val hr = it.get(Calendar.HOUR_OF_DAY) val hr = it.get(Calendar.HOUR_OF_DAY)
val mn = it.get(Calendar.MINUTE) val mn = it.get(Calendar.MINUTE)
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn) val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
@ -176,7 +165,7 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
} }
scheduleEnd?.setOnPreferenceClickListener { scheduleEnd?.setOnPreferenceClickListener {
UiUtil.showTimePicker(parentFragmentManager){ UiUtil.showTimePicker(parentFragmentManager, preferences){
val hr = it.get(Calendar.HOUR_OF_DAY) val hr = it.get(Calendar.HOUR_OF_DAY)
val mn = it.get(Calendar.MINUTE) val mn = it.get(Calendar.MINUTE)
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn) val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)

View file

@ -10,6 +10,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.text.Editable import android.text.Editable
@ -413,7 +414,7 @@ object UiUtil {
datePicker.show(fragmentManager, "datepicker") datePicker.show(fragmentManager, "datepicker")
} }
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){ fun showDatePicker(fragmentManager: FragmentManager , preferences: SharedPreferences, onSubmit : (chosenDate: Calendar) -> Unit ){
val currentDate = Calendar.getInstance() val currentDate = Calendar.getInstance()
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000 currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
val date = Calendar.getInstance() val date = Calendar.getInstance()
@ -430,38 +431,35 @@ object UiUtil {
datePicker.addOnPositiveButtonClickListener{ datePicker.addOnPositiveButtonClickListener{
date.timeInMillis = it date.timeInMillis = it
showTimePicker(fragmentManager, preferences) { chosenTime ->
val timepicker = MaterialTimePicker.Builder() date[Calendar.HOUR_OF_DAY] = chosenTime[Calendar.HOUR_OF_DAY]
.setTimeFormat(TimeFormat.CLOCK_24H) date[Calendar.MINUTE] = chosenTime[Calendar.MINUTE]
.setHour(currentDate.get(Calendar.HOUR_OF_DAY))
.setMinute(currentDate.get(Calendar.MINUTE))
.build()
timepicker.addOnPositiveButtonClickListener{
date[Calendar.HOUR_OF_DAY] = timepicker.hour
date[Calendar.MINUTE] = timepicker.minute
onSubmit(date) onSubmit(date)
} }
timepicker.show(fragmentManager, "timepicker")
} }
datePicker.show(fragmentManager, "datepicker") datePicker.show(fragmentManager, "datepicker")
} }
fun showTimePicker(fragmentManager: FragmentManager , onSubmit : (chosenTime: Calendar) -> Unit ){ fun showTimePicker(fragmentManager: FragmentManager , preferences: SharedPreferences, onSubmit : (chosenTime: Calendar) -> Unit ){
val currentDate = Calendar.getInstance() val currentDate = Calendar.getInstance()
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000 currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
val date = Calendar.getInstance() val date = Calendar.getInstance()
val lastTime = preferences.getString("latest_timepicker_date", "${currentDate.get(Calendar.HOUR_OF_DAY)}:${currentDate.get(Calendar.MINUTE)}")!!
val hour = lastTime.split(":")[0].toInt()
val minute = lastTime.split(":")[1].toInt()
val timepicker = MaterialTimePicker.Builder() val timepicker = MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H) .setTimeFormat(TimeFormat.CLOCK_24H)
.setHour(currentDate.get(Calendar.HOUR_OF_DAY)) .setHour(hour)
.setMinute(currentDate.get(Calendar.MINUTE)) .setMinute(minute)
.build() .build()
timepicker.addOnPositiveButtonClickListener{ timepicker.addOnPositiveButtonClickListener{
date[Calendar.HOUR_OF_DAY] = timepicker.hour date[Calendar.HOUR_OF_DAY] = timepicker.hour
date[Calendar.MINUTE] = timepicker.minute date[Calendar.MINUTE] = timepicker.minute
preferences.edit().putString("latest_timepicker_date", "${timepicker.hour}:${timepicker.minute}").apply()
onSubmit(date) onSubmit(date)
} }
timepicker.show(fragmentManager, "timepicker") timepicker.show(fragmentManager, "timepicker")

View file

@ -151,8 +151,7 @@ class NewPipeUtil(context: Context) {
fun getChannelData(url: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> { fun getChannelData(url: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
try { try {
//TODO BROKEN FOR NOW //return Result.failure(Throwable())
return Result.failure(Throwable())
val req = ChannelInfo.getInfo(ServiceList.YouTube, url) val req = ChannelInfo.getInfo(ServiceList.YouTube, url)
println(Gson().toJson(req)) println(Gson().toJson(req))
val items = mutableListOf<ResultItem>() val items = mutableListOf<ResultItem>()

View file

@ -49,17 +49,15 @@
</shortcut> </shortcut>
<shortcut <shortcut
android:shortcutId="newTemplate" android:shortcutId="terminal"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_terminal_red" android:icon="@drawable/ic_terminal_red"
android:shortcutShortLabel="@string/new_template" android:shortcutShortLabel="@string/terminal"
android:shortcutDisabledMessage="@string/new_template"> android:shortcutDisabledMessage="@string/terminal">
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.category.DEFAULT"
android:targetPackage="com.deniscerri.ytdl" android:targetPackage="com.deniscerri.ytdl"
android:targetClass="com.deniscerri.ytdl.receiver.TransparentActivity"> android:targetClass="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
<extra android:name="action" android:value="NEW_TEMPLATE" />
</intent> </intent>
</shortcut> </shortcut>