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.generatedDensities = []
versionCode 3020304
versionName "6.14.5"
versionCode 3020305
versionName "6.14.6"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -363,7 +363,7 @@ class PlaybackService : MediaLibraryService() {
val item_ = realm.query(Episode::class).query("id == $0", item!!.id).first().find()
if (item_ != null) {
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
if (media != null) {
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.SyncServiceException
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.getEpisodes
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.ui.activity.OpmlImportActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.Logd
import android.app.Activity.RESULT_OK
import android.app.ProgressDialog
@ -32,16 +33,33 @@ import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.text.format.Formatter
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.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
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.content.FileProvider
import androidx.documentfile.provider.DocumentFile
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
@ -57,6 +75,7 @@ import java.nio.channels.FileChannel
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.*
import kotlin.Throws
class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
@ -102,77 +121,120 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
private var progressDialog: ProgressDialog? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_import_export)
setupStorageScreen()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
(activity as PreferenceActivity).supportActionBar?.setTitle(R.string.import_export_pref)
progressDialog = ProgressDialog(context)
progressDialog!!.isIndeterminate = true
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)
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.import_export_pref)
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
importDatabase()
})) {
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 {
return String.format(fname, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date()))
Text(stringResource(R.string.media_files), 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 = {
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() {
findPreference<Preference>(IExport.prefOpmlExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Text(stringResource(R.string.preferences), 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 = {
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())
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 {
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 {
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
try {
chooseOpmlImportPathLauncher.launch("*/*")
} catch (e: ActivityNotFoundException) {
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 {
importDatabase()
true
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
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 {
exportDatabase()
true
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
importEpisodeProgress()
})) {
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 {
importPreferences()
true
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
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 {
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 {
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
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) {
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 {
private val TAG: String = ImportExportPreferencesFragment::class.simpleName ?: "Anonymous"

View File

@ -1,21 +1,78 @@
package ac.mdiq.podcini.preferences.fragments
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.activity.BugReportActivity
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.compose.CustomTheme
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import ac.mdiq.podcini.util.Logd
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
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.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.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
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() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -79,7 +136,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
true
}
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
}
findPreference<Preference>(Prefs.notifications.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@ -97,10 +154,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
true
}
findPreference<Preference>(Prefs.prefAbout.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.settingsContainer, AboutFragment())
.addToBackStack(getString(R.string.about_pref))
.commit()
parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, AboutFragment()).addToBackStack(getString(R.string.about_pref)).commit()
true
}
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_playback).addBreadcrumb(getTitleOfPage(R.xml.preferences_playback))
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)
.addBreadcrumb(getTitleOfPage(R.xml.preferences_downloads))
.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_notifications).addBreadcrumb(getTitleOfPage(R.xml.preferences_notifications))
// config.index(R.xml.feed_settings).addBreadcrumb(getTitleOfPage(R.xml.feed_settings))
config.index(R.xml.preferences_swipe)
.addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface))
.addBreadcrumb(getTitleOfPage(R.xml.preferences_swipe))
// config.index(R.xml.preferences_swipe)
// .addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface))
// .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")

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.setShowRemainTimeSetting
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.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
@ -91,19 +94,8 @@ class UserInterfacePreferencesFragment : PreferenceFragmentCompat() {
showFullNotificationButtonsDialog()
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 {
(activity as PreferenceActivity).openScreen(R.xml.preferences_swipe)
(activity as PreferenceActivity).openScreen(Screens.preferences_swipe)
true
}
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
fun SwipeActionsDialog(tag: String, onDismissRequest: () -> Unit, callback: ()->Unit) {
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.fragments.*
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.EventFlow
import ac.mdiq.podcini.util.FlowEvent
@ -13,10 +20,24 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
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.preference.PreferenceFragmentCompat
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)
}
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")
fun openScreen(screen: Int): PreferenceFragmentCompat? {
val fragment = getPreferenceScreen(screen)
fun openScreen(screen: Int): PreferenceFragmentCompat {
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) {
val intent = Intent()
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
startActivity(intent)
} 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
}
@ -149,6 +179,49 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
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 {
private const val FRAGMENT_TAG = "tag_preferences"
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_autodownload -> R.string.pref_automatic_download_title
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_synchronization -> R.string.synchronization_pref
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
}
}

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))
val curContext = LocalContext.current
val dateSizeText = " · " + formatDateTimeFlex(vm.episode.getPubDate()) +
if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "" +
" · " + getDurationStringLong(vm.durationState) + " · " +
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
" · " + getDurationStringLong(vm.durationState) +
(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)
}
} else {
@ -679,11 +679,11 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
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))
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)
}
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)
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))

View File

@ -244,7 +244,7 @@ fun RenameOrCreateSyntheticFeed(feed_: Feed? = null, onDismissRequest: () -> Uni
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Text(stringResource(R.string.rename_feed_label), color = textColor, style = MaterialTheme.typography.bodyLarge)
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 isYoutube by remember { mutableStateOf(false) }
if (feed_ == null) {
@ -259,6 +259,7 @@ fun RenameOrCreateSyntheticFeed(feed_: Feed? = null, onDismissRequest: () -> Uni
}
Row {
Button({ onDismissRequest() }) { Text(stringResource(R.string.cancel_label)) }
Spacer(Modifier.weight(1f))
Button({
val feed = feed_ ?: createSynthetic(0, name, hasVideo)
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.HistoryFragment.Companion.getNumberOfPlayed
import ac.mdiq.podcini.ui.utils.ThemeUtils
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import ac.mdiq.podcini.util.Logd
import android.R.attr
import android.app.Activity
@ -121,7 +122,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp))
Column {
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()
args.putLong(ARGUMENT_FEED_ID, f.id)
(activity as MainActivity).loadFragment(FeedEpisodesFragment.TAG, args)
@ -134,6 +135,10 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
}
}
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))
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clickable {
startActivity(Intent(activity, PreferenceActivity::class.java))

View File

@ -18,9 +18,7 @@ object IntentUtils {
@JvmStatic
fun isCallable(context: Context, intent: Intent?): Boolean {
val list = context.packageManager.queryIntentActivities(intent!!, PackageManager.MATCH_DEFAULT_ONLY)
for (info in list) {
if (info.activityInfo.exported) return true
}
for (info in list) if (info.activityInfo.exported) return true
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
* minor adjustments in episode lists layout