6.6.1 commit

This commit is contained in:
Xilin Jia 2024-09-13 23:34:06 +01:00
parent 7a1f2feac2
commit efd51ed6e1
14 changed files with 105 additions and 64 deletions

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020245
versionName "6.6.0"
versionCode 3020246
versionName "6.6.1"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -32,6 +32,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence
import ac.mdiq.podcini.preferences.UserPreferences.prefLowQualityMedia
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.storage.database.Episodes.addToHistory
@ -1432,7 +1433,7 @@ class PlaybackService : MediaLibraryService() {
val streamInfo = StreamInfo.getInfo(vService, url)
val audioStreamsList = getFilteredAudioStreams(streamInfo.audioStreams)
Logd(TAG, "setDataSource1 audioStreamsList ${audioStreamsList.size}")
val audioIndex = if (isNetworkRestricted) 0 else audioStreamsList.size - 1
val audioIndex = if (isNetworkRestricted && prefLowQualityMedia) 0 else audioStreamsList.size - 1
val audioStream = audioStreamsList[audioIndex]
Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate}")
val aSource = DefaultMediaSourceFactory(context).createMediaSource(

View File

@ -197,6 +197,12 @@ object UserPreferences {
appPrefs.edit().putBoolean(Prefs.prefStreamOverDownload.name, stream).apply()
}
var prefLowQualityMedia: Boolean
get() = appPrefs.getBoolean(Prefs.prefLowQualityOnMobile.name, false)
set(stream) {
appPrefs.edit().putBoolean(Prefs.prefLowQualityOnMobile.name, stream).apply()
}
/**
* Sets up the UserPreferences class.
* @throws IllegalArgumentException if context is null
@ -324,6 +330,7 @@ object UserPreferences {
prefPauseForFocusLoss,
prefPlaybackTimeRespectsSpeed,
prefStreamOverDownload,
prefLowQualityOnMobile,
prefSpeedforwardSpeed,
// Network

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.storage.database
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.net.download.DownloadError
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
@ -20,18 +19,20 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.TAG_ROOT
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.utils.FilesUtils.feedfilePath
import ac.mdiq.podcini.storage.utils.FilesUtils.getFeedfileName
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import android.app.backup.BackupManager
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.realm.kotlin.ext.asFlow
import io.realm.kotlin.notifications.*
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.SingleQueryChange
import io.realm.kotlin.notifications.UpdatedObject
import io.realm.kotlin.notifications.UpdatedResults
import kotlinx.coroutines.*
import java.io.File
import java.text.DateFormat
@ -241,7 +242,7 @@ object Feeds {
""".trimIndent()))
continue
}
var oldItem = EpisodeAssistant.searchEpisodeByIdentifyingValue(savedFeed.episodes, episode)
var oldItem = searchEpisodeByIdentifyingValue(savedFeed.episodes, episode)
if (!newFeed.isLocalFeed && oldItem == null) {
oldItem = EpisodeAssistant.searchEpisodeGuessDuplicate(savedFeed.episodes, episode)
if (oldItem != null) {
@ -299,7 +300,7 @@ object Feeds {
val it = savedFeed.episodes.toMutableList().iterator()
while (it.hasNext()) {
val feedItem = it.next()
if (EpisodeAssistant.searchEpisodeByIdentifyingValue(newFeed.episodes, feedItem) == null) {
if (searchEpisodeByIdentifyingValue(newFeed.episodes, feedItem) == null) {
unlistedItems.add(feedItem)
it.remove()
}
@ -442,15 +443,13 @@ object Feeds {
if (searchEpisodeByIdentifyingValue(feed.episodes, episode) != null) return
Logd(TAG, "addToYoutubeSyndicate adding new episode: ${episode.title}")
runOnIOScope {
episode.feed = feed
episode.id = Feed.newId()
episode.feedId = feed.id
episode.media?.id = episode.id
upsert(episode) {}
upsertBlk(episode) {}
feed.episodes.add(episode)
upsert(feed) {}
}
upsertBlk(feed) {}
}
/**

View File

@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.activity
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.Logd
import ac.mdiq.vista.extractor.Vista
@ -13,7 +12,6 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import androidx.activity.compose.setContent
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
@ -23,7 +21,6 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@ -32,14 +29,14 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URLDecoder
class ShareReceiverActivity : AppCompatActivity() {
var feedUrl: String? = null
@OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var feedUrl: String? = null
when {
intent.hasExtra(ARG_FEEDURL) -> feedUrl = intent.getStringExtra(ARG_FEEDURL)
intent.action == Intent.ACTION_SEND -> feedUrl = intent.getStringExtra(Intent.EXTRA_TEXT)
@ -50,7 +47,7 @@ class ShareReceiverActivity : AppCompatActivity() {
showNoPodcastFoundError()
return
}
if (!feedUrl.startsWith("http")) {
if (!feedUrl!!.startsWith("http")) {
val uri = Uri.parse(feedUrl)
val urlString = uri?.getQueryParameter("url")
if (urlString != null) feedUrl = URLDecoder.decode(urlString, "UTF-8")
@ -59,32 +56,26 @@ class ShareReceiverActivity : AppCompatActivity() {
when {
// plain text
feedUrl!!.matches(Regex("^[^\\s<>/]+\$")) -> {
val intent = MainActivity.showOnlineSearch(this, feedUrl)
val intent = MainActivity.showOnlineSearch(this, feedUrl!!)
startActivity(intent)
finish()
}
// Youtube media
feedUrl.startsWith("https://youtube.com/watch?") -> {
feedUrl!!.startsWith("https://youtube.com/watch?") -> {
Logd(TAG, "got youtube media")
CoroutineScope(Dispatchers.IO).launch {
val info = StreamInfo.getInfo(Vista.getService(0), feedUrl)
Logd(TAG, "info: $info")
val episode = episodeFromStreamInfo(info)
Logd(TAG, "episode: $episode")
withContext(Dispatchers.Main) {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(this@ShareReceiverActivity) {
confirmAddEpisode(showDialog.value, episode, onDismissRequest = { showDialog.value = false })
confirmAddEpisode(showDialog.value, onDismissRequest = {
showDialog.value = false
finish()
})
}
}
}
}
}
else -> {
Logd(TAG, "Activity was started with url $feedUrl")
val intent = MainActivity.showOnlineFeed(this, feedUrl)
val intent = MainActivity.showOnlineFeed(this, feedUrl!!)
// intent.putExtra(MainActivity.Extras.started_from_share.name, getIntent().getBooleanExtra(MainActivity.Extras.started_from_share.name, false))
startActivity(intent)
finish()
@ -93,7 +84,7 @@ class ShareReceiverActivity : AppCompatActivity() {
}
@Composable
fun confirmAddEpisode(showDialog: Boolean, episode: Episode, onDismissRequest: () -> Unit) {
fun confirmAddEpisode(showDialog: Boolean, onDismissRequest: () -> Unit) {
if (showDialog) {
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
@ -106,11 +97,11 @@ class ShareReceiverActivity : AppCompatActivity() {
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
var checked by remember { mutableStateOf(false) }
var audioOnly by remember { mutableStateOf(false) }
Row(Modifier.fillMaxWidth()) {
Checkbox(checked = checked,
Checkbox(checked = audioOnly,
onCheckedChange = {
checked = it
audioOnly = it
}
)
Text(
@ -119,8 +110,14 @@ class ShareReceiverActivity : AppCompatActivity() {
)
}
Button(onClick = {
addToYoutubeSyndicate(episode, !checked)
finish()
CoroutineScope(Dispatchers.IO).launch {
val info = StreamInfo.getInfo(Vista.getService(0), feedUrl!!)
Logd(TAG, "info: $info")
val episode = episodeFromStreamInfo(info)
Logd(TAG, "episode: $episode")
addToYoutubeSyndicate(episode, !audioOnly)
}
onDismissRequest()
}) {
Text("Confirm")
}

View File

@ -71,6 +71,8 @@ import java.util.concurrent.Semaphore
private lateinit var swipeActions: SwipeActions
private lateinit var nextPageLoader: MoreContentListFooterUtil
private var infoTextFiltered = ""
private var infoTextUpdate = ""
private var displayUpArrow = false
private var headerCreated = false
private var feedID: Long = 0
@ -155,6 +157,7 @@ import java.util.concurrent.Semaphore
})
binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
binding.swipeRefresh.setProgressViewEndTarget(false, 0)
binding.swipeRefresh.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
}
@ -429,6 +432,8 @@ import java.util.concurrent.Semaphore
private fun onFeedUpdateRunningEvent(event: FlowEvent.FeedUpdatingEvent) {
nextPageLoader.setLoadingState(event.isRunning)
if (!event.isRunning) nextPageLoader.root.visibility = View.GONE
infoTextUpdate = if (event.isRunning) getString(R.string.refreshing_label) else ""
binding.header.txtvInformation.text = ("{gmo-info} $infoTextFiltered $infoTextUpdate")
binding.swipeRefresh.isRefreshing = event.isRunning
}
@ -454,18 +459,20 @@ import java.util.concurrent.Semaphore
binding.header.txtvTitle.text = feed!!.title
binding.header.txtvAuthor.text = feed!!.author
binding.header.txtvInformation.setOnClickListener {}
infoTextFiltered = ""
if (!feed?.preferences?.filterString.isNullOrEmpty()) {
val filter: EpisodeFilter = feed!!.episodeFilter
if (filter.values.isNotEmpty()) {
binding.header.txtvInformation.text = ("{gmo-info} " + this.getString(R.string.filtered_label))
infoTextFiltered = this.getString(R.string.filtered_label)
binding.header.txtvInformation.setOnClickListener {
val dialog = FeedEpisodeFilterDialog(feed)
dialog.filter = feed!!.episodeFilter
dialog.show(childFragmentManager, null)
}
binding.header.txtvInformation.visibility = View.VISIBLE
} else binding.header.txtvInformation.visibility = View.GONE
} else binding.header.txtvInformation.visibility = View.GONE
}
}
binding.header.txtvInformation.text = ("{gmo-info} $infoTextFiltered $infoTextUpdate")
}
@UnstableApi private fun setupHeaderView() {

View File

@ -110,6 +110,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
private lateinit var catAdapter: ArrayAdapter<String>
private var infoTextFiltered = ""
private var infoTextUpdate = ""
private var tagFilterIndex = 1
// TODO: currently not used
private var displayedFolder: String = ""
@ -187,7 +189,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
} else false
}
binding.progressBar.visibility = View.VISIBLE
// binding.progressBar.visibility = View.VISIBLE
binding.progressBar.visibility = View.GONE
val subscriptionAddButton: FloatingActionButton = binding.subscriptionsAdd
subscriptionAddButton.setOnClickListener {
@ -218,8 +221,10 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
private fun setSwipeRefresh() {
if (swipeToRefresh) {
binding.swipeRefresh.isEnabled = true
binding.swipeRefresh.setProgressViewEndTarget(false, 0)
binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
binding.swipeRefresh.setOnRefreshListener {
Logd(TAG, "running FeedUpdateManager")
FeedUpdateManager.runOnceOrAsk(requireContext())
}
} else binding.swipeRefresh.isEnabled = false
@ -327,7 +332,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
when (event) {
is FlowEvent.FeedUpdatingEvent -> {
Logd(TAG, "FeedUpdateRunningEvent: ${event.isRunning}")
binding.swipeRefresh.isRefreshing = event.isRunning
infoTextUpdate = if (event.isRunning) " " + this@SubscriptionsFragment.getString(R.string.refreshing_label) else ""
binding.txtvInformation.text = (infoTextFiltered + infoTextUpdate)
if (swipeToRefresh) binding.swipeRefresh.isRefreshing = event.isRunning
if (!event.isRunning && event.id != prevFeedUpdatingEvent?.id) loadSubscriptions()
prevFeedUpdatingEvent = event
}
@ -377,18 +384,20 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
// We have fewer items. This can result in items being selected that are no longer visible.
if (feedListFiltered.size > feedList.size) adapter.endSelectMode()
filterOnTag()
binding.progressBar.visibility = View.GONE
// binding.progressBar.visibility = View.GONE
adapter.setItems(feedListFiltered)
binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString()
infoTextFiltered = " "
binding.txtvInformation.setOnClickListener {}
if (feedsFilter.isNotEmpty()) {
val filter = FeedFilter(feedsFilter)
binding.txtvInformation.text = ("{gmo-info} " + getString(R.string.filtered_label))
infoTextFiltered = getString(R.string.filtered_label)
binding.txtvInformation.setOnClickListener {
val dialog = FeedFilterDialog.newInstance(filter)
dialog.show(childFragmentManager, null)
}
binding.txtvInformation.visibility = View.VISIBLE
} else binding.txtvInformation.visibility = View.GONE
}
binding.txtvInformation.text = (infoTextFiltered + infoTextUpdate)
emptyView.updateVisibility()
}
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e))
@ -1187,7 +1196,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
}
}
fun onFilterChanged(newFilterValues: Set<String>) {
private fun onFilterChanged(newFilterValues: Set<String>) {
feedsFilter = StringUtils.join(newFilterValues, ",")
Logd(TAG, "onFilterChanged: $feedsFilter")
EventFlow.postEvent(FlowEvent.FeedsFilterEvent(newFilterValues))

View File

@ -187,7 +187,6 @@
android:padding="2dp"
android:background="?android:attr/colorBackground"
android:foreground="?android:attr/selectableItemBackground"
android:visibility="gone"
android:gravity="center"
android:textColor="?attr/colorAccent"
tools:visibility="visible"

View File

@ -50,7 +50,6 @@
android:padding="2dp"
android:background="?android:attr/colorBackground"
android:foreground="?android:attr/selectableItemBackground"
android:visibility="gone"
android:gravity="center"
android:textColor="?attr/colorAccent"
tools:visibility="visible"

View File

@ -119,6 +119,7 @@
<string name="error_label">Error</string>
<string name="error_msg_prefix">An error occurred:</string>
<string name="refresh_label">Refresh</string>
<string name="refreshing_label">Refreshing</string>
<string name="reconsile_label">Reconsile</string>
<string name="chapters_label">Chapters</string>
<string name="no_chapters_label">No chapters</string>
@ -478,6 +479,8 @@
<string name="pref_audio_only_sum">Only play audio of a video media</string>
<string name="pref_stream_over_download_title">Prefer streaming</string>
<string name="pref_stream_over_download_sum">Display stream button instead of download button in lists</string>
<string name="pref_low_quality_on_mobile_title">Prefer low quality on mobile</string>
<string name="pref_low_quality_on_mobile_sum">On metered network, only low quality media (if available) is fetched</string>
<string name="pref_mobileUpdate_title">Mobile updates</string>
<string name="pref_mobileUpdate_sum">Select what should be allowed over the mobile data connection</string>
<string name="pref_mobileUpdate_refresh">Podcast refresh</string>

View File

@ -61,6 +61,11 @@
android:key="prefStreamOverDownload"
android:summary="@string/pref_stream_over_download_sum"
android:title="@string/pref_stream_over_download_title"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="prefLowQualityOnMobile"
android:summary="@string/pref_low_quality_on_mobile_sum"
android:title="@string/pref_low_quality_on_mobile_title"/>
<Preference
android:title="@string/pref_playback_video_mode"
android:key="prefPlaybackVideoModeLauncher"

View File

@ -1,3 +1,11 @@
# 6.6.1
* the confirm dialog is more responsive when receiving a youtube media share
* receiving a youtube media share can be dismissed by not pressing the confirm button
* in Subscriptions and FeedEpisodes views, swipe down to refresh no longer blocks UI, only shows "Refreshing" status on the info bar
* added preference "Prefer low quality on mobile" under Settings -> Playback -> Playback control, and default it to false,
* if set true, Youtube media will use low quality audio on mobile network (which has been the default way of handling)
# 6.6.0
* added ability to receive shared Youtube media,

View File

@ -1,4 +1,4 @@
Version 6.5.10:
Version 6.6.0:
* added ability to receive shared Youtube media,
* once received, the user can choose to set it as "audio only" before confirm

View File

@ -0,0 +1,7 @@
Version 6.6.1:
* the confirm dialog is more responsive when receiving a youtube media share
* receiving a youtube media share can be dismissed by not pressing the confirm button
* in Subscriptions and FeedEpisodes views, swipe down to refresh no longer blocks UI, only shows "Refreshing" status on the info bar
* added preference "Prefer low quality on mobile" under Settings -> Playback -> Playback control, and default it to false,
* if set true, Youtube media will use low quality audio on mobile network (which has been the default way of handling)