fixed bugs with custom command and other small things

This commit is contained in:
Denis Çerri 2023-03-04 18:13:22 +01:00
parent 895476aa01
commit 5fed145313
No known key found for this signature in database
GPG key ID: 95C43D517D830350
24 changed files with 765 additions and 600 deletions

View file

@ -320,7 +320,7 @@
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
<map> <map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/content_paste/baseline_content_paste_24.xml" /> <entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/content_copy/baseline_content_copy_24.xml" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>
@ -330,7 +330,7 @@
</option> </option>
<option name="values"> <option name="values">
<map> <map>
<entry key="outputName" value="ic_clipboard" /> <entry key="outputName" value="ic_copy" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" /> <entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" />
</map> </map>
</option> </option>

View file

@ -1,144 +0,0 @@
package com.deniscerri.ytdlnis.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class CancelledDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, CancelledDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private var fileUtil: FileUtil
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
fileUtil = FileUtil()
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.queued_download_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.queued_download_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote.text = item.format.format_note
val codec = card.findViewById<TextView>(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
val fileSize = card.findViewById<TextView>(R.id.file_size)
fileSize.text = fileUtil.convertFileSize(item.format.filesize)
val downloadStartTime = card.findViewById<TextView>(R.id.time)
val time = item.downloadStartTime
if (time == 0L) {
downloadStartTime.visibility = View.GONE
} else {
downloadStartTime.visibility = View.VISIBLE
val cal = Calendar.getInstance()
val date = Date(time * 1000L)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
downloadStartTime.text = "$day $month $year - $timeString"
}
// CANCEL BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.queued_download_cancel)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
cancelButton.setOnClickListener {
onItemClickListener.onQueuedCancelClick(item.id)
}
}
interface OnItemClickListener {
fun onQueuedCancelClick(itemID: Long)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.url == newItem.url && oldItem.format.format_id == newItem.format.format_id
}
}
}
}

View file

@ -1,39 +1,33 @@
package com.deniscerri.ytdlnis.adapter package com.deniscerri.ytdlnis.adapter
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.graphics.Color import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadVideoFragment
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.io.File
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class ErroredDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ErroredDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, GenericDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private var fileUtil: FileUtil private var fileUtil: FileUtil
@ -47,40 +41,43 @@ class ErroredDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView val cardView: MaterialCardView
init { init {
cardView = itemView.findViewById(R.id.queued_download_card_view) cardView = itemView.findViewById(R.id.download_card_view)
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context) val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.queued_download_card, parent, false) .inflate(R.layout.generic_download_card, parent, false)
return ViewHolder(cardView, onItemClickListener) return ViewHolder(cardView, onItemClickListener)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
val card = holder.cardView val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ---------------------------------- // TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title) val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title var title = item!!.title
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title itemTitle.text = title
// TITLE ----------------------------------
val itemUrl = card.findViewById<TextView>(R.id.url)
itemUrl.text = item.url
val type = card.findViewById<TextView>(R.id.type)
type!!.text = item.type.toString().uppercase()
val formatNote = card.findViewById<TextView>(R.id.format_note) val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote.text = item.format.format_note if (item.type == DownloadViewModel.Type.command){
formatNote.visibility = View.GONE
}else{
formatNote.text = item.format.format_note
}
val codec = card.findViewById<TextView>(R.id.codec) val codec = card.findViewById<TextView>(R.id.codec)
val codecText = val codecText =
if (item.format.encoding != "") { if (item.format.encoding != "") {
@ -98,35 +95,34 @@ class ErroredDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
} }
val fileSize = card.findViewById<TextView>(R.id.file_size) val fileSize = card.findViewById<TextView>(R.id.file_size)
fileSize.text = fileUtil.convertFileSize(item.format.filesize) val fileSizeReadable = fileUtil.convertFileSize(item.format.filesize)
val downloadStartTime = card.findViewById<TextView>(R.id.time) if (fileSizeReadable == "?") fileSize.visibility = View.GONE
val time = item.downloadStartTime else fileSize.text = fileSizeReadable
if (time == 0L) {
downloadStartTime.visibility = View.GONE // ACTION BUTTON ----------------------------------
} else { val actionButton = card.findViewById<MaterialButton>(R.id.action_button)
downloadStartTime.visibility = View.VISIBLE if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null)
val cal = Calendar.getInstance()
val date = Date(time * 1000L) when(item.status){
cal.time = date DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
val day = cal[Calendar.DAY_OF_MONTH] DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()) else -> actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
downloadStartTime.text = "$day $month $year - $timeString"
} }
// CANCEL BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.queued_download_cancel)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
cancelButton.setOnClickListener {
onItemClickListener.onQueuedCancelClick(item.id) actionButton.setOnClickListener {
onItemClickListener.onActionButtonClick(item.id)
}
card.setOnClickListener {
onItemClickListener.onCardClick(item.id)
} }
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onQueuedCancelClick(itemID: Long) fun onActionButtonClick(itemID: Long)
fun onCardClick(itemID: Long)
} }
companion object { companion object {

View file

@ -124,8 +124,10 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
} }
if (item.type == DownloadViewModel.Type.audio) { if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music) if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
} else { } else if (item.type == DownloadViewModel.Type.video) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video) if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
}else{
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_terminal)
} }
if (btn.hasOnClickListeners()) btn.setOnClickListener(null) if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
if (checkedItems.contains(item.id)) { if (checkedItems.contains(item.id)) {

View file

@ -1,144 +0,0 @@
package com.deniscerri.ytdlnis.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, QueuedDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private var fileUtil: FileUtil
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
fileUtil = FileUtil()
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.queued_download_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.queued_download_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote.text = item.format.format_note
val codec = card.findViewById<TextView>(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
val fileSize = card.findViewById<TextView>(R.id.file_size)
fileSize.text = fileUtil.convertFileSize(item.format.filesize)
val downloadStartTime = card.findViewById<TextView>(R.id.time)
val time = item.downloadStartTime
if (time == 0L) {
downloadStartTime.visibility = View.GONE
} else {
downloadStartTime.visibility = View.VISIBLE
val cal = Calendar.getInstance()
val date = Date(time * 1000L)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
downloadStartTime.text = "$day $month $year - $timeString"
}
// CANCEL BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.queued_download_cancel)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
cancelButton.setOnClickListener {
onItemClickListener.onQueuedCancelClick(item.id)
}
}
interface OnItemClickListener {
fun onQueuedCancelClick(itemID: Long)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.url == newItem.url && oldItem.format.format_id == newItem.format.format_id
}
}
}
}

View file

@ -47,11 +47,11 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
val content = card.findViewById<TextView>(R.id.content) val content = card.findViewById<TextView>(R.id.content)
content.text = item?.content content.text = item?.content
val check = card.findViewById<Button>(R.id.check) // val check = card.findViewById<Button>(R.id.check)
//
check.setOnClickListener { // check.setOnClickListener {
onItemClickListener.onSelected(item!!) // onItemClickListener.onSelected(item!!)
} // }
title.setOnClickListener { title.setOnClickListener {
onItemClickListener.onItemClick(item!!) onItemClickListener.onItemClick(item!!)

View file

@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.ui
import android.app.Activity import android.app.Activity
import android.content.* import android.content.*
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -13,7 +12,6 @@ import android.view.*
import android.view.View.* import android.view.View.*
import android.widget.* import android.widget.*
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -28,6 +26,7 @@ import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.facebook.shimmer.ShimmerFrameLayout import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
@ -64,7 +63,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
private var selectedObjects: ArrayList<HistoryItem>? = null private var selectedObjects: ArrayList<HistoryItem>? = null
private var deleteFab: ExtendedFloatingActionButton? = null private var deleteFab: ExtendedFloatingActionButton? = null
private var fileUtil: FileUtil? = null private var fileUtil: FileUtil? = null
private var uiUtil: UiUtil? = null
private var _binding : FragmentHistoryBinding? = null private var _binding : FragmentHistoryBinding? = null
override fun onCreateView( override fun onCreateView(
@ -91,6 +90,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
websiteGroup = view.findViewById(R.id.website_chip_group) websiteGroup = view.findViewById(R.id.website_chip_group)
deleteFab = view.findViewById(R.id.delete_selected_fab) deleteFab = view.findViewById(R.id.delete_selected_fab)
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(FileUtil())
deleteFab?.tag = "deleteSelected" deleteFab?.tag = "deleteSelected"
deleteFab?.setOnClickListener{ deleteFab?.setOnClickListener{
removeSelectedItems() removeSelectedItems()
@ -144,7 +144,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
initChips() initChips()
} }
fun scrollToTop() { private fun scrollToTop() {
recyclerView!!.scrollToPosition(0) recyclerView!!.scrollToPosition(0)
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
(topAppBar!!.parent as AppBarLayout).setExpanded( (topAppBar!!.parent as AppBarLayout).setExpanded(
@ -372,41 +372,13 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
deleteDialog.show() deleteDialog.show()
} }
private fun copyLinkToClipBoard(url: String) {
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(url: String) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
if (bottomSheet != null) bottomSheet!!.hide()
startActivity(i)
}
private fun openFileIntent(downloadPath: String) { override fun onCardClick(itemID: Long, isPresent: Boolean) {
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(videoID: Long, isPresent: Boolean) {
bottomSheet = BottomSheetDialog(fragmentContext!!) bottomSheet = BottomSheetDialog(fragmentContext!!)
bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet!!.setContentView(R.layout.history_item_details_bottom_sheet) bottomSheet!!.setContentView(R.layout.history_item_details_bottom_sheet)
val video = findItem(videoID) val video = findItem(itemID)
val title = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_title) val title = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = video!!.title title!!.text = video!!.title
val author = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_author) val author = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_author)
@ -414,23 +386,23 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
val link = bottomSheet!!.findViewById<Button>(R.id.bottom_sheet_link) val link = bottomSheet!!.findViewById<Button>(R.id.bottom_sheet_link)
val url = video.url val url = video.url
link!!.text = url link!!.text = url
link.tag = videoID link.tag = itemID
link.setOnClickListener{ link.setOnClickListener{
openLinkIntent(video.url) uiUtil!!.openLinkIntent(requireContext(), video.url, bottomSheet)
} }
link.setOnLongClickListener{ link.setOnLongClickListener{
copyLinkToClipBoard(video.url) uiUtil!!.copyLinkToClipBoard(requireContext(), video.url, bottomSheet)
true true
} }
val remove = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_remove_button) val remove = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = videoID remove!!.tag = itemID
remove.setOnClickListener{ remove.setOnClickListener{
removeItem(video) removeItem(video)
} }
val openFile = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_open_file_button) val openFile = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.tag = videoID openFile!!.tag = itemID
openFile.setOnClickListener{ openFile.setOnClickListener{
openFileIntent(video.downloadPath) uiUtil!!.openFileIntent(requireContext(), video.downloadPath)
} }
if (!isPresent) openFile.visibility = GONE if (!isPresent) openFile.visibility = GONE
bottomSheet!!.show() bottomSheet!!.show()
@ -440,8 +412,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
) )
} }
override fun onCardSelect(videoID: Long, isChecked: Boolean) { override fun onCardSelect(itemID: Long, isChecked: Boolean) {
val item = findItem(videoID) val item = findItem(itemID)
if (isChecked) selectedObjects!!.add(item!!) if (isChecked) selectedObjects!!.add(item!!)
else selectedObjects!!.remove(item) else selectedObjects!!.remove(item)
if (selectedObjects!!.size > 1) { if (selectedObjects!!.size > 1) {

View file

@ -1,55 +1,50 @@
package com.deniscerri.ytdlnis.ui.downloadqueue package com.deniscerri.ytdlnis.ui.downloadqueue
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.DialogInterface
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.view.Window
import android.widget.AdapterView.OnItemClickListener import android.widget.AdapterView.OnItemClickListener
import android.widget.ArrayAdapter import android.widget.Button
import android.widget.AutoCompleteTextView import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.adapter.CancelledDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.downloadcard.FormatSelectionBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.OnFormatClickListener
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout import org.w3c.dom.Text
import kotlinx.coroutines.launch import java.sql.Date
import java.sql.Timestamp
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class CancelledDownloadsFragment() : Fragment(), CancelledDownloadAdapter.OnItemClickListener, OnItemClickListener { class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var cancelledRecyclerView : RecyclerView private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var cancelledDownloads : CancelledDownloadAdapter private lateinit var cancelledDownloads : GenericDownloadAdapter
lateinit var downloadItem: DownloadItem private lateinit var items : List<DownloadItem>
private lateinit var fileUtil: FileUtil
private lateinit var uiUtil : UiUtil
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -60,6 +55,9 @@ class CancelledDownloadsFragment() : Fragment(), CancelledDownloadAdapter.OnItem
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf()
fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
} }
@ -67,7 +65,7 @@ class CancelledDownloadsFragment() : Fragment(), CancelledDownloadAdapter.OnItem
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
cancelledDownloads = cancelledDownloads =
CancelledDownloadAdapter( GenericDownloadAdapter(
this, this,
requireActivity() requireActivity()
) )
@ -83,17 +81,112 @@ class CancelledDownloadsFragment() : Fragment(), CancelledDownloadAdapter.OnItem
} }
downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) { downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) {
items = it
cancelledDownloads.submitList(it) cancelledDownloads.submitList(it)
} }
} }
override fun onQueuedCancelClick(itemID: Long) { override fun onActionButtonClick(itemID: Long) {
TODO("Not yet implemented") val item = items.find { it.id == itemID }
if (item != null){
downloadViewModel.queueDownloads(listOf(item))
}else{
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
}
} }
override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { override fun onCardClick(itemID: Long) {
TODO("Not yet implemented") val item = items.find { it.id == itemID } ?: return
val bottomSheet = BottomSheetDialog(requireContext())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.download_details_bottom_sheet)
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = item.title
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
author!!.text = item.author
val type = bottomSheet.findViewById<TextView>(R.id.type)
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val scheduledTime = bottomSheet.findViewById<LinearLayout>(R.id.scheduled_time_linear)
type!!.text = item.type.toString().uppercase()
if (item.format.format_note == "?") formatNote!!.visibility = View.GONE
else formatNote!!.text = item.format.format_note
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
codec.text = codecText
}
val fileSizeReadable = fileUtil.convertFileSize(item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
link!!.text = item.url
link.tag = itemID
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
true
}
if (item.downloadStartTime == 0L){
scheduledTime!!.visibility = View.GONE
}else{
val time = bottomSheet.findViewById<TextView>(R.id.time)
val cal = Calendar.getInstance()
val date = Date(item.downloadStartTime * 1000L)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
val formattedTime = "$day $month $year - $timeString"
time!!.text = formattedTime
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = itemID
remove.setOnClickListener{
removeItem(item, bottomSheet)
}
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
} }
private fun removeItem(item: DownloadItem, bottomSheet: BottomSheetDialog?){
bottomSheet?.hide()
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
downloadViewModel.deleteDownload(item)
}
deleteDialog.show()
}
} }

View file

@ -2,37 +2,14 @@ package com.deniscerri.ytdlnis.ui.downloadqueue
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.*
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class DownloadQueueActivity : AppCompatActivity(){ class DownloadQueueActivity : AppCompatActivity(){

View file

@ -1,56 +1,49 @@
package com.deniscerri.ytdlnis.ui.downloadqueue package com.deniscerri.ytdlnis.ui.downloadqueue
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.DialogInterface
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener import android.widget.AdapterView.OnItemClickListener
import android.widget.ArrayAdapter import android.widget.Button
import android.widget.AutoCompleteTextView import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.adapter.ErroredDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.downloadcard.FormatSelectionBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.OnFormatClickListener
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout import java.io.File
import kotlinx.coroutines.launch import java.sql.Date
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class ErroredDownloadsFragment() : Fragment(), ErroredDownloadAdapter.OnItemClickListener, OnItemClickListener { class ErroredDownloadsFragment() : Fragment(), GenericDownloadAdapter.OnItemClickListener, OnItemClickListener {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var erroredRecyclerView : RecyclerView private lateinit var erroredRecyclerView : RecyclerView
private lateinit var erroredDownloads : ErroredDownloadAdapter private lateinit var erroredDownloads : GenericDownloadAdapter
lateinit var downloadItem: DownloadItem private lateinit var items : List<DownloadItem>
private lateinit var fileUtil: FileUtil
private lateinit var uiUtil : UiUtil
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -60,6 +53,9 @@ class ErroredDownloadsFragment() : Fragment(), ErroredDownloadAdapter.OnItemClic
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf()
fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
} }
@ -67,7 +63,7 @@ class ErroredDownloadsFragment() : Fragment(), ErroredDownloadAdapter.OnItemClic
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
erroredDownloads = erroredDownloads =
ErroredDownloadAdapter( GenericDownloadAdapter(
this, this,
requireActivity() requireActivity()
) )
@ -83,14 +79,110 @@ class ErroredDownloadsFragment() : Fragment(), ErroredDownloadAdapter.OnItemClic
} }
downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) { downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) {
items = it
erroredDownloads.submitList(it) erroredDownloads.submitList(it)
} }
} }
override fun onQueuedCancelClick(itemID: Long) { override fun onActionButtonClick(itemID: Long) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun onCardClick(itemID: Long) {
val item = items.find { it.id == itemID } ?: return
val bottomSheet = BottomSheetDialog(requireContext())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.download_details_bottom_sheet)
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = item.title
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
author!!.text = item.author
val type = bottomSheet.findViewById<TextView>(R.id.type)
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val scheduledTime = bottomSheet.findViewById<LinearLayout>(R.id.scheduled_time_linear)
type!!.text = item.type.toString().uppercase()
if (item.format.format_note == "?") formatNote!!.visibility = View.GONE
else formatNote!!.text = item.format.format_note
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
codec.text = codecText
}
val fileSizeReadable = fileUtil.convertFileSize(item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
link!!.text = item.url
link.tag = itemID
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
true
}
if (item.downloadStartTime == 0L){
scheduledTime!!.visibility = View.GONE
}else{
val time = bottomSheet.findViewById<TextView>(R.id.time)
val cal = Calendar.getInstance()
val date = Date(item.downloadStartTime * 1000L)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
val formattedTime = "$day $month $year - $timeString"
time!!.text = formattedTime
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = itemID
remove.setOnClickListener{
removeItem(item, bottomSheet)
}
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
private fun removeItem(item: DownloadItem, bottomSheet: BottomSheetDialog?){
bottomSheet?.hide()
val deleteDialog = MaterialAlertDialogBuilder(requireContext()!!)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
downloadViewModel.deleteDownload(item)
}
deleteDialog.show()
}
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") TODO("Not yet implemented")
} }

View file

@ -1,22 +1,16 @@
package com.deniscerri.ytdlnis.ui.downloadqueue package com.deniscerri.ytdlnis.ui.downloadqueue
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.view.Window
import android.widget.ArrayAdapter import android.widget.Button
import android.widget.AutoCompleteTextView import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -25,35 +19,35 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.downloadcard.FormatSelectionBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.OnFormatClickListener
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.sql.Date
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickListener, OnClickListener { class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var queuedRecyclerView : RecyclerView private lateinit var queuedRecyclerView : RecyclerView
private lateinit var queuedDownloads : QueuedDownloadAdapter private lateinit var queuedDownloads : GenericDownloadAdapter
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var items : List<DownloadItem>
private lateinit var fileUtil: FileUtil
private lateinit var uiUtil: UiUtil
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -65,6 +59,9 @@ class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickL
activity = getActivity() activity = getActivity()
notificationUtil = NotificationUtil(requireContext()) notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf()
fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
} }
@ -72,7 +69,7 @@ class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickL
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
queuedDownloads = queuedDownloads =
QueuedDownloadAdapter( GenericDownloadAdapter(
this, this,
requireActivity() requireActivity()
) )
@ -88,11 +85,12 @@ class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickL
} }
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) { downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) {
items = it
queuedDownloads.submitList(it) queuedDownloads.submitList(it)
} }
} }
override fun onQueuedCancelClick(itemID: Long) { override fun onActionButtonClick(itemID: Long) {
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteDownload(downloadViewModel.getItemByID(itemID)) downloadViewModel.deleteDownload(downloadViewModel.getItemByID(itemID))
@ -101,6 +99,86 @@ class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickL
cancelDownload(itemID) cancelDownload(itemID)
} }
override fun onCardClick(itemID: Long) {
val item = items.find { it.id == itemID } ?: return
val bottomSheet = BottomSheetDialog(requireContext())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.download_details_bottom_sheet)
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = item.title
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
author!!.text = item.author
val type = bottomSheet.findViewById<TextView>(R.id.type)
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val scheduledTime = bottomSheet.findViewById<LinearLayout>(R.id.scheduled_time_linear)
type!!.text = item.type.toString().uppercase()
if (item.format.format_note == "?") formatNote!!.visibility = View.GONE
else formatNote!!.text = item.format.format_note
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
codec.text = codecText
}
val fileSizeReadable = fileUtil.convertFileSize(item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
link!!.text = item.url
link.tag = itemID
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
true
}
if (item.downloadStartTime == 0L){
scheduledTime!!.visibility = View.GONE
}else{
val time = bottomSheet.findViewById<TextView>(R.id.time)
val cal = Calendar.getInstance()
val date = Date(item.downloadStartTime)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
val formattedTime = "$day $month $year - $timeString"
time!!.text = formattedTime
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.visibility = View.GONE
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
private fun cancelDownload(itemID: Long){ private fun cancelDownload(itemID: Long){
val id = itemID.toInt() val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
@ -108,9 +186,4 @@ class QueuedDownloadsFragment() : Fragment(), QueuedDownloadAdapter.OnItemClickL
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
} }
override fun onClick(p0: View?) {
TODO("Not yet implemented")
}
} }

View file

@ -100,10 +100,8 @@ class TerminalActivity : AppCompatActivity() {
private fun startDownload(command: String?) { private fun startDownload(command: String?) {
var cmd: String = "" val cmd = if (command!!.contains("yt-dlp")) command.replace("yt-dlp", "")
if (command!!.contains("yt-dlp")) cmd = command.replace("yt-dlp", "") else command
else cmd = command
cmd = cmd.trim()
downloadID = System.currentTimeMillis().toInt() downloadID = System.currentTimeMillis().toInt()
@ -126,23 +124,20 @@ class TerminalActivity : AppCompatActivity() {
notify(downloadID, commandNotification) notify(downloadID, commandNotification)
} }
} }
val commandRegex = "(-\\S+)|(\\S+)"
val request = YoutubeDLRequest(emptyList()) val request = YoutubeDLRequest(emptyList())
val tempFolder = StringBuilder(context!!.cacheDir.absolutePath + """/${command}##terminal""") val tempFolder = StringBuilder(context!!.cacheDir.absolutePath + """/${System.currentTimeMillis()}##terminal""")
val tempFileDir = File(tempFolder.toString()) val tempFileDir = File(tempFolder.toString())
tempFileDir.delete() tempFileDir.delete()
tempFileDir.mkdir() tempFileDir.mkdir()
Log.e(TAG, cmd) request.addOption(
val m = Pattern.compile(commandRegex).matcher(cmd) "--config-locations",
while (m.find()) { File(cacheDir, "config${System.currentTimeMillis()}.txt").apply {
if (m.group(1) != null) { writeText(cmd)
request.addOption(m.group(1)!!) }.absolutePath
} else { )
request.addOption(m.group(2)!!)
}
}
request.addOption("-P", tempFileDir.absolutePath) request.addOption("-P", tempFileDir.absolutePath)

View file

@ -1,22 +1,28 @@
package com.deniscerri.ytdlnis.ui.more.downloadLogs package com.deniscerri.ytdlnis.ui.more.downloadLogs
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.FileObserver import android.os.FileObserver
import android.text.method.ScrollingMovementMethod
import android.view.View
import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.NestedScrollView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import java.io.File import java.io.File
class DownloadLogActivity : AppCompatActivity() { class DownloadLogActivity : AppCompatActivity() {
private lateinit var content: TextView private lateinit var content: TextView
private lateinit var contentScrollView : NestedScrollView private lateinit var contentScrollView : ScrollView
private lateinit var topAppBar: MaterialToolbar private lateinit var topAppBar: MaterialToolbar
private lateinit var observer: FileObserver private lateinit var observer: FileObserver
private lateinit var copyLog : ExtendedFloatingActionButton
var context: Context? = null var context: Context? = null
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
@ -30,8 +36,17 @@ class DownloadLogActivity : AppCompatActivity() {
} }
content = findViewById(R.id.content) content = findViewById(R.id.content)
content.movementMethod = ScrollingMovementMethod()
contentScrollView = findViewById(R.id.content_scrollview) contentScrollView = findViewById(R.id.content_scrollview)
copyLog = findViewById(R.id.copy_log)
copyLog.setOnClickListener {
val clipboard: ClipboardManager =
getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = ClipData.newPlainText("Download Log", content.text)
clipboard.setPrimaryClip(clip)
}
val path = intent.getStringExtra("path") val path = intent.getStringExtra("path")
if (path == null) { if (path == null) {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
@ -57,6 +72,7 @@ class DownloadLogActivity : AppCompatActivity() {
runOnUiThread{ runOnUiThread{
content.text = File(path).readText() content.text = File(path).readText()
content.scrollTo(0, content.height) content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
} }
} }
} }

View file

@ -1,8 +1,11 @@
package com.deniscerri.ytdlnis.util package com.deniscerri.ytdlnis.util
import android.app.Activity import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log import android.util.Log
@ -11,10 +14,12 @@ import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.FileProvider
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
@ -25,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import java.io.File
class UiUtil(private val fileUtil: FileUtil) { class UiUtil(private val fileUtil: FileUtil) {
fun populateFormatCard(formatCard : ConstraintLayout, chosenFormat: Format){ fun populateFormatCard(formatCard : ConstraintLayout, chosenFormat: Format){
@ -165,5 +171,34 @@ class UiUtil(private val fileUtil: FileUtil) {
ViewGroup.LayoutParams.MATCH_PARENT ViewGroup.LayoutParams.MATCH_PARENT
) )
} }
fun copyLinkToClipBoard(context: Context, url: String, bottomSheet: BottomSheetDialog?) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(context.getString(R.string.url), url)
clipboard.setPrimaryClip(clip)
bottomSheet?.hide()
Toast.makeText(context, context.getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
fun openLinkIntent(context: Context, url: String, bottomSheet: BottomSheetDialog?) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
bottomSheet?.hide()
context.startActivity(i)
}
fun openFileIntent(fragmentContext: Context, downloadPath: String) {
val file = File(downloadPath)
val uri = FileProvider.getUriForFile(
fragmentContext,
fragmentContext.packageName + ".fileprovider",
file
)
val mime = fragmentContext.contentResolver.getType(uri)
val i = Intent(Intent.ACTION_VIEW)
i.setDataAndType(uri, mime)
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragmentContext.startActivity(i)
}
} }

View file

@ -184,15 +184,13 @@ class DownloadWorker(
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s") request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
} }
DownloadViewModel.Type.command -> { DownloadViewModel.Type.command -> {
val commandRegex = "\"([^\"]*)\"|(-\\S+)" request.addOption(
val m = Pattern.compile(commandRegex).matcher(downloadItem.format.format_note) "--config-locations",
while (m.find()) { File(context.cacheDir, "config${System.currentTimeMillis()}.txt").apply {
if (m.group(1) != null) { writeText(downloadItem.format.format_note)
request.addOption(m.group(1)!!) }.absolutePath
} else { )
request.addOption(m.group(2)!!)
}
}
request.addOption("-P", tempFileDir.absolutePath) request.addOption("-P", tempFileDir.absolutePath)
} }

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?attr/colorAccent" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View file

@ -23,26 +23,37 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView <ScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/content_scrollview" android:id="@+id/content_scrollview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="150dp" android:paddingBottom="150dp"
> >
<TextView <TextView
android:id="@+id/content" android:id="@+id/content"
android:padding="10dp" android:padding="10dp"
android:scrollHorizontally="true"
android:fontFamily="monospace"
android:textSize="15sp" android:textSize="15sp"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:textIsSelectable="true" android:textIsSelectable="true"
android:gravity="bottom"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</androidx.core.widget.NestedScrollView> </ScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:contentDescription="@string/copy_log"
android:text="@string/copy_log"
app:icon="@drawable/ic_copy"/>
<include layout="@layout/history_no_results" <include layout="@layout/history_no_results"
android:visibility="gone" /> android:visibility="gone" />

View file

@ -30,7 +30,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textMultiLine" android:inputType="textMultiLine"
android:gravity="start" android:gravity="start"
android:maxLines="5" /> android:maxLines="10" />
<ScrollView <ScrollView
android:id="@+id/custom_command_scrollview" android:id="@+id/custom_command_scrollview"
@ -44,6 +44,7 @@
android:id="@+id/custom_command_output" android:id="@+id/custom_command_output"
android:layout_width="match_parent" android:layout_width="match_parent"
android:textIsSelectable="true" android:textIsSelectable="true"
android:fontFamily="monospace"
android:gravity="bottom" android:gravity="bottom"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />

View file

@ -15,7 +15,7 @@
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/divider" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -26,28 +26,28 @@
android:layout_marginEnd="15dp" android:layout_marginEnd="15dp"
android:maxLines="2" android:maxLines="2"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/divider" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" /> app:layout_constraintTop_toBottomOf="@+id/title" />
<View <!-- <View-->
android:id="@+id/divider" <!-- android:id="@+id/divider"-->
style="@style/Divider.Vertical" <!-- style="@style/Divider.Vertical"-->
android:layout_height="0dp" <!-- android:layout_height="0dp"-->
app:layout_constraintBottom_toBottomOf="parent" <!-- app:layout_constraintBottom_toBottomOf="parent"-->
app:layout_constraintEnd_toStartOf="@+id/check" <!-- app:layout_constraintEnd_toStartOf="@+id/check"-->
app:layout_constraintStart_toEndOf="@+id/title" <!-- app:layout_constraintStart_toEndOf="@+id/title"-->
app:layout_constraintTop_toTopOf="parent" /> <!-- app:layout_constraintTop_toTopOf="parent" />-->
<RadioButton <!-- <RadioButton-->
android:id="@+id/check" <!-- android:id="@+id/check"-->
android:layout_width="wrap_content" <!-- android:layout_width="wrap_content"-->
android:layout_height="wrap_content" <!-- android:layout_height="wrap_content"-->
android:gravity="center" <!-- android:gravity="center"-->
android:minWidth="0dp" <!-- android:minWidth="0dp"-->
app:layout_constraintBottom_toBottomOf="parent" <!-- app:layout_constraintBottom_toBottomOf="parent"-->
app:layout_constraintEnd_toEndOf="parent" <!-- app:layout_constraintEnd_toEndOf="parent"-->
app:layout_constraintTop_toTopOf="parent" <!-- app:layout_constraintTop_toTopOf="parent"-->
app:layout_constraintVertical_bias="0.558" /> <!-- app:layout_constraintVertical_bias="0.558" />-->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/downloads_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:textSize="25sp"
android:textColor="?attr/colorOnSurface"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<TextView
android:id="@+id/bottom_sheet_author"
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:layout_marginStart="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/url">
<TextView
android:id="@+id/type"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorAccent"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/format_note"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<LinearLayout
android:id="@+id/scheduled_time_linear"
android:layout_width="match_parent"
android:layout_margin="20dp"
android:orientation="horizontal"
android:layout_height="wrap_content">
<Button
android:id="@+id/bottomsheet_schedule_button"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_clock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_marginEnd="5dp"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<Button
style="@style/Widget.Material3.Button.TextButton.Icon"
android:id="@+id/bottom_sheet_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_gravity="center"
app:icon="@drawable/ic_link"
android:text="@string/app_name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="end"
android:layout_margin="10dp"
android:orientation="horizontal">
<Button
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:id="@+id/bottomsheet_remove_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
android:text="@string/Remove" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -22,7 +22,7 @@
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
android:maxLines="4" android:maxLines="10"
android:inputType="textMultiLine" android:inputType="textMultiLine"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>

View file

@ -9,30 +9,21 @@
<RelativeLayout <RelativeLayout
android:id="@+id/queued_download_relative_layout" android:id="@+id/queued_download_relative_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="H,3:1"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/queued_download_card_view" android:id="@+id/download_card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:cardCornerRadius="10dp" app:cardCornerRadius="10dp"
app:cardMaxElevation="12dp" app:cardMaxElevation="12dp"
android:checkable="true"
app:strokeWidth="0dp" app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
android:layout_margin="10dp"> android:layout_margin="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -42,53 +33,46 @@
android:id="@+id/title" android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingStart="10dp" android:paddingStart="10dp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:paddingBottom="10dp" android:textSize="16sp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/queued_download_cancel" app:layout_constraintEnd_toStartOf="@+id/action_button"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" /> app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/queued_download_cancel" android:id="@+id/action_button"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp" android:layout_width="55dp"
android:layout_height="55dp" android:layout_height="55dp"
android:layout_margin="15dp" android:layout_margin="10dp"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24" app:icon="@drawable/ic_refresh"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/format_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="15sp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintVertical_bias="1.0" />
<LinearLayout <LinearLayout
android:id="@+id/linearLayout3" android:id="@+id/linearLayout3"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -96,9 +80,41 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="10dp" android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@+id/format_note"> app:layout_constraintTop_toBottomOf="@+id/url">
<TextView
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/format_note"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView <TextView
android:id="@+id/codec" android:id="@+id/codec"
@ -132,22 +148,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/time"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> </LinearLayout>

View file

@ -58,7 +58,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:endIconMode="custom" app:endIconMode="custom"
app:endIconDrawable="@drawable/ic_plus" app:endIconDrawable="@drawable/ic_plus"
android:hint="@string/create_template" android:hint="@string/create_shortcut"
style="@style/Widget.Material3.TextInputLayout.FilledBox"> style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText

View file

@ -193,4 +193,8 @@
<string name="shortcuts_desc">Use commonly used commands to create templates</string> <string name="shortcuts_desc">Use commonly used commands to create templates</string>
<string name="cancelled">Cancelled</string> <string name="cancelled">Cancelled</string>
<string name="errored">Errored</string> <string name="errored">Errored</string>
<string name="copy_log">Copy log</string>
<string name="create_shortcut">Create Shortcut</string>
<string name="error_restarting_download">Error trying to restart the download</string>
<string name="delete_temp_file_too">Delete temporary files from the device</string>
</resources> </resources>