diff --git a/app/build.gradle b/app/build.gradle index 74701b04..350d6ad9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { vectorDrawables.useSupportLibrary false vectorDrawables.generatedDensities = [] - versionCode 3020313 - versionName "6.15.4" + versionCode 3020314 + versionName "6.15.5" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e6027663..34f1b3f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -164,8 +164,6 @@ android:value="ac.mdiq.podcini.ui.activity.PreferenceActivity"/> - - - synchronized(notificationProgress) { - progressCopy = HashMap(notificationProgress) - } - for ((key, value) in progressCopy) { - bigTextB.append(String.format(Locale.getDefault(), "%s (%d%%)\n", key, value)) - } + synchronized(notificationProgress) { progressCopy = HashMap(notificationProgress) } + for ((key, value) in progressCopy) bigTextB.append(String.format(Locale.getDefault(), "%s (%d%%)\n", key, value)) val bigText = bigTextB.toString().trim { it <= ' ' } val contentText = if (progressCopy.size == 1) bigText else applicationContext.resources.getQuantityString(R.plurals.downloads_left, progressCopy.size, progressCopy.size) @@ -350,14 +346,13 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() { return constraints.build() } - private fun getRequest(item: Episode): OneTimeWorkRequest.Builder { Logd(TAG, "starting getRequest") val workRequest: OneTimeWorkRequest.Builder = OneTimeWorkRequest.Builder(EpisodeDownloadWorker::class.java) .setInitialDelay(0L, TimeUnit.MILLISECONDS) .addTag(WORK_TAG) .addTag(WORK_TAG_EPISODE_URL + item.media!!.downloadUrl) - if (enqueueDownloadedEpisodes()) { + if (appPrefs.getBoolean(UserPreferences.Prefs.prefEnqueueDownloaded.name, true)) { if (item.feed?.preferences?.queue != null) runBlocking { Queues.addToQueueSync(item, item.feed?.preferences?.queue) } workRequest.addTag(WORK_DATA_WAS_QUEUED) @@ -365,8 +360,5 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() { workRequest.setInputData(Data.Builder().putLong(WORK_DATA_MEDIA_ID, item.media!!.id).build()) return workRequest } - private fun enqueueDownloadedEpisodes(): Boolean { - return appPrefs.getBoolean(UserPreferences.Prefs.prefEnqueueDownloaded.name, true) - } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/Downloader.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/Downloader.kt index a785df35..221fd349 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/Downloader.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/Downloader.kt @@ -10,9 +10,6 @@ import java.util.* import java.util.concurrent.Callable import kotlin.concurrent.Volatile -/** - * Downloads files - */ abstract class Downloader(val downloadRequest: DownloadRequest) : Callable { @Volatile var isFinished: Boolean = false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt index be4db1d4..4ee9185f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt @@ -41,6 +41,7 @@ import java.io.File import java.io.IOException import java.net.HttpURLConnection import java.net.URL +import kotlin.Throws class FeedBuilder(val context: Context, val showError: (String?, String)->Unit) { private val TAG = "DirectSubscribe" @@ -259,10 +260,7 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit) } } selectedDownloadUrl = prepareUrl(url) - val request = create(Feed(selectedDownloadUrl, null)) - .withAuthentication(username, password) - .withInitiatedByUser(true) - .build() + val request = create(Feed(selectedDownloadUrl, null)).withAuthentication(username, password).withInitiatedByUser(true).build() CoroutineScope(Dispatchers.IO).launch { try { downloader = HttpDownloader(request) @@ -367,4 +365,104 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit) // } Logd(TAG, "fo.id: ${fo?.id} feed.id: ${feed.id}") } + + /** + * + * @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found). + */ +// private fun showFeedDiscoveryDialog(feedFile: File, baseUrl: String): Boolean { +// val fd = FeedDiscoverer() +// val urlsMap: Map +// try { +// urlsMap = fd.findLinks(feedFile, baseUrl) +// if (urlsMap.isEmpty()) return false +// } catch (e: IOException) { +// e.printStackTrace() +// return false +// } +// +// if (isRemoving || isPaused) return false +// val titles: MutableList = ArrayList() +// val urls: List = ArrayList(urlsMap.keys) +// for (url in urls) { +// titles.add(urlsMap[url]) +// } +// if (urls.size == 1) { +// // Skip dialog and display the item directly +// feeds = getFeedList() +// subscribe.startFeedBuilding(urls[0]) {feed, map -> showFeedInformation(feed, map) } +// return true +// } +// val adapter = ArrayAdapter(requireContext(), R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles) +// val onClickListener = DialogInterface.OnClickListener { dialog: DialogInterface, which: Int -> +// val selectedUrl = urls[which] +// dialog.dismiss() +// feeds = getFeedList() +// subscribe.startFeedBuilding(selectedUrl) {feed, map -> showFeedInformation(feed, map) } +// } +// val ab = MaterialAlertDialogBuilder(requireContext()) +// .setTitle(R.string.feeds_label) +// .setCancelable(true) +// .setOnCancelListener { _: DialogInterface? ->/* finish() */ } +// .setAdapter(adapter, onClickListener) +// requireActivity().runOnUiThread { +// if (dialog != null && dialog!!.isShowing) dialog!!.dismiss() +// dialog = ab.show() +// } +// return true +// } + + /** + * Finds RSS/Atom URLs in a HTML document using the auto-discovery techniques described here: + * http://www.rssboard.org/rss-autodiscovery + * http://blog.whatwg.org/feed-autodiscovery + */ +// class FeedDiscoverer { +// /** +// * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. +// * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if +// * a title cannot be found). +// */ +// @Throws(IOException::class) +// fun findLinks(inVal: File, baseUrl: String): Map { +// return findLinks(Jsoup.parse(inVal), baseUrl) +// } +// /** +// * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. +// * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if +// * a title cannot be found). +// */ +// fun findLinks(inVal: String, baseUrl: String): Map { +// return findLinks(Jsoup.parse(inVal), baseUrl) +// } +// private fun findLinks(document: Document, baseUrl: String): Map { +// val res: MutableMap = ArrayMap() +// val links = document.head().getElementsByTag("link") +// for (link in links) { +// val rel = link.attr("rel") +// val href = link.attr("href") +// if (href.isNotEmpty() && (rel == "alternate" || rel == "feed")) { +// val type = link.attr("type") +// if (type == MIME_RSS || type == MIME_ATOM) { +// val title = link.attr("title") +// val processedUrl = processURL(baseUrl, href) +// if (processedUrl != null) res[processedUrl] = title.ifEmpty { href } +// } +// } +// } +// return res +// } +// private fun processURL(baseUrl: String, strUrl: String): String? { +// val uri = Uri.parse(strUrl) +// if (uri.isRelative) { +// val res = Uri.parse(baseUrl).buildUpon().path(strUrl).build() +// return res?.toString() +// } else return strUrl +// } +// companion object { +// private const val MIME_RSS = "application/rss+xml" +// private const val MIME_ATOM = "application/atom+xml" +// } +// } + } \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt index 8d741f9f..9a78913b 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt @@ -7,6 +7,8 @@ import ac.mdiq.podcini.playback.base.InTheatre.curEpisode import ac.mdiq.podcini.playback.base.InTheatre.curIndexInQueue import ac.mdiq.podcini.playback.base.InTheatre.curMedia import ac.mdiq.podcini.playback.base.InTheatre.curQueue +import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs import ac.mdiq.podcini.storage.database.Episodes @@ -655,6 +657,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP // } exoPlayer = ExoPlayer.Builder(context, defaultRenderersFactory) .setTrackSelector(trackSelector!!) + .setSeekBackIncrementMs(rewindSecs * 1000L) + .setSeekForwardIncrementMs(fastForwardSecs * 1000L) .setLoadControl(loadControl.build()) .build() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt index 60d57a8d..f59fa0aa 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/algorithms/AutoDownloads.kt @@ -88,8 +88,6 @@ object AutoDownloads { } class FeedBasedAutoDLAlgorithm : AutoDownloadAlgorithm() { - - override fun autoDownloadEpisodeMedia(context: Context, feeds: List?): Runnable { return Runnable { // true if we should auto download based on network status diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt index 9d9cc31d..0b571a35 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt @@ -273,7 +273,7 @@ class MainActivity : CastEnabledActivity() { Text(stringResource(R.string.unrestricted_background_permission_text)) Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = dontAskAgain, onCheckedChange = { dontAskAgain = it }) - Text(stringResource(R.string.dont_ask_again)) + Text(stringResource(R.string.checkbox_do_not_show_again)) } } }, 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 f7c7a235..e24d9e23 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 @@ -27,6 +27,7 @@ import ac.mdiq.podcini.net.sync.nextcloud.NextcloudLoginFlow import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.hostPort import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.startInstantSync import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.prefPlaybackSpeed +import ac.mdiq.podcini.playback.base.VideoMode import ac.mdiq.podcini.preferences.* import ac.mdiq.podcini.preferences.OpmlTransporter.OpmlElement import ac.mdiq.podcini.preferences.OpmlTransporter.OpmlWriter @@ -39,6 +40,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs import ac.mdiq.podcini.preferences.UserPreferences.setVideoMode import ac.mdiq.podcini.preferences.UserPreferences.speedforwardSpeed +import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.hasAlmostEnded @@ -284,8 +286,8 @@ class PreferenceActivity : AppCompatActivity() { Column(modifier = Modifier.weight(1f).clickable(onClick = { navController.navigate(screen) })) { - Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(summaryRes), color = textColor) + Text(stringResource(titleRes), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) + Text(stringResource(summaryRes), color = textColor, style = MaterialTheme.typography.bodySmall) } } } @@ -296,7 +298,7 @@ class PreferenceActivity : AppCompatActivity() { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { Icon(imageVector = ImageVector.vectorResource(vecRes), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) Column(modifier = Modifier.weight(1f).clickable(onClick = { callback() })) { - Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(titleRes), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) } } } @@ -320,8 +322,8 @@ class PreferenceActivity : AppCompatActivity() { } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_backup_on_google_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_backup_on_google_sum), color = textColor) + Text(stringResource(R.string.pref_backup_on_google_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_backup_on_google_sum), color = textColor, style = MaterialTheme.typography.bodySmall) } var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefOPMLBackup.name, true)) } Switch(checked = isChecked, onCheckedChange = { @@ -334,7 +336,7 @@ class PreferenceActivity : AppCompatActivity() { }) } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) - Text(stringResource(R.string.project_pref), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.project_pref), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) IconTitleActionRow(R.drawable.ic_questionmark, R.string.documentation_support) { openInBrowser(this@PreferenceActivity, "https://github.com/XilinJia/Podcini") } IconTitleActionRow(R.drawable.ic_chat, R.string.visit_user_forum) { openInBrowser(this@PreferenceActivity, "https://github.com/XilinJia/Podcini/discussions") } IconTitleActionRow(R.drawable.ic_contribute, R.string.pref_contribute) { openInBrowser(this@PreferenceActivity, "https://github.com/XilinJia/Podcini") } @@ -363,7 +365,7 @@ class PreferenceActivity : AppCompatActivity() { // scope.launch { snackbarHostState.showSnackbar(getString(R.string.copied_to_clipboard), duration = SnackbarDuration.Short) } if (Build.VERSION.SDK_INT <= 32) Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show() })) { - Text(stringResource(R.string.podcini_version), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.podcini_version), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH), color = textColor) } } @@ -413,7 +415,7 @@ class PreferenceActivity : AppCompatActivity() { 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.titleLarge, fontWeight = FontWeight.Bold) + Text(licenses[curLicenseIndex].title, color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Row { Button(onClick = { openInBrowser(this@PreferenceActivity, licenses[curLicenseIndex].licenseUrl) }) { Text("View website") } Spacer(Modifier.weight(1f)) @@ -439,7 +441,7 @@ class PreferenceActivity : AppCompatActivity() { curLicenseIndex = index showDialog = true })) { - Text(item.title, color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(item.title, color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(item.subtitle, color = textColor, style = MaterialTheme.typography.bodySmall) } } @@ -506,7 +508,7 @@ class PreferenceActivity : AppCompatActivity() { val textColor = MaterialTheme.colorScheme.onSurface val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { - Text(stringResource(R.string.appearance), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.appearance), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) Row(verticalAlignment = Alignment.CenterVertically) { var checkIndex by remember { mutableIntStateOf( when(UserPreferences.theme) { @@ -540,7 +542,7 @@ class PreferenceActivity : AppCompatActivity() { } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_black_theme_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_black_theme_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(stringResource(R.string.pref_black_theme_message), color = textColor) } var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefThemeBlack.name, false)) } @@ -552,7 +554,7 @@ class PreferenceActivity : AppCompatActivity() { } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_tinted_theme_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_tinted_theme_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(stringResource(R.string.pref_tinted_theme_message), color = textColor) } var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefTintedColors.name, false)) } @@ -564,22 +566,22 @@ class PreferenceActivity : AppCompatActivity() { } TitleSummarySwitchPrefRow(R.string.pref_episode_cover_title, R.string.pref_episode_cover_summary, UserPreferences.Prefs.prefEpisodeCover.name) TitleSummarySwitchPrefRow(R.string.pref_show_remain_time_title, R.string.pref_show_remain_time_summary, UserPreferences.Prefs.showTimeLeft.name) - Text(stringResource(R.string.subscriptions_label), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.subscriptions_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) TitleSummarySwitchPrefRow(R.string.pref_swipe_refresh_title, R.string.pref_swipe_refresh_sum, UserPreferences.Prefs.prefSwipeToRefreshAll.name) TitleSummarySwitchPrefRow(R.string.pref_feedGridLayout_title, R.string.pref_feedGridLayout_sum, UserPreferences.Prefs.prefFeedGridLayout.name) - Text(stringResource(R.string.external_elements), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.external_elements), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) if (Build.VERSION.SDK_INT < 26) { TitleSummarySwitchPrefRow(R.string.pref_expandNotify_title, R.string.pref_expandNotify_sum, UserPreferences.Prefs.prefExpandNotify.name) } TitleSummarySwitchPrefRow(R.string.pref_persistNotify_title, R.string.pref_persistNotify_sum, UserPreferences.Prefs.prefPersistNotify.name) TitleSummaryActionColumn(R.string.pref_full_notification_buttons_title, R.string.pref_full_notification_buttons_sum) { showFullNotificationButtonsDialog() } - Text(stringResource(R.string.behavior), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.behavior), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) var showDefaultPageOptions by remember { mutableStateOf(false) } var tempSelectedOption by remember { mutableStateOf(appPrefs.getString(UserPreferences.Prefs.prefDefaultPage.name, DefaultPages.SubscriptionsFragment.name)!!) } TitleSummaryActionColumn(R.string.pref_default_page, R.string.pref_default_page_sum) { showDefaultPageOptions = true } if (showDefaultPageOptions) { AlertDialog(onDismissRequest = { showDefaultPageOptions = false }, - title = { Text(stringResource(R.string.pref_default_page), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_default_page), style = CustomTextStyles.titleCustom) }, text = { Column { DefaultPages.entries.forEach { option -> @@ -622,7 +624,7 @@ class PreferenceActivity : AppCompatActivity() { for (e in SwipePrefs.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, + Text(stringResource(e.res), color = textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(bottom = 10.dp).clickable(onClick = { showDialog.value = true })) } } @@ -641,11 +643,11 @@ class PreferenceActivity : AppCompatActivity() { val textColor = MaterialTheme.colorScheme.onSurface val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { - Text(stringResource(R.string.interruptions), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.interruptions), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) var prefUnpauseOnHeadsetReconnect by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true)) } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_pauseOnHeadsetDisconnect_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_pauseOnHeadsetDisconnect_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(stringResource(R.string.pref_pauseOnDisconnect_sum), color = textColor) } Switch(checked = prefUnpauseOnHeadsetReconnect, onCheckedChange = { @@ -658,10 +660,10 @@ class PreferenceActivity : AppCompatActivity() { TitleSummarySwitchPrefRow(R.string.pref_unpauseOnBluetoothReconnect_title, R.string.pref_unpauseOnBluetoothReconnect_sum, UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name) } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) - Text(stringResource(R.string.playback_control), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.playback_control), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.pref_fast_forward), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) + Text(stringResource(R.string.pref_fast_forward), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) var interval by remember { mutableStateOf(fastForwardSecs.toString()) } var showIcon by remember { mutableStateOf(false) } TextField(value = interval, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("seconds") }, @@ -684,7 +686,7 @@ class PreferenceActivity : AppCompatActivity() { } Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.pref_rewind), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) + Text(stringResource(R.string.pref_rewind), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) var interval by remember { mutableStateOf(rewindSecs.toString()) } var showIcon by remember { mutableStateOf(false) } TextField(value = interval, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("seconds") }, @@ -736,16 +738,16 @@ class PreferenceActivity : AppCompatActivity() { TitleSummarySwitchPrefRow(R.string.pref_low_quality_on_mobile_title, R.string.pref_low_quality_on_mobile_sum, UserPreferences.Prefs.prefLowQualityOnMobile.name) TitleSummarySwitchPrefRow(R.string.pref_use_adaptive_progress_title, R.string.pref_use_adaptive_progress_sum, UserPreferences.Prefs.prefUseAdaptiveProgressUpdate.name) var showVideoModeDialog by remember { mutableStateOf(false) } - if (showVideoModeDialog) VideoModeDialog(onDismissRequest = { showVideoModeDialog = false }) { mode -> setVideoMode(mode.code) } + if (showVideoModeDialog) VideoModeDialog(initMode = VideoMode.fromCode(videoPlayMode), onDismissRequest = { showVideoModeDialog = false }) { mode -> setVideoMode(mode.code) } TitleSummaryActionColumn(R.string.pref_playback_video_mode, R.string.pref_playback_video_mode_sum) { showVideoModeDialog = true } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) - Text(stringResource(R.string.reassign_hardware_buttons), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.reassign_hardware_buttons), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) var showHardwareForwardButtonOptions by remember { mutableStateOf(false) } var tempFFSelectedOption by remember { mutableStateOf(R.string.keycode_media_fast_forward) } TitleSummaryActionColumn(R.string.pref_hardware_forward_button_title, R.string.pref_hardware_forward_button_summary) { showHardwareForwardButtonOptions = true } if (showHardwareForwardButtonOptions) { AlertDialog(onDismissRequest = { showHardwareForwardButtonOptions = false }, - title = { Text(stringResource(R.string.pref_hardware_forward_button_title), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_hardware_forward_button_title), style = CustomTextStyles.titleCustom) }, text = { Column { PrefHardwareForwardButton.entries.forEach { option -> @@ -770,7 +772,7 @@ class PreferenceActivity : AppCompatActivity() { TitleSummaryActionColumn(R.string.pref_hardware_previous_button_title, R.string.pref_hardware_previous_button_summary) { showHardwarePreviousButtonOptions = true } if (showHardwarePreviousButtonOptions) { AlertDialog(onDismissRequest = { showHardwarePreviousButtonOptions = false }, - title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = CustomTextStyles.titleCustom) }, text = { Column { PrefHardwareForwardButton.entries.forEach { option -> @@ -791,14 +793,14 @@ class PreferenceActivity : AppCompatActivity() { ) } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) - Text(stringResource(R.string.queue_label), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) + Text(stringResource(R.string.queue_label), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) TitleSummarySwitchPrefRow(R.string.pref_enqueue_downloaded_title, R.string.pref_enqueue_downloaded_summary, UserPreferences.Prefs.prefEnqueueDownloaded.name) var showEnqueueLocationOptions by remember { mutableStateOf(false) } var tempLocationOption by remember { mutableStateOf(EnqueueLocation.BACK.name) } TitleSummaryActionColumn(R.string.pref_enqueue_location_title, R.string.pref_enqueue_location_sum) { showEnqueueLocationOptions = true } if (showEnqueueLocationOptions) { AlertDialog(onDismissRequest = { showEnqueueLocationOptions = false }, - title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = CustomTextStyles.titleCustom) }, text = { Column { EnqueueLocation.entries.forEach { option -> @@ -1410,7 +1412,7 @@ class PreferenceActivity : AppCompatActivity() { var showComboImportDialog by remember { mutableStateOf(false) } if (showComboImportDialog) { AlertDialog(onDismissRequest = { showComboImportDialog = false }, - title = { Text(stringResource(R.string.pref_select_properties), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_select_properties), style = CustomTextStyles.titleCustom) }, text = { Column { comboDic.keys.forEach { option -> @@ -1423,6 +1425,7 @@ class PreferenceActivity : AppCompatActivity() { } } } + if (comboDic["Media files"] != null && comboDic["Database"] == true) Text(stringResource(R.string.pref_import_media_files_later), modifier = Modifier.padding(start = 16.dp), style = MaterialTheme.typography.bodySmall) }, confirmButton = { TextButton(onClick = { @@ -1449,7 +1452,6 @@ class PreferenceActivity : AppCompatActivity() { } withContext(Dispatchers.Main) { showImporSuccessDialog.value = true -// showImportSuccessDialog() showProgress = false } } catch (e: Throwable) { @@ -1467,7 +1469,7 @@ class PreferenceActivity : AppCompatActivity() { var showComboExportDialog by remember { mutableStateOf(false) } if (showComboExportDialog) { AlertDialog(onDismissRequest = { showComboExportDialog = false }, - title = { Text(stringResource(R.string.pref_select_properties), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_select_properties), style = CustomTextStyles.titleCustom) }, text = { Column { comboDic.keys.forEach { option -> @@ -1839,10 +1841,10 @@ class PreferenceActivity : AppCompatActivity() { val scrollState = rememberScrollState() supportActionBar!!.setTitle(R.string.downloads_pref) Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { - Text(stringResource(R.string.automation), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.automation), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.feed_refresh_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) + Text(stringResource(R.string.feed_refresh_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) var interval by remember { mutableStateOf(appPrefs.getString(UserPreferences.Prefs.prefAutoUpdateIntervall.name, "12")!!) } var showIcon by remember { mutableStateOf(false) } TextField(value = interval, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("(hours)") }, @@ -1872,7 +1874,7 @@ class PreferenceActivity : AppCompatActivity() { TitleSummarySwitchPrefRow(R.string.pref_auto_delete_title, R.string.pref_auto_delete_sum, UserPreferences.Prefs.prefAutoDelete.name) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_auto_local_delete_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_auto_local_delete_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(stringResource(R.string.pref_auto_local_delete_sum), color = textColor) } var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefAutoDeleteLocal.name, false)) } @@ -1894,14 +1896,14 @@ class PreferenceActivity : AppCompatActivity() { } TitleSummarySwitchPrefRow(R.string.pref_keeps_important_episodes_title, R.string.pref_keeps_important_episodes_sum, UserPreferences.Prefs.prefFavoriteKeepsEpisode.name) TitleSummarySwitchPrefRow(R.string.pref_delete_removes_from_queue_title, R.string.pref_delete_removes_from_queue_sum, UserPreferences.Prefs.prefDeleteRemovesFromQueue.name) - Text(stringResource(R.string.download_pref_details), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, + Text(stringResource(R.string.download_pref_details), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) var showMeteredNetworkOptions by remember { mutableStateOf(false) } var tempSelectedOptions by remember { mutableStateOf(appPrefs.getStringSet(UserPreferences.Prefs.prefMobileUpdateTypes.name, setOf("images"))!!) } TitleSummaryActionColumn(R.string.pref_metered_network_title, R.string.pref_mobileUpdate_sum) { showMeteredNetworkOptions = true } if (showMeteredNetworkOptions) { AlertDialog(onDismissRequest = { showMeteredNetworkOptions = false }, - title = { Text(stringResource(R.string.pref_metered_network_title), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_metered_network_title), style = CustomTextStyles.titleCustom) }, text = { Column { MobileUpdateOptions.entries.forEach { option -> @@ -1949,7 +1951,7 @@ class PreferenceActivity : AppCompatActivity() { var isEnabled by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefEnableAutoDl.name, false)) } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_automatic_download_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.pref_automatic_download_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) Text(stringResource(R.string.pref_automatic_download_sum), color = textColor) } Switch(checked = isEnabled, onCheckedChange = { @@ -1960,7 +1962,7 @@ class PreferenceActivity : AppCompatActivity() { if (isEnabled) { Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.pref_episode_cache_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) + Text(stringResource(R.string.pref_episode_cache_title), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) var interval by remember { mutableStateOf(appPrefs.getString(UserPreferences.Prefs.prefEpisodeCacheSize.name, "25")!!) } var showIcon by remember { mutableStateOf(false) } TextField(value = interval, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("integer") }, @@ -1989,7 +1991,7 @@ class PreferenceActivity : AppCompatActivity() { var interval by remember { mutableStateOf(appPrefs.getString(UserPreferences.Prefs.prefEpisodeCleanup.name, "-1")!!) } if ((interval.toIntOrNull() ?: -1) > 0) tempCleanupOption = EpisodeCleanupOptions.LimitBy.num.toString() AlertDialog(onDismissRequest = { showCleanupOptions = false }, - title = { Text(stringResource(R.string.pref_episode_cleanup_title), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(R.string.pref_episode_cleanup_title), style = CustomTextStyles.titleCustom) }, text = { Column { EpisodeCleanupOptions.entries.forEach { option -> @@ -2588,7 +2590,7 @@ class PreferenceActivity : AppCompatActivity() { val textColor = MaterialTheme.colorScheme.onSurface val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { - Text(stringResource(R.string.notification_group_errors), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Text(stringResource(R.string.notification_group_errors), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) TitleSummarySwitchPrefRow(R.string.notification_channel_download_error, R.string.notification_channel_download_error_description, UserPreferences.Prefs.prefShowDownloadReport.name) if (isProviderConnected) TitleSummarySwitchPrefRow(R.string.notification_channel_sync_error, R.string.notification_channel_sync_error_description, UserPreferences.Prefs.pref_gpodnet_notifications.name) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/SubscriptionShortcutActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/SubscriptionShortcutActivity.kt index 021e26fb..62ee2d23 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/SubscriptionShortcutActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/SubscriptionShortcutActivity.kt @@ -49,7 +49,7 @@ class SubscriptionShortcutActivity : AppCompatActivity() { Card(modifier = Modifier.padding(vertical = 30.dp, horizontal = 16.dp), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { val textColor = MaterialTheme.colorScheme.onSurface Column { - Text(stringResource(R.string.shortcut_select_subscription), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 5.dp)) + Text(stringResource(R.string.shortcut_select_subscription), color = textColor, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 5.dp)) var checkedIndex by remember { mutableIntStateOf(-1) } val lazyListState = rememberLazyListState() LazyColumn(state = lazyListState, modifier = Modifier.weight(1f).fillMaxWidth().padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 20.dp), diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt index 83a3501e..e4f2b9bb 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt @@ -1,18 +1,14 @@ package ac.mdiq.podcini.ui.activity import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.AudioControlsBinding -import ac.mdiq.podcini.databinding.VideoEpisodeFragmentBinding import ac.mdiq.podcini.databinding.VideoplayerActivityBinding -import ac.mdiq.podcini.playback.ServiceStatusHandler import ac.mdiq.podcini.playback.base.InTheatre.curMedia +import ac.mdiq.podcini.playback.base.LocalMediaPlayer import ac.mdiq.podcini.playback.base.MediaPlayerBase import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.VideoMode import ac.mdiq.podcini.playback.cast.CastEnabledActivity import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curDurationFB -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curPositionFB -import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curSpeedFB import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isCasting import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isPlayingVideoLocally @@ -22,22 +18,18 @@ import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackServic import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs -import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting -import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode import ac.mdiq.podcini.storage.database.RealmDB.upsert -import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.Playable -import ac.mdiq.podcini.storage.utils.DurationConverter.getDurationStringLong -import ac.mdiq.podcini.storage.utils.TimeSpeedConverter import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter import ac.mdiq.podcini.ui.compose.ChaptersDialog +import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.PlaybackSpeedFullDialog -import ac.mdiq.podcini.ui.compose.SkipDialog -import ac.mdiq.podcini.ui.compose.SkipDirection -import ac.mdiq.podcini.ui.dialog.* +import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog +import ac.mdiq.podcini.ui.dialog.ShareDialog +import ac.mdiq.podcini.ui.dialog.SleepTimerDialog import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.view.ShownotesWebView import ac.mdiq.podcini.util.EventFlow @@ -45,56 +37,56 @@ import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.IntentUtils.openInBrowser import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.ShareUtils.hasLinkToShare -import android.app.Activity -import android.app.Dialog import android.content.DialogInterface import android.content.Intent -import android.content.pm.ActivityInfo import android.content.pm.PackageManager -import android.graphics.PixelFormat +import android.content.res.Configuration import android.graphics.drawable.ColorDrawable import android.media.AudioManager +import android.os.Build import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.util.Log -import android.util.Pair import android.view.* import android.view.MenuItem.SHOW_AS_ACTION_NEVER -import android.view.animation.* import android.widget.EditText -import android.widget.FrameLayout -import android.widget.SeekBar -import android.widget.SeekBar.OnSeekBarChangeListener -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.core.app.ActivityCompat.invalidateOptionsMenu -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.WindowCompat +import androidx.core.view.WindowCompat.getInsetsController +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope -import androidx.window.layout.WindowMetricsCalculator +import androidx.media3.ui.PlayerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlin.math.max -import kotlin.math.min class VideoplayerActivity : CastEnabledActivity() { private var _binding: VideoplayerActivityBinding? = null private val binding get() = _binding!! - private lateinit var videoEpisodeFragment: VideoEpisodeFragment var switchToAudioOnly = false + private var cleanedNotes by mutableStateOf(null) + private var feedTitle by mutableStateOf("") + private var episodeTitle by mutableStateOf("") + private var showAcrionBar by mutableStateOf(false) + var landscape by mutableStateOf(false) override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.Theme_Podcini_VideoPlayer) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) var vmCode = 0 if (curMedia is EpisodeMedia) { @@ -117,46 +109,117 @@ class VideoplayerActivity : CastEnabledActivity() { Logd(TAG, "videoMode not selected, use window mode") videoMode = VideoMode.WINDOW_VIEW } + landscape = videoMode == VideoMode.FULL_SCREEN_VIEW supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY) - setForVideoMode() super.onCreate(savedInstanceState) _binding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this)) + setForVideoMode() + + binding.mainView.setContent { + CustomTheme(this) { + if (landscape) Box(modifier = Modifier.fillMaxSize()) { VideoPlayer() } + else { + val textColor = MaterialTheme.colorScheme.onSurface + Column(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxWidth().aspectRatio(16 / 9f)) { VideoPlayer() } + Text(curMedia?.getFeedTitle()?:"", color = textColor, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(horizontal = 10.dp)) + Text(curMedia?.getEpisodeTitle()?:"", color = textColor, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(horizontal = 10.dp)) + MediaDetails() + } + } + } + } setContentView(binding.root) supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000)) supportActionBar?.setDisplayHomeAsUpEnabled(true) - - val fm = supportFragmentManager - val transaction = fm.beginTransaction() - videoEpisodeFragment = VideoEpisodeFragment() - transaction.replace(R.id.main_view, videoEpisodeFragment, "VideoEpisodeFragment") - transaction.commit() + setForVideoMode() } private fun setForVideoMode() { Logd(TAG, "setForVideoMode videoMode: $videoMode") + setTheme(R.style.Theme_Podcini_VideoPlayer) + supportActionBar?.hide() when (videoMode) { - VideoMode.FULL_SCREEN_VIEW -> { - window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) - window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN) - setTheme(R.style.Theme_Podcini_VideoPlayer) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) - window.setFormat(PixelFormat.TRANSPARENT) + VideoMode.FULL_SCREEN_VIEW -> hideSystemUI() + VideoMode.WINDOW_VIEW -> showSystemUI() + else -> {} + } + val flags = window.attributes.flags + Logd(TAG, "Current Flags: $flags") + } + + private fun showSystemUI() { + WindowCompat.setDecorFitsSystemWindows(window, true) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.decorView.apply { systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE } + } else { + window.insetsController?.apply { + show(WindowInsetsCompat.Type.systemBars()) + systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT } - VideoMode.WINDOW_VIEW -> { - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN) -// setTheme(R.style.Theme_Podcini_VideoEpisode) - setTheme(R.style.Theme_Podcini_VideoPlayer) - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - window.setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) - window.setFormat(PixelFormat.TRANSPARENT) + } + } + + fun hideSystemUI() { + WindowCompat.setDecorFitsSystemWindows(window, false) + WindowInsetsControllerCompat(window, window.decorView).hide(WindowInsetsCompat.Type.systemBars()) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.decorView.apply { systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN } + } else { + window.insetsController?.apply { + hide(WindowInsetsCompat.Type.systemBars()) + systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } - else -> {} } - if (::videoEpisodeFragment.isInitialized) videoEpisodeFragment.setForVideoMode() + } + + @Composable + fun VideoPlayer() { + AndroidView(modifier = Modifier.fillMaxWidth(), + factory = { context -> + PlayerView(context).apply { + this.player = LocalMediaPlayer.exoPlayer + useController = true + setControllerVisibilityListener( + PlayerView.ControllerVisibilityListener { visibility -> + if (visibility == View.VISIBLE) { + showAcrionBar = true + supportActionBar?.show() + } else { + showAcrionBar = false + supportActionBar?.hide() + } + } + ) + } + } + ) + } + + @Composable + fun MediaDetails() { + val textColor = MaterialTheme.colorScheme.onSurface + if (cleanedNotes == null) loadMediaInfo() + AndroidView(modifier = Modifier.fillMaxSize(), factory = { context -> + ShownotesWebView(context).apply { + setTimecodeSelectedListener { time: Int -> seekTo(time) } + setPageFinishedListener { + postDelayed({ }, 50) + } + } + }, update = { webView -> webView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes?:"No notes", "text/html", "utf-8", "about:blank") }) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + videoMode = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) VideoMode.FULL_SCREEN_VIEW else VideoMode.WINDOW_VIEW + setForVideoMode() + landscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE + Logd(TAG, "onConfigurationChanged landscape: $landscape") } override fun onResume() { @@ -166,7 +229,6 @@ class VideoplayerActivity : CastEnabledActivity() { if (isCasting) { val intent = getPlayerActivityIntent(this) if (intent.component?.className != VideoplayerActivity::class.java.name) { - videoEpisodeFragment.destroyingDueToReload = true finish() startActivity(intent) } @@ -174,10 +236,10 @@ class VideoplayerActivity : CastEnabledActivity() { } override fun onDestroy() { - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN) + val insetsController = getInsetsController(window, window.decorView) + insetsController.show(WindowInsetsCompat.Type.statusBars()) + insetsController.show(WindowInsetsCompat.Type.navigationBars()) window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) _binding = null super.onDestroy() } @@ -197,9 +259,50 @@ class VideoplayerActivity : CastEnabledActivity() { cancelFlowEvents() } - fun toggleViews() { - videoMode = if (videoMode == VideoMode.FULL_SCREEN_VIEW) VideoMode.WINDOW_VIEW else VideoMode.FULL_SCREEN_VIEW - setForVideoMode() + private var loadItemsRunning = false + private fun loadMediaInfo() { + Logd(TAG, "loadMediaInfo called") + if (curMedia == null) return + if (MediaPlayerBase.status == PlayerStatus.PLAYING && !isPlayingVideoLocally) { + Logd(TAG, "Closing, no longer video") + finish() + MainActivityStarter(this).withOpenPlayer().start() + return + } + if (!loadItemsRunning) { + loadItemsRunning = true + lifecycleScope.launch { + try { + val episode = withContext(Dispatchers.IO) { + var episode_ = (curMedia as? EpisodeMedia)?.episodeOrFetch() + if (episode_ != null) { + val duration = episode_.media?.getDuration() ?: Int.MAX_VALUE + val url = episode_.media?.downloadUrl + val shownotesCleaner = ShownotesCleaner(this@VideoplayerActivity) + if (url?.contains("youtube.com") == true && episode_.description?.startsWith("Short:") == true) { + Logd(TAG, "getting extended description: ${episode_.title}") + try { + val info = episode_.streamInfo + if (info?.description?.content != null) { + episode_ = upsert(episode_) { it.description = info.description?.content } + cleanedNotes = shownotesCleaner.processShownotes(info.description!!.content, duration) + } else cleanedNotes = shownotesCleaner.processShownotes(episode_.description ?: "", duration) + } catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") } + } else cleanedNotes = shownotesCleaner.processShownotes(episode_.description ?: "", duration) + } + episode_ + } + } catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) + } finally { loadItemsRunning = false } + } + } + val media = curMedia + if (media != null) { + feedTitle = media.getFeedTitle() + episodeTitle = media.getEpisodeTitle() + supportActionBar?.subtitle = episodeTitle + supportActionBar?.title = feedTitle + } } private var eventSink: Job? = null @@ -257,13 +360,6 @@ class VideoplayerActivity : CastEnabledActivity() { val isItemHasDownloadLink = isEpisodeMedia && (media as EpisodeMedia?)?.downloadUrl != null menu.findItem(R.id.share_item).isVisible = hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink -// menu.findItem(R.id.add_to_favorites_item).setVisible(false) -// menu.findItem(R.id.remove_from_favorites_item).setVisible(false) -// if (isEpisodeMedia) { -// menu.findItem(R.id.add_to_favorites_item).setVisible(!videoEpisodeFragment.isFavorite) -// menu.findItem(R.id.remove_from_favorites_item).setVisible(videoEpisodeFragment.isFavorite) -// } - menu.findItem(R.id.set_sleeptimer_item).isVisible = !isSleepTimerActive() menu.findItem(R.id.disable_sleeptimer_item).isVisible = isSleepTimerActive() menu.findItem(R.id.player_switch_to_audio_only).isVisible = true @@ -273,8 +369,6 @@ class VideoplayerActivity : CastEnabledActivity() { menu.findItem(R.id.player_show_chapters).isVisible = true if (videoMode == VideoMode.WINDOW_VIEW) { -// menu.findItem(R.id.add_to_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER) -// menu.findItem(R.id.remove_from_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.set_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.disable_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.player_switch_to_audio_only).setShowAsAction(SHOW_AS_ACTION_NEVER) @@ -323,8 +417,18 @@ class VideoplayerActivity : CastEnabledActivity() { item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item -> SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog") item.itemId == R.id.audio_controls -> { - val dialog = PlaybackControlsDialog.newInstance() - dialog.show(supportFragmentManager, "playback_controls") +// val dialog = PlaybackControlsDialog.newInstance() +// dialog.show(supportFragmentManager, "playback_controls") + val composeView = ComposeView(this).apply { + setContent { + var showAudioControlDialog by remember { mutableStateOf(true) } + if (showAudioControlDialog) PlaybackControlsDialog(onDismiss = { + showAudioControlDialog = false + (parent as? ViewGroup)?.removeView(this) + }) + } + } + (window.decorView as? ViewGroup)?.addView(composeView) } item.itemId == R.id.open_feed_item && feedItem != null -> { val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId!!) @@ -362,7 +466,7 @@ class VideoplayerActivity : CastEnabledActivity() { private fun compatEnterPictureInPicture() { if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { if (videoMode == VideoMode.FULL_SCREEN_VIEW) supportActionBar?.hide() - videoEpisodeFragment.hideVideoControls(false) +// videoEpisodeFragment.hideVideoControls(false) enterPictureInPictureMode() } } @@ -374,18 +478,15 @@ class VideoplayerActivity : CastEnabledActivity() { val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager when (keyCode) { KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE -> { - videoEpisodeFragment.onPlayPause() - videoEpisodeFragment.toggleVideoControlsVisibility() + playPause() return true } KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_COMMA -> { - videoEpisodeFragment.onRewind() - videoEpisodeFragment.showSkipAnimation(false) + playbackService?.mPlayer?.seekDelta(-rewindSecs * 1000) return true } KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_PERIOD -> { - videoEpisodeFragment.onFastForward() - videoEpisodeFragment.showSkipAnimation(true) + playbackService?.mPlayer?.seekDelta(fastForwardSecs * 1000) return true } KeyEvent.KEYCODE_F, KeyEvent.KEYCODE_ESCAPE -> { @@ -418,560 +519,27 @@ class VideoplayerActivity : CastEnabledActivity() { return super.onKeyUp(keyCode, event) } -// fun supportsPictureInPicture(): Boolean { -//// val packageManager = activity.packageManager -// return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) -// } - override fun isInPictureInPictureMode(): Boolean { return if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) super.isInPictureInPictureMode else false } - class PlaybackControlsDialog : DialogFragment() { - private lateinit var dialog: AlertDialog - private var _binding: AudioControlsBinding? = null - private val binding get() = _binding!! - private var statusHandler: ServiceStatusHandler? = null - - override fun onStart() { - super.onStart() - statusHandler = object : ServiceStatusHandler(requireActivity()) { - override fun loadMediaInfo() { - setupAudioTracks() - } - } - statusHandler?.init() - } - override fun onStop() { - super.onStop() - statusHandler?.release() - statusHandler = null - } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - _binding = AudioControlsBinding.inflate(layoutInflater) - dialog = MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.audio_controls) - .setView(R.layout.audio_controls) - .setPositiveButton(R.string.close_label, null).create() - return dialog - } - override fun onDestroyView() { - Logd(TAG, "onDestroyView") - _binding = null - super.onDestroyView() - } - private fun setupAudioTracks() { - val butAudioTracks = binding.audioTracks - if (audioTracks.size < 2 || selectedAudioTrack < 0) { - butAudioTracks.visibility = View.GONE - return - } - butAudioTracks.visibility = View.VISIBLE - butAudioTracks.text = audioTracks[selectedAudioTrack] - butAudioTracks.setOnClickListener { -// setAudioTrack((selectedAudioTrack + 1) % audioTracks.size) - playbackService?.mPlayer?.setAudioTrack((selectedAudioTrack + 1) % audioTracks.size) - Handler(Looper.getMainLooper()).postDelayed({ this.setupAudioTracks() }, 500) - } - } - - companion object { - fun newInstance(): PlaybackControlsDialog { - val dialog = PlaybackControlsDialog() - return dialog - } - } - } - - class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { - private var _binding: VideoEpisodeFragmentBinding? = null - private val binding get() = _binding!! - private lateinit var root: ViewGroup - private var videoControlsVisible = true - private var videoSurfaceCreated = false - private var lastScreenTap: Long = 0 - private val videoControlsHider = Handler(Looper.getMainLooper()) - private var showTimeLeft = false - private var prog = 0f - - private var itemsLoaded = false - private var episode: Episode? = null - private var webviewData: String = "" - private var webvDescription: ShownotesWebView? = null - - var destroyingDueToReload = false - private var statusHandler: ServiceStatusHandler? = null - var isFavorite = false - - private val onVideoviewTouched = View.OnTouchListener { v: View, event: MotionEvent -> - Logd(TAG, "onVideoviewTouched ${event.action}") - if (event.action != MotionEvent.ACTION_DOWN) return@OnTouchListener false - if (requireActivity().isInPictureInPictureMode) return@OnTouchListener true - videoControlsHider.removeCallbacks(hideVideoControls) - Logd(TAG, "onVideoviewTouched $videoControlsVisible ${System.currentTimeMillis() - lastScreenTap}") - if (System.currentTimeMillis() - lastScreenTap < 300) { - if (event.x > v.measuredWidth / 2.0f) { - onFastForward() - showSkipAnimation(true) - } else { - onRewind() - showSkipAnimation(false) - } - if (videoControlsVisible) { - hideVideoControls(false) - videoControlsVisible = false - } - return@OnTouchListener true - } - toggleVideoControlsVisibility() - if (videoControlsVisible) setupVideoControlsToggler() - lastScreenTap = System.currentTimeMillis() - true - } - - private val surfaceHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback { - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - holder.setFixedSize(width, height) - } - - override fun surfaceCreated(holder: SurfaceHolder) { - Logd(TAG, "Videoview holder created") - videoSurfaceCreated = true - if (MediaPlayerBase.status == PlayerStatus.PLAYING) playbackService?.mPlayer?.setVideoSurface(holder) - setupVideoAspectRatio() - } - override fun surfaceDestroyed(holder: SurfaceHolder) { - Logd(TAG, "Videosurface was destroyed") - videoSurfaceCreated = false - (activity as? VideoplayerActivity)?.finish() - } - } - - private val hideVideoControls = Runnable { - if (videoControlsVisible) { - Logd(TAG, "Hiding video controls") - hideVideoControls(true) - videoControlsVisible = false - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - super.onCreateView(inflater, container, savedInstanceState) - Logd(TAG, "fragment onCreateView") - _binding = VideoEpisodeFragmentBinding.inflate(inflater) - root = binding.root - statusHandler = newStatusHandler() - statusHandler!!.init() - setupView() - return root - } - - private fun newStatusHandler(): ServiceStatusHandler { - return object : ServiceStatusHandler(requireActivity()) { - override fun updatePlayButton(showPlay: Boolean) { - Logd(TAG, "updatePlayButtonShowsPlay called") - binding.playButton.setIsShowPlay(showPlay) - if (showPlay) (activity as AppCompatActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - else { - (activity as AppCompatActivity).window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - setupVideoAspectRatio() - if (videoSurfaceCreated) { - Logd(TAG, "Videosurface already created, setting videosurface now") - playbackService?.mPlayer?.setVideoSurface(binding.videoView.holder) - } - } - } - override fun loadMediaInfo() { - this@VideoEpisodeFragment.loadMediaInfo() - } - override fun onPlaybackEnd() { - activity?.finish() - } - } - } - - override fun onStart() { - super.onStart() - onPositionObserverUpdate() - procFlowEvents() - } - - override fun onStop() { - super.onStop() - cancelFlowEvents() - if (!requireActivity().isInPictureInPictureMode) videoControlsHider.removeCallbacks(hideVideoControls) - // Controller released; we will not receive buffering updates - binding.progressBar.visibility = View.GONE - } - - override fun onDestroyView() { - Logd(TAG, "onDestroyView") - if (webvDescription != null) { - root.removeView(webvDescription!!) - webvDescription!!.destroy() - } - _binding = null - statusHandler?.release() - statusHandler = null // prevent leak - super.onDestroyView() - } - - private var eventSink: Job? = null - private fun cancelFlowEvents() { - eventSink?.cancel() - eventSink = null - } - private fun procFlowEvents() { - if (eventSink != null) return - eventSink = lifecycleScope.launch { - EventFlow.events.collectLatest { event -> - Logd(TAG, "Received event: ${event.TAG}") - when (event) { - is FlowEvent.BufferUpdateEvent -> bufferUpdate(event) - is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate() - else -> {} + @Composable + fun PlaybackControlsDialog(onDismiss: ()-> Unit) { + val textColor = MaterialTheme.colorScheme.onSurface + AlertDialog(onDismissRequest = onDismiss, title = { Text(stringResource(R.string.audio_controls)) }, + text = { + LazyColumn { + items(audioTracks.size) {index -> + Text(audioTracks[index], color = textColor, modifier = Modifier.clickable(onClick = { + playbackService?.mPlayer?.setAudioTrack((selectedAudioTrack + 1) % audioTracks.size) +// Handler(Looper.getMainLooper()).postDelayed({ setupAudioTracks() }, 500) + })) } } - } - } - - fun setForVideoMode() { - when (videoMode) { - VideoMode.FULL_SCREEN_VIEW -> { - Logd(TAG, "setForVideoMode setting for FULL_SCREEN_VIEW") - webvDescription?.visibility = View.GONE - val layoutParams = binding.videoPlayerContainer.layoutParams - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - binding.videoPlayerContainer.layoutParams = layoutParams - binding.topBar.visibility = View.GONE - } - VideoMode.WINDOW_VIEW -> { - Logd(TAG, "setForVideoMode setting for WINDOW_VIEW") - webvDescription?.visibility = View.VISIBLE - val layoutParams = binding.videoPlayerContainer.layoutParams - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT - binding.videoPlayerContainer.layoutParams = layoutParams - binding.topBar.visibility = View.VISIBLE - } - else -> {} - } - setupVideoAspectRatio() - } - - private fun bufferUpdate(event: FlowEvent.BufferUpdateEvent) { - when { - event.hasStarted() -> binding.progressBar.visibility = View.VISIBLE - event.hasEnded() -> binding.progressBar.visibility = View.INVISIBLE - else -> binding.sbPosition.secondaryProgress = (event.progress * binding.sbPosition.max).toInt() - } - } - - private fun setupVideoAspectRatio() { - if (videoSurfaceCreated) { - val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity as Activity) - val videoWidth = when (videoMode) { - VideoMode.FULL_SCREEN_VIEW -> max(windowMetrics.bounds.width(), windowMetrics.bounds.height()) - VideoMode.WINDOW_VIEW -> min(windowMetrics.bounds.width(), windowMetrics.bounds.height()) - else -> min(windowMetrics.bounds.width(), windowMetrics.bounds.height()) - } - val videoHeight: Int - if (videoSize != null && videoSize!!.first > 0 && videoSize!!.second > 0) { - Logd(TAG, "setupVideoAspectRatio video width: ${videoSize!!.first} height: ${videoSize!!.second}") - videoHeight = (videoWidth.toFloat() / videoSize!!.first * videoSize!!.second).toInt() - Logd(TAG, "setupVideoAspectRatio adjusted video width: $videoWidth height: $videoHeight") - } else { - videoHeight = (videoWidth.toFloat() / 16 * 9).toInt() - Logd(TAG, "setupVideoAspectRatio Could not determine video size, use: $videoWidth $videoHeight") - } - val lp = binding.videoView.layoutParams - lp.width = videoWidth - lp.height = videoHeight - binding.videoView.layoutParams = lp - } - } - - private var loadItemsRunning = false - - private fun loadMediaInfo() { - Logd(TAG, "loadMediaInfo called") - if (curMedia == null) return - if (MediaPlayerBase.status == PlayerStatus.PLAYING && !isPlayingVideoLocally) { - Logd(TAG, "Closing, no longer video") - destroyingDueToReload = true - activity?.finish() - MainActivityStarter(requireContext()).withOpenPlayer().start() - return - } - showTimeLeft = shouldShowRemainingTime() - onPositionObserverUpdate() - if (!loadItemsRunning) { - loadItemsRunning = true - lifecycleScope.launch { - try { - episode = withContext(Dispatchers.IO) { - var episode_ = (curMedia as? EpisodeMedia)?.episodeOrFetch() - if (episode_ != null) { - val duration = episode_.media?.getDuration() ?: Int.MAX_VALUE - val url = episode_.media?.downloadUrl - val shownotesCleaner = ShownotesCleaner(requireContext()) - if (url?.contains("youtube.com") == true && episode_.description?.startsWith("Short:") == true) { - Logd(TAG, "getting extended description: ${episode_.title}") - try { - val info = episode_.streamInfo - if (info?.description?.content != null) { - episode_ = upsert(episode_) { it.description = info.description?.content } - webviewData = shownotesCleaner.processShownotes(info.description!!.content, duration) - } else webviewData = shownotesCleaner.processShownotes(episode_.description ?: "", duration) - } catch (e: Exception) { Logd(TAG, "StreamInfo error: ${e.message}") } - } else webviewData = shownotesCleaner.processShownotes(episode_.description ?: "", duration) - } - episode_ - } - withContext(Dispatchers.Main) { - Logd(TAG, "load() item ${episode?.id}") - if (episode != null) { - val isFav = episode!!.isSUPER - if (isFavorite != isFav) { - isFavorite = isFav - invalidateOptionsMenu(activity) - } - } - if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData, "text/html", "utf-8", "about:blank") - itemsLoaded = true - } - } catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) - } finally { loadItemsRunning = false } - } - } - val media = curMedia - if (media != null) { - (activity as AppCompatActivity).supportActionBar?.subtitle = media.getEpisodeTitle() - (activity as AppCompatActivity).supportActionBar?.title = media.getFeedTitle() - } - } - - private fun setupView() { - showTimeLeft = shouldShowRemainingTime() - Logd(TAG, "setupView showTimeLeft: $showTimeLeft") - binding.durationLabel.setOnClickListener { - showTimeLeft = !showTimeLeft - val media = curMedia ?: return@setOnClickListener - val converter = TimeSpeedConverter(curSpeedFB) - val length: String - if (showTimeLeft) { - val remainingTime = converter.convert(media.getDuration() - media.getPosition()) - length = "-" + getDurationStringLong(remainingTime) - } else { - val duration = converter.convert(media.getDuration()) - length = getDurationStringLong(duration) - } - binding.durationLabel.text = length - setShowRemainTimeSetting(showTimeLeft) - Logd("timeleft on click", if (showTimeLeft) "true" else "false") - } - binding.sbPosition.setOnSeekBarChangeListener(this) - binding.rewindButton.setOnClickListener { onRewind() } - binding.rewindButton.setOnLongClickListener { - val composeView = ComposeView(requireContext()).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(requireContext()) { - SkipDialog(SkipDirection.SKIP_REWIND, onDismissRequest = { - showDialog.value = false - (view as? ViewGroup)?.removeView(this@apply) - }) {} - } - } - } - (view as? ViewGroup)?.addView(composeView) - true - } - binding.playButton.setIsVideoScreen(true) - binding.playButton.setOnClickListener { onPlayPause() } - binding.fastForwardButton.setOnClickListener { onFastForward() } - binding.fastForwardButton.setOnLongClickListener { - val composeView = ComposeView(requireContext()).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(requireContext()) { - SkipDialog(SkipDirection.SKIP_FORWARD, onDismissRequest = { - showDialog.value = false - (view as? ViewGroup)?.removeView(this@apply) - }) {} - } - } - } - (view as? ViewGroup)?.addView(composeView) - false - } - // To suppress touches directly below the slider - binding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true } - binding.videoView.holder.addCallback(surfaceHolderCallback) - setupVideoControlsToggler() - binding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched) - webvDescription = binding.webvDescription -// webvDescription.setTimecodeSelectedListener { time: Int? -> -// val cMedia = getMedia -// if (item?.media?.getIdentifier() == cMedia?.getIdentifier()) { -// seekTo(time ?: 0) -// } else { -// (activity as MainActivity).showSnackbarAbovePlayer(R.string.play_this_to_seek_position, -// Snackbar.LENGTH_LONG) -// } -// } -// registerForContextMenu(webvDescription) -// webvDescription.visibility = View.GONE - binding.toggleViews.setOnClickListener { (activity as VideoplayerActivity).toggleViews() } - binding.audioOnly.setOnClickListener { - (activity as? VideoplayerActivity)?.switchToAudioOnly = true - (curMedia as? EpisodeMedia)?.forceVideo = false - (activity as? VideoplayerActivity)?.finish() - } - if (!itemsLoaded) webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData, "text/html", "utf-8", "about:blank") - } - - fun toggleVideoControlsVisibility() { - if (videoControlsVisible) hideVideoControls(true) - else showVideoControls() - videoControlsVisible = !videoControlsVisible - } - - fun showSkipAnimation(isForward: Boolean) { - val skipAnimation = AnimationSet(true) - skipAnimation.addAnimation(ScaleAnimation(1f, 2f, 1f, 2f, - Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)) - skipAnimation.addAnimation(AlphaAnimation(1f, 0f)) - skipAnimation.fillAfter = false - skipAnimation.duration = 800 - - val params = binding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams - if (isForward) { - binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white) - params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL - } else { - binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white) - params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL - } - binding.skipAnimationImage.visibility = View.VISIBLE - binding.skipAnimationImage.layoutParams = params - binding.skipAnimationImage.startAnimation(skipAnimation) - skipAnimation.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) {} - override fun onAnimationEnd(animation: Animation) { - binding.skipAnimationImage.visibility = View.GONE - } - override fun onAnimationRepeat(animation: Animation) {} - }) - } - - fun onRewind() { - playbackService?.mPlayer?.seekDelta(-rewindSecs * 1000) - setupVideoControlsToggler() - } - - fun onPlayPause() { - playPause() - setupVideoControlsToggler() - } - - fun onFastForward() { - playbackService?.mPlayer?.seekDelta(fastForwardSecs * 1000) - setupVideoControlsToggler() - } - - private fun setupVideoControlsToggler() { - videoControlsHider.removeCallbacks(hideVideoControls) - videoControlsHider.postDelayed(hideVideoControls, 2500) - } - - private fun showVideoControls() { - Logd(TAG, "showVideoControls") - binding.bottomControlsContainer.visibility = View.VISIBLE - binding.controlsContainer.visibility = View.VISIBLE - val animation = AnimationUtils.loadAnimation(activity, R.anim.fade_in) - if (animation != null) { - binding.bottomControlsContainer.startAnimation(animation) - binding.controlsContainer.startAnimation(animation) - } - (activity as AppCompatActivity).window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) - (activity as AppCompatActivity).supportActionBar?.show() - } - - fun hideVideoControls(showAnimation: Boolean) { - Logd(TAG, "hideVideoControls $showAnimation") - if (!isAdded) return - if (showAnimation) { - val animation = AnimationUtils.loadAnimation(activity, R.anim.fade_out) - if (animation != null) { - binding.bottomControlsContainer.startAnimation(animation) - binding.controlsContainer.startAnimation(animation) - } - } - (activity as AppCompatActivity).window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) - binding.bottomControlsContainer.visibility = View.GONE - binding.controlsContainer.visibility = View.GONE - if (videoMode == VideoMode.FULL_SCREEN_VIEW) (activity as AppCompatActivity).supportActionBar?.hide() - } - - private fun onPositionObserverUpdate() { - val converter = TimeSpeedConverter(curSpeedFB) - val currentPosition = converter.convert(curPositionFB) - val duration_ = converter.convert(curDurationFB) - val remainingTime = converter.convert(curDurationFB - curPositionFB) - // Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition)); - if (currentPosition == Playable.INVALID_TIME || duration_ == Playable.INVALID_TIME) { - Log.w(TAG, "Could not react to position observer update because of invalid time") - return - } - binding.positionLabel.text = getDurationStringLong(currentPosition) - if (showTimeLeft) binding.durationLabel.text = "-" + getDurationStringLong(remainingTime) - else binding.durationLabel.text = getDurationStringLong(duration_) - updateProgressbarPosition(currentPosition, duration_) - } - - private fun updateProgressbarPosition(position: Int, duration: Int) { - Logd(TAG, "updateProgressbarPosition ($position, $duration)") - val progress = (position.toFloat()) / duration - binding.sbPosition.progress = (progress * binding.sbPosition.max).toInt() - } - - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - if (fromUser) { - prog = progress / (seekBar.max.toFloat()) - val converter = TimeSpeedConverter(curSpeedFB) - val position = converter.convert((prog * curDurationFB).toInt()) - binding.seekPositionLabel.text = getDurationStringLong(position) - } - } - - override fun onStartTrackingTouch(seekBar: SeekBar) { - binding.seekCardView.scaleX = .8f - binding.seekCardView.scaleY = .8f - binding.seekCardView.animate() - .setInterpolator(FastOutSlowInInterpolator()) - .alpha(1f).scaleX(1f).scaleY(1f) - .setDuration(200) - .start() - videoControlsHider.removeCallbacks(hideVideoControls) - } - - override fun onStopTrackingTouch(seekBar: SeekBar) { - seekTo((prog * curDurationFB).toInt()) - binding.seekCardView.scaleX = 1f - binding.seekCardView.scaleY = 1f - binding.seekCardView.animate() - .setInterpolator(FastOutSlowInInterpolator()) - .alpha(0f).scaleX(.8f).scaleY(.8f) - .setDuration(200) - .start() - setupVideoControlsToggler() - } - - companion object { - val videoSize: Pair? - get() = playbackService?.mPlayer?.getVideoSize() - } + }, + confirmButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(R.string.close_label)) } } + ) } companion object { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt index 651ff705..95edaa5f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt @@ -20,12 +20,16 @@ import androidx.core.content.ContextCompat private const val TAG = "AppTheme" -val Typography = Typography( +val CustomTypography = Typography( displayLarge = TextStyle(fontFamily = FontFamily.Default, fontWeight = FontWeight.Bold, fontSize = 30.sp), - bodyLarge = TextStyle(fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp) + bodyLarge = TextStyle(fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp), // Add other text styles as needed ) +object CustomTextStyles { + val titleCustom = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Medium) +} + val Shapes = Shapes(small = RoundedCornerShape(4.dp), medium = RoundedCornerShape(4.dp), large = RoundedCornerShape(0.dp)) fun getColorFromAttr(context: Context, @AttrRes attrColor: Int): Int { @@ -47,5 +51,5 @@ fun CustomTheme(context: Context, content: @Composable () -> Unit) { ThemePreference.BLACK -> DarkColors.copy(surface = Color(0xFF000000)) ThemePreference.SYSTEM -> if (isSystemInDarkTheme()) DarkColors else LightColors } - MaterialTheme(colorScheme = colors, typography = Typography, shapes = Shapes, content = content) + MaterialTheme(colorScheme = colors, typography = CustomTypography, shapes = Shapes, content = content) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt index 6a164e74..48702b46 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt @@ -1,15 +1,10 @@ package ac.mdiq.podcini.ui.compose import ac.mdiq.podcini.preferences.UserPreferences.appPrefs -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -26,7 +21,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import kotlinx.coroutines.delay -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -133,7 +127,7 @@ fun LargeTextEditingDialog(textState: TextFieldValue, onTextChange: (TextFieldVa Surface(modifier = Modifier.fillMaxWidth().padding(16.dp), shape = MaterialTheme.shapes.medium, border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { val textColor = MaterialTheme.colorScheme.onSurface Column(modifier = Modifier.padding(16.dp)) { - Text(text = "Add comment", color = textColor, style = MaterialTheme.typography.titleLarge) + Text(text = "Add comment", color = textColor, style = CustomTextStyles.titleCustom) Spacer(modifier = Modifier.height(16.dp)) BasicTextField(value = textState, onValueChange = { onTextChange(it) }, textStyle = TextStyle(fontSize = 16.sp, color = textColor), modifier = Modifier.fillMaxWidth().height(300.dp).padding(10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small) @@ -179,10 +173,10 @@ fun SimpleSwitchDialog(title: String, text: String, onDismissRequest: ()->Unit, val textColor = MaterialTheme.colorScheme.onSurface var isChecked by remember { mutableStateOf(false) } AlertDialog(onDismissRequest = { onDismissRequest() }, - title = { Text(title, style = MaterialTheme.typography.titleLarge) }, + title = { Text(title, style = CustomTextStyles.titleCustom) }, text = { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Text(text, color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) + Text(text, color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) Switch(checked = isChecked, onCheckedChange = { isChecked = it }) } }, @@ -202,8 +196,8 @@ fun IconTitleSummaryActionRow(vecRes: Int, titleRes: Int, summaryRes: Int, callb Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { Icon(imageVector = ImageVector.vectorResource(vecRes), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) Column(modifier = Modifier.weight(1f).clickable(onClick = { callback() })) { - Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(summaryRes), color = textColor) + Text(stringResource(titleRes), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) + Text(stringResource(summaryRes), color = textColor, style = MaterialTheme.typography.bodySmall) } } } @@ -212,8 +206,8 @@ fun IconTitleSummaryActionRow(vecRes: Int, titleRes: Int, summaryRes: Int, callb fun TitleSummaryActionColumn(titleRes: Int, summaryRes: Int, callback: ()-> Unit) { val textColor = MaterialTheme.colorScheme.onSurface Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { callback() })) { - Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - if (summaryRes != 0) Text(stringResource(summaryRes), color = textColor) + Text(stringResource(titleRes), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) + if (summaryRes != 0) Text(stringResource(summaryRes), color = textColor, style = MaterialTheme.typography.bodySmall) } } @@ -222,8 +216,8 @@ fun TitleSummarySwitchPrefRow(titleRes: Int, summaryRes: Int, prefName: String) val textColor = MaterialTheme.colorScheme.onSurface Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { - Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(summaryRes), color = textColor) + Text(stringResource(titleRes), color = textColor, style = CustomTextStyles.titleCustom, fontWeight = FontWeight.Bold) + Text(stringResource(summaryRes), color = textColor, style = MaterialTheme.typography.bodySmall) } var isChecked by remember { mutableStateOf(appPrefs.getBoolean(prefName, false)) } Switch(checked = isChecked, onCheckedChange = { 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 1989f28d..c9e8a016 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 @@ -652,15 +652,15 @@ fun OpmlImportSelectionDialog(readElements: SnapshotStateList Unit, callback: (VideoMode) -> Unit) { - var selectedOption by remember { mutableStateOf(VideoMode.NONE.name) } +fun VideoModeDialog(initMode: VideoMode?, onDismissRequest: () -> Unit, callback: (VideoMode) -> Unit) { + var selectedOption by remember { mutableStateOf(initMode?.tag ?: VideoMode.NONE.tag) } Dialog(onDismissRequest = { onDismissRequest() }) { Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column { VideoMode.entries.forEach { mode -> - val text = mode.tag Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) { + val text = remember { mode.tag } Checkbox(checked = (text == selectedOption), onCheckedChange = { if (text != selectedOption) { selectedOption = text diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Playback.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Playback.kt index 8ce9a056..52caea26 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Playback.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Playback.kt @@ -30,7 +30,7 @@ fun SkipDialog(direction: SkipDirection, onDismissRequest: ()->Unit, callBack: ( val titleRes = if (direction == SkipDirection.SKIP_FORWARD) R.string.pref_fast_forward else R.string.pref_rewind var interval by remember { mutableStateOf((if (direction == SkipDirection.SKIP_FORWARD) UserPreferences.fastForwardSecs else UserPreferences.rewindSecs).toString()) } AlertDialog(onDismissRequest = { onDismissRequest() }, - title = { Text(stringResource(titleRes), style = MaterialTheme.typography.titleLarge) }, + title = { Text(stringResource(titleRes), style = CustomTextStyles.titleCustom) }, text = { TextField(value = interval, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Companion.Number), label = { Text("seconds") }, singleLine = true, onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) interval = it }) }, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index f9f811c1..82084aaa 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -38,6 +38,7 @@ import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter import ac.mdiq.podcini.ui.compose.ChaptersDialog import ac.mdiq.podcini.ui.compose.ChooseRatingDialog +import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.PlaybackSpeedFullDialog import ac.mdiq.podcini.ui.compose.SkipDialog @@ -494,7 +495,7 @@ class AudioPlayerFragment : Fragment() { Text(episodeDate, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.bodyMedium) Spacer(modifier = Modifier.weight(0.6f)) } - Text(titleText, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp) + Text(titleText, textAlign = TextAlign.Center, color = textColor, style = CustomTextStyles.titleCustom, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp) .combinedClickable(onClick = {}, onLongClick = { copyText(currentItem?.title?:"") })) // fun restoreFromPreference(): Boolean { // if ((activity as MainActivity).bottomSheet.state != BottomSheetBehavior.STATE_EXPANDED) return false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt index d3434d8b..8b1e54ef 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt @@ -289,7 +289,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { if (!episode?.chapters.isNullOrEmpty()) Text(stringResource(id = R.string.chapters_label), color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp).clickable(onClick = { showChaptersDialog = true })) Text(stringResource(R.string.my_opinion_label) + if (commentTextState.text.isEmpty()) " (Add)" else "", - color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp).clickable { showEditComment = true }) Text(commentTextState.text, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, bottom = 10.dp)) Text(itemLink, color = textColor, style = MaterialTheme.typography.bodySmall) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt index 1ae6701e..cb5db14a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt @@ -17,6 +17,7 @@ import ac.mdiq.podcini.storage.model.FeedFunding import ac.mdiq.podcini.storage.model.Rating import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.ChooseRatingDialog +import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.LargeTextEditingDialog import ac.mdiq.podcini.ui.compose.RemoveFeedDialog @@ -237,7 +238,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { Text(stringResource(R.string.description_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp)) Text(HtmlToPlainText.getPlainText(feed.description?:""), color = textColor, style = MaterialTheme.typography.bodyMedium) Text(stringResource(R.string.my_opinion_label) + if (commentTextState.text.isEmpty()) " (Add)" else "", - color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp).clickable { showEditComment = true }) Text(commentTextState.text, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, bottom = 10.dp)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt index fdee5e1b..da17e04e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt @@ -1,7 +1,7 @@ package ac.mdiq.podcini.ui.fragment import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.FeedsettingsBinding +import ac.mdiq.podcini.databinding.ComposeFragmentBinding import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce import ac.mdiq.podcini.playback.base.VideoMode import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload @@ -25,9 +25,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField @@ -53,8 +51,9 @@ import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.Fragment class FeedSettingsFragment : Fragment() { - private var _binding: FeedsettingsBinding? = null + private var _binding: ComposeFragmentBinding? = null private val binding get() = _binding!! + private var feed: Feed? = null private var autoDeleteSummaryResId by mutableIntStateOf(R.string.global_default) private var curPrefQueue by mutableStateOf(feed?.preferences?.queueTextExt ?: "Default") @@ -78,24 +77,26 @@ class FeedSettingsFragment : Fragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _binding = FeedsettingsBinding.inflate(inflater) + _binding = ComposeFragmentBinding.inflate(inflater) Logd(TAG, "fragment onCreateView") val toolbar = binding.toolbar toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() } + toolbar.title = getString(R.string.feed_settings_label) getVideoModePolicy() getAutoDeletePolicy() - binding.composeView.setContent { + binding.mainView.setContent { CustomTheme(requireContext()) { val textColor = MaterialTheme.colorScheme.onSurface - Column(modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + val scrollState = rememberScrollState() + Column(modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp).verticalScroll(scrollState), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.rounded_responsive_layout_24), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.use_wide_layout), style = MaterialTheme.typography.titleLarge, color = textColor) + Text(text = stringResource(R.string.use_wide_layout), style = CustomTextStyles.titleCustom, color = textColor) Spacer(modifier = Modifier.weight(1f)) var checked by remember { mutableStateOf(feed?.preferences?.useWideLayout == true) } Switch(checked = checked, modifier = Modifier.height(24.dp), @@ -113,7 +114,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge, color = textColor) + Text(text = stringResource(R.string.keep_updated), style = CustomTextStyles.titleCustom, color = textColor) Spacer(modifier = Modifier.weight(1f)) var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated == true) } Switch(checked = checked, modifier = Modifier.height(24.dp), @@ -133,7 +134,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.baseline_audiotrack_24), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_feed_audio_type), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.pref_feed_audio_type), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { selectedOption = feed!!.preferences?.audioTypeSetting?.tag ?: FeedPreferences.AudioType.SPEECH.tag showDialog = true @@ -147,13 +148,13 @@ class FeedSettingsFragment : Fragment() { Column { Row(Modifier.fillMaxWidth()) { var showDialog by remember { mutableStateOf(false) } - if (showDialog) VideoModeDialog(onDismissRequest = { showDialog = false }) { mode -> + if (showDialog) VideoModeDialog(initMode = feed?.preferences?.videoModePolicy, onDismissRequest = { showDialog = false }) { mode -> feed = upsertBlk(feed!!) { it.preferences?.videoModePolicy = mode } getVideoModePolicy() } Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.feed_video_mode_label), style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { showDialog = true })) + Text(text = stringResource(R.string.feed_video_mode_label), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog = true })) } Text(text = stringResource(videoModeSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor) } @@ -164,7 +165,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_stream), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_stream_over_download_title), style = MaterialTheme.typography.titleLarge, color = textColor) + Text(text = stringResource(R.string.pref_stream_over_download_title), style = CustomTextStyles.titleCustom, color = textColor) Spacer(modifier = Modifier.weight(1f)) var checked by remember { mutableStateOf(feed?.preferences?.prefStreamOverDownload == true) } Switch(checked = checked, modifier = Modifier.height(24.dp), @@ -186,7 +187,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.baseline_audiotrack_24), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_feed_audio_quality), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.pref_feed_audio_quality), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { selectedOption = feed!!.preferences?.audioQualitySetting?.tag ?: FeedPreferences.AVQuality.GLOBAL.tag showDialog = true @@ -204,7 +205,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_videocam), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_feed_video_quality), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.pref_feed_video_quality), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { selectedOption = feed!!.preferences?.videoQualitySetting?.tag ?: FeedPreferences.AVQuality.GLOBAL.tag showDialog = true @@ -224,7 +225,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_feed_associated_queue), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.pref_feed_associated_queue), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { selectedOption = feed?.preferences?.queueText ?: "Default" showDialog = true @@ -239,7 +240,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = androidx.media3.session.R.drawable.media3_icon_queue_add), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.audo_add_new_queue), style = MaterialTheme.typography.titleLarge, color = textColor) + Text(text = stringResource(R.string.audo_add_new_queue), style = CustomTextStyles.titleCustom, color = textColor) Spacer(modifier = Modifier.weight(1f)) var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue != false) } Switch(checked = checked, modifier = Modifier.height(24.dp), @@ -260,7 +261,7 @@ class FeedSettingsFragment : Fragment() { if (showDialog.value) AutoDeleteDialog(onDismissRequest = { showDialog.value = false }) Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.auto_delete_label), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.auto_delete_label), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })) } Text(text = stringResource(autoDeleteSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor) @@ -273,7 +274,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_tag), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.feed_tags_label), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.feed_tags_label), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog = true })) } Text(text = stringResource(R.string.feed_tags_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) @@ -288,7 +289,7 @@ class FeedSettingsFragment : Fragment() { } Icon(ImageVector.vectorResource(id = R.drawable.ic_playback_speed), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.playback_speed), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.playback_speed), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })) } Text(text = stringResource(R.string.pref_feed_playback_speed_sum), style = MaterialTheme.typography.bodyMedium, color = textColor) @@ -300,7 +301,7 @@ class FeedSettingsFragment : Fragment() { if (showDialog.value) AutoSkipDialog(onDismiss = { showDialog.value = false }) Icon(ImageVector.vectorResource(id = R.drawable.ic_skip_24dp), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.pref_feed_skip), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.pref_feed_skip), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })) } Text(text = stringResource(R.string.pref_feed_skip_sum), style = MaterialTheme.typography.bodyMedium, color = textColor) @@ -312,7 +313,7 @@ class FeedSettingsFragment : Fragment() { if (showDialog.value) VolumeAdaptionDialog(onDismissRequest = { showDialog.value = false }) Icon(ImageVector.vectorResource(id = R.drawable.ic_volume_adaption), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.feed_volume_adapdation), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.feed_volume_adapdation), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })) } Text(text = stringResource(R.string.feed_volume_adaptation_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) @@ -325,90 +326,91 @@ class FeedSettingsFragment : Fragment() { if (showDialog.value) AuthenticationDialog(onDismiss = { showDialog.value = false }) Icon(ImageVector.vectorResource(id = R.drawable.ic_key), "", tint = textColor) Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.authentication_label), style = MaterialTheme.typography.titleLarge, color = textColor, + Text(text = stringResource(R.string.authentication_label), style = CustomTextStyles.titleCustom, color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })) } Text(text = stringResource(R.string.authentication_descr), style = MaterialTheme.typography.bodyMedium, color = textColor) } } + var autoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload == true) } if (isEnableAutodownload && feed?.type != Feed.FeedType.YOUTUBE.name) { // auto download - var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload == true) } Column { Row(Modifier.fillMaxWidth()) { - Text(text = stringResource(R.string.auto_download_label), style = MaterialTheme.typography.titleLarge, color = textColor) + Text(text = stringResource(R.string.auto_download_label), style = CustomTextStyles.titleCustom, color = textColor) Spacer(modifier = Modifier.weight(1f)) - Switch(checked = audoDownloadChecked, modifier = Modifier.height(24.dp), + Switch(checked = autoDownloadChecked, modifier = Modifier.height(24.dp), onCheckedChange = { - audoDownloadChecked = it - feed = upsertBlk(feed!!) { f -> f.preferences?.autoDownload = audoDownloadChecked } + autoDownloadChecked = it + feed = upsertBlk(feed!!) { f -> f.preferences?.autoDownload = autoDownloadChecked } }) } if (!isEnableAutodownload) Text(text = stringResource(R.string.auto_download_disabled_globally), style = MaterialTheme.typography.bodyMedium, color = textColor) } - if (audoDownloadChecked) { - // auto download policy - Column (modifier = Modifier.padding(start = 20.dp)){ - Row(Modifier.fillMaxWidth()) { - val showDialog = remember { mutableStateOf(false) } - if (showDialog.value) AutoDownloadPolicyDialog(onDismissRequest = { showDialog.value = false }) - Text(text = stringResource(R.string.feed_auto_download_policy), style = MaterialTheme.typography.titleLarge, color = textColor, - modifier = Modifier.clickable(onClick = { showDialog.value = true })) - } - } - // episode cache - Column (modifier = Modifier.padding(start = 20.dp)) { - Row(Modifier.fillMaxWidth()) { - val showDialog = remember { mutableStateOf(false) } - if (showDialog.value) SetEpisodesCacheDialog(onDismiss = { showDialog.value = false }) - Text(text = stringResource(R.string.pref_episode_cache_title), style = MaterialTheme.typography.titleLarge, color = textColor, - modifier = Modifier.clickable(onClick = { showDialog.value = true })) - } - Text(text = stringResource(R.string.pref_episode_cache_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) + } + if (autoDownloadChecked) { + // auto download policy + Column (modifier = Modifier.padding(start = 20.dp)){ + Row(Modifier.fillMaxWidth()) { + val showDialog = remember { mutableStateOf(false) } + if (showDialog.value) AutoDownloadPolicyDialog(onDismissRequest = { showDialog.value = false }) + Text(text = stringResource(R.string.feed_auto_download_policy), style = CustomTextStyles.titleCustom, color = textColor, + modifier = Modifier.clickable(onClick = { showDialog.value = true })) } - // counting played - Column (modifier = Modifier.padding(start = 20.dp)) { - Row(Modifier.fillMaxWidth()) { - Text(text = stringResource(R.string.pref_auto_download_counting_played_title), style = MaterialTheme.typography.titleLarge, color = textColor) - Spacer(modifier = Modifier.weight(1f)) - var checked by remember { mutableStateOf(feed?.preferences?.countingPlayed != false) } - Switch(checked = checked, modifier = Modifier.height(24.dp), - onCheckedChange = { - checked = it - feed = upsertBlk(feed!!) { f -> f.preferences?.countingPlayed = checked } - } - ) - } - Text(text = stringResource(R.string.pref_auto_download_counting_played_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) + } + // episode cache + Column (modifier = Modifier.padding(start = 20.dp)) { + Row(Modifier.fillMaxWidth()) { + val showDialog = remember { mutableStateOf(false) } + if (showDialog.value) SetEpisodesCacheDialog(onDismiss = { showDialog.value = false }) + Text(text = stringResource(R.string.pref_episode_cache_title), style = CustomTextStyles.titleCustom, color = textColor, + modifier = Modifier.clickable(onClick = { showDialog.value = true })) } - // inclusive filter - Column (modifier = Modifier.padding(start = 20.dp)) { - Row(Modifier.fillMaxWidth()) { - val showDialog = remember { mutableStateOf(false) } - if (showDialog.value) AutoDownloadFilterDialog(feed?.preferences!!.autoDownloadFilter!!, ADLIncExc.INCLUDE, onDismiss = { showDialog.value = false }) { filter -> - feed = upsertBlk(feed!!) { it.preferences?.autoDownloadFilter = filter } + Text(text = stringResource(R.string.pref_episode_cache_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) + } + // counting played + Column (modifier = Modifier.padding(start = 20.dp)) { + Row(Modifier.fillMaxWidth()) { + Text(text = stringResource(R.string.pref_auto_download_counting_played_title), style = CustomTextStyles.titleCustom, color = textColor) + Spacer(modifier = Modifier.weight(1f)) + var checked by remember { mutableStateOf(feed?.preferences?.countingPlayed != false) } + Switch(checked = checked, modifier = Modifier.height(24.dp), + onCheckedChange = { + checked = it + feed = upsertBlk(feed!!) { f -> f.preferences?.countingPlayed = checked } } - Text(text = stringResource(R.string.episode_inclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor, - modifier = Modifier.clickable(onClick = { showDialog.value = true }) - ) + ) + } + Text(text = stringResource(R.string.pref_auto_download_counting_played_summary), style = MaterialTheme.typography.bodyMedium, color = textColor) + } + // inclusive filter + Column (modifier = Modifier.padding(start = 20.dp)) { + Row(Modifier.fillMaxWidth()) { + val showDialog = remember { mutableStateOf(false) } + if (showDialog.value) AutoDownloadFilterDialog(feed?.preferences!!.autoDownloadFilter!!, ADLIncExc.INCLUDE, onDismiss = { showDialog.value = false }) { filter -> + feed = upsertBlk(feed!!) { it.preferences?.autoDownloadFilter = filter } } - Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor) + Text(text = stringResource(R.string.episode_inclusive_filters_label), style = CustomTextStyles.titleCustom, color = textColor, + modifier = Modifier.clickable(onClick = { showDialog.value = true }) + ) } - // exclusive filter - Column (modifier = Modifier.padding(start = 20.dp)) { - Row(Modifier.fillMaxWidth()) { - val showDialog = remember { mutableStateOf(false) } - if (showDialog.value) AutoDownloadFilterDialog(feed?.preferences!!.autoDownloadFilter!!, ADLIncExc.EXCLUDE, onDismiss = { showDialog.value = false }) { filter -> - feed = upsertBlk(feed!!) { it.preferences?.autoDownloadFilter = filter } - } - Text(text = stringResource(R.string.episode_exclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor, - modifier = Modifier.clickable(onClick = { showDialog.value = true }) - ) + Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor) + } + // exclusive filter + Column (modifier = Modifier.padding(start = 20.dp)) { + Row(Modifier.fillMaxWidth()) { + val showDialog = remember { mutableStateOf(false) } + if (showDialog.value) AutoDownloadFilterDialog(feed?.preferences!!.autoDownloadFilter!!, ADLIncExc.EXCLUDE, onDismiss = { showDialog.value = false }) { filter -> + feed = upsertBlk(feed!!) { it.preferences?.autoDownloadFilter = filter } } - Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor) + Text(text = stringResource(R.string.episode_exclusive_filters_label), style = CustomTextStyles.titleCustom, color = textColor, + modifier = Modifier.clickable(onClick = { showDialog.value = true }) + ) } + Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor) } + } } } @@ -445,34 +447,6 @@ class FeedSettingsFragment : Fragment() { } } } -// @Composable -// fun VideoModeDialog(onDismissRequest: () -> Unit) { -// var selectedOption by remember { mutableStateOf(videoMode) } -// Dialog(onDismissRequest = { onDismissRequest() }) { -// Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { -// Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { -// Column { -// VideoMode.entries.forEach { mode -> -// val text = mode.tag -// Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) { -// Checkbox(checked = (text == selectedOption), onCheckedChange = { -// Logd(TAG, "row clicked: $text $selectedOption") -// if (text != selectedOption) { -// selectedOption = text -// feed = upsertBlk(feed!!) { it.preferences?.videoModePolicy = mode } -// getVideoModePolicy() -// onDismissRequest() -// } -// }) -// Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp)) -// } -// } -// } -// } -// } -// } -// } - private fun getAutoDeletePolicy() { when (feed?.preferences!!.autoDeleteAction) { AutoDeleteAction.GLOBAL -> { 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 b493400f..b9adc3ba 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 @@ -9,6 +9,7 @@ import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.EpisodeFilter.Companion.unfiltered import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity +import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment.Companion.ARGUMENT_FEED_ID import ac.mdiq.podcini.ui.utils.ThemeUtils @@ -113,7 +114,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener { }) { Icon(imageVector = ImageVector.vectorResource(nav.iconRes), tint = textColor, contentDescription = nav.tag, modifier = Modifier.padding(start = 10.dp)) // val nametag = if (nav.tag != QueuesFragment.TAG) stringResource(nav.nameRes) else curQueue.name - Text(stringResource(nav.nameRes), color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(start = 20.dp)) + Text(stringResource(nav.nameRes), color = textColor, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 20.dp)) Spacer(Modifier.weight(1f)) if (nav.count > 0) Text(nav.count.toString(), color = textColor, modifier = Modifier.padding(end = 10.dp)) } @@ -143,7 +144,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener { startActivity(Intent(activity, PreferenceActivity::class.java)) }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_settings), tint = textColor, contentDescription = "settings", modifier = Modifier.padding(start = 10.dp)) - Text(stringResource(R.string.settings_label), color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(start = 20.dp)) + Text(stringResource(R.string.settings_label), color = textColor, style = CustomTextStyles.titleCustom, modifier = Modifier.padding(start = 20.dp)) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt index 84869c24..938ea02e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt @@ -20,6 +20,7 @@ import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.Rating.Companion.fromCode import ac.mdiq.podcini.storage.model.SubscriptionLog.Companion.feedLogsMap import ac.mdiq.podcini.ui.activity.MainActivity +import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent @@ -29,7 +30,6 @@ import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences -import android.net.Uri import android.os.Bundle import android.text.Spannable import android.text.SpannableString @@ -40,7 +40,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread -import androidx.collection.ArrayMap import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -66,10 +65,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest -import org.jsoup.Jsoup -import org.jsoup.nodes.Document -import java.io.File -import java.io.IOException import java.util.* /** @@ -462,7 +457,7 @@ class OnlineFeedFragment : Fragment() { val ratingRes = remember { fromCode(sLog.rating).res } if (commentTextState.text.isNotEmpty()) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp)) { - Text(stringResource(R.string.my_opinion_label), color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.titleMedium) + Text(stringResource(R.string.my_opinion_label), color = MaterialTheme.colorScheme.primary, style = CustomTextStyles.titleCustom) Icon(imageVector = ImageVector.vectorResource(ratingRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = null, modifier = Modifier.padding(start = 5.dp)) } Text(commentTextState.text, color = textColor, style = MaterialTheme.typography.bodyMedium, @@ -588,51 +583,6 @@ class OnlineFeedFragment : Fragment() { // builder.show() // } - /** - * - * @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found). - */ -// private fun showFeedDiscoveryDialog(feedFile: File, baseUrl: String): Boolean { -// val fd = FeedDiscoverer() -// val urlsMap: Map -// try { -// urlsMap = fd.findLinks(feedFile, baseUrl) -// if (urlsMap.isEmpty()) return false -// } catch (e: IOException) { -// e.printStackTrace() -// return false -// } -// -// if (isRemoving || isPaused) return false -// val titles: MutableList = ArrayList() -// val urls: List = ArrayList(urlsMap.keys) -// for (url in urls) { -// titles.add(urlsMap[url]) -// } -// if (urls.size == 1) { -// // Skip dialog and display the item directly -// feeds = getFeedList() -// subscribe.startFeedBuilding(urls[0]) {feed, map -> showFeedInformation(feed, map) } -// return true -// } -// val adapter = ArrayAdapter(requireContext(), R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles) -// val onClickListener = DialogInterface.OnClickListener { dialog: DialogInterface, which: Int -> -// val selectedUrl = urls[which] -// dialog.dismiss() -// feeds = getFeedList() -// subscribe.startFeedBuilding(selectedUrl) {feed, map -> showFeedInformation(feed, map) } -// } -// val ab = MaterialAlertDialogBuilder(requireContext()) -// .setTitle(R.string.feeds_label) -// .setCancelable(true) -// .setOnCancelListener { _: DialogInterface? ->/* finish() */ } -// .setAdapter(adapter, onClickListener) -// requireActivity().runOnUiThread { -// if (dialog != null && dialog!!.isShowing) dialog!!.dismiss() -// dialog = ab.show() -// } -// return true -// } private fun showNoPodcastFoundError() { requireActivity().runOnUiThread { @@ -645,69 +595,6 @@ class OnlineFeedFragment : Fragment() { } } -// private inner class FeedViewAuthenticationDialog(context: Context, titleRes: Int, private val feedUrl: String) : -// AuthenticationDialog(context, titleRes, true, username, password) { -// override fun onConfirmed(username: String, password: String) { -// this@OnlineFeedFragment.username = username -// this@OnlineFeedFragment.password = password -// feeds = getFeedList() -// subscribe.startFeedBuilding(feedUrl) {feed, map -> showFeedInformation(feed, map) } -// } -// } - - /** - * Finds RSS/Atom URLs in a HTML document using the auto-discovery techniques described here: - * http://www.rssboard.org/rss-autodiscovery - * http://blog.whatwg.org/feed-autodiscovery - */ - class FeedDiscoverer { - /** - * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. - * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if - * a title cannot be found). - */ - @Throws(IOException::class) - fun findLinks(inVal: File, baseUrl: String): Map { - return findLinks(Jsoup.parse(inVal), baseUrl) - } - /** - * Discovers links to RSS and Atom feeds in the given File which must be a HTML document. - * @return A map which contains the feed URLs as keys and titles as values (the feed URL is also used as a title if - * a title cannot be found). - */ - fun findLinks(inVal: String, baseUrl: String): Map { - return findLinks(Jsoup.parse(inVal), baseUrl) - } - private fun findLinks(document: Document, baseUrl: String): Map { - val res: MutableMap = ArrayMap() - val links = document.head().getElementsByTag("link") - for (link in links) { - val rel = link.attr("rel") - val href = link.attr("href") - if (href.isNotEmpty() && (rel == "alternate" || rel == "feed")) { - val type = link.attr("type") - if (type == MIME_RSS || type == MIME_ATOM) { - val title = link.attr("title") - val processedUrl = processURL(baseUrl, href) - if (processedUrl != null) res[processedUrl] = title.ifEmpty { href } - } - } - } - return res - } - private fun processURL(baseUrl: String, strUrl: String): String? { - val uri = Uri.parse(strUrl) - if (uri.isRelative) { - val res = Uri.parse(baseUrl).buildUpon().path(strUrl).build() - return res?.toString() - } else return strUrl - } - companion object { - private const val MIME_RSS = "application/rss+xml" - private const val MIME_ATOM = "application/atom+xml" - } - } - class RemoteEpisodesFragment : BaseEpisodesFragment() { private var episodeList: MutableList = mutableListOf() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index d81d1667..a37a3886 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -1,7 +1,6 @@ package ac.mdiq.podcini.ui.fragment import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.CheckboxDoNotShowAgainBinding import ac.mdiq.podcini.databinding.ComposeFragmentBinding import ac.mdiq.podcini.net.download.DownloadStatus import ac.mdiq.podcini.net.feed.FeedUpdateManager @@ -35,12 +34,10 @@ import ac.mdiq.podcini.util.Logd import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context -import android.content.DialogInterface import android.content.SharedPreferences import android.os.Bundle import android.util.Log import android.view.* -import android.widget.CheckBox import androidx.appcompat.widget.Toolbar import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi @@ -72,7 +69,6 @@ import coil.compose.AsyncImage import coil.request.CachePolicy import coil.request.ImageRequest import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors @@ -124,6 +120,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener { var sortOrder by mutableStateOf(EpisodeSortOrder.DATE_NEW_OLD) private val showClearQueueDialog = mutableStateOf(false) + private var shouldShowLockWarningDiwload by mutableStateOf(false) private lateinit var browserFuture: ListenableFuture @@ -177,6 +174,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener { binding.mainView.setContent { CustomTheme(requireContext()) { ComfirmDialog(titleRes = R.string.clear_queue_label, message = stringResource(R.string.clear_queue_confirmation_msg), showDialog = showClearQueueDialog) { clearQueue() } + if (shouldShowLockWarningDiwload) ShowLockWarning { shouldShowLockWarningDiwload = false } if (showBin) { Column { @@ -616,27 +614,51 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener { // val isLocked: Boolean = isQueueLocked if (isQueueLocked) setQueueLock(false) else { - val shouldShowLockWarning: Boolean = prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true) - if (!shouldShowLockWarning) setQueueLock(true) + val shouldShowLockWarning = mutableStateOf(prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true)) + if (!shouldShowLockWarning.value) setQueueLock(true) else { - val builder = MaterialAlertDialogBuilder(requireContext()) - builder.setTitle(R.string.lock_queue) - builder.setMessage(R.string.queue_lock_warning) - val view = View.inflate(context, R.layout.checkbox_do_not_show_again, null) - val binding_ = CheckboxDoNotShowAgainBinding.bind(view) - val checkDoNotShowAgain: CheckBox = binding_.checkboxDoNotShowAgain - builder.setView(view) - builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int -> - prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply() - setQueueLock(true) - } - builder.setNegativeButton(R.string.cancel_label, null) - builder.show() + shouldShowLockWarningDiwload = true +// val builder = MaterialAlertDialogBuilder(requireContext()) +// builder.setTitle(R.string.lock_queue) +// builder.setMessage(R.string.queue_lock_warning) +// val view = View.inflate(context, R.layout.checkbox_do_not_show_again, null) +// val binding_ = CheckboxDoNotShowAgainBinding.bind(view) +// val checkDoNotShowAgain: CheckBox = binding_.checkboxDoNotShowAgain +// builder.setView(view) +// builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int -> +// prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply() +// setQueueLock(true) +// } +// builder.setNegativeButton(R.string.cancel_label, null) +// builder.show() } } } - private fun setQueueLock(locked: Boolean) { + @Composable + private fun ShowLockWarning(onDismiss: () -> Unit) { + var dontAskAgain by remember { mutableStateOf(false) } + AlertDialog(onDismissRequest = onDismiss, title = { Text(stringResource(R.string.lock_queue)) }, + text = { + Column { + Text(stringResource(R.string.queue_lock_warning)) + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = dontAskAgain, onCheckedChange = { dontAskAgain = it }) + Text(stringResource(R.string.checkbox_do_not_show_again)) + } + } + }, + confirmButton = { + TextButton(onClick = { + prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !dontAskAgain).apply() + onDismiss() + }) { Text(stringResource(R.string.lock_queue)) } + }, + dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } } + ) + } + + private fun setQueueLock(locked: Boolean) { isQueueLocked = locked appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueLocked.name, locked).apply() dragDropEnabled = !(isQueueKeepSorted || isQueueLocked) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt index 7961fd40..0b33d010 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt @@ -429,26 +429,6 @@ class StatisticsFragment : Fragment(), Toolbar.OnMenuItemClickListener { } } -// private fun confirmResetStatistics() { -// val conDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(), -// R.string.statistics_reset_data, R.string.statistics_reset_data_msg) { -// override fun onConfirmButtonPressed(dialog: DialogInterface) { -// dialog.dismiss() -// prefs.edit()?.putBoolean(PREF_INCLUDE_MARKED_PLAYED, false)?.putLong(PREF_FILTER_FROM, 0)?.putLong(PREF_FILTER_TO, Long.MAX_VALUE)?.apply() -// lifecycleScope.launch { -// try { -// withContext(Dispatchers.IO) { -// val mediaAll = realm.query(EpisodeMedia::class).find() -// for (m in mediaAll) update(m) { m.playedDuration = 0 } -// } -// statisticsState++ -// } catch (error: Throwable) { Log.e(TAG, Log.getStackTraceString(error)) } -// } -// } -// } -// conDialog.createNewDialog().show() -// } - class LineChartData(val values: MutableList) { val sum: Float diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index 557bf8df..9fd4f94e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -486,7 +486,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { Row(Modifier.fillMaxWidth()) { Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "") Spacer(modifier = Modifier.width(20.dp)) - Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge) + Text(text = stringResource(R.string.keep_updated), style = CustomTextStyles.titleCustom) Spacer(modifier = Modifier.weight(1f)) var checked by remember { mutableStateOf(false) } Switch(checked = checked, onCheckedChange = { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/view/PlayButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/view/PlayButton.kt deleted file mode 100644 index a696214b..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/view/PlayButton.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ac.mdiq.podcini.ui.view - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatImageButton -import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat -import ac.mdiq.podcini.R - -class PlayButton : AppCompatImageButton { - private var isShowPlay = true - private var isVideoScreen = false - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - fun setIsVideoScreen(isVideoScreen: Boolean) { - this.isVideoScreen = isVideoScreen - } - - fun setIsShowPlay(showPlay: Boolean) { - if (this.isShowPlay != showPlay) { - this.isShowPlay = showPlay - contentDescription = context.getString(if (showPlay) R.string.play_label else R.string.pause_label) - when { - isVideoScreen -> setImageResource(if (showPlay) R.drawable.ic_play_video_white else R.drawable.ic_pause_video_white) - !isShown -> setImageResource(if (showPlay) R.drawable.ic_play_48dp else R.drawable.ic_pause) - showPlay -> { - val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_animate_pause_play) - setImageDrawable(drawable) - drawable?.start() - } - else -> { - val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_animate_play_pause) - setImageDrawable(drawable) - drawable?.start() - } - } - } - } -} diff --git a/app/src/main/res/layout/audio_controls.xml b/app/src/main/res/layout/audio_controls.xml deleted file mode 100644 index ab6bd35c..00000000 --- a/app/src/main/res/layout/audio_controls.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -