added swipe gestures in history screen and notifications when downloads are finished and errored with actions open/share/open logs
This commit is contained in:
parent
f3faaeadb7
commit
d46fd82f72
9 changed files with 248 additions and 6 deletions
|
|
@ -73,6 +73,10 @@ Join our [Telegram Channel](https://t.me/ytdlnis) or [Discord](https://discord.g
|
|||
The App's package name is com.deniscerri.ytdl
|
||||
|
||||
## 📝 Help Translate on Weblate
|
||||
<a href="https://hosted.weblate.org/engage/ytdlnis/">
|
||||
<img src="https://hosted.weblate.org/widgets/ytdlnis/-/strings/open-graph.png" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/ytdlnis/">
|
||||
<img src="https://hosted.weblate.org/widgets/ytdlnis/-/multi-auto.svg" alt="Translation status" />
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@
|
|||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.SharedDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.OpenDownloadNotificationReceiver" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.deniscerri.ytdlnis.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
||||
class OpenDownloadNotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, intent: Intent) {
|
||||
val message = intent.getStringExtra("open")
|
||||
val path = intent.getStringExtra("path")
|
||||
val notificationId = intent.getIntExtra("notificationID", 0)
|
||||
if (message != null && path != null) {
|
||||
if (notificationId != 0) NotificationUtil(c).cancelDownloadNotification(notificationId)
|
||||
val uiUtil = UiUtil(FileUtil())
|
||||
uiUtil.openFileIntent(c, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.deniscerri.ytdlnis.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
|
||||
class SharedDownloadNotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, intent: Intent) {
|
||||
val message = intent.getStringExtra("share")
|
||||
val path = intent.getStringExtra("path")
|
||||
val notificationId = intent.getIntExtra("notificationID", 0)
|
||||
if (message != null && path != null) {
|
||||
if (notificationId != 0) NotificationUtil(c).cancelDownloadNotification(notificationId)
|
||||
val uiUtil = UiUtil(FileUtil())
|
||||
uiUtil.shareFileIntent(c, listOf(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import android.content.Context
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
|
@ -23,6 +25,7 @@ import androidx.core.view.forEach
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
|
|
@ -43,7 +46,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
|||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
|
|
@ -115,6 +121,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
)
|
||||
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
||||
recyclerView?.adapter = historyAdapter
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
|
||||
val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
val displayMetrics: DisplayMetrics = requireContext().resources.displayMetrics
|
||||
|
|
@ -386,7 +394,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
}
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
historyViewModel.delete(item, deleteFile[0])
|
||||
historyViewModel.delete(item, deleteFile[0])
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
|
@ -491,7 +499,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
redownload.setOnLongClickListener {
|
||||
bottomSheet!!.cancel()
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromHistory(item), item.type, null)
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromHistory(item), item.type, downloadViewModel.createDownloadItemFromHistory(item))
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
true
|
||||
}
|
||||
|
|
@ -614,6 +622,72 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
selectedObjects?.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = historyList!![position]
|
||||
historyAdapter!!.notifyItemChanged(position)
|
||||
removeItem(deletedItem!!)
|
||||
}
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
val item = historyList!![position]!!
|
||||
historyAdapter!!.notifyItemChanged(position)
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromHistory(item), item.type, downloadViewModel.createDownloadItemFromHistory(item))
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildDraw(
|
||||
c: Canvas,
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
dX: Float,
|
||||
dY: Float,
|
||||
actionState: Int,
|
||||
isCurrentlyActive: Boolean
|
||||
) {
|
||||
RecyclerViewSwipeDecorator.Builder(
|
||||
requireContext(),
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
.addSwipeLeftBackgroundColor(Color.RED)
|
||||
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
||||
.addSwipeRightBackgroundColor(
|
||||
MaterialColors.getColor(
|
||||
requireContext(),
|
||||
R.attr.colorOnSurfaceInverse, Color.TRANSPARENT
|
||||
)
|
||||
)
|
||||
.addSwipeRightActionIcon(R.drawable.ic_refresh)
|
||||
.create()
|
||||
.decorate()
|
||||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "historyFragment"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@ import android.os.Build
|
|||
import androidx.core.app.NotificationCompat
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.OpenDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.SharedDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogActivity
|
||||
|
||||
class NotificationUtil(var context: Context) {
|
||||
private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
private val finishedDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
||||
|
||||
fun createNotificationChannel() {
|
||||
|
|
@ -38,6 +42,14 @@ class NotificationUtil(var context: Context) {
|
|||
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
|
||||
//finished or errored downloads
|
||||
name = context.getString(R.string.finished_download_notification_channel_name)
|
||||
description =
|
||||
context.getString(R.string.finished_download_notification_channel_description)
|
||||
channel = NotificationChannel(DOWNLOAD_FINISHED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
||||
channel.description = description
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +57,7 @@ class NotificationUtil(var context: Context) {
|
|||
when(channel) {
|
||||
DOWNLOAD_SERVICE_CHANNEL_ID -> { return downloadNotificationBuilder}
|
||||
COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID -> { return commandDownloadNotificationBuilder }
|
||||
DOWNLOAD_FINISHED_CHANNEL_ID -> { return finishedDownloadNotificationBuilder }
|
||||
}
|
||||
return downloadNotificationBuilder
|
||||
}
|
||||
|
|
@ -89,6 +102,84 @@ class NotificationUtil(var context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
fun createDownloadFinished(title: String?,
|
||||
filepath: String?,
|
||||
channel: String
|
||||
) {
|
||||
val notificationBuilder = getBuilder(channel)
|
||||
|
||||
val openIntent = Intent(context, OpenDownloadNotificationReceiver::class.java)
|
||||
openIntent.putExtra("open", "")
|
||||
openIntent.putExtra("path", filepath)
|
||||
openIntent.putExtra("notificationID", DOWNLOAD_FINISHED_NOTIFICATION_ID)
|
||||
val openNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
1,
|
||||
openIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val shareIntent = Intent(context, SharedDownloadNotificationReceiver::class.java)
|
||||
shareIntent.putExtra("share", "")
|
||||
shareIntent.putExtra("path", filepath)
|
||||
openIntent.putExtra("notificationID", DOWNLOAD_FINISHED_NOTIFICATION_ID)
|
||||
val shareNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
1,
|
||||
shareIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${context.getString(R.string.downloaded)} $title")
|
||||
.setSmallIcon(R.drawable.ic_app_icon)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
R.drawable.ic_app_icon
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
if (filepath != null){
|
||||
notificationBuilder.addAction(0, context.getString(R.string.Open_File), openNotificationPendingIntent)
|
||||
notificationBuilder.addAction(0, context.getString(R.string.share), shareNotificationPendingIntent)
|
||||
}
|
||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
fun createDownloadErrored(title: String?,
|
||||
error: String?,
|
||||
logFilePath: String?,
|
||||
channel: String
|
||||
) {
|
||||
val notificationBuilder = getBuilder(channel)
|
||||
|
||||
val intent = Intent(context, DownloadLogActivity::class.java)
|
||||
intent.putExtra("path", logFilePath)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val errorPendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${context.getString(R.string.errored)}: $title")
|
||||
.setContentText(error)
|
||||
.setSmallIcon(R.drawable.ic_app_icon)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
R.drawable.ic_app_icon
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
if (logFilePath != null){
|
||||
notificationBuilder.addAction(0, context.getString(R.string.logs), errorPendingIntent)
|
||||
}
|
||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
fun updateDownloadNotification(
|
||||
id: Int,
|
||||
desc: String,
|
||||
|
|
@ -118,9 +209,9 @@ class NotificationUtil(var context: Context) {
|
|||
|
||||
companion object {
|
||||
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
|
||||
const val DOWNLOAD_NOTIFICATION_ID = 1
|
||||
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
||||
const val COMMAND_DOWNLOAD_NOTIFICATION_ID = 2
|
||||
const val DOWNLOAD_FINISHED_CHANNEL_ID = "3"
|
||||
const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3
|
||||
const val FILE_TRANSFER_CHANNEL_ID = "3"
|
||||
private const val PROGRESS_MAX = 100
|
||||
private const val PROGRESS_CURR = 0
|
||||
|
|
|
|||
|
|
@ -228,7 +228,12 @@ class UiUtil(private val fileUtil: FileUtil) {
|
|||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.setDataAndType(uri, mime)
|
||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
fragmentContext.startActivity(i)
|
||||
try {
|
||||
fragmentContext.startActivity(i)
|
||||
}catch (e: Exception){
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
fragmentContext.startActivity(i)
|
||||
}
|
||||
}
|
||||
|
||||
fun shareFileIntent(fragmentContext: Context, paths: List<String>){
|
||||
|
|
@ -251,7 +256,13 @@ class UiUtil(private val fileUtil: FileUtil) {
|
|||
type = "*/*"
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
}
|
||||
fragmentContext.startActivity(Intent.createChooser(shareIntent, null))
|
||||
try {
|
||||
fragmentContext.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}catch (e: Exception){
|
||||
val intent = Intent.createChooser(shareIntent, null)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
fragmentContext.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -309,6 +310,11 @@ class DownloadWorker(
|
|||
historyDao.insert(historyItem)
|
||||
}
|
||||
}
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPath.equals(context.getString(R.string.unfound_file))) null else finalPath,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
dao.delete(downloadItem.id)
|
||||
}
|
||||
|
|
@ -342,6 +348,13 @@ class DownloadWorker(
|
|||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
notificationUtil.createDownloadErrored(
|
||||
downloadItem.title, it.message,
|
||||
if (logDownloads) logFile.absolutePath else null,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
return Result.failure(
|
||||
Data.Builder().putString("output", it.toString()).build()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -238,4 +238,7 @@
|
|||
<string name="purple">Purple</string>
|
||||
<string name="yellow">Yellow</string>
|
||||
<string name="monochrome">Monochrome</string>
|
||||
<string name="downloaded">"Downloaded: "</string>
|
||||
<string name="finished_download_notification_channel_name">Finished Downloads</string>
|
||||
<string name="finished_download_notification_channel_description">Notification showing when the download has succesfully finished or errored out</string>
|
||||
</resources>
|
||||
Loading…
Reference in a new issue