6.14.6 commit

This commit is contained in:
Xilin Jia 2024-11-23 13:37:42 +01:00
parent cd31132516
commit 9093c59068
18 changed files with 439 additions and 516 deletions

View File

@ -26,8 +26,8 @@ android {
vectorDrawables.useSupportLibrary false vectorDrawables.useSupportLibrary false
vectorDrawables.generatedDensities = [] vectorDrawables.generatedDensities = []
versionCode 3020304 versionCode 3020305
versionName "6.14.5" versionName "6.14.6"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -363,7 +363,7 @@ class PlaybackService : MediaLibraryService() {
val item_ = realm.query(Episode::class).query("id == $0", item!!.id).first().find() val item_ = realm.query(Episode::class).query("id == $0", item!!.id).first().find()
if (item_ != null) { if (item_ != null) {
item = upsert(item_) { item = upsert(item_) {
it.playState = PlayState.PLAYED.code if (it.playState < PlayState.PLAYED.code || it.playState == PlayState.IGNORED.code) it.playState = PlayState.PLAYED.code
val media = it.media val media = it.media
if (media != null) { if (media != null) {
media.startPosition = playable.startPosition media.startPosition = playable.startPosition

View File

@ -1,152 +0,0 @@
package ac.mdiq.podcini.preferences.fragments
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import javax.xml.parsers.DocumentBuilderFactory
class AboutFragment : PreferenceFragmentCompat() {
@SuppressLint("CommitTransaction")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_about)
findPreference<Preference>("about_version")!!.summary = String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH)
findPreference<Preference>("about_version")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.bug_report_title), findPreference<Preference>("about_version")!!.summary)
clipboard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT <= 32) Snackbar.make(requireView(), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show()
true
}
findPreference<Preference>("about_help")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/")
true
}
findPreference<Preference>("about_privacy_policy")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md")
true
}
findPreference<Preference>("about_licenses")!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, LicensesFragment()).addToBackStack(getString(R.string.translators)).commit()
true
}
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.about_pref)
}
class LicensesFragment : Fragment() {
private val licenses = mutableStateListOf<LicenseItem>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { MainView() } } }
lifecycleScope.launch(Dispatchers.IO) {
licenses.clear()
val stream = requireContext().assets.open("licenses.xml")
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val libraryList = docBuilder.parse(stream).getElementsByTagName("library")
for (i in 0 until libraryList.length) {
val lib = libraryList.item(i).attributes
licenses.add(LicenseItem(lib.getNamedItem("name").textContent,
String.format("By %s, %s license", lib.getNamedItem("author").textContent, lib.getNamedItem("license").textContent), lib.getNamedItem("website").textContent, lib.getNamedItem("licenseText").textContent))
}
}.invokeOnCompletion { throwable -> if (throwable!= null) Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show() }
return composeView
}
@Composable
fun MainView() {
val lazyListState = rememberLazyListState()
val textColor = MaterialTheme.colorScheme.onSurface
var showDialog by remember { mutableStateOf(false) }
var curLicenseIndex by remember { mutableIntStateOf(-1) }
if (showDialog) Dialog(onDismissRequest = { showDialog = false }) {
Surface(shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(licenses[curLicenseIndex].title, color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Row {
Button(onClick = { openInBrowser(requireContext(), licenses[curLicenseIndex].licenseUrl) }) { Text("View website") }
Spacer(Modifier.weight(1f))
Button(onClick = { showLicenseText(licenses[curLicenseIndex].licenseTextFile) }) { Text("View license") }
}
}
}
}
LazyColumn(state = lazyListState, modifier = Modifier.fillMaxWidth().padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(licenses) { index, item ->
Column(Modifier.clickable(onClick = {
curLicenseIndex = index
showDialog = true
})) {
Text(item.title, color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(item.subtitle, color = textColor, style = MaterialTheme.typography.bodySmall)
}
}
}
}
private fun showLicenseText(licenseTextFile: String) {
try {
val reader = BufferedReader(InputStreamReader(requireContext().assets.open(licenseTextFile), "UTF-8"))
val licenseText = StringBuilder()
var line = ""
while ((reader.readLine()?.also { line = it }) != null) licenseText.append(line).append("\n")
MaterialAlertDialogBuilder(requireContext()).setMessage(licenseText).show()
} catch (e: IOException) { e.printStackTrace() }
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.licenses)
}
private class LicenseItem(val title: String, val subtitle: String, val licenseUrl: String, val licenseTextFile: String)
}
}

View File

@ -8,7 +8,7 @@ import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.model.EpisodeAction.Companion.readFromJsonObject import ac.mdiq.podcini.net.sync.model.EpisodeAction.Companion.readFromJsonObject
import ac.mdiq.podcini.net.sync.model.SyncServiceException import ac.mdiq.podcini.net.sync.model.SyncServiceException
import ac.mdiq.podcini.preferences.ExportWriter import ac.mdiq.podcini.preferences.ExportWriter
import ac.mdiq.podcini.preferences.OpmlTransporter.* import ac.mdiq.podcini.preferences.OpmlTransporter.OpmlWriter
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.Feeds.getFeedList
@ -20,6 +20,7 @@ import ac.mdiq.podcini.storage.utils.FileNameGenerator.generateFileName
import ac.mdiq.podcini.storage.utils.FilesUtils.getDataFolder import ac.mdiq.podcini.storage.utils.FilesUtils.getDataFolder
import ac.mdiq.podcini.ui.activity.OpmlImportActivity import ac.mdiq.podcini.ui.activity.OpmlImportActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.app.ProgressDialog import android.app.ProgressDialog
@ -32,16 +33,33 @@ import android.os.Bundle
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.app.ShareCompat.IntentBuilder import androidx.core.app.ShareCompat.IntentBuilder
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -57,6 +75,7 @@ import java.nio.channels.FileChannel
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.Throws
class ImportExportPreferencesFragment : PreferenceFragmentCompat() { class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
@ -102,77 +121,120 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
private var progressDialog: ProgressDialog? = null private var progressDialog: ProgressDialog? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
addPreferencesFromResource(R.xml.preferences_import_export)
setupStorageScreen() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
(activity as PreferenceActivity).supportActionBar?.setTitle(R.string.import_export_pref)
progressDialog = ProgressDialog(context) progressDialog = ProgressDialog(context)
progressDialog!!.isIndeterminate = true progressDialog!!.isIndeterminate = true
progressDialog!!.setMessage(requireContext().getString(R.string.please_wait)) progressDialog!!.setMessage(requireContext().getString(R.string.please_wait))
return ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) {
val textColor = MaterialTheme.colorScheme.onSurface
val scrollState = rememberScrollState()
Column(modifier = Modifier.fillMaxWidth().padding(16.dp).verticalScroll(scrollState)) {
Text(stringResource(R.string.database), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
exportDatabase()
})) {
Text(stringResource(R.string.database_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.database_export_summary), color = textColor)
} }
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
override fun onStart() { importDatabase()
super.onStart() })) {
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.import_export_pref) Text(stringResource(R.string.database_import_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.database_import_summary), color = textColor)
} }
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
private fun dateStampFilename(fname: String): String { Text(stringResource(R.string.media_files), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp))
return String.format(fname, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())) Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
exportMediaFiles()
})) {
Text(stringResource(R.string.media_files_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.media_files_export_summary), color = textColor)
} }
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
importMediaFiles()
})) {
Text(stringResource(R.string.media_files_import_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.media_files_import_summary), color = textColor)
}
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
private fun setupStorageScreen() { Text(stringResource(R.string.preferences), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp))
findPreference<Preference>(IExport.prefOpmlExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
exportPreferences()
})) {
Text(stringResource(R.string.preferences_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.preferences_export_summary), color = textColor)
}
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
importPreferences()
})) {
Text(stringResource(R.string.preferences_import_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.preferences_import_summary), color = textColor)
}
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
Text(stringResource(R.string.opml), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp))
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher, OpmlWriter()) openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher, OpmlWriter())
true })) {
Text(stringResource(R.string.opml_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.opml_export_summary), color = textColor)
} }
findPreference<Preference>(IExport.prefHtmlExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher, HtmlWriter())
true
}
findPreference<Preference>(IExport.prefProgressExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openExportPathPicker(Export.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter())
true
}
findPreference<Preference>(IExport.prefProgressImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
importEpisodeProgress()
true
}
findPreference<Preference>(IExport.prefOpmlImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
try { try {
chooseOpmlImportPathLauncher.launch("*/*") chooseOpmlImportPathLauncher.launch("*/*")
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Log.e(TAG, "No activity found. Should never happen...") Log.e(TAG, "No activity found. Should never happen...")
} }
true })) {
Text(stringResource(R.string.opml_import_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.opml_import_summary), color = textColor)
} }
findPreference<Preference>(IExport.prefDatabaseImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
importDatabase()
true Text(stringResource(R.string.progress), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp))
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
openExportPathPicker(Export.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter())
})) {
Text(stringResource(R.string.progress_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.progress_export_summary), color = textColor)
} }
findPreference<Preference>(IExport.prefDatabaseExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
exportDatabase() importEpisodeProgress()
true })) {
Text(stringResource(R.string.progress_import_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.progress_import_summary), color = textColor)
} }
findPreference<Preference>(IExport.prefPrefImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
importPreferences()
true Text(stringResource(R.string.html), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp))
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher, HtmlWriter())
})) {
Text(stringResource(R.string.html_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.html_export_summary), color = textColor)
} }
findPreference<Preference>(IExport.prefPrefExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
exportPreferences()
true
}
findPreference<Preference>(IExport.prefMediaFilesImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
importMediaFiles()
true
}
findPreference<Preference>(IExport.prefMediaFilesExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
exportMediaFiles()
true
}
findPreference<Preference>(IExport.prefFavoritesExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter()) openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter())
true })) {
Text(stringResource(R.string.favorites_export_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.favorites_export_summary), color = textColor)
} }
} }
}
}
}
}
private fun dateStampFilename(fname: String): String {
return String.format(fname, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date()))
}
private fun exportWithWriter(exportWriter: ExportWriter, uri: Uri?, exportType: Export) { private fun exportWithWriter(exportWriter: ExportWriter, uri: Uri?, exportType: Export) {
val context: Context? = activity val context: Context? = activity
@ -1112,22 +1174,6 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
} }
} }
@Suppress("EnumEntryName")
private enum class IExport {
prefOpmlExport,
prefOpmlImport,
prefProgressExport,
prefProgressImport,
prefHtmlExport,
prefPrefImport,
prefPrefExport,
prefMediaFilesImport,
prefMediaFilesExport,
prefDatabaseImport,
prefDatabaseExport,
prefFavoritesExport,
}
companion object { companion object {
private val TAG: String = ImportExportPreferencesFragment::class.simpleName ?: "Anonymous" private val TAG: String = ImportExportPreferencesFragment::class.simpleName ?: "Anonymous"

View File

@ -1,21 +1,78 @@
package ac.mdiq.podcini.preferences.fragments package ac.mdiq.podcini.preferences.fragments
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.activity.BugReportActivity import ac.mdiq.podcini.ui.activity.BugReportActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity.Companion.getTitleOfPage import ac.mdiq.podcini.ui.activity.PreferenceActivity.Companion.getTitleOfPage
import ac.mdiq.podcini.ui.activity.PreferenceActivity.Screens
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.IntentUtils.openInBrowser import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import com.bytehamster.lib.preferencesearch.SearchPreference import com.bytehamster.lib.preferencesearch.SearchPreference
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import javax.xml.parsers.DocumentBuilderFactory
class MainPreferencesFragment : PreferenceFragmentCompat() { class MainPreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -79,7 +136,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
true true
} }
findPreference<Preference>(Prefs.prefScreenImportExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference<Preference>(Prefs.prefScreenImportExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
(activity as PreferenceActivity).openScreen(R.xml.preferences_import_export) (activity as PreferenceActivity).openScreen(Screens.preferences_import_export)
true true
} }
findPreference<Preference>(Prefs.notifications.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference<Preference>(Prefs.notifications.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@ -97,10 +154,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
true true
} }
findPreference<Preference>(Prefs.prefAbout.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference<Preference>(Prefs.prefAbout.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
parentFragmentManager.beginTransaction() parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, AboutFragment()).addToBackStack(getString(R.string.about_pref)).commit()
.replace(R.id.settingsContainer, AboutFragment())
.addToBackStack(getString(R.string.about_pref))
.commit()
true true
} }
findPreference<Preference>(Prefs.prefDocumentation.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference<Preference>(Prefs.prefDocumentation.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@ -131,7 +185,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
config.index(R.xml.preferences_user_interface).addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface)) config.index(R.xml.preferences_user_interface).addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface))
config.index(R.xml.preferences_playback).addBreadcrumb(getTitleOfPage(R.xml.preferences_playback)) config.index(R.xml.preferences_playback).addBreadcrumb(getTitleOfPage(R.xml.preferences_playback))
config.index(R.xml.preferences_downloads).addBreadcrumb(getTitleOfPage(R.xml.preferences_downloads)) config.index(R.xml.preferences_downloads).addBreadcrumb(getTitleOfPage(R.xml.preferences_downloads))
config.index(R.xml.preferences_import_export).addBreadcrumb(getTitleOfPage(R.xml.preferences_import_export)) // config.index(R.xml.preferences_import_export).addBreadcrumb(getTitleOfPage(R.xml.preferences_import_export))
config.index(R.xml.preferences_autodownload) config.index(R.xml.preferences_autodownload)
.addBreadcrumb(getTitleOfPage(R.xml.preferences_downloads)) .addBreadcrumb(getTitleOfPage(R.xml.preferences_downloads))
.addBreadcrumb(R.string.automation) .addBreadcrumb(R.string.automation)
@ -139,9 +193,136 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
config.index(R.xml.preferences_synchronization).addBreadcrumb(getTitleOfPage(R.xml.preferences_synchronization)) config.index(R.xml.preferences_synchronization).addBreadcrumb(getTitleOfPage(R.xml.preferences_synchronization))
config.index(R.xml.preferences_notifications).addBreadcrumb(getTitleOfPage(R.xml.preferences_notifications)) config.index(R.xml.preferences_notifications).addBreadcrumb(getTitleOfPage(R.xml.preferences_notifications))
// config.index(R.xml.feed_settings).addBreadcrumb(getTitleOfPage(R.xml.feed_settings)) // config.index(R.xml.feed_settings).addBreadcrumb(getTitleOfPage(R.xml.feed_settings))
config.index(R.xml.preferences_swipe) // config.index(R.xml.preferences_swipe)
.addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface)) // .addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface))
.addBreadcrumb(getTitleOfPage(R.xml.preferences_swipe)) // .addBreadcrumb(getTitleOfPage(R.xml.preferences_swipe))
}
class AboutFragment : PreferenceFragmentCompat() {
@SuppressLint("CommitTransaction")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
(activity as PreferenceActivity).supportActionBar?.setTitle(R.string.about_pref)
return ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) {
val textColor = MaterialTheme.colorScheme.onSurface
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Image(painter = painterResource(R.drawable.teaser), contentDescription = "")
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_star), contentDescription = "", tint = textColor)
Column(Modifier.padding(start = 10.dp).clickable(onClick = {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.bug_report_title), findPreference<Preference>("about_version")!!.summary)
clipboard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT <= 32) Snackbar.make(requireView(), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show()
})) {
Text(stringResource(R.string.podcini_version), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH), color = textColor)
}
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_questionmark), contentDescription = "", tint = textColor)
Column(Modifier.padding(start = 10.dp).clickable(onClick = {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/")
})) {
Text(stringResource(R.string.online_help), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.online_help_sum), color = textColor)
}
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "", tint = textColor)
Column(Modifier.padding(start = 10.dp).clickable(onClick = {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md")
})) {
Text(stringResource(R.string.privacy_policy), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text("Podcini PrivacyPolicy", color = textColor)
}
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "", tint = textColor)
Column(Modifier.padding(start = 10.dp).clickable(onClick = {
parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, LicensesFragment()).addToBackStack(getString(R.string.translators)).commit()
})) {
Text(stringResource(R.string.licenses), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.licenses_summary), color = textColor)
}
}
}
}
}
}
}
class LicensesFragment : Fragment() {
private val licenses = mutableStateListOf<LicenseItem>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { MainView() } } }
lifecycleScope.launch(Dispatchers.IO) {
licenses.clear()
val stream = requireContext().assets.open("licenses.xml")
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val libraryList = docBuilder.parse(stream).getElementsByTagName("library")
for (i in 0 until libraryList.length) {
val lib = libraryList.item(i).attributes
licenses.add(LicenseItem(lib.getNamedItem("name").textContent,
String.format("By %s, %s license", lib.getNamedItem("author").textContent, lib.getNamedItem("license").textContent), lib.getNamedItem("website").textContent, lib.getNamedItem("licenseText").textContent))
}
}.invokeOnCompletion { throwable -> if (throwable!= null) Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show() }
return composeView
}
@Composable
fun MainView() {
val lazyListState = rememberLazyListState()
val textColor = MaterialTheme.colorScheme.onSurface
var showDialog by remember { mutableStateOf(false) }
var curLicenseIndex by remember { mutableIntStateOf(-1) }
if (showDialog) Dialog(onDismissRequest = { showDialog = false }) {
Surface(shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(licenses[curLicenseIndex].title, color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Row {
Button(onClick = { openInBrowser(requireContext(), licenses[curLicenseIndex].licenseUrl) }) { Text("View website") }
Spacer(Modifier.weight(1f))
Button(onClick = { showLicenseText(licenses[curLicenseIndex].licenseTextFile) }) { Text("View license") }
}
}
}
}
LazyColumn(state = lazyListState, modifier = Modifier.fillMaxWidth().padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(licenses) { index, item ->
Column(Modifier.clickable(onClick = {
curLicenseIndex = index
showDialog = true
})) {
Text(item.title, color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(item.subtitle, color = textColor, style = MaterialTheme.typography.bodySmall)
}
}
}
}
private fun showLicenseText(licenseTextFile: String) {
try {
val reader = BufferedReader(InputStreamReader(requireContext().assets.open(licenseTextFile), "UTF-8"))
val licenseText = StringBuilder()
var line = ""
while ((reader.readLine()?.also { line = it }) != null) licenseText.append(line).append("\n")
MaterialAlertDialogBuilder(requireContext()).setMessage(licenseText).show()
} catch (e: IOException) { e.printStackTrace() }
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.licenses)
}
private class LicenseItem(val title: String, val subtitle: String, val licenseUrl: String, val licenseTextFile: String)
}
} }
@Suppress("EnumEntryName") @Suppress("EnumEntryName")

View File

@ -1,67 +0,0 @@
package ac.mdiq.podcini.preferences.fragments
import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.showSettingDialog
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.*
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
class SwipePreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_swipe)
findPreference<Preference>(Prefs.prefSwipeQueue.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// SwipeActionsDialog(requireContext(), QueuesFragment.TAG).show(object : SwipeActionsDialog.Callback {
// override fun onCall() {}
// })
showSettingDialog(this, QueuesFragment.TAG)
true
}
findPreference<Preference>(Prefs.prefSwipeEpisodes.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// SwipeActionsDialog(requireContext(), AllEpisodesFragment.TAG).show (object : SwipeActionsDialog.Callback {
// override fun onCall() {}
// })
showSettingDialog(this, AllEpisodesFragment.TAG)
true
}
findPreference<Preference>(Prefs.prefSwipeDownloads.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// SwipeActionsDialog(requireContext(), DownloadsFragment.TAG).show (object : SwipeActionsDialog.Callback {
// override fun onCall() {}
// })
showSettingDialog(this, DownloadsFragment.TAG)
true
}
findPreference<Preference>(Prefs.prefSwipeFeed.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// SwipeActionsDialog(requireContext(), FeedEpisodesFragment.TAG).show (object : SwipeActionsDialog.Callback {
// override fun onCall() {}
// })
showSettingDialog(this, FeedEpisodesFragment.TAG)
true
}
findPreference<Preference>(Prefs.prefSwipeHistory.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// SwipeActionsDialog(requireContext(), HistoryFragment.TAG).show (object : SwipeActionsDialog.Callback {
// override fun onCall() {}
// })
showSettingDialog(this, HistoryFragment.TAG)
true
}
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar?.setTitle(R.string.swipeactions_label)
}
@Suppress("EnumEntryName")
private enum class Prefs {
prefSwipeQueue,
// prefSwipeStatistics,
prefSwipeEpisodes,
prefSwipeDownloads,
prefSwipeFeed,
prefSwipeHistory
}
}

View File

@ -7,6 +7,9 @@ import ac.mdiq.podcini.preferences.UserPreferences.fullNotificationButtons
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity.Companion.getTitleOfPage
import ac.mdiq.podcini.ui.activity.PreferenceActivity.Screens
import ac.mdiq.podcini.ui.activity.PreferenceActivity.SwipePreferencesFragment
import ac.mdiq.podcini.ui.fragment.NavDrawerFragment.Companion.navMap import ac.mdiq.podcini.ui.fragment.NavDrawerFragment.Companion.navMap
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
@ -91,19 +94,8 @@ class UserInterfacePreferencesFragment : PreferenceFragmentCompat() {
showFullNotificationButtonsDialog() showFullNotificationButtonsDialog()
true true
} }
// findPreference<Preference>(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener =
// (Preference.OnPreferenceClickListener {
// SubscriptionsFilterDialog().show(childFragmentManager, "filter")
// true
// })
// findPreference<Preference>(UserPreferences.Prefs.prefDrawerFeedOrder.name)?.onPreferenceClickListener = (Preference.OnPreferenceClickListener {
//// FeedSortDialog.showDialog(requireContext())
// FeedSortDialog().show(childFragmentManager, "FeedSortDialog")
// true
// })
findPreference<Preference>(PREF_SWIPE)?.setOnPreferenceClickListener { findPreference<Preference>(PREF_SWIPE)?.setOnPreferenceClickListener {
(activity as PreferenceActivity).openScreen(R.xml.preferences_swipe) (activity as PreferenceActivity).openScreen(Screens.preferences_swipe)
true true
} }
if (Build.VERSION.SDK_INT >= 26) findPreference<Preference>(UserPreferences.Prefs.prefExpandNotify.name)!!.isVisible = false if (Build.VERSION.SDK_INT >= 26) findPreference<Preference>(UserPreferences.Prefs.prefExpandNotify.name)!!.isVisible = false

View File

@ -632,21 +632,6 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De
} }
} }
fun showSettingDialog(fragment: Fragment, tag: String) {
val composeView = ComposeView(fragment.requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(fragment.requireContext()) {
SwipeActionsDialog(tag, onDismissRequest = {
showDialog.value = false
(fragment.view as? ViewGroup)?.removeView(this@apply)
}) {}
}
}
}
(fragment.view as? ViewGroup)?.addView(composeView)
}
@Composable @Composable
fun SwipeActionsDialog(tag: String, onDismissRequest: () -> Unit, callback: ()->Unit) { fun SwipeActionsDialog(tag: String, onDismissRequest: () -> Unit, callback: ()->Unit) {
val context = LocalContext.current val context = LocalContext.current

View File

@ -5,6 +5,13 @@ import ac.mdiq.podcini.databinding.SettingsActivityBinding
import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme
import ac.mdiq.podcini.preferences.fragments.* import ac.mdiq.podcini.preferences.fragments.*
import ac.mdiq.podcini.preferences.fragments.SynchronizationPreferencesFragment import ac.mdiq.podcini.preferences.fragments.SynchronizationPreferencesFragment
import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment
import ac.mdiq.podcini.ui.fragment.DownloadsFragment
import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment
import ac.mdiq.podcini.ui.fragment.HistoryFragment
import ac.mdiq.podcini.ui.fragment.QueuesFragment
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
@ -13,10 +20,24 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult import com.bytehamster.lib.preferencesearch.SearchPreferenceResult
@ -53,33 +74,42 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) openScreen(R.xml.preferences_autodownload) if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) openScreen(R.xml.preferences_autodownload)
} }
private fun getPreferenceScreen(screen: Int): PreferenceFragmentCompat? {
var prefFragment: PreferenceFragmentCompat? = null
when (screen) {
R.xml.preferences_user_interface -> prefFragment = UserInterfacePreferencesFragment()
R.xml.preferences_downloads -> prefFragment = DownloadsPreferencesFragment()
R.xml.preferences_import_export -> prefFragment = ImportExportPreferencesFragment()
R.xml.preferences_autodownload -> prefFragment = AutoDownloadPreferencesFragment()
R.xml.preferences_synchronization -> prefFragment = SynchronizationPreferencesFragment()
R.xml.preferences_playback -> prefFragment = PlaybackPreferencesFragment()
R.xml.preferences_notifications -> prefFragment = NotificationPreferencesFragment()
R.xml.preferences_swipe -> prefFragment = SwipePreferencesFragment()
}
return prefFragment
}
@SuppressLint("CommitTransaction") @SuppressLint("CommitTransaction")
fun openScreen(screen: Int): PreferenceFragmentCompat? { fun openScreen(screen: Int): PreferenceFragmentCompat {
val fragment = getPreferenceScreen(screen) val fragment = when (screen) {
R.xml.preferences_user_interface -> UserInterfacePreferencesFragment()
R.xml.preferences_downloads -> DownloadsPreferencesFragment()
// R.xml.preferences_import_export -> ImportExportPreferencesFragment()
R.xml.preferences_autodownload -> AutoDownloadPreferencesFragment()
R.xml.preferences_synchronization -> SynchronizationPreferencesFragment()
R.xml.preferences_playback -> PlaybackPreferencesFragment()
R.xml.preferences_notifications -> NotificationPreferencesFragment()
// R.xml.preferences_swipe -> SwipePreferencesFragment()
else -> UserInterfacePreferencesFragment()
}
if (screen == R.xml.preferences_notifications && Build.VERSION.SDK_INT >= 26) { if (screen == R.xml.preferences_notifications && Build.VERSION.SDK_INT >= 26) {
val intent = Intent() val intent = Intent()
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS) intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
startActivity(intent) startActivity(intent)
} else } else
supportFragmentManager.beginTransaction().replace(binding.settingsContainer.id, fragment!!).addToBackStack(getString(getTitleOfPage(screen))).commit() supportFragmentManager.beginTransaction().replace(binding.settingsContainer.id, fragment).addToBackStack(getString(getTitleOfPage(screen))).commit()
return fragment
}
fun openScreen(screen: Screens): PreferenceFragmentCompat {
val fragment = when (screen) {
Screens.preferences_user_interface -> UserInterfacePreferencesFragment()
Screens.preferences_downloads -> DownloadsPreferencesFragment()
Screens.preferences_import_export -> ImportExportPreferencesFragment()
Screens.preferences_autodownload -> AutoDownloadPreferencesFragment()
Screens.preferences_synchronization -> SynchronizationPreferencesFragment()
Screens.preferences_playback -> PlaybackPreferencesFragment()
Screens.preferences_notifications -> NotificationPreferencesFragment()
Screens.preferences_swipe -> SwipePreferencesFragment()
}
supportFragmentManager.beginTransaction().replace(binding.settingsContainer.id, fragment).addToBackStack(getString(screen.titleRes)).commit()
return fragment return fragment
} }
@ -149,6 +179,49 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
s.show() s.show()
} }
class SwipePreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
(activity as PreferenceActivity).supportActionBar?.setTitle(R.string.swipeactions_label)
return ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) {
val textColor = MaterialTheme.colorScheme.onSurface
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
for (e in Prefs.entries) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) SwipeActionsDialog(e.tag, onDismissRequest = { showDialog.value = false }) {}
Text(stringResource(e.res), color = textColor, style = MaterialTheme.typography.headlineMedium, modifier = Modifier.padding(bottom = 10.dp).clickable(onClick = {
showDialog.value = true
}))
}
}
}
}
}
}
@Suppress("EnumEntryName")
private enum class Prefs(val res: Int, val tag: String) {
prefSwipeQueue(R.string.queue_label, QueuesFragment.TAG),
prefSwipeEpisodes(R.string.episodes_label, AllEpisodesFragment.TAG),
prefSwipeDownloads(R.string.downloads_label, DownloadsFragment.TAG),
prefSwipeFeed(R.string.individual_subscription, FeedEpisodesFragment.TAG),
prefSwipeHistory(R.string.playback_history_label, HistoryFragment.TAG)
}
}
@Suppress("EnumEntryName")
enum class Screens(val titleRes: Int) {
preferences_swipe(R.string.swipeactions_label),
preferences_downloads(R.string.downloads_pref),
preferences_autodownload(R.string.pref_automatic_download_title),
preferences_playback(R.string.playback_pref),
preferences_import_export(R.string.import_export_pref),
preferences_user_interface(R.string.user_interface_label),
preferences_synchronization(R.string.synchronization_pref),
preferences_notifications(R.string.notification_pref_fragment);
}
companion object { companion object {
private const val FRAGMENT_TAG = "tag_preferences" private const val FRAGMENT_TAG = "tag_preferences"
const val OPEN_AUTO_DOWNLOAD_SETTINGS: String = "OpenAutoDownloadSettings" const val OPEN_AUTO_DOWNLOAD_SETTINGS: String = "OpenAutoDownloadSettings"
@ -158,11 +231,11 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
R.xml.preferences_downloads -> R.string.downloads_pref R.xml.preferences_downloads -> R.string.downloads_pref
R.xml.preferences_autodownload -> R.string.pref_automatic_download_title R.xml.preferences_autodownload -> R.string.pref_automatic_download_title
R.xml.preferences_playback -> R.string.playback_pref R.xml.preferences_playback -> R.string.playback_pref
R.xml.preferences_import_export -> R.string.import_export_pref // R.xml.preferences_import_export -> R.string.import_export_pref
R.xml.preferences_user_interface -> R.string.user_interface_label R.xml.preferences_user_interface -> R.string.user_interface_label
R.xml.preferences_synchronization -> R.string.synchronization_pref R.xml.preferences_synchronization -> R.string.synchronization_pref
R.xml.preferences_notifications -> R.string.notification_pref_fragment R.xml.preferences_notifications -> R.string.notification_pref_fragment
R.xml.preferences_swipe -> R.string.swipeactions_label // R.xml.preferences_swipe -> R.string.swipeactions_label
else -> R.string.settings_label else -> R.string.settings_label
} }
} }

View File

@ -661,9 +661,9 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(16.dp).height(16.dp)) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(16.dp).height(16.dp))
val curContext = LocalContext.current val curContext = LocalContext.current
val dateSizeText = " · " + formatDateTimeFlex(vm.episode.getPubDate()) + val dateSizeText = " · " + formatDateTimeFlex(vm.episode.getPubDate()) +
if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "" + " · " + getDurationStringLong(vm.durationState) +
" · " + getDurationStringLong(vm.durationState) + " · " + (if ((vm.episode.media?.size ?: 0) > 0) " · " + Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "") +
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "" (if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "")
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
} }
} else { } else {
@ -679,11 +679,11 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
if (vm.episode.media?.getMediaType() == MediaType.VIDEO) if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(16.dp).height(16.dp)) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(16.dp).height(16.dp))
val dateSizeText = " · " + getDurationStringLong(vm.durationState) + val dateSizeText = " · " + getDurationStringLong(vm.durationState) +
if ((vm.episode.media?.size ?: 0) > 0) " · " + Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "" (if ((vm.episode.media?.size ?: 0) > 0) " · " + Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "")
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
val dateSizeText = formatDateTimeFlex(vm.episode.getPubDate()) + if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "" val dateSizeText = formatDateTimeFlex(vm.episode.getPubDate()) + (if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "")
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (vm.viewCount > 0) if (vm.viewCount > 0)
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_people_alt_24), tint = textColor, contentDescription = "people", modifier = Modifier.width(16.dp).height(16.dp)) Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_people_alt_24), tint = textColor, contentDescription = "people", modifier = Modifier.width(16.dp).height(16.dp))

View File

@ -244,7 +244,7 @@ fun RenameOrCreateSyntheticFeed(feed_: Feed? = null, onDismissRequest: () -> Uni
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Text(stringResource(R.string.rename_feed_label), color = textColor, style = MaterialTheme.typography.bodyLarge) Text(stringResource(R.string.rename_feed_label), color = textColor, style = MaterialTheme.typography.bodyLarge)
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
TextField(value = name, onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) name = it }, label = { Text(stringResource(R.string.new_namee)) }) TextField(value = name, onValueChange = { name = it }, label = { Text(stringResource(R.string.new_namee)) })
var hasVideo by remember { mutableStateOf(true) } var hasVideo by remember { mutableStateOf(true) }
var isYoutube by remember { mutableStateOf(false) } var isYoutube by remember { mutableStateOf(false) }
if (feed_ == null) { if (feed_ == null) {
@ -259,6 +259,7 @@ fun RenameOrCreateSyntheticFeed(feed_: Feed? = null, onDismissRequest: () -> Uni
} }
Row { Row {
Button({ onDismissRequest() }) { Text(stringResource(R.string.cancel_label)) } Button({ onDismissRequest() }) { Text(stringResource(R.string.cancel_label)) }
Spacer(Modifier.weight(1f))
Button({ Button({
val feed = feed_ ?: createSynthetic(0, name, hasVideo) val feed = feed_ ?: createSynthetic(0, name, hasVideo)
if (feed_ == null) { if (feed_ == null) {

View File

@ -13,6 +13,7 @@ import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment.Companion.ARGUMENT_FEED_ID import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment.Companion.ARGUMENT_FEED_ID
import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getNumberOfPlayed import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getNumberOfPlayed
import ac.mdiq.podcini.ui.utils.ThemeUtils import ac.mdiq.podcini.ui.utils.ThemeUtils
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.R.attr import android.R.attr
import android.app.Activity import android.app.Activity
@ -121,7 +122,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
Column { Column {
for (f in feeds) { for (f in feeds) {
Row(verticalAlignment = Alignment.Top, modifier = Modifier.padding(bottom = 5.dp).clickable { Row(verticalAlignment = Alignment.Top, modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp).clickable {
val args = Bundle() val args = Bundle()
args.putLong(ARGUMENT_FEED_ID, f.id) args.putLong(ARGUMENT_FEED_ID, f.id)
(activity as MainActivity).loadFragment(FeedEpisodesFragment.TAG, args) (activity as MainActivity).loadFragment(FeedEpisodesFragment.TAG, args)
@ -134,6 +135,10 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
} }
} }
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Text("Currently launching on Google Play, please kindly support with closed testing. Thank you!", color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.clickable(onClick = {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/discussions/120")
}))
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clickable { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clickable {
startActivity(Intent(activity, PreferenceActivity::class.java)) startActivity(Intent(activity, PreferenceActivity::class.java))

View File

@ -18,9 +18,7 @@ object IntentUtils {
@JvmStatic @JvmStatic
fun isCallable(context: Context, intent: Intent?): Boolean { fun isCallable(context: Context, intent: Intent?): Boolean {
val list = context.packageManager.queryIntentActivities(intent!!, PackageManager.MATCH_DEFAULT_ONLY) val list = context.packageManager.queryIntentActivities(intent!!, PackageManager.MATCH_DEFAULT_ONLY)
for (info in list) { for (info in list) if (info.activityInfo.exported) return true
if (info.activityInfo.exported) return true
}
return false return false
} }

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:adjustViewBounds="true"
android:layout_height="wrap_content"
app:srcCompat="@drawable/teaser"
android:importantForAccessibility="no"/>

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:layout="@layout/about_teaser"/>
<Preference
android:key="about_version"
android:title="@string/podcini_version"
android:icon="@drawable/ic_star"
android:summary="1.7.2 (asd8qs)"/>
<Preference
android:key="about_help"
android:icon="@drawable/ic_questionmark"
android:summary="@string/online_help_sum"
android:title="@string/online_help"/>
<Preference
android:key="about_privacy_policy"
android:icon="@drawable/ic_questionmark"
android:summary="Podcini PrivacyPolicy.md"
android:title="@string/privacy_policy"/>
<Preference
android:key="about_licenses"
android:icon="@drawable/ic_info"
android:summary="@string/licenses_summary"
android:title="@string/licenses"/>
<!-- <Preference-->
<!-- android:key="about_contributors"-->
<!-- android:icon="@drawable/ic_settings"-->
<!-- android:summary="@string/contributors_summary"-->
<!-- android:title="@string/contributors"/>-->
</PreferenceScreen>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch">
<PreferenceCategory android:title="@string/database">
<Preference
android:key="prefDatabaseExport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/database_export_label"
android:summary="@string/database_export_summary"/>
<Preference
android:key="prefDatabaseImport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/database_import_label"
android:summary="@string/database_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/media_files">
<Preference
android:key="prefMediaFilesExport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/media_files_export_label"
android:summary="@string/media_files_export_summary"/>
<Preference
android:key="prefMediaFilesImport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/media_files_import_label"
android:summary="@string/media_files_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences">
<Preference
android:key="prefPrefExport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/preferences_export_label"
android:summary="@string/preferences_export_summary"/>
<Preference
android:key="prefPrefImport"
search:keywords="@string/import_export_search_keywords"
android:title="@string/preferences_import_label"
android:summary="@string/preferences_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/opml">
<Preference
android:key="prefOpmlExport"
android:title="@string/opml_export_label"
android:summary="@string/opml_export_summary"/>
<Preference
android:key="prefOpmlImport"
android:title="@string/opml_import_label"
android:summary="@string/opml_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/progress">
<Preference
android:key="prefProgressExport"
android:title="@string/progress_export_label"
android:summary="@string/progress_export_summary"/>
<Preference
android:key="prefProgressImport"
android:title="@string/progress_import_label"
android:summary="@string/progress_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/html">
<Preference
android:key="prefHtmlExport"
android:title="@string/html_export_label"
android:summary="@string/html_export_summary"/>
<Preference
android:key="prefFavoritesExport"
android:title="@string/favorites_export_label"
android:summary="@string/favorites_export_summary"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:key="prefSwipeQueue"
android:title="@string/queue_label"/>
<Preference
android:key="prefSwipeEpisodes"
android:title="@string/episodes_label"/>
<Preference
android:key="prefSwipeDownloads"
android:title="@string/downloads_label"/>
<Preference
android:key="prefSwipeHistory"
android:title="@string/playback_history_label"/>
<!-- <Preference-->
<!-- android:key="prefSwipeStatistics"-->
<!-- android:title="@string/statistics_label"/>-->
<Preference
android:key="prefSwipeFeed"
android:title="@string/individual_subscription"/>
</PreferenceScreen>

View File

@ -1,3 +1,11 @@
# 6.14.6
* fixed issue of unable to input in rename feed
* fixed issue of marking played after playing even when the episode has been marked as Again or Forever
* fixed duration not shown in narrow episode lists
* some preferences fragments are in Compose
* temp message on NavDrawer for closed testing
# 6.14.5 # 6.14.5
* minor adjustments in episode lists layout * minor adjustments in episode lists layout