From 448c4bc66d7dff16ab2d0ee31da4db5452d4d8d9 Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:09:29 +0100 Subject: [PATCH] finish history pagination & other features --- .../ytdl/database/dao/HistoryDao.kt | 53 ++++--- .../database/repository/HistoryRepository.kt | 43 ++++-- .../database/repository/ResultRepository.kt | 1 - .../database/viewmodel/HistoryViewModel.kt | 39 +++-- .../ui/adapter/HistoryPaginatedAdapter.kt | 17 ++- .../downloadcard/DownloadBottomSheetDialog.kt | 2 +- .../DownloadMultipleBottomSheetDialog.kt | 2 +- .../ObserveSourcesBottomSheetDialog.kt | 2 +- .../ui/downloads/ErroredDownloadsFragment.kt | 2 +- .../ytdl/ui/downloads/HistoryFragment.kt | 143 +++++++++--------- .../ui/downloads/QueuedDownloadsFragment.kt | 5 +- .../downloads/ScheduledDownloadsFragment.kt | 2 +- .../more/settings/DownloadSettingsFragment.kt | 15 +- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 28 ++-- .../util/extractors/newpipe/NewPipeUtil.kt | 3 +- app/src/main/res/xml-v25/shortcuts.xml | 12 +- 16 files changed, 199 insertions(+), 170 deletions(-) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/HistoryDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/HistoryDao.kt index 75b66635..84dd3914 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/dao/HistoryDao.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/HistoryDao.kt @@ -8,31 +8,12 @@ import androidx.room.Query import androidx.room.Update import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.HistoryItem +import com.deniscerri.ytdl.database.repository.HistoryRepository import kotlinx.coroutines.flow.Flow @Dao 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 - - @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 - - @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 - - - @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," + @@ -57,6 +38,29 @@ interface HistoryDao { "CASE WHEN :sort = '' THEN filesize END DESC ") fun getHistorySortedByFilesizePaginated(query : String, type : String, site : String, sort : String) : PagingSource + @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 + + @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 + + @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 + + @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 @Query("SELECT * FROM history") fun getAllHistory() : Flow> @@ -79,6 +83,12 @@ interface HistoryDao { @Query("SELECT * FROM history WHERE url=:url") fun getAllHistoryByURL(url: String) : List + @Query("SELECT * FROM history WHERE id in (:ids)") + fun getAllHistoryByIDs(ids: List) : List + + @Query("SELECT downloadPath FROM history WHERE id in (:ids)") + fun getDownloadPathsFromIDs(ids: List) : List + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(item: HistoryItem) @@ -88,6 +98,9 @@ interface HistoryDao { @Query("DELETE FROM history") suspend fun deleteAll() + @Query("DELETE FROM history where id in (:ids)") + suspend fun deleteAllByIDs(ids: List) + @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() diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/HistoryRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/HistoryRepository.kt index 7799bc40..3ccb5aaf 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/HistoryRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/HistoryRepository.kt @@ -35,19 +35,17 @@ class HistoryRepository(private val historyDao: HistoryDao) { return historyDao.getAllHistoryByURL(url) } + data class HistoryIDsAndPaths( + val id: Long, + val downloadPath: List + ) - fun getFiltered(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List { + fun getFilteredIDs (query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List { var filtered = when(sortType){ - HistorySortType.DATE -> historyDao.getHistorySortedByID(query, type, site, sort.toString()) - HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, type, site, sort.toString()) - HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, type, site, sort.toString()) - HistorySortType.FILESIZE -> { - 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 } - } - } + HistorySortType.DATE -> historyDao.getHistoryIDsSortedByID(query, type, site, sort.toString()) + HistorySortType.TITLE -> historyDao.getHistoryIDsSortedByTitle(query, type, site, sort.toString()) + HistorySortType.AUTHOR -> historyDao.getHistoryIDsSortedByAuthor(query, type, site, sort.toString()) + HistorySortType.FILESIZE -> historyDao.getHistoryIDsSortedByFilesize(query, type, site, sort.toString()) } when(statusFilter) { @@ -59,8 +57,7 @@ class HistoryRepository(private val historyDao: HistoryDao) { } else -> {} } - - return filtered + return filtered.map { it.id } } fun getPaginatedSource(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING) : PagingSource { @@ -101,6 +98,26 @@ class HistoryRepository(private val historyDao: HistoryDao) { historyDao.deleteAll() } + suspend fun deleteAllWithIDs(ids: List, 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 + ) + + fun getDownloadPathsFromIDs(ids: List) : List> { + val res = historyDao.getDownloadPathsFromIDs(ids) + return res.map { it.downloadPath } + } + suspend fun deleteDuplicates(){ historyDao.deleteDuplicates() } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt index 8dec814e..0cab5ef6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt @@ -118,7 +118,6 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co 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!") - //TODO use below code after youtubedl-android has fixed issue #295 in their repo val items = mutableListOf() val ytExtractorResult = newPipeUtil?.getPlaylistData(inputQuery) { if (addToResults){ diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt index be6e374d..b2c45b07 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) class HistoryViewModel(application: Application) : AndroidViewModel(application) { @@ -52,7 +53,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) var paginatedItems : Flow> var websites : Flow> - var totalCount : Flow + var totalCount = MutableStateFlow(0) data class HistoryFilters( var type: String = "", @@ -67,7 +68,6 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) val dao = DBManager.getInstance(application).historyDao repository = HistoryRepository(dao) websites = repository.websites - totalCount = repository.count val filters = listOf(dao.getAllHistory(), sortOrder, sortType, websiteFilter, statusFilter, queryFilter, typeFilter) paginatedItems = combine(filters) { f -> @@ -107,6 +107,10 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) else -> {} } + withContext(Dispatchers.IO) { + totalCount.value = repository.getFilteredIDs(query, type, website, sortType, sortOrder, status).count() + } + pager }.flatMapLatest { it } } @@ -138,9 +142,16 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) 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 { + 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) : List { + 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 { @@ -159,6 +170,14 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) repository.delete(item, deleteFile) } + fun deleteAllWithIDs(ids: List, deleteFile: Boolean = false) = viewModelScope.launch(Dispatchers.IO) { + repository.deleteAllWithIDs(ids, deleteFile) + } + + fun getDownloadPathsFromIDs(ids: List) : List> { + return repository.getDownloadPathsFromIDs(ids) + } + fun deleteAll(deleteFile: Boolean = false) = viewModelScope.launch(Dispatchers.IO) { repository.deleteAll(deleteFile) } @@ -175,16 +194,4 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) repository.clearDeletedHistory() } - fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List { - 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 } - } - } - } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HistoryPaginatedAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HistoryPaginatedAdapter.kt index 6888c8a9..449899de 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HistoryPaginatedAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HistoryPaginatedAdapter.kt @@ -84,6 +84,7 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) ?: return val card = holder.cardView + holder.itemView.tag = item.id.toString() card.tag = item.id.toString() card.popup() @@ -147,7 +148,7 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity if (btn.hasOnClickListeners()) btn.setOnClickListener(null) btn.isClickable = filesPresent - if (checkedItems.contains(item.id)) { + if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) { card.isChecked = true card.strokeWidth = 5 } else { @@ -156,12 +157,12 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity } val finalFilePresent = filesPresent card.setOnLongClickListener { - checkCard(card, item.id) + checkCard(card, item.id, position) true } card.setOnClickListener { if (checkedItems.size > 0) { - checkCard(card, item.id) + checkCard(card, item.id, position) } else { 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) { card.strokeWidth = 0 if (inverted) checkedItems.add(itemID) @@ -219,13 +220,13 @@ class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity else checkedItems.add(itemID) } card.isChecked = !card.isChecked - onItemClickListener.onCardSelect(itemID, card.isChecked) + onItemClickListener.onCardSelect(card.isChecked, position) } interface OnItemClickListener { - fun onCardClick(itemID: Long, isPresent: Boolean) - fun onButtonClick(itemID: Long, isPresent: Boolean) - fun onCardSelect(itemID: Long, isChecked: Boolean) + fun onButtonClick(itemID: Long, filePresent: Boolean) + fun onCardClick(itemID: Long, filePresent: Boolean) + fun onCardSelect(isChecked: Boolean, position: Int) } companion object { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt index 17044d3c..8d791328 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -309,7 +309,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { scheduleBtn.setOnClickListener{ - UiUtil.showDatePicker(fragmentManager) { + UiUtil.showDatePicker(fragmentManager, sharedPreferences) { lifecycleScope.launch { resultViewModel.cancelUpdateItemData() resultViewModel.cancelUpdateFormatsItemData() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index 09dedb76..59dc57b3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -220,7 +220,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure scheduleBtn.setOnClickListener{ - UiUtil.showDatePicker(parentFragmentManager) { cal -> + UiUtil.showDatePicker(parentFragmentManager, preferences) { cal -> toggleLoading(true) lifecycleScope.launch { withContext(Dispatchers.IO){ diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt index a398736b..601aad90 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt @@ -315,7 +315,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { isFocusable = false isClickable = false setOnClickListener{ - UiUtil.showTimePicker(fragmentManager){ + UiUtil.showTimePicker(fragmentManager, sharedPreferences){ everyTime.editText?.setText( SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(it.timeInMillis) ) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt index 3239d9d4..eedb5e08 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt @@ -228,7 +228,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL } override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { - TODO("Not yet implemented") + } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt index b511a55c..194079ff 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt @@ -48,6 +48,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter 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.NavbarUtil 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 { - val item = withContext(Dispatchers.IO) { - historyViewModel.getByID(itemID) - } - - if (actionMode == null) actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) val selectedObjects = historyAdapter.getSelectedObjectsCount(totalCount) + if (actionMode == null) actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) actionMode?.apply { if (selectedObjects == 0){ this.finish() @@ -538,15 +535,28 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" this.menu.findItem(R.id.select_between).isVisible = false if(selectedObjects == 2){ - //TODO -// val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } -// val resultsInMiddle = withContext(Dispatchers.IO){ -// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id) -// }.toMutableList() -// this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty() + val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + historyViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last()) + } + this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.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 { return when (item!!.itemId) { R.id.select_between -> { - //TODO -// lifecycleScope.launch { -// val selectedIDs = selectedObjects.sortedBy { it.id } -// val resultsInMiddle = withContext(Dispatchers.IO){ -// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id) -// }.toMutableList() -// if (resultsInMiddle.isNotEmpty()){ -// selectedObjects.addAll(resultsInMiddle) -// historyAdapter.checkMultipleItems(selectedObjects.map { it.id }) -// actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}" -// } -// mode?.menu?.findItem(R.id.select_between)?.isVisible = false -// } + lifecycleScope.launch { + val selectedIDs = getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + historyViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last()) + }.toMutableList() + idsInMiddle.addAll(selectedIDs) + if (idsInMiddle.isNotEmpty()){ + historyAdapter.checkMultipleItems(idsInMiddle) + actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}" + } + mode?.menu?.findItem(R.id.select_between)?.isVisible = false + } true } R.id.delete_results -> { - //TODO -// val deleteFile = booleanArrayOf(false) -// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!) -// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) -// deleteDialog.setMultiChoiceItems( -// arrayOf(getString(R.string.delete_files_too)), -// booleanArrayOf(false) -// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b } -// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } -// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> -// for (obj in selectedObjects){ -// historyViewModel.delete(obj, deleteFile[0]) -// } -// clearCheckedItems() -// actionMode?.finish() -// } -// deleteDialog.show() + val deleteFile = booleanArrayOf(false) + val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!) + deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) + deleteDialog.setMultiChoiceItems( + arrayOf(getString(R.string.delete_files_too)), + booleanArrayOf(false) + ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b } + deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } + deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + historyAdapter.clearCheckedItems() + historyViewModel.deleteAllWithIDs(selectedObjects, deleteFile[0]) + actionMode?.finish() + } + } + deleteDialog.show() true } R.id.share -> { - //TODO -// FileUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten()) -// clearCheckedItems() -// actionMode?.finish() + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + val paths = withContext(Dispatchers.IO){ + historyViewModel.getDownloadPathsFromIDs(selectedObjects) + } + FileUtil.shareFileIntent(requireContext(), paths.flatten()) + historyAdapter.clearCheckedItems() + actionMode?.finish() + } true } R.id.select_all -> { - //TODO -// historyAdapter.checkAll() -// historyList?.forEach { selectedObjects.add(it!!) } -// mode?.title = "(${selectedObjects.size}) ${resources.getString(R.string.all_items_selected)}" + historyAdapter.checkAll() + val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount) + mode?.title = "(${selectedCount}) ${resources.getString(R.string.all_items_selected)}" true } R.id.invert_selected -> { - //TODO -// historyAdapter.invertSelected() -// val invertedList = arrayListOf() -// historyList?.forEach { -// if (!selectedObjects.contains(it)!!) invertedList.add(it!!) -// } -// selectedObjects.clear() -// selectedObjects.addAll(invertedList) -// actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}" -// if (invertedList.isEmpty()) actionMode?.finish() + historyAdapter.invertSelected() + val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount) + actionMode?.title = "$selectedCount ${getString(R.string.selected)}" + if (selectedCount == 0) actionMode?.finish() true } else -> false @@ -647,15 +654,13 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ } suspend fun getSelectedIDs() : List{ - //TODO -// return if (historyAdapter.inverted || historyAdapter.checkedItems.isEmpty()) { -// withContext(Dispatchers.IO){ -// historyViewModel.getItemIDsNotPresentIn(historyAdapter.checkedItems.toList()) -// } -// }else{ -// historyAdapter.checkedItems.toList() -// } - return listOf() + return if (historyAdapter.inverted || historyAdapter.checkedItems.isEmpty()){ + withContext(Dispatchers.IO){ + historyViewModel.getItemIDsNotPresentIn(historyAdapter.checkedItems.toList()) + } + }else{ + historyAdapter.checkedItems.toList() + } } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt index cb8ccfd3..26cf99f6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt @@ -4,6 +4,7 @@ import android.animation.AnimatorSet import android.annotation.SuppressLint import android.app.Activity import android.content.DialogInterface +import android.content.SharedPreferences import android.graphics.Canvas import android.graphics.Color import android.os.Bundle @@ -66,6 +67,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis private lateinit var notificationUtil: NotificationUtil private lateinit var fileSize: TextView private lateinit var dragHandleToggle: TextView + private lateinit var sharedPreferences: SharedPreferences private var totalSize: Int = 0 private var actionMode : ActionMode? = null @@ -84,6 +86,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis @SuppressLint("SetTextI18n", "RestrictedApi") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) fileSize = view.findViewById(R.id.filesize) dragHandleToggle = view.findViewById(R.id.drag) val itemTouchHelper = ItemTouchHelper(queuedDragDropHelper) @@ -473,7 +476,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis } }, scheduleButtonClick = {downloadItem -> - UiUtil.showDatePicker(parentFragmentManager) { + UiUtil.showDatePicker(parentFragmentManager, sharedPreferences) { Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() downloadViewModel.deleteDownload(downloadItem.id) downloadItem.downloadStartTime = it.timeInMillis diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt index b70d6d47..78644182 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt @@ -198,7 +198,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl ) }, scheduleButtonClick = {downloadItem -> - UiUtil.showDatePicker(parentFragmentManager) { + UiUtil.showDatePicker(parentFragmentManager, preferences) { Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show() downloadViewModel.deleteDownload(downloadItem.id) downloadItem.downloadStartTime = it.timeInMillis diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt index 0ead49e4..7141f97a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt @@ -35,17 +35,6 @@ class DownloadSettingsFragment : BaseSettingsFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.downloading_preferences, rootKey) val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - - findPreference("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("remember_download_type") val downloadType = findPreference("preferred_download_type") downloadType?.isEnabled = rememberDownloadType?.isChecked == false @@ -163,7 +152,7 @@ class DownloadSettingsFragment : BaseSettingsFragment() { } scheduleStart?.setOnPreferenceClickListener { - UiUtil.showTimePicker(parentFragmentManager){ + UiUtil.showTimePicker(parentFragmentManager, preferences){ val hr = it.get(Calendar.HOUR_OF_DAY) val mn = it.get(Calendar.MINUTE) val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn) @@ -176,7 +165,7 @@ class DownloadSettingsFragment : BaseSettingsFragment() { } scheduleEnd?.setOnPreferenceClickListener { - UiUtil.showTimePicker(parentFragmentManager){ + UiUtil.showTimePicker(parentFragmentManager, preferences){ val hr = it.get(Calendar.HOUR_OF_DAY) val mn = it.get(Calendar.MINUTE) val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index 31f89a46..3eb2dd1a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -10,6 +10,7 @@ import android.content.ClipboardManager import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.content.SharedPreferences import android.net.Uri import android.os.Build import android.text.Editable @@ -413,7 +414,7 @@ object UiUtil { 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() currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000 val date = Calendar.getInstance() @@ -430,38 +431,35 @@ object UiUtil { datePicker.addOnPositiveButtonClickListener{ date.timeInMillis = it - - val timepicker = MaterialTimePicker.Builder() - .setTimeFormat(TimeFormat.CLOCK_24H) - .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 + showTimePicker(fragmentManager, preferences) { chosenTime -> + date[Calendar.HOUR_OF_DAY] = chosenTime[Calendar.HOUR_OF_DAY] + date[Calendar.MINUTE] = chosenTime[Calendar.MINUTE] onSubmit(date) } - timepicker.show(fragmentManager, "timepicker") } 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() currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000 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() .setTimeFormat(TimeFormat.CLOCK_24H) - .setHour(currentDate.get(Calendar.HOUR_OF_DAY)) - .setMinute(currentDate.get(Calendar.MINUTE)) + .setHour(hour) + .setMinute(minute) .build() timepicker.addOnPositiveButtonClickListener{ date[Calendar.HOUR_OF_DAY] = timepicker.hour date[Calendar.MINUTE] = timepicker.minute + preferences.edit().putString("latest_timepicker_date", "${timepicker.hour}:${timepicker.minute}").apply() onSubmit(date) } timepicker.show(fragmentManager, "timepicker") diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt index f0a93f36..562392a2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt @@ -151,8 +151,7 @@ class NewPipeUtil(context: Context) { fun getChannelData(url: String, progress: (pagedResults: MutableList) -> Unit) : Result> { try { - //TODO BROKEN FOR NOW - return Result.failure(Throwable()) + //return Result.failure(Throwable()) val req = ChannelInfo.getInfo(ServiceList.YouTube, url) println(Gson().toJson(req)) val items = mutableListOf() diff --git a/app/src/main/res/xml-v25/shortcuts.xml b/app/src/main/res/xml-v25/shortcuts.xml index f514e2f3..1b0c5b12 100644 --- a/app/src/main/res/xml-v25/shortcuts.xml +++ b/app/src/main/res/xml-v25/shortcuts.xml @@ -49,17 +49,15 @@ + android:shortcutShortLabel="@string/terminal" + android:shortcutDisabledMessage="@string/terminal"> - - + android:targetClass="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">