added ability to edit command template & delete. Fixed logs not scrolling. Implemented import/export templates from clipboard. Implemented Error Items Log Button

This commit is contained in:
Denis Çerri 2023-03-04 22:57:10 +01:00
parent 5fed145313
commit 7d5f32b4bf
No known key found for this signature in database
GPG key ID: 95C43D517D830350
30 changed files with 331 additions and 110 deletions

View file

@ -6,6 +6,7 @@ plugins {
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt' id 'kotlin-kapt'
id "org.jetbrains.kotlin.plugin.serialization" version "1.7.21"
} }
def properties = new Properties() def properties = new Properties()
@ -175,5 +176,4 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
} }

View file

@ -23,6 +23,7 @@ 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 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.*
@ -106,7 +107,13 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
when(item.status){ when(item.status){
DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh) DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24) DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
else -> actionButton.setIconResource(R.drawable.ic_baseline_file_open_24) else -> {
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
val logFile = File(activity.filesDir.absolutePath + """/logs/${item.id} - ${item.title}##${item.type}##${item.format.format_id}.log""")
if (!logFile.exists()){
actionButton.visibility = View.GONE
}
}
} }

View file

@ -53,14 +53,19 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
// onItemClickListener.onSelected(item!!) // onItemClickListener.onSelected(item!!)
// } // }
title.setOnClickListener { card.setOnClickListener {
onItemClickListener.onItemClick(item!!) onItemClickListener.onItemClick(item!!)
} }
card.setOnLongClickListener {
onItemClickListener.onDelete(item!!); true
}
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onItemClick(commandTemplate: CommandTemplate) fun onItemClick(commandTemplate: CommandTemplate)
fun onSelected(commandTemplate: CommandTemplate) fun onSelected(commandTemplate: CommandTemplate)
fun onDelete(commandTemplate: CommandTemplate)
} }
companion object { companion object {

View file

@ -17,7 +17,10 @@ interface CommandTemplateDao {
fun getAllTemplatesLiveData() : LiveData<List<CommandTemplate>> fun getAllTemplatesLiveData() : LiveData<List<CommandTemplate>>
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC") @Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcuts() : LiveData<List<TemplateShortcut>> fun getAllShortcutsLiveData() : LiveData<List<TemplateShortcut>>
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcuts() : List<TemplateShortcut>
@Query("SELECT COUNT(id) FROM commandTemplates") @Query("SELECT COUNT(id) FROM commandTemplates")
fun getTotalNumber() : Int fun getTotalNumber() : Int

View file

@ -16,13 +16,13 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Queued'") @Query("SELECT * FROM downloads WHERE status='Queued'")
fun getQueuedDownloads() : LiveData<List<DownloadItem>> fun getQueuedDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Cancelled'") @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloads() : LiveData<List<DownloadItem>> fun getCancelledDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Error'") @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
fun getErroredDownloads() : LiveData<List<DownloadItem>> fun getErroredDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Processing'") @Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC")
fun getProcessingDownloads() : LiveData<List<DownloadItem>> fun getProcessingDownloads() : LiveData<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")

View file

@ -1,13 +1,14 @@
package com.deniscerri.ytdlnis.database.models package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "commandTemplates") @Entity(tableName = "commandTemplates")
data class CommandTemplate( data class CommandTemplate(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long, var id: Long,
val title: String, var title: String,
val content: String var content: String
) )

View file

@ -0,0 +1,9 @@
package com.deniscerri.ytdlnis.database.models
import kotlinx.serialization.Serializable
@Serializable
data class CommandTemplateExport(
val templates: List<CommandTemplate>,
val shortcuts: List<TemplateShortcut>
)

View file

@ -1,9 +1,10 @@
package com.deniscerri.ytdlnis.database.models package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "templateShortcuts") @Entity(tableName = "templateShortcuts")
data class TemplateShortcut( data class TemplateShortcut(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View file

@ -7,7 +7,7 @@ import com.deniscerri.ytdlnis.database.models.TemplateShortcut
class CommandTemplateRepository(private val commandDao: CommandTemplateDao) { class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
val items : LiveData<List<CommandTemplate>> = commandDao.getAllTemplatesLiveData() val items : LiveData<List<CommandTemplate>> = commandDao.getAllTemplatesLiveData()
val shortcuts : LiveData<List<TemplateShortcut>> = commandDao.getAllShortcuts() val shortcuts : LiveData<List<TemplateShortcut>> = commandDao.getAllShortcutsLiveData()
fun getAll() : List<CommandTemplate> { fun getAll() : List<CommandTemplate> {
return commandDao.getAllTemplates(); return commandDao.getAllTemplates();
@ -25,6 +25,10 @@ class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
commandDao.delete(item.id) commandDao.delete(item.id)
} }
fun getAllShortCuts() : List<TemplateShortcut> {
return commandDao.getAllShortcuts();
}
suspend fun insertShortcut(item: TemplateShortcut){ suspend fun insertShortcut(item: TemplateShortcut){
commandDao.insertShortcut(item.content) commandDao.insertShortcut(item.content)
} }

View file

@ -1,9 +1,15 @@
package com.deniscerri.ytdlnis.database.viewmodel package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application import android.app.Application
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.widget.Toast
import androidx.core.content.ContextCompat.getSystemService
import androidx.lifecycle.* import androidx.lifecycle.*
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
@ -11,13 +17,20 @@ import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.gson.Gson
import com.google.gson.JsonObject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class CommandTemplateViewModel(application: Application) : AndroidViewModel(application) { class CommandTemplateViewModel(private val application: Application) : AndroidViewModel(application) {
private val repository: CommandTemplateRepository private val repository: CommandTemplateRepository
val items: LiveData<List<CommandTemplate>> val items: LiveData<List<CommandTemplate>>
val shortcuts : LiveData<List<TemplateShortcut>> val shortcuts : LiveData<List<TemplateShortcut>>
private val jsonFormat = Json { prettyPrint = true }
init { init {
val dao = DBManager.getInstance(application).commandTemplateDao val dao = DBManager.getInstance(application).commandTemplateDao
@ -58,12 +71,53 @@ class CommandTemplateViewModel(application: Application) : AndroidViewModel(appl
repository.update(item) repository.update(item)
} }
fun importFromClipboard() = viewModelScope.launch(Dispatchers.IO) { suspend fun importFromClipboard() : Int {
val allTemplates = repository.getAll()
val allShortcuts = repository.getAllShortCuts()
var count = 0
val clipboard = application.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text.toString()
try{
jsonFormat.decodeFromString<CommandTemplateExport>(clip).run {
templates.filterNot {
allTemplates.contains(it)
}.run {
this.forEach {
repository.insert(it.copy(id=0))
count++
}
}
shortcuts.filterNot {
allShortcuts.contains(it)
}.run{
this.forEach {
repository.insertShortcut(it.copy(id=0))
count++
}
}
}
}catch (e: Exception){
e.printStackTrace()
}
return count
} }
fun exportToClipboard() {
fun exportToClipboard() = viewModelScope.launch(Dispatchers.IO) {
val allTemplates = repository.getAll()
val allShortcuts = repository.getAllShortCuts()
val output = jsonFormat.encodeToString(
CommandTemplateExport(
templates = allTemplates,
shortcuts = allShortcuts
)
)
val clipboard: ClipboardManager =
application.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(output)
} }
} }

View file

@ -105,7 +105,14 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
} }
fun addSearchQueryToHistory(query: String) = viewModelScope.launch(Dispatchers.IO) { fun addSearchQueryToHistory(query: String) = viewModelScope.launch(Dispatchers.IO) {
searchHistoryRepository.insert(query) val allQueries = searchHistoryRepository.getAll()
allQueries.filterNot {
it.query == query
}.run {
this.forEach {
searchHistoryRepository.insert(it.query)
}
}
} }
fun deleteAllSearchQueryHistory() = viewModelScope.launch(Dispatchers.IO){ fun deleteAllSearchQueryHistory() = viewModelScope.launch(Dispatchers.IO){

View file

@ -121,10 +121,10 @@ class ShareActivity : AppCompatActivity() {
private fun showDownloadSheet(it: ResultItem){ private fun showDownloadSheet(it: ResultItem){
if (sharedPreferences.getBoolean("download_card", true)){ if (sharedPreferences.getBoolean("download_card", true)){
val bottomSheet = DownloadBottomSheetDialog(it, DownloadViewModel.Type.video) val bottomSheet = DownloadBottomSheetDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
bottomSheet.show(supportFragmentManager, "downloadSingleSheet") bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
}else{ }else{
val downloadItem = downloadViewModel.createDownloadItemFromResult(it, DownloadViewModel.Type.video) val downloadItem = downloadViewModel.createDownloadItemFromResult(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
downloadViewModel.queueDownloads(listOf(downloadItem)) downloadViewModel.queueDownloads(listOf(downloadItem))
this.finish() this.finish()
} }

View file

@ -144,7 +144,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
}else if (resultViewModel.itemCount.value!! == 1){ }else if (resultViewModel.itemCount.value!! == 1){
if (sharedPreferences!!.getBoolean("download_card", true)){ if (sharedPreferences!!.getBoolean("download_card", true)){
if(it.size == 1 && !firstBoot && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){ if(it.size == 1 && !firstBoot && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
showSingleDownloadSheet(it[0], DownloadViewModel.Type.video) showSingleDownloadSheet(it[0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!))
} }
} }
}else{ }else{

View file

@ -109,16 +109,18 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
when(type) { when(type) {
Type.audio -> { Type.audio -> {
tabLayout.selectTab(tabLayout.getTabAt(0)) tabLayout.getTabAt(0)!!.select()
viewPager2.setCurrentItem(0, false) viewPager2.setCurrentItem(0, false)
} }
Type.video -> { Type.video -> {
tabLayout.selectTab(tabLayout.getTabAt(1)) tabLayout.getTabAt(1)!!.select()
viewPager2.setCurrentItem(1, false) viewPager2.setCurrentItem(1, false)
} }
else -> { else -> {
tabLayout.selectTab(tabLayout.getTabAt(2)) tabLayout.getTabAt(2)!!.select()
viewPager2.setCurrentItem(2, false) viewPager2.postDelayed( {
viewPager2.setCurrentItem(2, false)
}, 200)
} }
} }

View file

@ -20,6 +20,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
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.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
@ -68,8 +69,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
lifecycleScope.launch { lifecycleScope.launch {
val sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE) val sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
try { try {
val templates = withContext(Dispatchers.IO){ val templates : MutableList<CommandTemplate> = withContext(Dispatchers.IO){
commandTemplateViewModel.getAll() commandTemplateViewModel.getAll().toMutableList()
} }
val id = sharedPreferences.getLong("commandTemplate", templates[0].id) val id = sharedPreferences.getLong("commandTemplate", templates[0].id)
val chosenCommand = templates.find { it.id == id } val chosenCommand = templates.find { it.id == id }
@ -117,21 +118,10 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
} }
} }
val templateTitles = templates.map {it.title}
val commandTemplates = view.findViewById<TextInputLayout>(R.id.template) val commandTemplates = view.findViewById<TextInputLayout>(R.id.template)
val autoCompleteTextView = val autoCompleteTextView =
view.findViewById<AutoCompleteTextView>(R.id.template_textview) requireView().findViewById<AutoCompleteTextView>(R.id.template_textview)
autoCompleteTextView?.setAdapter( populateCommandTemplates(templates, autoCompleteTextView)
ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
templateTitles
)
)
if (templateTitles.isNotEmpty()) {
autoCompleteTextView!!.setText(downloadItem.format.format_id, false)
}
(commandTemplates!!.editText as AutoCompleteTextView?)!!.onItemClickListener = (commandTemplates!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
@ -168,29 +158,40 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
val newTemplate : Chip = view.findViewById(R.id.newTemplate) val newTemplate : Chip = view.findViewById(R.id.newTemplate)
newTemplate.setOnClickListener { newTemplate.setOnClickListener {
uiUtil.showCreationSheet(requireActivity(), viewLifecycleOwner, commandTemplateViewModel) { uiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
templates.add(it)
chosenCommandView.editText!!.setText(it.content)
downloadItem.format = Format(
it.title,
"",
"",
"",
"",
0,
it.content
)
populateCommandTemplates(templates, autoCompleteTextView)
} }
} }
// val editSelected : Chip = view.findViewById(R.id.editSelected)
// val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles) editSelected.setOnClickListener {
// embedSubs!!.isChecked = embedSubs.isChecked var current = templates.find { it.title == autoCompleteTextView.text.toString() }
// embedSubs.setOnClickListener { if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString())
// downloadItem.embedSubs = embedSubs.isChecked uiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
// } templates.add(it)
// chosenCommandView.editText!!.setText(it.content)
// val addChapters = view.findViewById<Chip>(R.id.add_chapters) downloadItem.format = Format(
// addChapters!!.isChecked = addChapters.isChecked it.title,
// addChapters.setOnClickListener{ "",
// downloadItem.addChapters = addChapters.isChecked "",
// } "",
// "",
// val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail) 0,
// saveThumbnail!!.isChecked = saveThumbnail.isChecked it.content
// saveThumbnail.setOnClickListener { )
// downloadItem.SaveThumb = saveThumbnail.isChecked }
// } }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -198,6 +199,22 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
} }
} }
private fun populateCommandTemplates(templates: List<CommandTemplate>, autoCompleteTextView: AutoCompleteTextView?){
val templateTitles = templates.map {it.title}
autoCompleteTextView?.setAdapter(
ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
templateTitles
)
)
if (templateTitles.isNotEmpty()) {
autoCompleteTextView!!.setText(downloadItem.format.format_id, false)
}
}
private var commandPathResultLauncher = registerForActivityResult( private var commandPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->

View file

@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.downloadqueue
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -23,6 +24,7 @@ import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogActivity
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.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
@ -85,7 +87,11 @@ class ErroredDownloadsFragment() : Fragment(), GenericDownloadAdapter.OnItemClic
} }
override fun onActionButtonClick(itemID: Long) { override fun onActionButtonClick(itemID: Long) {
TODO("Not yet implemented") val item = items.find { it.id == itemID } ?: return
val file = File(requireContext().filesDir.absolutePath + """/logs/${item.id} - ${item.title}##${item.type}##${item.format.format_id}.log""")
val intent = Intent(requireContext(), DownloadLogActivity::class.java)
intent.putExtra("path", file.absolutePath)
startActivity(intent)
} }
override fun onCardClick(itemID: Long) { override fun onCardClick(itemID: Long) {

View file

@ -1,32 +1,27 @@
package com.deniscerri.ytdlnis.ui.more package com.deniscerri.ytdlnis.ui.more
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.os.* import android.os.*
import android.text.Editable
import android.text.TextWatcher
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup
import android.view.Window
import android.widget.* import android.widget.*
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.TemplatesAdapter import com.deniscerri.ytdlnis.adapter.TemplatesAdapter
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
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.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CommandTemplatesActivity : AppCompatActivity(), TemplatesAdapter.OnItemClickListener { class CommandTemplatesActivity : AppCompatActivity(), TemplatesAdapter.OnItemClickListener {
@ -68,9 +63,19 @@ class CommandTemplatesActivity : AppCompatActivity(), TemplatesAdapter.OnItemCli
topAppBar.setOnMenuItemClickListener { m: MenuItem -> topAppBar.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId val itemId = m.itemId
if (itemId == R.id.export_clipboard) { if (itemId == R.id.export_clipboard) {
commandTemplateViewModel.exportToClipboard() lifecycleScope.launch(Dispatchers.IO){
commandTemplateViewModel.exportToClipboard()
}
}else if (itemId == R.id.import_clipboard){ }else if (itemId == R.id.import_clipboard){
commandTemplateViewModel.importFromClipboard() lifecycleScope.launch{
withContext(Dispatchers.IO){
val count = commandTemplateViewModel.importFromClipboard()
runOnUiThread{
Toast.makeText(this@CommandTemplatesActivity, "$count items imported!", Toast.LENGTH_LONG).show()
}
}
}
} }
true true
} }
@ -79,7 +84,7 @@ class CommandTemplatesActivity : AppCompatActivity(), TemplatesAdapter.OnItemCli
private fun initChips() { private fun initChips() {
val new = findViewById<Chip>(R.id.newTemplate) val new = findViewById<Chip>(R.id.newTemplate)
new.setOnClickListener { new.setOnClickListener {
uiUtil.showCreationSheet(this@CommandTemplatesActivity, this, commandTemplateViewModel) {} uiUtil.showCommandTemplateCreationOrUpdatingSheet(null,this@CommandTemplatesActivity, this, commandTemplateViewModel) {}
} }
val shortcuts = findViewById<Chip>(R.id.shortcuts) val shortcuts = findViewById<Chip>(R.id.shortcuts)
shortcuts.setOnClickListener { shortcuts.setOnClickListener {
@ -92,10 +97,21 @@ class CommandTemplatesActivity : AppCompatActivity(), TemplatesAdapter.OnItemCli
} }
override fun onItemClick(commandTemplate: CommandTemplate) { override fun onItemClick(commandTemplate: CommandTemplate) {
TODO("Not yet implemented") uiUtil.showCommandTemplateCreationOrUpdatingSheet(commandTemplate,this@CommandTemplatesActivity, this, commandTemplateViewModel) {}
} }
override fun onSelected(commandTemplate: CommandTemplate) { override fun onSelected(commandTemplate: CommandTemplate) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun onDelete(commandTemplate: CommandTemplate) {
val deleteDialog = MaterialAlertDialogBuilder(this)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + commandTemplate.title + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
commandTemplateViewModel.delete(commandTemplate)
}
deleteDialog.show()
}
} }

View file

@ -36,15 +36,13 @@ 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 = findViewById(R.id.copy_log)
copyLog.setOnClickListener { copyLog.setOnClickListener {
val clipboard: ClipboardManager = val clipboard: ClipboardManager =
getSystemService(CLIPBOARD_SERVICE) as ClipboardManager getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip: ClipData = ClipData.newPlainText("Download Log", content.text) clipboard.setText(content.text)
clipboard.setPrimaryClip(clip)
} }
val path = intent.getStringExtra("path") val path = intent.getStringExtra("path")
@ -60,8 +58,10 @@ class DownloadLogActivity : AppCompatActivity() {
observer = object : FileObserver(file.absolutePath, MODIFY) { observer = object : FileObserver(file.absolutePath, MODIFY) {
override fun onEvent(event: Int, p: String?) { override fun onEvent(event: Int, p: String?) {
runOnUiThread{ runOnUiThread{
content.text = File(path).readText() val newText = File(path).readText()
content.text = newText
content.scrollTo(0, content.height) content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN)
} }
} }
} }
@ -70,7 +70,8 @@ class DownloadLogActivity : AppCompatActivity() {
observer = object : FileObserver(file, MODIFY) { observer = object : FileObserver(file, MODIFY) {
override fun onEvent(event: Int, p: String?) { override fun onEvent(event: Int, p: String?) {
runOnUiThread{ runOnUiThread{
content.text = File(path).readText() val newText = File(path).readText()
content.text = newText
content.scrollTo(0, content.height) content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN) contentScrollView.fullScroll(View.FOCUS_DOWN)
} }

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis.ui.more.downloadLogs package com.deniscerri.ytdlnis.ui.more.downloadLogs
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.* import android.os.*
import android.view.MenuItem import android.view.MenuItem
@ -13,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter import com.deniscerri.ytdlnis.adapter.DownloadLogsAdapter
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.File import java.io.File
@ -22,6 +24,7 @@ class DownloadLogListActivity : AppCompatActivity(), DownloadLogsAdapter.OnItemC
private lateinit var noResults: RelativeLayout private lateinit var noResults: RelativeLayout
private lateinit var fileList: MutableList<File> private lateinit var fileList: MutableList<File>
private lateinit var topAppBar: MaterialToolbar private lateinit var topAppBar: MaterialToolbar
private lateinit var logFolder : File
var context: Context? = null var context: Context? = null
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
@ -43,7 +46,7 @@ class DownloadLogListActivity : AppCompatActivity(), DownloadLogsAdapter.OnItemC
noResults = findViewById(R.id.no_results) noResults = findViewById(R.id.no_results)
noResults.visibility = View.GONE noResults.visibility = View.GONE
val logFolder = File(filesDir.absolutePath + "/logs") logFolder = File(filesDir.absolutePath + "/logs")
updateList(logFolder) updateList(logFolder)
if(Build.VERSION.SDK_INT < 29){ if(Build.VERSION.SDK_INT < 29){
@ -73,9 +76,18 @@ class DownloadLogListActivity : AppCompatActivity(), DownloadLogsAdapter.OnItemC
val itemId = m.itemId val itemId = m.itemId
if (itemId == R.id.remove_logs) { if (itemId == R.id.remove_logs) {
try{ try{
logFolder.listFiles()!!.forEach { val deleteDialog = MaterialAlertDialogBuilder(this)
it.delete() deleteDialog.setTitle(getString(R.string.confirm_delete_history))
deleteDialog.setMessage(getString(R.string.confirm_delete_logs_desc))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
logFolder.listFiles()!!.forEach {
it.delete()
}.run {
updateList(logFolder)
}
} }
deleteDialog.show()
}catch (e: Exception){ }catch (e: Exception){
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
} }
@ -113,6 +125,14 @@ class DownloadLogListActivity : AppCompatActivity(), DownloadLogsAdapter.OnItemC
} }
override fun onDeleteClick(file: File) { override fun onDeleteClick(file: File) {
file.delete() val deleteDialog = MaterialAlertDialogBuilder(this)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + file.name + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
file.delete().run {
updateList(logFolder)
}
}
deleteDialog.show()
} }
} }

View file

@ -23,6 +23,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var videoPath: Preference? = null private var videoPath: Preference? = null
private var commandPath: Preference? = null private var commandPath: Preference? = null
private var incognito: SwitchPreferenceCompat? = null private var incognito: SwitchPreferenceCompat? = null
private var preferredDownloadType : ListPreference? = null
private var downloadCard: SwitchPreferenceCompat? = null private var downloadCard: SwitchPreferenceCompat? = null
private var apiKey: EditTextPreference? = null private var apiKey: EditTextPreference? = null
private var concurrentFragments: SeekBarPreference? = null private var concurrentFragments: SeekBarPreference? = null
@ -64,6 +65,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
videoPath = findPreference("video_path") videoPath = findPreference("video_path")
commandPath = findPreference("command_path") commandPath = findPreference("command_path")
incognito = findPreference("incognito") incognito = findPreference("incognito")
preferredDownloadType = findPreference("preferred_download_type")
downloadCard = findPreference("download_card") downloadCard = findPreference("download_card")
apiKey = findPreference("api_key") apiKey = findPreference("api_key")
concurrentFragments = findPreference("concurrent_fragments") concurrentFragments = findPreference("concurrent_fragments")
@ -111,6 +113,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.putString("command_path", getString(R.string.command_path)) editor.putString("command_path", getString(R.string.command_path))
} }
editor.putBoolean("incognito", incognito!!.isChecked) editor.putBoolean("incognito", incognito!!.isChecked)
editor.putString("preferred_download_type", preferredDownloadType!!.value)
editor.putBoolean("download_card", downloadCard!!.isChecked) editor.putBoolean("download_card", downloadCard!!.isChecked)
editor.putString("api_key", apiKey!!.text) editor.putString("api_key", apiKey!!.text)
editor.putInt("concurrent_fragments", concurrentFragments!!.value) editor.putInt("concurrent_fragments", concurrentFragments!!.value)
@ -186,6 +189,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply() editor.apply()
true true
} }
preferredDownloadType!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
editor.putString("preferred_download_type", newValue.toString())
editor.apply()
true
}
downloadCard!!.onPreferenceChangeListener = downloadCard!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val enable = newValue as Boolean val enable = newValue as Boolean

View file

@ -8,7 +8,6 @@ import android.content.Intent
import android.net.Uri 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.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
@ -18,7 +17,6 @@ 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.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
@ -55,7 +53,7 @@ class UiUtil(private val fileUtil: FileUtil) {
} }
fun showCreationSheet(context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){ fun showCommandTemplateCreationOrUpdatingSheet(item: CommandTemplate?, context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){
val bottomSheet = BottomSheetDialog(context) val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.create_command_template) bottomSheet.setContentView(R.layout.create_command_template)
@ -66,7 +64,15 @@ class UiUtil(private val fileUtil: FileUtil) {
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!! val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!! val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
ok.isEnabled = false if (item != null){
title.editText!!.setText(item.title)
content.editText!!.setText(item.content)
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_subtitle)!!.text = content.resources.getString(R.string.update_template)
ok.text = content.resources.getString(R.string.update)
ok.isEnabled = true
}else{
ok.isEnabled = false
}
title.editText!!.addTextChangedListener(object : TextWatcher { title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
@ -91,6 +97,10 @@ class UiUtil(private val fileUtil: FileUtil) {
} }
}) })
if (content.editText!!.text.isEmpty()){
}
content.setEndIconOnClickListener { content.setEndIconOnClickListener {
if(content.editText!!.text.isEmpty()){ if(content.editText!!.text.isEmpty()){
val clipboard: ClipboardManager = val clipboard: ClipboardManager =
@ -118,9 +128,16 @@ class UiUtil(private val fileUtil: FileUtil) {
} }
ok.setOnClickListener { ok.setOnClickListener {
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString()) if (item == null){
commandTemplateViewModel.insert(t) val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString())
newTemplate(t) commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
item.title = title.editText!!.text.toString()
item.content = content.editText!!.text.toString()
commandTemplateViewModel.update(item)
newTemplate(item)
}
bottomSheet.cancel() bottomSheet.cancel()
} }

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" <vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:textColorPrimary" android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h18v14zM8,15c0,-1.66 1.34,-3 3,-3 0.35,0 0.69,0.07 1,0.18L12,6h5v2h-3v7.03c-0.02,1.64 -1.35,2.97 -3,2.97 -1.66,0 -3,-1.34 -3,-3z"/> <path android:fillColor="?attr/colorAccent" android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h18v14zM8,15c0,-1.66 1.34,-3 3,-3 0.35,0 0.69,0.07 1,0.18L12,6h5v2h-3v7.03c-0.02,1.64 -1.35,2.97 -3,2.97 -1.66,0 -3,-1.34 -3,-3z"/>
</vector> </vector>

View file

@ -23,27 +23,34 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<ScrollView <LinearLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/content_scrollview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:clipToPadding="false" android:descendantFocusability="blocksDescendants"
android:fillViewport="true" android:orientation="vertical">
android:paddingBottom="150dp"
>
<TextView <ScrollView
android:id="@+id/content" android:id="@+id/content_scrollview"
android:padding="10dp" android:layout_width="match_parent"
android:scrollHorizontally="true" android:layout_height="match_parent"
android:fontFamily="monospace" android:clipToPadding="false"
android:textSize="15sp" android:paddingBottom="150dp"
android:layout_width="wrap_content" >
android:textIsSelectable="true"
android:layout_height="wrap_content" />
</ScrollView> <TextView
android:id="@+id/content"
android:padding="10dp"
android:fontFamily="monospace"
android:textSize="15sp"
android:gravity="bottom"
android:layout_width="match_parent"
android:textIsSelectable="true"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy_log" android:id="@+id/copy_log"

View file

@ -6,6 +6,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="15dp"> android:padding="15dp">
<TextView <TextView

View file

@ -46,7 +46,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="all" android:autoLink="all"
android:text="@string/ok" android:text="@string/create"
app:icon="@drawable/ic_check" app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -9,6 +9,8 @@
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:clickable="true"
android:background="?android:attr/selectableItemBackground"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
@ -17,7 +19,8 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/delete" app:layout_constraintEnd_toStartOf="@+id/delete"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
android:focusable="true" />
<Button <Button
android:id="@+id/delete" android:id="@+id/delete"

View file

@ -75,4 +75,17 @@
<item>tr</item> <item>tr</item>
<item>vi</item> <item>vi</item>
</string-array> </string-array>
<string-array name="download_types">
<item>@string/audio</item>
<item>@string/video</item>
<item>@string/command</item>
</string-array>
<string-array name="download_types_values">
<item>audio</item>
<item>video</item>
<item>command</item>
</string-array>
</resources> </resources>

View file

@ -197,4 +197,10 @@
<string name="create_shortcut">Create Shortcut</string> <string name="create_shortcut">Create Shortcut</string>
<string name="error_restarting_download">Error trying to restart the download</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> <string name="delete_temp_file_too">Delete temporary files from the device</string>
<string name="update_template">Update Template</string>
<string name="create">Create</string>
<string name="update">Update</string>
<string name="preferred_download_type">Preferred Download Type</string>
<string name="preferred_download_type_summary">Default tab the download card will open to</string>
<string name="confirm_delete_logs_desc">Delete the entire log list, permanently.</string>
</resources> </resources>

View file

@ -45,6 +45,16 @@
app:summary="@string/download_card_summary" app:summary="@string/download_card_summary"
app:title="@string/show_download_card" /> app:title="@string/show_download_card" />
<ListPreference
android:dependency="download_card"
android:defaultValue="video"
android:entries="@array/download_types"
android:entryValues="@array/download_types_values"
android:icon="@drawable/ic_download_type"
app:key="preferred_download_type"
app:summary="@string/preferred_download_type_summary"
app:title="@string/preferred_download_type" />
<EditTextPreference <EditTextPreference
android:icon="@drawable/ic_key" android:icon="@drawable/ic_key"
app:key="api_key" app:key="api_key"

View file

@ -40,6 +40,7 @@ plugins {
id 'com.android.application' version '7.4.1' apply false id 'com.android.application' version '7.4.1' apply false
id 'com.android.library' version '7.4.1' apply false id 'com.android.library' version '7.4.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.21' apply false id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
id "org.jetbrains.kotlin.plugin.serialization" version "1.7.21" apply false
} }