6.15.2 commit

This commit is contained in:
Xilin Jia 2024-12-02 18:22:39 +01:00
parent f2f4b8dcee
commit dcbbbd745f
23 changed files with 237 additions and 476 deletions

View File

@ -8,12 +8,13 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
[<img src="./images/external/getItGithub.png" alt="GitHub" height="50">](https://github.com/XilinJia/Podcini/releases/latest)
[<img src="./images/external/getItIzzyOnDroid.png" alt="IzzyOnDroid" height="50">](https://apt.izzysoft.de/fdroid/index/apk/ac.mdiq.podcini.R)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Google Play" height="50">](https://play.google.com/apps/testing/ac.mdiq.podcini.R)
[<img src="./images/external/getItf-droid.png" alt="F-Droid" height="50">](https://f-droid.org/packages/ac.mdiq.podcini.R/)
[<img src="./images/external/amazon.png" alt="Amazon" height="40">](https://www.amazon.com/%E8%B4%BE%E8%A5%BF%E6%9E%97-Podcini-R/dp/B0D9WR8P13)
### Note:
#### *** Podcini is being launched on Google Play and needs closed testing, please kindly support it through [here](https://github.com/XilinJia/Podcini/discussions/120)
#### *** Podcini is being launched on Google Play. Early testers will become pre-IPO members on the contributors list. Start with a comment [here](https://github.com/XilinJia/Podcini/discussions/120)
#### Podcini.R 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.

View File

@ -26,8 +26,8 @@ android {
vectorDrawables.useSupportLibrary false
vectorDrawables.generatedDensities = []
versionCode 3020309
versionName "6.15.1"
versionCode 3020310
versionName "6.15.2"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -225,9 +225,9 @@
</activity>
<activity android:name=".ui.activity.SelectSubscriptionActivity"
<activity android:name=".ui.activity.SubscriptionShortcutActivity"
android:label="@string/shortcut_subscription_label"
android:icon="@drawable/ic_subscriptions_shortcut"
android:icon="@drawable/ic_launcher_foreground"
android:theme="@style/Theme.Podcini.Dark.Translucent"
android:exported="true">
<intent-filter>

View File

@ -144,6 +144,7 @@ abstract class MediaPlayerBase protected constructor(protected val context: Cont
}
}
val audioStream = audioStreamsList[audioIndex]
media.bitrate = audioStream.bitrate
Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate} forceVideo: ${media.forceVideo}")
media.audioUrl = audioStream.content

View File

@ -500,31 +500,13 @@ object Feeds {
feed = createSynthetic(feedId, name)
feed.type = Feed.FeedType.YOUTUBE.name
feed.hasVideoMedia = video
feed.preferences!!.audioTypeSetting = if (music) AudioType.MOVIE else AudioType.SPEECH
feed.preferences!!.audioTypeSetting = if (music) AudioType.MUSIC else AudioType.SPEECH
feed.preferences!!.videoModePolicy = if (video) VideoMode.WINDOW_VIEW else VideoMode.AUDIO_ONLY
upsertBlk(feed) {}
EventFlow.postEvent(FlowEvent.FeedListEvent(FlowEvent.FeedListEvent.Action.ADDED))
return feed
}
fun addToYoutubeSyndicate(episode: Episode, video: Boolean) : Int {
val feed = getYoutubeSyndicate(video, episode.media?.downloadUrl?.contains("music") == true)
Logd(TAG, "addToYoutubeSyndicate: feed: ${feed.title}")
if (searchEpisodeByIdentifyingValue(feed.episodes, episode) != null) return 2
Logd(TAG, "addToYoutubeSyndicate adding new episode: ${episode.title}")
episode.feed = feed
episode.id = Feed.newId()
episode.feedId = feed.id
episode.media?.id = episode.id
upsertBlk(episode) {}
upsertBlk(feed) {
it.episodes.add(episode)
}
EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
return 1
}
fun addToSyndicate(episode: Episode, feed: Feed) : Int {
Logd(TAG, "addToYoutubeSyndicate: feed: ${feed.title}")
if (searchEpisodeByIdentifyingValue(feed.episodes, episode) != null) return 2

View File

@ -94,9 +94,8 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
@Ignore
var audioUrl = ""
/* Used for loading item when restoring from parcel. */
// var episodeId: Long = 0
// private set
@Ignore
var bitrate: Int = 0
constructor() {}

View File

@ -26,6 +26,7 @@ import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
import ac.mdiq.podcini.ui.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.Companion.media3Controller
import ac.mdiq.podcini.ui.fragment.NavDrawerFragment.Companion.getLastNavFragmentArg
import ac.mdiq.podcini.ui.fragment.StatisticsFragment
import ac.mdiq.podcini.ui.utils.ThemeUtils.getDrawableFromAttr
import ac.mdiq.podcini.ui.utils.TransitionEffect
@ -222,12 +223,9 @@ class MainActivity : CastEnabledActivity() {
if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) loadFragment(defaultPage, null)
else {
val lastFragment = NavDrawerFragment.getLastNavFragment()
if (NavDrawerFragment.navMap.keys.contains(lastFragment)) loadFragment(lastFragment, null)
else {
// it's not a number, this happens if we removed a label from the NAV_DRAWER_TAGS give them a nice default...
try { loadFeedFragmentById(lastFragment.toInt().toLong(), null) }
catch (e: NumberFormatException) { loadFragment(SubscriptionsFragment.TAG, null) }
}
Logd(TAG, "lastFragment: $lastFragment")
if (NavDrawerFragment.navMap.keys.contains(lastFragment) || lastFragment == FeedEpisodesFragment.TAG) loadFragment(lastFragment, null)
else loadFragment(SubscriptionsFragment.TAG, null)
}
}
@ -437,8 +435,7 @@ class MainActivity : CastEnabledActivity() {
val params = mainView.layoutParams as MarginLayoutParams
val externalPlayerHeight = resources.getDimension(R.dimen.external_player_height).toInt()
Logd(TAG, "externalPlayerHeight: $externalPlayerHeight ${navigationBarInsets.bottom}")
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
mainView.layoutParams = params
audioPlayerView.visibility = if (visible) View.VISIBLE else View.GONE
}
@ -451,23 +448,23 @@ class MainActivity : CastEnabledActivity() {
var tag = tag
var args = args
Logd(TAG, "loadFragment(tag: $tag, args: $args)")
val fragment: Fragment
when (tag) {
QueuesFragment.TAG -> fragment = QueuesFragment()
EpisodesFragment.TAG -> fragment = EpisodesFragment()
// AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
// DownloadsFragment.TAG -> fragment = DownloadsFragment()
LogsFragment.TAG -> fragment = LogsFragment()
// HistoryFragment.TAG -> fragment = HistoryFragment()
OnlineSearchFragment.TAG -> fragment = OnlineSearchFragment()
SubscriptionsFragment.TAG -> fragment = SubscriptionsFragment()
StatisticsFragment.TAG -> fragment = StatisticsFragment()
FeedEpisodesFragment.TAG -> fragment = FeedEpisodesFragment()
val fragment: Fragment = when (tag) {
QueuesFragment.TAG -> QueuesFragment()
EpisodesFragment.TAG -> EpisodesFragment()
LogsFragment.TAG -> LogsFragment()
OnlineSearchFragment.TAG -> OnlineSearchFragment()
SubscriptionsFragment.TAG -> SubscriptionsFragment()
StatisticsFragment.TAG -> StatisticsFragment()
FeedEpisodesFragment.TAG -> {
if (args == null) {
val feedId = getLastNavFragmentArg().toLongOrNull()
if (feedId != null) FeedEpisodesFragment.newInstance(feedId) else SubscriptionsFragment()
} else FeedEpisodesFragment()
}
else -> {
// default to subscriptions screen
fragment = SubscriptionsFragment()
tag = SubscriptionsFragment.TAG
args = null
SubscriptionsFragment()
}
}
if (args != null) fragment.arguments = args
@ -478,16 +475,14 @@ class MainActivity : CastEnabledActivity() {
fun loadFeedFragmentById(feedId: Long, args: Bundle?) {
val fragment: Fragment = FeedEpisodesFragment.newInstance(feedId)
if (args != null) fragment.arguments = args
NavDrawerFragment.saveLastNavFragment(feedId.toString())
NavDrawerFragment.saveLastNavFragment(FeedEpisodesFragment.TAG, feedId.toString())
loadFragment(fragment)
}
private fun loadFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
// clear back stack
for (i in 0 until fragmentManager.backStackEntryCount) {
fragmentManager.popBackStack()
}
for (i in 0 until fragmentManager.backStackEntryCount) fragmentManager.popBackStack()
val t = fragmentManager.beginTransaction()
t.replace(R.id.main_view, fragment, MAIN_FRAGMENT_TAG)
fragmentManager.popBackStack()
@ -609,10 +604,6 @@ class MainActivity : CastEnabledActivity() {
}
}
fun openDrawer() {
drawerLayout?.openDrawer(navDrawer)
}
private var eventSink: Job? = null
private var eventStickySink: Job? = null
private fun cancelFlowEvents() {
@ -635,12 +626,7 @@ class MainActivity : CastEnabledActivity() {
}
}
if (eventStickySink == null) eventStickySink = lifecycleScope.launch {
EventFlow.stickyEvents.collectLatest { event ->
Logd(TAG, "Received sticky event: ${event.TAG}")
// when (event) {
// else -> {}
// }
}
EventFlow.stickyEvents.collectLatest { event -> Logd(TAG, "Received sticky event: ${event.TAG}") }
}
}
@ -651,9 +637,11 @@ class MainActivity : CastEnabledActivity() {
intent.hasExtra(Extras.fragment_feed_id.name) -> {
val feedId = intent.getLongExtra(Extras.fragment_feed_id.name, 0)
val args = intent.getBundleExtra(MainActivityStarter.Extras.fragment_args.name)
Logd(TAG, "handleNavIntent: feedId: $feedId")
if (feedId > 0) {
val startedFromShare = intent.getBooleanExtra(Extras.started_from_share.name, false)
val addToBackStack = intent.getBooleanExtra(Extras.add_to_back_stack.name, false)
Logd(TAG, "handleNavIntent: startedFromShare: $startedFromShare addToBackStack: $addToBackStack")
if (startedFromShare || addToBackStack) loadChildFragment(FeedEpisodesFragment.newInstance(feedId))
else loadFeedFragmentById(feedId, args)
}
@ -704,7 +692,6 @@ class MainActivity : CastEnabledActivity() {
s = Snackbar.make(mainView, text, duration)
if (audioPlayerView.visibility == View.VISIBLE) s.anchorView = audioPlayerView
} else s = Snackbar.make(binding.root, text, duration)
s.show()
return s
}
@ -715,14 +702,11 @@ class MainActivity : CastEnabledActivity() {
/**
* Handles the deep link incoming via App Actions.
* Performs an in-app search or opens the relevant feature of the app
* depending on the query.
*
* Performs an in-app search or opens the relevant feature of the app depending on the query
* @param uri incoming deep link
*/
private fun handleDeeplink(uri: Uri?) {
if (uri?.path == null) return
Logd(TAG, "Handling deeplink: $uri")
when (uri.path) {
"/deeplink/search" -> {
@ -732,8 +716,6 @@ class MainActivity : CastEnabledActivity() {
"/deeplink/main" -> {
val feature = uri.getQueryParameter("page") ?: return
when (feature) {
// "DOWNLOADS" -> loadFragment(DownloadsFragment.TAG, null)
// "HISTORY" -> loadFragment(HistoryFragment.TAG, null)
"EPISODES" -> loadFragment(EpisodesFragment.TAG, null)
"QUEUE" -> loadFragment(QueuesFragment.TAG, null)
"SUBSCRIPTIONS" -> loadFragment(SubscriptionsFragment.TAG, null)

View File

@ -150,8 +150,7 @@ import kotlin.Throws
import kotlin.math.round
/**
* PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
* PreferenceController.
* PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see PreferenceController.
*/
class PreferenceActivity : AppCompatActivity() {
private var _binding: SettingsActivityBinding? = null
@ -426,7 +425,6 @@ class PreferenceActivity : AppCompatActivity() {
}
}
}
private fun showLicenseText(licenseTextFile: String) {
try {
val reader = BufferedReader(InputStreamReader(requireContext().assets.open(licenseTextFile), "UTF-8"))
@ -436,12 +434,10 @@ class PreferenceActivity : AppCompatActivity() {
MaterialAlertDialogBuilder(requireContext()).setMessage(licenseText).show()
} catch (e: IOException) { e.printStackTrace() }
}
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.licenses)
}
private class LicenseItem(val title: String, val subtitle: String, val licenseUrl: String, val licenseTextFile: String)
}
}
@ -553,12 +549,6 @@ class PreferenceActivity : AppCompatActivity() {
}
TitleSummarySwitchPrefRow(R.string.pref_back_button_opens_drawer, R.string.pref_back_button_opens_drawer_summary, UserPreferences.Prefs.prefBackButtonOpensDrawer.name)
TitleSummaryActionColumn(R.string.swipeactions_label, R.string.swipeactions_summary) { (activity as PreferenceActivity).openScreen(Screens.preferences_swipe) }
// Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = {
// (activity as PreferenceActivity).openScreen(Screens.preferences_swipe)
// })) {
// Text(stringResource(R.string.swipeactions_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
// Text(stringResource(R.string.swipeactions_summary), color = textColor)
// }
}
}
}
@ -569,8 +559,6 @@ class PreferenceActivity : AppCompatActivity() {
SubscriptionsFragment(R.string.subscriptions_label),
QueuesFragment(R.string.queue_label),
EpisodesFragment(R.string.episodes_label),
// DownloadsFragment(R.string.downloads_label),
PlaybackHistoryFragment(R.string.playback_history_label),
AddFeedFragment(R.string.add_feed_label),
StatisticsFragment(R.string.statistics_label),
remember(R.string.remember_last_page);

View File

@ -1,142 +0,0 @@
package ac.mdiq.podcini.ui.activity
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.SubscriptionSelectionActivityBinding
import ac.mdiq.podcini.preferences.ThemeSwitcher
import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.storage.model.Feed
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.lifecycleScope
import coil.imageLoader
import coil.request.ErrorResult
import coil.request.ImageRequest
import coil.request.SuccessResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// TODO: need to enable
class SelectSubscriptionActivity : AppCompatActivity() {
private var _binding: SubscriptionSelectionActivityBinding? = null
private val binding get() = _binding!!
@Volatile
private var listItems: List<Feed> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(ThemeSwitcher.getTranslucentTheme(this))
super.onCreate(savedInstanceState)
_binding = SubscriptionSelectionActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
setTitle(R.string.shortcut_select_subscription)
binding.transparentBackground.setOnClickListener { finish() }
binding.card.setOnClickListener(null)
loadSubscriptions()
val checkedPosition = arrayOfNulls<Int>(1)
binding.list.choiceMode = ListView.CHOICE_MODE_SINGLE
binding.list.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
checkedPosition[0] = position
}
binding.shortcutBtn.setOnClickListener {
if (checkedPosition[0] != null && Intent.ACTION_CREATE_SHORTCUT == intent.action) getBitmapFromUrl(listItems[checkedPosition[0]!!])
}
}
fun getFeedItems(items: List<Feed?>, result: MutableList<Feed>): List<Feed> {
for (item in items) {
if (item == null) continue
val feed: Feed = item
if (!result.contains(feed)) result.add(feed)
}
return result
}
private fun addShortcut(feed: Feed, bitmap: Bitmap?) {
val intent = Intent(this, MainActivity::class.java)
intent.setAction(Intent.ACTION_MAIN)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.putExtra(MainActivity.Extras.fragment_feed_id.name, feed.id.toString())
val id = "subscription-" + feed.id
val icon: IconCompat = if (bitmap != null) IconCompat.createWithAdaptiveBitmap(bitmap)
else IconCompat.createWithResource(this, R.drawable.ic_subscriptions_shortcut)
val shortcut: ShortcutInfoCompat = ShortcutInfoCompat.Builder(this, id)
.setShortLabel(feed.title?:"")
.setLongLabel(feed.eigenTitle?:"")
.setIntent(intent)
.setIcon(icon)
.build()
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
finish()
}
private fun getBitmapFromUrl(feed: Feed) {
val iconSize = (128 * resources.displayMetrics.density).toInt()
val request = ImageRequest.Builder(this)
.data(feed.imageUrl)
.setHeader("User-Agent", "Mozilla/5.0")
.placeholder(R.color.light_gray)
.listener(object : ImageRequest.Listener {
override fun onError(request: ImageRequest, throwable: ErrorResult) {
addShortcut(feed, null)
}
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
addShortcut(feed, (result.drawable as BitmapDrawable).bitmap)
}
})
.size(iconSize, iconSize)
.build()
imageLoader.enqueue(request)
}
private fun loadSubscriptions() {
lifecycleScope.launch {
try {
val result = withContext(Dispatchers.IO) {
getFeedList()
// val data: NavDrawerData = DBReader.getNavDrawerData()
// getFeedItems(data.items, ArrayList())
}
withContext(Dispatchers.Main) {
listItems = result
val titles = ArrayList<String>()
for (feed in result) {
if (feed.title != null) titles.add(feed.title!!)
}
val adapter: ArrayAdapter<String> = ArrayAdapter<String>(this@SelectSubscriptionActivity, R.layout.simple_list_item_multiple_choice_on_start, titles)
binding.list.adapter = adapter
}
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
}
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
companion object {
private val TAG: String = SelectSubscriptionActivity::class.simpleName ?: "Anonymous"
}
}

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.ShareLog
import ac.mdiq.podcini.ui.compose.ConfirmAddYoutubeEpisode1
import ac.mdiq.podcini.ui.compose.ConfirmAddYoutubeEpisode
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.Logd
import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeServiceURL
@ -52,7 +52,7 @@ class ShareReceiverActivity : AppCompatActivity() {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(this) {
ConfirmAddYoutubeEpisode1(listOf(sharedUrl!!), showDialog.value, onDismissRequest = {
ConfirmAddYoutubeEpisode(listOf(sharedUrl!!), showDialog.value, onDismissRequest = {
showDialog.value = false
finish()
})

View File

@ -0,0 +1,133 @@
package ac.mdiq.podcini.ui.activity
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.ui.compose.CustomTheme
import android.app.ActivityManager
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.lifecycleScope
import coil.imageLoader
import coil.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SubscriptionShortcutActivity : AppCompatActivity() {
private val listItems = mutableStateListOf<Feed>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CustomTheme(this) {
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))
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),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(listItems) { index, item ->
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable(onClick = { checkedIndex = index })) {
var checked by remember { mutableStateOf(false) }
Checkbox(checked = checkedIndex == index, onCheckedChange = {
checkedIndex = index
checked = it
})
Text(item.title?: "No title", color = textColor, style = MaterialTheme.typography.titleSmall)
}
}
}
Button(onClick = { if (checkedIndex >= 0 && Intent.ACTION_CREATE_SHORTCUT == intent.action) getBitmapFromUrl(listItems[checkedIndex]) }) { Text(stringResource(R.string.add_shortcut)) }
}
}
}
}
loadSubscriptions()
}
private fun addShortcut(feed: Feed, bitmap: Bitmap?) {
val intent = Intent(this, MainActivity::class.java)
intent.setAction(Intent.ACTION_MAIN)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.putExtra(MainActivity.Extras.fragment_feed_id.name, feed.id)
val id = "subscription-" + feed.id
// val icon: IconCompat = if (bitmap != null) IconCompat.createWithAdaptiveBitmap(bitmap)
val icon: IconCompat = if (bitmap != null) bitmapToIconCompat(bitmap, getAppIconSize())
else IconCompat.createWithResource(this, R.drawable.ic_subscriptions_shortcut)
val shortcut: ShortcutInfoCompat = ShortcutInfoCompat.Builder(this, id)
.setShortLabel(feed.title?:"")
.setLongLabel(feed.eigenTitle?:"")
.setIntent(intent)
.setIcon(icon)
.build()
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
finish()
}
private fun getBitmapFromUrl(feed: Feed) {
val iconSize = (128 * resources.displayMetrics.density).toInt()
val request = ImageRequest.Builder(this)
.data(feed.imageUrl)
.setHeader("User-Agent", "Mozilla/5.0")
.placeholder(R.mipmap.ic_launcher)
.listener(onError = {_, e -> addShortcut(feed, null) }, onSuccess = { _, result -> addShortcut(feed, result.drawable.toBitmap()) })
.size(iconSize, iconSize)
.build()
imageLoader.enqueue(request)
}
fun getAppIconSize(): Int {
val activityManager = getSystemService(ActivityManager::class.java)
val appIconSize = try { activityManager.launcherLargeIconSize } catch (e: Exception) { TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, resources.displayMetrics).toInt() }
return appIconSize
}
fun bitmapToIconCompat(bitmap: Bitmap, desiredSizeDp: Int): IconCompat {
val desiredSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, desiredSizeDp.toFloat(), resources.displayMetrics).toInt()
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, desiredSizePx, desiredSizePx, true)
return IconCompat.createWithBitmap(resizedBitmap)
}
private fun loadSubscriptions() {
lifecycleScope.launch {
try {
val result = withContext(Dispatchers.IO) { getFeedList() }
withContext(Dispatchers.Main) { listItems.addAll(result) }
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
}
}
companion object {
private val TAG: String = SubscriptionShortcutActivity::class.simpleName ?: "Anonymous"
}
}

View File

@ -19,7 +19,6 @@ import ac.mdiq.podcini.storage.database.Episodes.prefRemoveFromQueueMarkedPlayed
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete
import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
@ -48,7 +47,7 @@ import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.MiscFormatter.formatDateTimeFlex
import ac.mdiq.podcini.util.MiscFormatter.formatNumber
import ac.mdiq.podcini.util.MiscFormatter.formatLargeInteger
import ac.mdiq.podcini.util.MiscFormatter.localDateTimeString
import ac.mdiq.vista.extractor.Vista
import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeServiceURL
@ -101,7 +100,6 @@ import androidx.documentfile.provider.DocumentFile
import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.skydoves.balloon.textForm
import io.realm.kotlin.notifications.SingleQueryChange
import io.realm.kotlin.notifications.UpdatedObject
import kotlinx.coroutines.*
@ -457,7 +455,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
val showConfirmYoutubeDialog = remember { mutableStateOf(false) }
val youtubeUrls = remember { mutableListOf<String>() }
ConfirmAddYoutubeEpisode1(youtubeUrls, showConfirmYoutubeDialog.value, onDismissRequest = { showConfirmYoutubeDialog.value = false })
ConfirmAddYoutubeEpisode(youtubeUrls, showConfirmYoutubeDialog.value, onDismissRequest = { showConfirmYoutubeDialog.value = false })
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(selected) { showChooseRatingDialog = false }
@ -664,7 +662,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
val dateSizeText = " · " + formatDateTimeFlex(vm.episode.getPubDate()) +
" · " + getDurationStringLong(vm.durationState) +
(if ((vm.episode.media?.size ?: 0) > 0) " · " + Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else "") +
(if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "")
(if (vm.viewCount > 0) " · " + formatLargeInteger(vm.viewCount) else "")
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
} else {
@ -684,7 +682,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
Row(verticalAlignment = Alignment.CenterVertically) {
val dateSizeText = formatDateTimeFlex(vm.episode.getPubDate()) + (if (vm.viewCount > 0) " · " + formatNumber(vm.viewCount) else "")
val dateSizeText = formatDateTimeFlex(vm.episode.getPubDate()) + (if (vm.viewCount > 0) " · " + formatLargeInteger(vm.viewCount) else "")
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (vm.viewCount > 0)
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_people_alt_24), tint = textColor, contentDescription = "people", modifier = Modifier.width(16.dp).height(16.dp))
@ -899,7 +897,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
}
@Composable
fun ConfirmAddYoutubeEpisode1(sharedUrls: List<String>, showDialog: Boolean, onDismissRequest: () -> Unit) {
fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDismissRequest: () -> Unit) {
val TAG = "confirmAddEpisode"
var showToast by remember { mutableStateOf(false) }
var toastMassege by remember { mutableStateOf("")}

View File

@ -233,7 +233,7 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
else -> ""
}
if (authorText.isNotEmpty()) Text(authorText, color = textColor, style = MaterialTheme.typography.bodyMedium)
if (feed.subscriberCount > 0) Text(MiscFormatter.formatNumber(feed.subscriberCount) + " subscribers", color = textColor, style = MaterialTheme.typography.bodyMedium)
if (feed.subscriberCount > 0) Text(MiscFormatter.formatLargeInteger(feed.subscriberCount) + " subscribers", color = textColor, style = MaterialTheme.typography.bodyMedium)
Row {
if (feed.count != null && feed.count > 0) Text(feed.count.toString() + " episodes", color = textColor, style = MaterialTheme.typography.bodyMedium)
Spacer(Modifier.weight(1f))

View File

@ -49,6 +49,7 @@ import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.MiscFormatter
import ac.mdiq.podcini.util.MiscFormatter.formatLargeInteger
import android.app.Activity
import android.content.*
import android.os.Build
@ -249,10 +250,6 @@ class AudioPlayerFragment : Fragment() {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
SpeedometerWithArc(speed = curPlaybackSpeed*100, maxSpeed = 300f, trackColor = textColor,
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = { showSpeedDialog = true }))
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, contentDescription = "speed",
// modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
// VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
// }))
Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodySmall)
}
Spacer(Modifier.weight(0.1f))
@ -337,6 +334,9 @@ class AudioPlayerFragment : Fragment() {
Row {
Text(DurationConverter.getDurationStringLong(currentPosition), color = textColor, style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.weight(1f))
val bitrate = (curMedia as? EpisodeMedia)?.bitrate ?: 0
if (bitrate > 0) Text(formatLargeInteger(bitrate) + "bits", color = textColor, style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.weight(1f))
showTimeLeft = UserPreferences.shouldShowRemainingTime()
Text(txtvLengtTexth, color = textColor, style = MaterialTheme.typography.bodySmall, modifier = Modifier.clickable {
if (controller == null) return@clickable

View File

@ -125,23 +125,17 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// }
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
NavDrawerFragment.saveLastNavFragment(TAG, feedID.toString())
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
updateToolbar()
swipeActions = SwipeActions(this, TAG)
fun filterClick() {
if (enableFilter && feed != null) {
showFilterDialog = true
// val dialog = FeedEpisodeFilterDialog(feed)
// dialog.filter = feed!!.episodeFilter
// dialog.show(childFragmentManager, null)
}
}
fun filterLongClick() {
if (feed != null) {
enableFilter = !enableFilter
waitForLoading()
while (loadItemsRunning) Thread.sleep(50)
loadItemsRunning = true
val etmp = mutableListOf<Episode>()
if (enableFilter) {
@ -153,8 +147,7 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
filterButtonColor.value = Color.Red
etmp.addAll(feed!!.episodes)
}
val sortOrder = fromCode(feed?.preferences?.sortOrderCode ?: 0)
if (sortOrder != null) getPermutor(sortOrder).reorder(etmp)
getPermutor(fromCode(feed?.preferences?.sortOrderCode ?: 0)).reorder(etmp)
episodes.clear()
episodes.addAll(etmp)
ieMap = episodes.withIndex().associate { (index, episode) -> episode.id to index }
@ -198,7 +191,9 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
Column {
FeedEpisodesHeader(activity = (activity as MainActivity), filterButColor = filterButtonColor.value, filterClickCB = {filterClick()}, filterLongClickCB = {filterLongClick()})
FeedEpisodesHeader(activity = (activity as MainActivity), filterButColor = filterButtonColor.value, filterClickCB = {
if (enableFilter && feed != null) showFilterDialog = true
}, filterLongClickCB = {filterLongClick()})
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() })
EpisodeLazyColumn(activity as MainActivity, vms = vms, feed = feed, layoutMode = layoutMode,
refreshCB = { FeedUpdateManager.runOnceOrAsk(requireContext(), feed) },
@ -218,29 +213,12 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
lifecycle.addObserver(swipeActions)
refreshSwipeTelltale()
// val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(requireContext(), binding.toolbar, binding.collapsingToolbar) {
// override fun doTint(themedContext: Context) {
// binding.toolbar.menu.findItem(R.id.refresh_feed).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_refresh))
// binding.toolbar.menu.findItem(R.id.action_search).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_search))
// }
// }
// iconTintManager.updateTint()
// binding.appBar.addOnOffsetChangedListener(iconTintManager)
// binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
// binding.swipeRefresh.setProgressViewEndTarget(false, 0)
// binding.swipeRefresh.setOnRefreshListener {
// FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
// }
return binding.root
}
override fun onStart() {
Logd(TAG, "onStart() called")
super.onStart()
// adapter.refreshFragPosCallback = ::refreshPosCallback
loadFeed()
procFlowEvents()
}
@ -262,41 +240,30 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
rating = feed!!.rating
}
ConstraintLayout(modifier = Modifier.fillMaxWidth().height(120.dp)) {
val (bgImage, bgColor, controlRow, image1, image2, imgvCover, taColumn) = createRefs()
AsyncImage(model = feed?.imageUrl?:"", contentDescription = "bgImage", contentScale = ContentScale.FillBounds,
error = painterResource(R.drawable.teaser),
modifier = Modifier.fillMaxSize().blur(radiusX = 15.dp, radiusY = 15.dp)
.constrainAs(bgImage) {
val (bgImage, bgColor, controlRow, imgvCover) = createRefs()
AsyncImage(model = feed?.imageUrl?:"", contentDescription = "bgImage", contentScale = ContentScale.FillBounds, error = painterResource(R.drawable.teaser),
modifier = Modifier.fillMaxSize().blur(radiusX = 15.dp, radiusY = 15.dp).constrainAs(bgImage) {
bottom.linkTo(parent.bottom)
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface.copy(alpha = 0.75f))
.constrainAs(bgColor) {
end.linkTo(parent.end) })
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface.copy(alpha = 0.75f)).constrainAs(bgColor) {
bottom.linkTo(parent.bottom)
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp)
.constrainAs(controlRow) {
end.linkTo(parent.end) })
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp).constrainAs(controlRow) {
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
width = Dimension.fillToConstraints
}, verticalAlignment = Alignment.CenterVertically) {
}) {
Spacer(modifier = Modifier.weight(0.7f))
val ratingIconRes = Rating.fromCode(rating).res
Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(30.dp).height(30.dp).clickable(onClick = {
showChooseRatingDialog = true
}))
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(30.dp).height(30.dp).clickable(onClick = { showChooseRatingDialog = true }))
Spacer(modifier = Modifier.weight(0.2f))
Icon(imageVector = ImageVector.vectorResource(R.drawable.arrows_sort), tint = textColor, contentDescription = "butSort",
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).clickable(onClick = {
showSortDialog = true
// SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog")
}))
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).clickable(onClick = { showSortDialog = true }))
Spacer(modifier = Modifier.width(15.dp))
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter_white), tint = if (filterButColor == Color.White) textColor else filterButColor, contentDescription = "butFilter",
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).combinedClickable(onClick = filterClickCB, onLongClick = filterLongClickCB))
@ -311,16 +278,6 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Spacer(modifier = Modifier.weight(0.4f))
Text(episodes.size.toString() + " / " + feed?.episodes?.size?.toString(), textAlign = TextAlign.End, color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge)
}
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
// bottom.linkTo(parent.bottom)
// start.linkTo(parent.start)
// })
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_right), contentDescription = "right_corner",
// Modifier.width(12.dp).height(12.dp).constrainAs(image2) {
// bottom.linkTo(parent.bottom)
// end.linkTo(parent.end)
// })
Row(verticalAlignment = Alignment.Top, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).constrainAs(imgvCover) {
top.linkTo(parent.top)
start.linkTo(parent.start)
@ -381,14 +338,6 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
super.onDestroyView()
}
//
// private fun refreshPosCallback(pos: Int, episode: Episode) {
// Logd(TAG, "FeedEpisode refreshPosCallback: $pos ${episode.title}")
//// if (pos >= 0 && pos < episodes.size) episodes[pos] = episode
// redoFilter()
//// adapter.notifyDataSetChanged()
// }
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_UP_ARROW, displayUpArrow)
super.onSaveInstanceState(outState)
@ -441,14 +390,12 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
(activity as MainActivity).loadChildFragment(qFrag)
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
}
// R.id.init_tts -> initTTS()
else -> return false
}
return true
}
private fun onPlayEvent(event: FlowEvent.PlayEvent) {
// Logd(TAG, "onPlayEvent ${event.episode.title}")
if (feed != null) {
val pos: Int = ieMap[event.episode.id] ?: -1
if (pos >= 0) {
@ -467,7 +414,6 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val pos: Int = ueMap[url] ?: -1
if (pos >= 0) {
Logd(TAG, "onEpisodeDownloadEvent $pos ${event.map[url]?.state} ${episodes[pos].media?.downloaded} ${episodes[pos].title}")
// episodes[pos].downloadState.value = event.map[url]?.state ?: DownloadStatus.State.UNKNOWN.ordinal
vms[pos].downloadState = event.map[url]?.state ?: DownloadStatus.State.UNKNOWN.ordinal
}
}
@ -517,12 +463,9 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
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 ""
infoBarText.value = "$infoTextFiltered $infoTextUpdate"
if (!event.isRunning) loadFeed()
// binding.swipeRefresh.isRefreshing = event.isRunning
}
private fun refreshSwipeTelltale() {
@ -531,15 +474,11 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
private fun refreshHeaderView() {
setupHeaderView()
if (feed == null) {
Log.e(TAG, "Unable to refresh header view")
return
}
// loadFeedImage()
// if (feed!!.lastUpdateFailed) binding.header.txtvFailure.visibility = View.VISIBLE
// else binding.header.txtvFailure.visibility = View.GONE
if (!headerCreated) headerCreated = true
infoTextFiltered = ""
if (!feed?.preferences?.filterString.isNullOrEmpty()) {
val filter: EpisodeFilter = feed!!.episodeFilter
@ -548,36 +487,6 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
infoBarText.value = "$infoTextFiltered $infoTextUpdate"
}
private fun setupHeaderView() {
if (feed == null || headerCreated) return
// binding.imgvBackground.colorFilter = LightingColorFilter(-0x99999a, 0x000000)
// binding.header.imgvCover.setOnClickListener { showFeedInfo() }
// binding.header.txtvFailure.setOnClickListener { showErrorDetails() }
headerCreated = true
}
// private fun showErrorDetails() {
// lifecycleScope.launch {
// val downloadResult = withContext(Dispatchers.IO) {
// val feedDownloadLog: List<DownloadResult> = getFeedDownloadLog(feedID)
// if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) null else feedDownloadLog[0]
// }
// withContext(Dispatchers.Main) {
// if (downloadResult != null) DownloadLogDetailsDialog(requireContext(), downloadResult).show()
// else DownloadLogFragment().show(childFragmentManager, null)
// }
// }.invokeOnCompletion { throwable ->
// throwable?.printStackTrace()
// }
// }
private var loadItemsRunning = false
private fun waitForLoading() {
while (loadItemsRunning) Thread.sleep(50)
}
private fun isFilteredOut(episode: Episode): Boolean {
if (enableFilter && !feed?.preferences?.filterString.isNullOrEmpty()) {
val episodes_ = realm.query(Episode::class).query("feedId == ${feed!!.id}").query(feed!!.episodeFilter.queryString()).find()
@ -592,6 +501,7 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return false
}
private var loadItemsRunning = false
private fun loadFeed() {
if (!loadItemsRunning) {
loadItemsRunning = true
@ -635,12 +545,7 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
withContext(Dispatchers.Main) {
Logd(TAG, "loadItems subscribe called ${feed?.title}")
rating = feed?.rating ?: Rating.UNRATED.code
// swipeActions.setFilter(feed?.episodeFilter)
refreshHeaderView()
// if (feed != null) {
// adapter.updateItems(episodes, feed)
// binding.header.counts.text = episodes.size.toString()
// }
updateToolbar()
}
} catch (e: Throwable) {
@ -676,7 +581,6 @@ class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
companion object {
val TAG = FeedEpisodesFragment::class.simpleName ?: "Anonymous"
const val ARGUMENT_FEED_ID = "argument.ac.mdiq.podcini.feed_id"
private const val KEY_UP_ARROW = "up_arrow"

View File

@ -13,7 +13,7 @@ import ac.mdiq.podcini.storage.model.Rating.Companion.fromCode
import ac.mdiq.podcini.ui.actions.DownloadActionButton
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.ShareReceiverActivity.Companion.receiveShared
import ac.mdiq.podcini.ui.compose.ConfirmAddYoutubeEpisode1
import ac.mdiq.podcini.ui.compose.ConfirmAddYoutubeEpisode
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
@ -120,7 +120,7 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
var showYTMediaConfirmDialog by remember { mutableStateOf(false) }
var sharedUrl by remember { mutableStateOf("") }
if (showYTMediaConfirmDialog)
ConfirmAddYoutubeEpisode1(listOf(sharedUrl), showYTMediaConfirmDialog, onDismissRequest = { showYTMediaConfirmDialog = false })
ConfirmAddYoutubeEpisode(listOf(sharedUrl), showYTMediaConfirmDialog, onDismissRequest = { showYTMediaConfirmDialog = false })
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 6.dp, top = 5.dp, bottom = 5.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) {

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.database.Feeds.getFeedCount
@ -135,7 +134,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
}
}
Spacer(Modifier.weight(1f))
Text("Currently launching on Google Play, please kindly support with closed testing. Thank you!", color = MaterialTheme.colorScheme.tertiary,
Text("Currently launching on Google Play, early testers will become pre-IPO members. Click for details", color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.clickable(onClick = {
openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/discussions/120")
}))
@ -197,6 +196,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
@VisibleForTesting
const val PREF_LAST_FRAGMENT_TAG: String = "prefLastFragmentTag"
const val PREF_LAST_FRAGMENT_ARG: String = "prefLastFragmentArg"
@VisibleForTesting
const val PREF_NAME: String = "NavDrawerPrefs"
@ -220,26 +220,24 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
SubscriptionsFragment.TAG to NavItem(SubscriptionsFragment.TAG, R.drawable.ic_subscriptions, R.string.subscriptions_label),
QueuesFragment.TAG to NavItem(QueuesFragment.TAG, R.drawable.ic_playlist_play, R.string.queue_label),
EpisodesFragment.TAG to NavItem(EpisodesFragment.TAG, R.drawable.ic_feed, R.string.episodes_label),
// AllEpisodesFragment.TAG to NavItem(AllEpisodesFragment.TAG, R.drawable.ic_feed, R.string.episodes_label),
// DownloadsFragment.TAG to NavItem(DownloadsFragment.TAG, R.drawable.ic_download, R.string.downloads_label),
// HistoryFragment.TAG to NavItem(HistoryFragment.TAG, R.drawable.baseline_work_history_24, R.string.playback_history_label),
LogsFragment.TAG to NavItem(LogsFragment.TAG, R.drawable.ic_history, R.string.logs_label),
StatisticsFragment.TAG to NavItem(StatisticsFragment.TAG, R.drawable.ic_chart_box, R.string.statistics_label),
OnlineSearchFragment.TAG to NavItem(OnlineSearchFragment.TAG, R.drawable.ic_add, R.string.add_feed_label)
)
fun saveLastNavFragment(tag: String?) {
fun saveLastNavFragment(tag: String?, arg: String? = null) {
Logd(TAG, "saveLastNavFragment(tag: $tag)")
val edit: SharedPreferences.Editor? = prefs?.edit()
if (tag != null) edit?.putString(PREF_LAST_FRAGMENT_TAG, tag)
if (tag != null) {
edit?.putString(PREF_LAST_FRAGMENT_TAG, tag)
if (arg != null) edit?.putString(PREF_LAST_FRAGMENT_ARG, arg)
}
else edit?.remove(PREF_LAST_FRAGMENT_TAG)
edit?.apply()
}
fun getLastNavFragment(): String {
val lastFragment: String = prefs?.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionsFragment.TAG)?:""
return lastFragment
}
fun getLastNavFragment(): String = prefs?.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionsFragment.TAG) ?: ""
fun getLastNavFragmentArg(): String = prefs?.getString(PREF_LAST_FRAGMENT_ARG, "0") ?: ""
/**
* Returns data necessary for displaying the navigation drawer. This includes
@ -251,9 +249,6 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
feedCount = getFeedCount()
navMap[QueuesFragment.TAG]?.count = realm.query(PlayQueue::class).find().sumOf { it.size()}
navMap[SubscriptionsFragment.TAG]?.count = feedCount
// navMap[HistoryFragment.TAG]?.count = getNumberOfPlayed().toInt()
// navMap[DownloadsFragment.TAG]?.count = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name))
// navMap[AllEpisodesFragment.TAG]?.count = numItems
navMap[EpisodesFragment.TAG]?.count = numItems
navMap[LogsFragment.TAG]?.count = realm.query(ShareLog::class).count().find().toInt() +
realm.query(SubscriptionLog::class).count().find().toInt() +

View File

@ -74,7 +74,7 @@ object MiscFormatter {
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
}
fun formatNumber(n: Int): String {
fun formatLargeInteger(n: Int): String {
return when {
n < 1000 -> n.toString()
n < 1_000_000 -> String.format(Locale.getDefault(), "%.2fK", n / 1000.0)

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on simple_list_item_multiple_choice.xml
from The Android Open Source Project
This one puts the check box at the start of the view (rather than at the end).
-->
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
android:drawableLeft="?android:attr/listChoiceIndicatorMultiple"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:maxLines="2"
android:ellipsize="end"
/>

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/transparentBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="32dp"
android:elevation="16dp"
app:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/toolbar"
android:background="?android:attr/listDivider" />
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:layout_below="@id/divider"
android:paddingBottom="88dp" />
<Button
android:id="@+id/shortcutBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_alignParentBottom="true"
android:text="@string/add_shortcut" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@ -1,3 +1,10 @@
# 6.15.2
* FeedEpisodes is remembered as last opened page and can be opened if default page set to "Remember"
* revamped SubscriptionShortcut activity in Compose to create proper shortcuts on home screen of the phone
* feed shortcut on home screen opens the feed (with FeedEpisodes) in Podcini
* when playing a youtube audio, bitrate is shown in PlayerUI
# 6.15.1
* Consolidated Compose code blocks in PreferenceActivity with function calls

View File

@ -0,0 +1,6 @@
Version 6.15.2
* FeedEpisodes is remembered as last opened page and can be opened if default page set to "Remember"
* revamped SubscriptionShortcut activity in Compose to create proper shortcuts on home screen of the phone
* feed shortcut on home screen opens the feed (with FeedEpisodes) in Podcini
* when playing a youtube audio, bitrate is shown in PlayerUI

View File

@ -466,7 +466,7 @@ QStatusBar QComboBox::down-arrow {
padding: 0;
height: 14px;
width: 14px;
image: url(/tmp/ZPXwRX.png)
image: url(/tmp/ZBphJY.png)
}
QStatusBar QComboBox::drop-down:hover {