diff --git a/app/build.gradle b/app/build.gradle index c1f07b57..b489bec9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 7bc06e86..5f66fd19 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/AboutFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/AboutFragment.kt deleted file mode 100644 index d10c8e7e..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/AboutFragment.kt +++ /dev/null @@ -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("about_version")!!.summary = String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH) - findPreference("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("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("about_help")!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/") - true - } - findPreference("about_privacy_policy")!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md") - true - } - findPreference("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() - - 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) - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt index f41fa22d..e56a73e2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt @@ -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,78 +121,121 @@ 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) + } + 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)) - override fun onStart() { - super.onStart() - (activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.import_export_pref) + 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)) + + 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()) + })) { + 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) + } + 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...") + } + })) { + 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) + } + 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) + } + 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) + } + 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) + } + Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { + openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter()) + })) { + 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 setupStorageScreen() { - findPreference(IExport.prefOpmlExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openExportPathPicker(Export.OPML, chooseOpmlExportPathLauncher, OpmlWriter()) - true - } - findPreference(IExport.prefHtmlExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher, HtmlWriter()) - true - } - findPreference(IExport.prefProgressExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openExportPathPicker(Export.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter()) - true - } - findPreference(IExport.prefProgressImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - importEpisodeProgress() - true - } - findPreference(IExport.prefOpmlImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - try { - chooseOpmlImportPathLauncher.launch("*/*") - } catch (e: ActivityNotFoundException) { - Log.e(TAG, "No activity found. Should never happen...") - } - true - } - findPreference(IExport.prefDatabaseImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - importDatabase() - true - } - findPreference(IExport.prefDatabaseExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - exportDatabase() - true - } - findPreference(IExport.prefPrefImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - importPreferences() - true - } - findPreference(IExport.prefPrefExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - exportPreferences() - true - } - findPreference(IExport.prefMediaFilesImport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - importMediaFiles() - true - } - findPreference(IExport.prefMediaFilesExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - exportMediaFiles() - true - } - findPreference(IExport.prefFavoritesExport.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openExportPathPicker(Export.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter()) - true - } - } - private fun exportWithWriter(exportWriter: ExportWriter, uri: Uri?, exportType: Export) { val context: Context? = activity progressDialog!!.show() @@ -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" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/MainPreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/MainPreferencesFragment.kt index a4b55dea..7220718e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/MainPreferencesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/MainPreferencesFragment.kt @@ -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(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(Prefs.notifications.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -97,10 +154,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() { true } findPreference(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(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("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() + + 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") diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/SwipePreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/SwipePreferencesFragment.kt deleted file mode 100644 index 0681872f..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/SwipePreferencesFragment.kt +++ /dev/null @@ -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(Prefs.prefSwipeQueue.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// SwipeActionsDialog(requireContext(), QueuesFragment.TAG).show(object : SwipeActionsDialog.Callback { -// override fun onCall() {} -// }) - showSettingDialog(this, QueuesFragment.TAG) - true - } - findPreference(Prefs.prefSwipeEpisodes.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// SwipeActionsDialog(requireContext(), AllEpisodesFragment.TAG).show (object : SwipeActionsDialog.Callback { -// override fun onCall() {} -// }) - showSettingDialog(this, AllEpisodesFragment.TAG) - true - } - findPreference(Prefs.prefSwipeDownloads.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// SwipeActionsDialog(requireContext(), DownloadsFragment.TAG).show (object : SwipeActionsDialog.Callback { -// override fun onCall() {} -// }) - showSettingDialog(this, DownloadsFragment.TAG) - true - } - findPreference(Prefs.prefSwipeFeed.name)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// SwipeActionsDialog(requireContext(), FeedEpisodesFragment.TAG).show (object : SwipeActionsDialog.Callback { -// override fun onCall() {} -// }) - showSettingDialog(this, FeedEpisodesFragment.TAG) - true - } - findPreference(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 - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/UserInterfacePreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/UserInterfacePreferencesFragment.kt index fa7117e3..5173d418 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/UserInterfacePreferencesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/UserInterfacePreferencesFragment.kt @@ -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(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener = -// (Preference.OnPreferenceClickListener { -// SubscriptionsFilterDialog().show(childFragmentManager, "filter") -// true -// }) - -// findPreference(UserPreferences.Prefs.prefDrawerFeedOrder.name)?.onPreferenceClickListener = (Preference.OnPreferenceClickListener { -//// FeedSortDialog.showDialog(requireContext()) -// FeedSortDialog().show(childFragmentManager, "FeedSortDialog") -// true -// }) findPreference(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(UserPreferences.Prefs.prefExpandNotify.name)!!.isVisible = false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt index 9d485837..3b53bd1b 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt @@ -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 diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt index e66ede71..9e1dd5f2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt @@ -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 } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt index a288b589..1513c3fe 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt @@ -661,9 +661,9 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, 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, 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)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Feeds.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Feeds.kt index fd16ba33..8fc75c5a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Feeds.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Feeds.kt @@ -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) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt index 92ecbae4..ab2fa432 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt @@ -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)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/util/IntentUtils.kt b/app/src/main/kotlin/ac/mdiq/podcini/util/IntentUtils.kt index f18833e6..fa9fd6d4 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/util/IntentUtils.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/util/IntentUtils.kt @@ -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 } diff --git a/app/src/main/res/layout/about_teaser.xml b/app/src/main/res/layout/about_teaser.xml deleted file mode 100644 index 4e7f0454..00000000 --- a/app/src/main/res/layout/about_teaser.xml +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_about.xml b/app/src/main/res/xml/preferences_about.xml deleted file mode 100644 index 00a594f2..00000000 --- a/app/src/main/res/xml/preferences_about.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/preferences_import_export.xml b/app/src/main/res/xml/preferences_import_export.xml deleted file mode 100644 index 3822b50a..00000000 --- a/app/src/main/res/xml/preferences_import_export.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/preferences_swipe.xml b/app/src/main/res/xml/preferences_swipe.xml deleted file mode 100644 index 2da316da..00000000 --- a/app/src/main/res/xml/preferences_swipe.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/changelog.md b/changelog.md index 8e5e7e66..b20d4f4d 100644 --- a/changelog.md +++ b/changelog.md @@ -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