changes in click handling and refresh bug fix

This commit is contained in:
Xilin Jia 2024-03-03 18:02:33 +01:00
parent e24c764e2a
commit da66b322e7
59 changed files with 486 additions and 414 deletions

View File

@ -22,8 +22,8 @@ android {
// Version code schema: // Version code schema:
// "1.2.3-beta4" -> 1020304 // "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395 // "1.2.3" -> 1020395
versionCode 3020103 versionCode 3020104
versionName "4.2.0" versionName "4.2.1"
def commit = "" def commit = ""
try { try {

View File

@ -21,7 +21,6 @@
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"

View File

@ -30,6 +30,8 @@ import ac.mdiq.podcini.ui.gui.NotificationUtils
import ac.mdiq.podcini.storage.model.download.DownloadError import ac.mdiq.podcini.storage.model.download.DownloadError
import ac.mdiq.podcini.storage.model.download.DownloadResult import ac.mdiq.podcini.storage.model.download.DownloadResult
import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.Feed
import android.os.Build
import android.widget.Toast
import java.util.* import java.util.*
class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(context, params) { class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
@ -107,11 +109,7 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
} }
@UnstableApi private fun refreshFeeds(toUpdate: MutableList<Feed>, force: Boolean) { @UnstableApi private fun refreshFeeds(toUpdate: MutableList<Feed>, force: Boolean) {
while (toUpdate.isNotEmpty()) { if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this.applicationContext,
if (isStopped) {
return
}
if (ActivityCompat.checkSelfPermission(this.applicationContext,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -120,6 +118,13 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "refreshFeeds: require POST_NOTIFICATIONS permission")
// Toast.makeText(applicationContext, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return
}
while (toUpdate.isNotEmpty()) {
if (isStopped) {
return return
} }
notificationManager.notify(R.id.notification_updating_feeds, createNotification(toUpdate)) notificationManager.notify(R.id.notification_updating_feeds, createNotification(toUpdate))
@ -187,9 +192,9 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
newEpisodesNotification.showIfNeeded(applicationContext, feedSyncTask.savedFeed!!) newEpisodesNotification.showIfNeeded(applicationContext, feedSyncTask.savedFeed!!)
if (!request.source.isNullOrEmpty()) { if (!request.source.isNullOrEmpty()) {
if (!downloader.permanentRedirectUrl.isNullOrEmpty()) { if (!downloader.permanentRedirectUrl.isNullOrEmpty()) {
DBWriter.updateFeedDownloadURL(request.source!!, downloader.permanentRedirectUrl!!) DBWriter.updateFeedDownloadURL(request.source, downloader.permanentRedirectUrl!!)
} else if (feedSyncTask.redirectUrl.isNotEmpty() && feedSyncTask.redirectUrl != request.source) { } else if (feedSyncTask.redirectUrl.isNotEmpty() && feedSyncTask.redirectUrl != request.source) {
DBWriter.updateFeedDownloadURL(request.source!!, feedSyncTask.redirectUrl) DBWriter.updateFeedDownloadURL(request.source, feedSyncTask.redirectUrl)
} }
} }
} }

View File

@ -19,6 +19,7 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.work.Data import androidx.work.Data
@ -79,8 +80,8 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
e.printStackTrace() e.printStackTrace()
result = Result.failure() result = Result.failure()
} }
if (result == Result.failure() && downloader != null) { if (result == Result.failure() && downloader?.downloadRequest?.destination != null) {
FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination)) FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination!!))
} }
progressUpdaterThread.interrupt() progressUpdaterThread.interrupt()
try { try {
@ -102,9 +103,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
override fun onStopped() { override fun onStopped() {
super.onStopped() super.onStopped()
if (downloader != null) { downloader?.cancel()
downloader!!.cancel()
}
} }
override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> { override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
@ -112,7 +111,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
ForegroundInfo(R.id.notification_downloading, generateProgressNotification())) ForegroundInfo(R.id.notification_downloading, generateProgressNotification()))
} }
private fun performDownload(media: FeedMedia, request: DownloadRequest): Result { @OptIn(UnstableApi::class) private fun performDownload(media: FeedMedia, request: DownloadRequest): Result {
val dest = File(request.destination) val dest = File(request.destination)
if (!dest.exists()) { if (!dest.exists()) {
try { try {
@ -162,7 +161,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
if (status.reason == DownloadError.ERROR_HTTP_DATA_ERROR if (status.reason == DownloadError.ERROR_HTTP_DATA_ERROR
&& status.reasonDetailed.toInt() == 416) { && status.reasonDetailed.toInt() == 416) {
Log.d(TAG, "Requested invalid range, restarting download from the beginning") Log.d(TAG, "Requested invalid range, restarting download from the beginning")
FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination)) if (downloader?.downloadRequest?.destination != null) FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination!!))
sendMessage(request.title?:"", false) sendMessage(request.title?:"", false)
return retry3times() return retry3times()
} }
@ -199,7 +198,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
EventBus.getDefault().post(MessageEvent( EventBus.getDefault().post(MessageEvent(
applicationContext.getString( applicationContext.getString(
if (retrying) R.string.download_error_retrying else R.string.download_error_not_retrying, if (retrying) R.string.download_error_retrying else R.string.download_error_not_retrying,
episodeTitle), { ctx: Context -> MainActivityStarter(ctx!!).withDownloadLogsOpen().start() }, episodeTitle), { ctx: Context -> MainActivityStarter(ctx).withDownloadLogsOpen().start() },
applicationContext.getString(R.string.download_error_details))) applicationContext.getString(R.string.download_error_details)))
} }
@ -251,8 +250,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
applicationContext.resources.getQuantityString(R.plurals.downloads_left, applicationContext.resources.getQuantityString(R.plurals.downloads_left,
progressCopy.size, progressCopy.size) progressCopy.size, progressCopy.size)
} }
val builder = NotificationCompat.Builder(applicationContext, val builder = NotificationCompat.Builder(applicationContext, NotificationUtils.CHANNEL_ID_DOWNLOADING)
NotificationUtils.CHANNEL_ID_DOWNLOADING)
builder.setTicker(applicationContext.getString(R.string.download_notification_title_episodes)) builder.setTicker(applicationContext.getString(R.string.download_notification_title_episodes))
.setContentTitle(applicationContext.getString(R.string.download_notification_title_episodes)) .setContentTitle(applicationContext.getString(R.string.download_notification_title_episodes))
.setContentText(contentText) .setContentText(contentText)

View File

@ -1,6 +1,7 @@
package ac.mdiq.podcini.service.download package ac.mdiq.podcini.service.download
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.service.FeedUpdateWorker
import android.Manifest import android.Manifest
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ComponentName import android.content.ComponentName
@ -19,6 +20,7 @@ import ac.mdiq.podcini.ui.gui.NotificationUtils
import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.Feed
import ac.mdiq.podcini.storage.model.feed.FeedCounter import ac.mdiq.podcini.storage.model.feed.FeedCounter
import ac.mdiq.podcini.storage.database.PodDBAdapter import ac.mdiq.podcini.storage.database.PodDBAdapter
import android.widget.Toast
class NewEpisodesNotification { class NewEpisodesNotification {
private var countersBefore: Map<Long, Int>? = null private var countersBefore: Map<Long, Int>? = null
@ -80,7 +82,7 @@ class NewEpisodesNotification {
.setAutoCancel(true) .setAutoCancel(true)
.build() .build()
if (ActivityCompat.checkSelfPermission(context, if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(context,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -89,6 +91,8 @@ class NewEpisodesNotification {
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "showNotification: require POST_NOTIFICATIONS permission")
Toast.makeText(context, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return return
} }
notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS, notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS,
@ -117,7 +121,7 @@ class NewEpisodesNotification {
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setAutoCancel(true) .setAutoCancel(true)
.build() .build()
if (ActivityCompat.checkSelfPermission(context, if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(context,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -126,6 +130,8 @@ class NewEpisodesNotification {
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "showGroupSummaryNotification: require POST_NOTIFICATIONS permission")
Toast.makeText(context, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return return
} }
notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS, 0, notificationGroupSummary) notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS, 0, notificationGroupSummary)

View File

@ -103,6 +103,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.showNextChapterOnFullNotifica
import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification
import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification
import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed
import ac.mdiq.podcini.service.download.NewEpisodesNotification
import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.appstartintent.VideoPlayerActivityStarter import ac.mdiq.podcini.ui.appstartintent.VideoPlayerActivityStarter
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
@ -246,7 +247,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
if (notificationBuilder.playerStatus == PlayerStatus.PLAYING) { if (notificationBuilder.playerStatus == PlayerStatus.PLAYING) {
notificationBuilder.playerStatus = PlayerStatus.STOPPED notificationBuilder.playerStatus = PlayerStatus.STOPPED
val notificationManager = NotificationManagerCompat.from(this) val notificationManager = NotificationManagerCompat.from(this)
if (ActivityCompat.checkSelfPermission(this, if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -255,6 +256,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "onDestroy: require POST_NOTIFICATIONS permission")
Toast.makeText(applicationContext, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return return
} }
notificationManager.notify(R.id.notification_playing, notificationBuilder.build()) notificationManager.notify(R.id.notification_playing, notificationBuilder.build())
@ -576,7 +579,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
pendingIntentAlwaysAllow) pendingIntentAlwaysAllow)
.setAutoCancel(true) .setAutoCancel(true)
val notificationManager = NotificationManagerCompat.from(this) val notificationManager = NotificationManagerCompat.from(this)
if (ActivityCompat.checkSelfPermission(this, if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -585,6 +588,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "displayStreamingNotAllowedNotification: require POST_NOTIFICATIONS permission")
Toast.makeText(applicationContext, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return return
} }
notificationManager.notify(R.id.notification_streaming_confirmation, builder.build()) notificationManager.notify(R.id.notification_streaming_confirmation, builder.build())
@ -1303,7 +1308,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed) notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed)
val notificationManager = NotificationManagerCompat.from(this) val notificationManager = NotificationManagerCompat.from(this)
if (ActivityCompat.checkSelfPermission(this, if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling // TODO: Consider calling
// ActivityCompat#requestPermissions // ActivityCompat#requestPermissions
@ -1312,6 +1317,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults) // int[] grantResults)
// to handle the case where the user grants the permission. See the documentation // to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details. // for ActivityCompat#requestPermissions for more details.
Log.e(TAG, "setupNotification: require POST_NOTIFICATIONS permission")
Toast.makeText(applicationContext, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
return return
} }
notificationManager.notify(R.id.notification_playing, notificationBuilder.build()) notificationManager.notify(R.id.notification_playing, notificationBuilder.build())

View File

@ -37,10 +37,11 @@ open class AutomaticDownloadAlgorithm {
@UnstableApi open fun autoDownloadUndownloadedItems(context: Context): Runnable? { @UnstableApi open fun autoDownloadUndownloadedItems(context: Context): Runnable? {
return Runnable { return Runnable {
// true if we should auto download based on network status // true if we should auto download based on network status
val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload) val networkShouldAutoDl = (isAutoDownloadAllowed)
// val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
// true if we should auto download based on power status // true if we should auto download based on power status
val powerShouldAutoDl = (deviceCharging(context!!) || isEnableAutodownloadOnBattery) val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery)
// we should only auto download if both network AND power are happy // we should only auto download if both network AND power are happy
if (networkShouldAutoDl && powerShouldAutoDl) { if (networkShouldAutoDl && powerShouldAutoDl) {
@ -48,8 +49,7 @@ open class AutomaticDownloadAlgorithm {
val candidates: MutableList<FeedItem> val candidates: MutableList<FeedItem>
val queue = getQueue() val queue = getQueue()
val newItems = getEpisodes(0, Int.MAX_VALUE, val newItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.NEW), SortOrder.DATE_NEW_OLD)
FeedItemFilter(FeedItemFilter.NEW), SortOrder.DATE_NEW_OLD)
candidates = ArrayList(queue.size + newItems.size) candidates = ArrayList(queue.size + newItems.size)
candidates.addAll(queue) candidates.addAll(queue)
for (newItem in newItems) { for (newItem in newItems) {

View File

@ -75,10 +75,12 @@ object DBReader {
fun buildTags() { fun buildTags() {
val tagsSet = mutableSetOf<String>() val tagsSet = mutableSetOf<String>()
for (feed in feeds) { for (feed in feeds) {
if (feed.preferences != null) {
for (tag in feed.preferences!!.getTags()) { for (tag in feed.preferences!!.getTags()) {
if (tag != TAG_ROOT) tagsSet.add(tag) if (tag != TAG_ROOT) tagsSet.add(tag)
} }
} }
}
tags.clear() tags.clear()
tags.addAll(tagsSet) tags.addAll(tagsSet)
tags.sort() tags.sort()
@ -177,18 +179,23 @@ object DBReader {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
try { try {
adapter.getItemsOfFeedCursor(feed!!, filter).use { cursor -> if (feed != null) {
adapter.getItemsOfFeedCursor(feed, filter).use { cursor ->
val items = extractItemlistFromCursor(adapter, cursor).toMutableList() val items = extractItemlistFromCursor(adapter, cursor).toMutableList()
getPermutor(sortOrder!!).reorder(items) if (sortOrder != null) getPermutor(sortOrder).reorder(items)
feed.items = items feed.items = items
for (item in items) { for (item in items) {
item.feed = feed item.feed = feed
} }
return items return items
} }
} else {
Log.e(TAG, "getFeedItemList feed is null")
}
} finally { } finally {
adapter.close() adapter.close()
} }
return listOf()
} }
@JvmStatic @JvmStatic
@ -204,7 +211,8 @@ object DBReader {
} }
private fun extractItemlistFromCursor(adapter: PodDBAdapter?, cursor: Cursor?): List<FeedItem> { private fun extractItemlistFromCursor(adapter: PodDBAdapter?, cursor: Cursor?): List<FeedItem> {
val result: MutableList<FeedItem> = ArrayList(cursor!!.count) if (cursor == null) return listOf()
val result: MutableList<FeedItem> = ArrayList(cursor.count)
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val indexMediaId = cursor.getColumnIndexOrThrow(PodDBAdapter.SELECT_KEY_MEDIA_ID) val indexMediaId = cursor.getColumnIndexOrThrow(PodDBAdapter.SELECT_KEY_MEDIA_ID)
do { do {
@ -228,7 +236,7 @@ object DBReader {
@JvmStatic @JvmStatic
fun getQueue(adapter: PodDBAdapter?): List<FeedItem> { fun getQueue(adapter: PodDBAdapter?): List<FeedItem> {
// Log.d(TAG, "getQueue()") // Log.d(TAG, "getQueue()")
adapter!!.queueCursor.use { cursor -> adapter?.queueCursor.use { cursor ->
val items = extractItemlistFromCursor(adapter, cursor) val items = extractItemlistFromCursor(adapter, cursor)
loadAdditionalFeedItemListData(items) loadAdditionalFeedItemListData(items)
return items return items
@ -253,13 +261,14 @@ object DBReader {
} }
private fun getQueueIDList(adapter: PodDBAdapter?): LongList { private fun getQueueIDList(adapter: PodDBAdapter?): LongList {
adapter!!.queueIDCursor.use { cursor -> adapter?.queueIDCursor?.use { cursor ->
val queueIds = LongList(cursor.count) val queueIds = LongList(cursor.count)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
queueIds.add(cursor.getLong(0)) queueIds.add(cursor.getLong(0))
} }
return queueIds return queueIds
} }
return LongList()
} }
@JvmStatic @JvmStatic
@ -490,7 +499,7 @@ object DBReader {
Log.d(TAG, "Loading feeditem with id $itemId") Log.d(TAG, "Loading feeditem with id $itemId")
var item: FeedItem? = null var item: FeedItem? = null
adapter!!.getFeedItemCursor(itemId.toString()).use { cursor -> adapter?.getFeedItemCursor(itemId.toString())?.use { cursor ->
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
val list = extractItemlistFromCursor(adapter, cursor) val list = extractItemlistFromCursor(adapter, cursor)
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
@ -500,6 +509,7 @@ object DBReader {
} }
return item return item
} }
return item
} }
/** /**
@ -577,7 +587,8 @@ object DBReader {
private fun getFeedItemByGuidOrEpisodeUrl(guid: String?, episodeUrl: String, private fun getFeedItemByGuidOrEpisodeUrl(guid: String?, episodeUrl: String,
adapter: PodDBAdapter? adapter: PodDBAdapter?
): FeedItem? { ): FeedItem? {
adapter!!.getFeedItemCursor(guid, episodeUrl).use { cursor -> if (adapter != null) {
adapter.getFeedItemCursor(guid, episodeUrl).use { cursor ->
if (!cursor.moveToNext()) { if (!cursor.moveToNext()) {
return null return null
} }
@ -588,6 +599,8 @@ object DBReader {
return null return null
} }
} }
return null
}
/** /**
* Returns credentials based on image URL * Returns credentials based on image URL
@ -608,8 +621,9 @@ object DBReader {
} }
private fun getImageAuthentication(imageUrl: String, adapter: PodDBAdapter?): String { private fun getImageAuthentication(imageUrl: String, adapter: PodDBAdapter?): String {
var credentials: String var credentials: String = ""
adapter!!.getImageAuthenticationCursor(imageUrl).use { cursor -> if (adapter != null) {
adapter.getImageAuthenticationCursor(imageUrl).use { cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val username = cursor.getString(0) val username = cursor.getString(0)
val password = cursor.getString(1) val password = cursor.getString(1)
@ -622,6 +636,7 @@ object DBReader {
credentials = "" credentials = ""
} }
} }
}
return credentials return credentials
} }
@ -696,7 +711,8 @@ object DBReader {
} }
private fun loadChaptersOfFeedItem(adapter: PodDBAdapter?, item: FeedItem): List<Chapter>? { private fun loadChaptersOfFeedItem(adapter: PodDBAdapter?, item: FeedItem): List<Chapter>? {
adapter!!.getSimpleChaptersOfFeedItemCursor(item).use { cursor -> if (adapter != null) {
adapter.getSimpleChaptersOfFeedItemCursor(item).use { cursor ->
val chaptersCount = cursor.count val chaptersCount = cursor.count
if (chaptersCount == 0) { if (chaptersCount == 0) {
item.chapters = null item.chapters = null
@ -709,6 +725,8 @@ object DBReader {
return chapters return chapters
} }
} }
return null
}
/** /**
* Searches the DB for a FeedMedia of the given id. * Searches the DB for a FeedMedia of the given id.
@ -746,15 +764,18 @@ object DBReader {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
try { try {
adapter.getFeedItemCursorByUrl(urls!!).use { itemCursor -> if (urls != null) {
adapter.getFeedItemCursorByUrl(urls).use { itemCursor ->
val items = extractItemlistFromCursor(adapter, itemCursor).toMutableList() val items = extractItemlistFromCursor(adapter, itemCursor).toMutableList()
loadAdditionalFeedItemListData(items) loadAdditionalFeedItemListData(items)
items.sortWith(PlaybackCompletionDateComparator()) items.sortWith(PlaybackCompletionDateComparator())
return items return items
} }
}
} finally { } finally {
adapter.close() adapter.close()
} }
return listOf()
} }
fun getMonthlyTimeStatistics(): List<MonthlyStatisticsItem> { fun getMonthlyTimeStatistics(): List<MonthlyStatisticsItem> {
@ -861,7 +882,7 @@ object DBReader {
// reverse natural order: podcast with most unplayed episodes first // reverse natural order: podcast with most unplayed episodes first
return@Comparator -1 return@Comparator -1
} else if (counterLhs == counterRhs) { } else if (counterLhs == counterRhs) {
return@Comparator lhs.title!!.compareTo(rhs.title!!, ignoreCase = true) return@Comparator lhs.title?.compareTo(rhs.title!!, ignoreCase = true) ?: -1
} else { } else {
return@Comparator 1 return@Comparator 1
} }

View File

@ -91,7 +91,7 @@ import java.util.concurrent.*
media.setDownloaded(false) media.setDownloaded(false)
media.setFile_url(null) media.setFile_url(null)
DBWriter.setFeedMedia(media) DBWriter.setFeedMedia(media)
EventBus.getDefault().post(updated(media.getItem()!!)) if (media.getItem() != null) EventBus.getDefault().post(updated(media.getItem()!!))
EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(context.getString(R.string.error_file_not_found))) EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(context.getString(R.string.error_file_not_found)))
} }
@ -153,7 +153,8 @@ import java.util.concurrent.*
* Get a FeedItem by its identifying value. * Get a FeedItem by its identifying value.
*/ */
private fun searchFeedItemByIdentifyingValue(items: List<FeedItem>?, searchItem: FeedItem): FeedItem? { private fun searchFeedItemByIdentifyingValue(items: List<FeedItem>?, searchItem: FeedItem): FeedItem? {
for (item in items!!) { if (items == null) return null
for (item in items) {
if (TextUtils.equals(item.identifyingValue, searchItem.identifyingValue)) { if (TextUtils.equals(item.identifyingValue, searchItem.identifyingValue)) {
return item return item
} }
@ -166,7 +167,8 @@ import java.util.concurrent.*
* This is to work around podcasters breaking their GUIDs. * This is to work around podcasters breaking their GUIDs.
*/ */
private fun searchFeedItemGuessDuplicate(items: List<FeedItem>?, searchItem: FeedItem): FeedItem? { private fun searchFeedItemGuessDuplicate(items: List<FeedItem>?, searchItem: FeedItem): FeedItem? {
for (item in items!!) { if (items == null) return null
for (item in items) {
if (FeedItemDuplicateGuesser.seemDuplicates(item, searchItem)) { if (FeedItemDuplicateGuesser.seemDuplicates(item, searchItem)) {
return item return item
} }
@ -220,7 +222,7 @@ import java.util.concurrent.*
Log.d(TAG, "New feed has a higher page number.") Log.d(TAG, "New feed has a higher page number.")
savedFeed.nextPageLink = newFeed.nextPageLink savedFeed.nextPageLink = newFeed.nextPageLink
} }
if (savedFeed.preferences!!.compareWithOther(newFeed.preferences)) { if (savedFeed.preferences != null && savedFeed.preferences!!.compareWithOther(newFeed.preferences)) {
Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences") Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences")
savedFeed.preferences!!.updateFromOther(newFeed.preferences) savedFeed.preferences!!.updateFromOther(newFeed.preferences)
} }
@ -240,7 +242,7 @@ import java.util.concurrent.*
if (!newFeed.isLocalFeed && possibleDuplicate != null && item !== possibleDuplicate) { if (!newFeed.isLocalFeed && possibleDuplicate != null && item !== possibleDuplicate) {
// Canonical episode is the first one returned (usually oldest) // Canonical episode is the first one returned (usually oldest)
DBWriter.addDownloadStatus(DownloadResult(savedFeed, DBWriter.addDownloadStatus(DownloadResult(savedFeed,
item.title!!, DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false, item.title?:"", DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false,
""" """
The podcast host appears to have added the same episode twice. Podcini still refreshed the feed and attempted to repair it. The podcast host appears to have added the same episode twice. Podcini still refreshed the feed and attempted to repair it.
@ -259,7 +261,7 @@ import java.util.concurrent.*
if (oldItem != null) { if (oldItem != null) {
Log.d(TAG, "Repaired duplicate: $oldItem, $item") Log.d(TAG, "Repaired duplicate: $oldItem, $item")
DBWriter.addDownloadStatus(DownloadResult(savedFeed, DBWriter.addDownloadStatus(DownloadResult(savedFeed,
item.title!!, DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false, item.title?:"", DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false,
""" """
The podcast host changed the ID of an existing episode instead of just updating the episode itself. Podcini still refreshed the feed and attempted to repair it. The podcast host changed the ID of an existing episode instead of just updating the episode itself. Podcini still refreshed the feed and attempted to repair it.
@ -381,12 +383,14 @@ import java.util.concurrent.*
fun searchFeedItems(feedID: Long, query: String): FutureTask<List<FeedItem>> { fun searchFeedItems(feedID: Long, query: String): FutureTask<List<FeedItem>> {
return FutureTask(object : QueryTask<List<FeedItem>>() { return FutureTask(object : QueryTask<List<FeedItem>>() {
override fun execute(adapter: PodDBAdapter?) { override fun execute(adapter: PodDBAdapter?) {
val searchResult = adapter!!.searchItems(feedID, query) val searchResult = adapter?.searchItems(feedID, query)
if (searchResult != null) {
val items = extractItemlistFromCursor(searchResult) val items = extractItemlistFromCursor(searchResult)
loadAdditionalFeedItemListData(items) loadAdditionalFeedItemListData(items)
setResult(items) setResult(items)
searchResult.close() searchResult.close()
} }
}
}) })
} }
@ -394,7 +398,8 @@ import java.util.concurrent.*
fun searchFeeds(query: String): FutureTask<List<Feed>> { fun searchFeeds(query: String): FutureTask<List<Feed>> {
return FutureTask(object : QueryTask<List<Feed>>() { return FutureTask(object : QueryTask<List<Feed>>() {
override fun execute(adapter: PodDBAdapter?) { override fun execute(adapter: PodDBAdapter?) {
val cursor = adapter!!.searchFeeds(query) if (adapter != null) {
val cursor = adapter.searchFeeds(query)
val items: MutableList<Feed> = ArrayList() val items: MutableList<Feed> = ArrayList()
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
@ -404,6 +409,7 @@ import java.util.concurrent.*
setResult(items) setResult(items)
cursor.close() cursor.close()
} }
}
}) })
} }

View File

@ -93,8 +93,9 @@ import java.util.concurrent.TimeUnit
if (media != null) { if (media != null) {
val result = deleteFeedMediaSynchronous(context, media) val result = deleteFeedMediaSynchronous(context, media)
if (result && shouldDeleteRemoveFromQueue()) { val item = media.getItem()
removeQueueItemSynchronous(context, false, media.getItem()!!.id) if (result && item != null && shouldDeleteRemoveFromQueue()) {
removeQueueItemSynchronous(context, false, item.id)
} }
} }
} }
@ -104,7 +105,8 @@ import java.util.concurrent.TimeUnit
Log.i(TAG, String.format(Locale.US, "Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s", Log.i(TAG, String.format(Locale.US, "Requested to delete FeedMedia [id=%d, title=%s, downloaded=%s",
media.id, media.getEpisodeTitle(), media.isDownloaded())) media.id, media.getEpisodeTitle(), media.isDownloaded()))
var localDelete = false var localDelete = false
if (media.getFile_url() != null && media.getFile_url()!!.startsWith("content://")) { val url = media.getFile_url()
if (url != null && url.startsWith("content://")) {
// Local feed // Local feed
val documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getFile_url())) val documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getFile_url()))
if (documentFile == null || !documentFile.exists() || !documentFile.delete()) { if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
@ -113,9 +115,9 @@ import java.util.concurrent.TimeUnit
} }
media.setFile_url(null) media.setFile_url(null)
localDelete = true localDelete = true
} else if (media.getFile_url() != null) { } else if (url != null) {
// delete downloaded media file // delete downloaded media file
val mediaFile = File(media.getFile_url()!!) val mediaFile = File(url)
if (mediaFile.exists() && !mediaFile.delete()) { if (mediaFile.exists() && !mediaFile.delete()) {
val evt = MessageEvent(context.getString(R.string.delete_failed)) val evt = MessageEvent(context.getString(R.string.delete_failed))
EventBus.getDefault().post(evt) EventBus.getDefault().post(evt)
@ -138,13 +140,13 @@ import java.util.concurrent.TimeUnit
nm.cancel(R.id.notification_playing) nm.cancel(R.id.notification_playing)
} }
if (localDelete) {
// Do full update of this feed to get rid of the item
updateFeed(media.getItem()!!.feed!!, context.applicationContext, null)
} else {
// Gpodder: queue delete action for synchronization
val item = media.getItem() val item = media.getItem()
if (item != null) { if (item != null) {
if (localDelete) {
// Do full update of this feed to get rid of the item
if (item.feed != null) updateFeed(item.feed!!, context.applicationContext, null)
} else {
// Gpodder: queue delete action for synchronization
val action = EpisodeAction.Builder(item, EpisodeAction.DELETE) val action = EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp() .currentTimestamp()
.build() .build()
@ -287,8 +289,9 @@ import java.util.concurrent.TimeUnit
@JvmOverloads @JvmOverloads
fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> { fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> {
return runOnDbThread { return runOnDbThread {
if (media != null) {
Log.d(TAG, "Adding item to playback history") Log.d(TAG, "Adding item to playback history")
media!!.setPlaybackCompletionDate(date) media.setPlaybackCompletionDate(date)
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -297,6 +300,7 @@ import java.util.concurrent.TimeUnit
EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated()) EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated())
} }
} }
}
/** /**
* Adds a Download status object to the download log. * Adds a Download status object to the download log.
@ -305,13 +309,15 @@ import java.util.concurrent.TimeUnit
*/ */
fun addDownloadStatus(status: DownloadResult?): Future<*> { fun addDownloadStatus(status: DownloadResult?): Future<*> {
return runOnDbThread { return runOnDbThread {
if (status != null) {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.setDownloadStatus(status!!) adapter.setDownloadStatus(status)
adapter.close() adapter.close()
EventBus.getDefault().post(DownloadLogEvent.listUpdated()) EventBus.getDefault().post(DownloadLogEvent.listUpdated())
} }
} }
}
/** /**
* Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to
@ -466,9 +472,10 @@ import java.util.concurrent.TimeUnit
// do not shuffle the list on every change // do not shuffle the list on every change
return return
} }
val permutor = getPermutor(sortOrder!!) if (sortOrder != null) {
val permutor = getPermutor(sortOrder)
permutor.reorder(queue) permutor.reorder(queue)
}
// Replace ADDED events by a single SORTED event // Replace ADDED events by a single SORTED event
events.clear() events.clear()
events.add(QueueEvent.sorted(queue)) events.add(QueueEvent.sorted(queue))
@ -610,9 +617,7 @@ import java.util.concurrent.TimeUnit
* @param itemId The item to move to the bottom of the queue * @param itemId The item to move to the bottom of the queue
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/ */
fun moveQueueItemToBottom(itemId: Long, fun moveQueueItemToBottom(itemId: Long, broadcastUpdate: Boolean): Future<*> {
broadcastUpdate: Boolean
): Future<*> {
return runOnDbThread { return runOnDbThread {
val queueIdList = getQueueIDList() val queueIdList = getQueueIDList()
val index = queueIdList.indexOf(itemId) val index = queueIdList.indexOf(itemId)
@ -678,12 +683,14 @@ import java.util.concurrent.TimeUnit
fun resetPagedFeedPage(feed: Feed?): Future<*> { fun resetPagedFeedPage(feed: Feed?): Future<*> {
return runOnDbThread { return runOnDbThread {
if (feed != null) {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.resetPagedFeedPage(feed!!) adapter.resetPagedFeedPage(feed)
adapter.close() adapter.close()
} }
} }
}
/* /*
* Sets the 'read'-attribute of all specified FeedItems * Sets the 'read'-attribute of all specified FeedItems
@ -726,7 +733,7 @@ import java.util.concurrent.TimeUnit
* @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object. * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
*/ */
fun markItemPlayed(item: FeedItem, played: Int, resetMediaPosition: Boolean): Future<*> { fun markItemPlayed(item: FeedItem, played: Int, resetMediaPosition: Boolean): Future<*> {
val mediaId = if ((item.hasMedia())) item.media!!.id else 0 val mediaId = if (item.media != null) item.media!!.id else 0
return markItemPlayed(item.id, played, mediaId, resetMediaPosition) return markItemPlayed(item.id, played, mediaId, resetMediaPosition)
} }
@ -834,12 +841,14 @@ import java.util.concurrent.TimeUnit
@JvmStatic @JvmStatic
fun setFeedMediaPlaybackInformation(media: FeedMedia?): Future<*> { fun setFeedMediaPlaybackInformation(media: FeedMedia?): Future<*> {
return runOnDbThread { return runOnDbThread {
if (media != null) {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.setFeedMediaPlaybackInformation(media!!) adapter.setFeedMediaPlaybackInformation(media)
adapter.close() adapter.close()
} }
} }
}
/** /**
* Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including
@ -850,13 +859,15 @@ import java.util.concurrent.TimeUnit
@JvmStatic @JvmStatic
fun setFeedItem(item: FeedItem?): Future<*> { fun setFeedItem(item: FeedItem?): Future<*> {
return runOnDbThread { return runOnDbThread {
if (item != null) {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.setSingleFeedItem(item!!) adapter.setSingleFeedItem(item)
adapter.close() adapter.close()
EventBus.getDefault().post(updated(item)) EventBus.getDefault().post(updated(item))
} }
} }
}
/** /**
* Updates download URL of a feed * Updates download URL of a feed
@ -893,7 +904,7 @@ import java.util.concurrent.TimeUnit
private fun indexInItemList(items: List<FeedItem?>, itemId: Long): Int { private fun indexInItemList(items: List<FeedItem?>, itemId: Long): Int {
for (i in items.indices) { for (i in items.indices) {
val item = items[i] val item = items[i]
if (item!!.id == itemId) { if (item?.id == itemId) {
return i return i
} }
} }

View File

@ -21,11 +21,15 @@ import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.common.ThemeUtils.getDrawableFromAttr import ac.mdiq.podcini.ui.common.ThemeUtils.getDrawableFromAttr
import ac.mdiq.podcini.ui.dialog.RatingDialog import ac.mdiq.podcini.ui.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri import android.net.Uri
@ -38,6 +42,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import android.widget.EditText import android.widget.EditText
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -54,6 +60,7 @@ import com.bumptech.glide.Glide
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.ArrayUtils
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -107,6 +114,11 @@ class MainActivity : CastEnabledActivity() {
mainView = findViewById(R.id.main_view) mainView = findViewById(R.id.main_view)
if (Build.VERSION.SDK_INT >= 33 && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(this, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
requestPostNotificationPermission()
}
// Consume navigation bar insets - we apply them in setPlayerVisible() // Consume navigation bar insets - we apply them in setPlayerVisible()
ViewCompat.setOnApplyWindowInsetsListener(mainView) { _: View?, insets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(mainView) { _: View?, insets: WindowInsetsCompat ->
navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
@ -219,6 +231,22 @@ class MainActivity : CastEnabledActivity() {
} }
} }
fun requestPostNotificationPermission() {
if (Build.VERSION.SDK_INT >= 33) requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
return@registerForActivityResult
}
MaterialAlertDialogBuilder(this)
.setMessage(R.string.notification_permission_text)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> {} }
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int -> finish() }
.show()
}
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
updateInsets() updateInsets()
@ -331,12 +359,12 @@ class MainActivity : CastEnabledActivity() {
val fragment: Fragment val fragment: Fragment
when (tag) { when (tag) {
QueueFragment.TAG -> fragment = QueueFragment() QueueFragment.TAG -> fragment = QueueFragment()
// InboxFragment.TAG -> fragment = InboxFragment()
AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment() AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
CompletedDownloadsFragment.TAG -> fragment = CompletedDownloadsFragment() CompletedDownloadsFragment.TAG -> fragment = CompletedDownloadsFragment()
PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment() PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment()
AddFeedFragment.TAG -> fragment = AddFeedFragment() AddFeedFragment.TAG -> fragment = AddFeedFragment()
SubscriptionFragment.TAG -> fragment = SubscriptionFragment() SubscriptionFragment.TAG -> fragment = SubscriptionFragment()
StatisticsFragment.TAG -> fragment = StatisticsFragment()
else -> { else -> {
// default to subscriptions screen // default to subscriptions screen
fragment = SubscriptionFragment() fragment = SubscriptionFragment()
@ -451,6 +479,7 @@ class MainActivity : CastEnabledActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
handleNavIntent() handleNavIntent()
RatingDialog.check() RatingDialog.check()
@ -633,6 +662,7 @@ class MainActivity : CastEnabledActivity() {
"EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null) "EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null)
"QUEUE" -> loadFragment(QueueFragment.TAG, null) "QUEUE" -> loadFragment(QueueFragment.TAG, null)
"SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null) "SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null)
"STATISTCS" -> loadFragment(StatisticsFragment.TAG, null)
else -> { else -> {
showSnackbarAbovePlayer(getString(R.string.app_action_not_found)+feature, Snackbar.LENGTH_LONG) showSnackbarAbovePlayer(getString(R.string.app_action_not_found)+feature, Snackbar.LENGTH_LONG)
return return

View File

@ -71,7 +71,30 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
val item: FeedItem = episodes[pos] val item: FeedItem = episodes[pos]
holder.bind(item) holder.bind(item)
holder.itemView.setOnClickListener { holder.coverHolder.setOnCreateContextMenuListener(this)
holder.coverHolder.setOnLongClickListener {
longPressedItem = item
longPressedPosition = holder.bindingAdapterPosition
startSelectMode(longPressedPosition)
false
}
holder.coverHolder.setOnClickListener {
if (inActionMode()) {
toggleSelection(holder.bindingAdapterPosition)
} else {
longPressedItem = item
longPressedPosition = holder.bindingAdapterPosition
it.showContextMenu()
}
}
// holder.infoCard.setOnCreateContextMenuListener(this)
// holder.infoCard.setOnLongClickListener {
// longPressedItem = item
// longPressedPosition = holder.bindingAdapterPosition
// false
// }
holder.infoCard.setOnClickListener {
val activity: MainActivity? = mainActivityRef.get() val activity: MainActivity? = mainActivityRef.get()
if (!inActionMode()) { if (!inActionMode()) {
val ids: LongArray = FeedItemUtil.getIds(episodes) val ids: LongArray = FeedItemUtil.getIds(episodes)
@ -81,12 +104,6 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
toggleSelection(holder.bindingAdapterPosition) toggleSelection(holder.bindingAdapterPosition)
} }
} }
holder.itemView.setOnCreateContextMenuListener(this)
holder.itemView.setOnLongClickListener {
longPressedItem = item
longPressedPosition = holder.bindingAdapterPosition
false
}
holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent -> holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) { if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) {

View File

@ -11,6 +11,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.subscriptionsFilter
import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -96,12 +97,11 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
private fun getDrawable(tag: String?): Int { private fun getDrawable(tag: String?): Int {
return when (tag) { return when (tag) {
QueueFragment.TAG -> R.drawable.ic_playlist_play QueueFragment.TAG -> R.drawable.ic_playlist_play
// InboxFragment.TAG -> R.drawable.ic_inbox
AllEpisodesFragment.TAG -> R.drawable.ic_feed AllEpisodesFragment.TAG -> R.drawable.ic_feed
CompletedDownloadsFragment.TAG -> R.drawable.ic_download CompletedDownloadsFragment.TAG -> R.drawable.ic_download
PlaybackHistoryFragment.TAG -> R.drawable.ic_history PlaybackHistoryFragment.TAG -> R.drawable.ic_history
SubscriptionFragment.TAG -> R.drawable.ic_subscriptions SubscriptionFragment.TAG -> R.drawable.ic_subscriptions
// StatisticsFragment.TAG -> R.drawable.ic_sta StatisticsFragment.TAG -> R.drawable.ic_chart_box
AddFeedFragment.TAG -> R.drawable.ic_add AddFeedFragment.TAG -> R.drawable.ic_add
else -> 0 else -> 0
} }
@ -260,7 +260,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
} }
private fun bindSectionDivider(holder: DividerHolder) { private fun bindSectionDivider(holder: DividerHolder) {
val context = activity.get() ?: return // val context = activity.get() ?: return
if (subscriptionsFilter.isEnabled && showSubscriptionList) { if (subscriptionsFilter.isEnabled && showSubscriptionList) {
holder.itemView.isEnabled = true holder.itemView.isEnabled = true

View File

@ -33,7 +33,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
if (!dragDropEnabled) { if (!dragDropEnabled) {
holder.dragHandle.setVisibility(View.GONE) holder.dragHandle.setVisibility(View.GONE)
holder.dragHandle.setOnTouchListener(null) holder.dragHandle.setOnTouchListener(null)
holder.coverHolder.setOnTouchListener(null) // holder.coverHolder.setOnTouchListener(null)
} else { } else {
holder.dragHandle.setVisibility(View.VISIBLE) holder.dragHandle.setVisibility(View.VISIBLE)
holder.dragHandle.setOnTouchListener { _: View?, event: MotionEvent -> holder.dragHandle.setOnTouchListener { _: View?, event: MotionEvent ->
@ -59,7 +59,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
} }
if (inActionMode()) { if (inActionMode()) {
holder.dragHandle.setOnTouchListener(null) holder.dragHandle.setOnTouchListener(null)
holder.coverHolder.setOnTouchListener(null) // holder.coverHolder.setOnTouchListener(null)
} }
holder.isInQueue.setVisibility(View.GONE) holder.isInQueue.setVisibility(View.GONE)
@ -71,7 +71,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) { if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true) // menu.findItem(R.id.multi_select).setVisible(true)
val keepSorted: Boolean = UserPreferences.isQueueKeepSorted val keepSorted: Boolean = UserPreferences.isQueueKeepSorted
if (getItem(0)?.id === longPressedItem?.id || keepSorted) { if (getItem(0)?.id === longPressedItem?.id || keepSorted) {
menu.findItem(R.id.move_to_top_item).setVisible(false) menu.findItem(R.id.move_to_top_item).setVisible(false)

View File

@ -54,15 +54,13 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
@UnstableApi override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) { @UnstableApi override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
val drawerItem: NavDrawerData.FeedDrawerItem = listItems[position] val drawerItem: NavDrawerData.FeedDrawerItem = listItems[position]
holder.bind(drawerItem) holder.bind(drawerItem)
holder.itemView.setOnCreateContextMenuListener(this)
if (inActionMode()) { if (inActionMode()) {
holder.selectCheckbox.visibility = View.VISIBLE holder.selectCheckbox.visibility = View.VISIBLE
holder.selectView.visibility = View.VISIBLE holder.selectView.visibility = View.VISIBLE
holder.selectCheckbox.setChecked((isSelected(position))) holder.selectCheckbox.setChecked((isSelected(position)))
holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
setSelected(holder.bindingAdapterPosition, setSelected(holder.bindingAdapterPosition, isChecked)
isChecked)
} }
holder.coverImage.alpha = 0.6f holder.coverImage.alpha = 0.6f
holder.count.visibility = View.GONE holder.count.visibility = View.GONE
@ -71,13 +69,30 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
holder.coverImage.alpha = 1.0f holder.coverImage.alpha = 1.0f
} }
holder.itemView.setOnLongClickListener { holder.coverImage.setOnCreateContextMenuListener(this)
if (!inActionMode()) { holder.coverImage.setOnLongClickListener {
longPressedPosition = holder.bindingAdapterPosition longPressedPosition = holder.bindingAdapterPosition
selectedItem = drawerItem selectedItem = drawerItem
} startSelectMode(longPressedPosition)
false false
} }
holder.coverImage.setOnClickListener {
if (inActionMode()) {
holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
} else {
longPressedPosition = holder.bindingAdapterPosition
selectedItem = drawerItem
it.showContextMenu()
}
}
// holder.infoCard.setOnCreateContextMenuListener(this)
// holder.infoCard.setOnLongClickListener {
// if (!inActionMode()) {
// longPressedPosition = holder.bindingAdapterPosition
// selectedItem = drawerItem
// }
// false
// }
holder.itemView.setOnTouchListener { _: View?, e: MotionEvent -> holder.itemView.setOnTouchListener { _: View?, e: MotionEvent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -95,8 +110,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
if (inActionMode()) { if (inActionMode()) {
holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
} else { } else {
val fragment: Fragment = FeedItemlistFragment val fragment: Fragment = FeedItemlistFragment.newInstance(drawerItem.feed.id)
.newInstance(drawerItem.feed.id)
mainActivityRef.get()?.loadChildFragment(fragment) mainActivityRef.get()?.loadChildFragment(fragment)
} }
} }
@ -114,14 +128,19 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
} }
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
if (inActionMode() || selectedItem == null) { if (selectedItem == null) {
return return
} }
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
if (inActionMode()) {
inflater.inflate(R.menu.multi_select_context_popup, menu)
// menu.findItem(R.id.multi_select).setVisible(true)
} else {
inflater.inflate(R.menu.nav_feed_context, menu) inflater.inflate(R.menu.nav_feed_context, menu)
menu.findItem(R.id.multi_select).setVisible(true) // menu.findItem(R.id.multi_select).setVisible(true)
menu.setHeaderTitle(selectedItem?.title) menu.setHeaderTitle(selectedItem?.title)
} }
}
fun onContextItemSelected(item: MenuItem): Boolean { fun onContextItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.multi_select) { if (item.itemId == R.id.multi_select) {
@ -156,6 +175,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
val count: TextView = binding.countLabel val count: TextView = binding.countLabel
val coverImage: ImageView = binding.coverImage val coverImage: ImageView = binding.coverImage
val infoCard: LinearLayout = binding.infoCard
val selectView: FrameLayout = binding.selectContainer val selectView: FrameLayout = binding.selectContainer
val selectCheckbox: CheckBox = binding.selectCheckBox val selectCheckbox: CheckBox = binding.selectCheckBox
private val card: CardView = binding.outerContainer private val card: CardView = binding.outerContainer

View File

@ -37,14 +37,6 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
var forFragment = "" var forFragment = ""
when (tag) { when (tag) {
// InboxFragment.TAG -> {
// forFragment = context.getString(R.string.inbox_label)
// keys = Stream.of(keys).filter { a: SwipeAction ->
// (!a.getId().equals(SwipeAction.TOGGLE_PLAYED)
// && !a.getId().equals(SwipeAction.DELETE)
// && !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY))
// }.toList()
// }
AllEpisodesFragment.TAG -> { AllEpisodesFragment.TAG -> {
forFragment = context.getString(R.string.episodes_label) forFragment = context.getString(R.string.episodes_label)
keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY) }.toList() keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY) }.toList()
@ -52,8 +44,7 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
CompletedDownloadsFragment.TAG -> { CompletedDownloadsFragment.TAG -> {
forFragment = context.getString(R.string.downloads_label) forFragment = context.getString(R.string.downloads_label)
keys = Stream.of(keys).filter { a: SwipeAction -> keys = Stream.of(keys).filter { a: SwipeAction ->
(!a.getId().equals(SwipeAction.REMOVE_FROM_INBOX) (!a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)
&& !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)
&& !a.getId().equals(SwipeAction.START_DOWNLOAD)) }.toList() && !a.getId().equals(SwipeAction.START_DOWNLOAD)) }.toList()
} }
FeedItemlistFragment.TAG -> { FeedItemlistFragment.TAG -> {
@ -64,12 +55,11 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
forFragment = context.getString(R.string.queue_label) forFragment = context.getString(R.string.queue_label)
keys = Stream.of(keys).filter { a: SwipeAction -> keys = Stream.of(keys).filter { a: SwipeAction ->
(!a.getId().equals(SwipeAction.ADD_TO_QUEUE) (!a.getId().equals(SwipeAction.ADD_TO_QUEUE)
&& !a.getId().equals(SwipeAction.REMOVE_FROM_INBOX)
&& !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)) }.toList() && !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)) }.toList()
} }
PlaybackHistoryFragment.TAG -> { PlaybackHistoryFragment.TAG -> {
forFragment = context.getString(R.string.playback_history_label) forFragment = context.getString(R.string.playback_history_label)
keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_INBOX) }.toList() keys = Stream.of(keys).toList()
} }
else -> {} else -> {}
} }
@ -169,7 +159,6 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
view.container.alpha = 0.3f view.container.alpha = 0.3f
view.secondaryActionButton.secondaryAction.visibility = View.GONE view.secondaryActionButton.secondaryAction.visibility = View.GONE
view.dragHandle.visibility = View.GONE view.dragHandle.visibility = View.GONE
view.statusInbox.visibility = View.GONE
view.txtvTitle.text = "███████" view.txtvTitle.text = "███████"
view.txtvPosition.text = "█████" view.txtvPosition.text = "█████"
} }

View File

@ -113,7 +113,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
speedDialView.removeActionItemById(R.id.mark_read_batch) speedDialView.removeActionItemById(R.id.mark_read_batch)
speedDialView.removeActionItemById(R.id.mark_unread_batch) speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.remove_from_queue_batch) speedDialView.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView.removeActionItemById(R.id.remove_all_inbox_item) // speedDialView.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean { override fun onMainActionSelected(): Boolean {
return false return false
@ -343,9 +343,9 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) { // if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true) // menu.findItem(R.id.multi_select).setVisible(true)
} // }
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
this@CompletedDownloadsFragment.onContextItemSelected(item) this@CompletedDownloadsFragment.onContextItemSelected(item)
} }

View File

@ -124,9 +124,8 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
val viewBinding = EpisodesListFragmentBinding.inflate(inflater) val viewBinding = EpisodesListFragmentBinding.inflate(inflater)
// val root: View = inflater.inflate(R.layout.episodes_list_fragment, container, false)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
txtvInformation = viewBinding.txtvInformation txtvInformation = viewBinding.txtvInformation
toolbar = viewBinding.toolbar toolbar = viewBinding.toolbar
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
@ -163,9 +162,9 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
listAdapter = object : EpisodeItemListAdapter(activity as MainActivity) { listAdapter = object : EpisodeItemListAdapter(activity as MainActivity) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) { // if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true) // menu.findItem(R.id.multi_select).setVisible(true)
} // }
MenuItemUtils.setOnClickListeners(menu MenuItemUtils.setOnClickListeners(menu
) { item: MenuItem -> ) { item: MenuItem ->
this@EpisodesListFragment.onContextItemSelected(item) this@EpisodesListFragment.onContextItemSelected(item)
@ -231,8 +230,7 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
} }
@UnstableApi private fun performMultiSelectAction(actionItemId: Int) { @UnstableApi private fun performMultiSelectAction(actionItemId: Int) {
val handler = val handler = EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId)
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId)
Completable.fromAction { Completable.fromAction {
handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>()) handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>())
if (listAdapter.shouldSelectLazyLoadedItems()) { if (listAdapter.shouldSelectLazyLoadedItems()) {

View File

@ -362,7 +362,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed != null && feed!!.isLocalFeed) { if (feed != null && feed!!.isLocalFeed) {
speedDialBinding.fabSD.removeActionItemById(R.id.download_batch) speedDialBinding.fabSD.removeActionItemById(R.id.download_batch)
} }
speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item) // speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item)
speedDialBinding.fabSD.visibility = View.VISIBLE speedDialBinding.fabSD.visibility = View.VISIBLE
updateToolbar() updateToolbar()
} }
@ -431,12 +431,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed != null && feed!!.itemFilter != null) { if (feed != null && feed!!.itemFilter != null) {
val filter: FeedItemFilter? = feed!!.itemFilter val filter: FeedItemFilter? = feed!!.itemFilter
if (filter != null && filter.values.isNotEmpty()) { if (filter != null && filter.values.isNotEmpty()) {
viewBinding.header.txtvInformation.text = ("{md-info-outline} " viewBinding.header.txtvInformation.text = ("{md-info-outline} " + this.getString(R.string.filtered_label))
+ this.getString(R.string.filtered_label))
Iconify.addIcons(viewBinding.header.txtvInformation) Iconify.addIcons(viewBinding.header.txtvInformation)
viewBinding.header.txtvInformation.setOnClickListener { viewBinding.header.txtvInformation.setOnClickListener {
FeedItemFilterDialog.newInstance(feed!!).show( FeedItemFilterDialog.newInstance(feed!!).show(childFragmentManager, null)
childFragmentManager, null)
} }
viewBinding.header.txtvInformation.visibility = View.VISIBLE viewBinding.header.txtvInformation.visibility = View.VISIBLE
} else { } else {
@ -571,14 +569,14 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
private inner class FeedItemListAdapter(mainActivity: MainActivity) : EpisodeItemListAdapter(mainActivity) { private inner class FeedItemListAdapter(mainActivity: MainActivity) : EpisodeItemListAdapter(mainActivity) {
@UnstableApi override fun beforeBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) { @UnstableApi override fun beforeBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
holder.coverHolder.visibility = View.GONE // holder.coverHolder.visibility = View.GONE
} }
@UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { @UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) { // if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true) // menu.findItem(R.id.multi_select).setVisible(true)
} // }
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
this@FeedItemlistFragment.onContextItemSelected(item) this@FeedItemlistFragment.onContextItemSelected(item)
} }

View File

@ -15,6 +15,7 @@ import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.common.ThemeUtils import ac.mdiq.podcini.ui.common.ThemeUtils
import ac.mdiq.podcini.ui.dialog.* import ac.mdiq.podcini.ui.dialog.*
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import android.R.attr import android.R.attr
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@ -159,18 +160,18 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
@OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean { @OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean {
val itemId = item.itemId val itemId = item.itemId
when (itemId) { when (itemId) {
R.id.remove_all_inbox_item -> { // R.id.remove_all_inbox_item -> {
val removeAllNewFlagsConfirmationDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(), // val removeAllNewFlagsConfirmationDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(),
R.string.remove_all_inbox_label, // R.string.remove_all_inbox_label,
R.string.remove_all_inbox_confirmation_msg) { // R.string.remove_all_inbox_confirmation_msg) {
@OptIn(UnstableApi::class) override fun onConfirmButtonPressed(dialog: DialogInterface) { // @OptIn(UnstableApi::class) override fun onConfirmButtonPressed(dialog: DialogInterface) {
dialog.dismiss() // dialog.dismiss()
DBWriter.removeFeedNewFlag(feed.id) // DBWriter.removeFeedNewFlag(feed.id)
} // }
} // }
removeAllNewFlagsConfirmationDialog.createNewDialog().show() // removeAllNewFlagsConfirmationDialog.createNewDialog().show()
return true // return true
} // }
R.id.edit_tags -> { R.id.edit_tags -> {
if (feed.preferences != null) if (feed.preferences != null)
TagSettingsDialog.newInstance(listOf(feed.preferences!!)).show(childFragmentManager, TagSettingsDialog.TAG) TagSettingsDialog.newInstance(listOf(feed.preferences!!)).show(childFragmentManager, TagSettingsDialog.TAG)
@ -378,9 +379,8 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
QueueFragment.TAG, QueueFragment.TAG,
AllEpisodesFragment.TAG, AllEpisodesFragment.TAG,
CompletedDownloadsFragment.TAG, CompletedDownloadsFragment.TAG,
// InboxFragment.TAG,
PlaybackHistoryFragment.TAG, PlaybackHistoryFragment.TAG,
// StatisticsFragment.TAG, StatisticsFragment.TAG,
AddFeedFragment.TAG, AddFeedFragment.TAG,
) )

View File

@ -62,6 +62,7 @@ import java.util.*
* Shows all items in the queue. * Shows all items in the queue.
*/ */
class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener { class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
private lateinit var infoBar: TextView private lateinit var infoBar: TextView
private lateinit var recyclerView: EpisodeItemListRecyclerView private lateinit var recyclerView: EpisodeItemListRecyclerView
private lateinit var emptyView: EmptyViewHandler private lateinit var emptyView: EmptyViewHandler
@ -87,7 +88,6 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
val binding = QueueFragmentBinding.inflate(inflater) val binding = QueueFragmentBinding.inflate(inflater)
// val root: View = inflater.inflate(R.layout.queue_fragment, container, false)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
toolbar = binding.toolbar toolbar = binding.toolbar
@ -151,7 +151,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
// speedDialView.removeActionItemById(R.id.mark_read_batch) // speedDialView.removeActionItemById(R.id.mark_read_batch)
// speedDialView.removeActionItemById(R.id.mark_unread_batch) // speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.add_to_queue_batch) speedDialView.removeActionItemById(R.id.add_to_queue_batch)
speedDialView.removeActionItemById(R.id.remove_all_inbox_item) // speedDialView.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean { override fun onMainActionSelected(): Boolean {
return false return false

View File

@ -16,7 +16,6 @@ import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog
import ac.mdiq.podcini.ui.fragment.actions.FeedMultiSelectActionHandler import ac.mdiq.podcini.ui.fragment.actions.FeedMultiSelectActionHandler
import ac.mdiq.podcini.ui.menuhandler.FeedMenuHandler import ac.mdiq.podcini.ui.menuhandler.FeedMenuHandler
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.util.event.FeedListUpdateEvent import ac.mdiq.podcini.util.event.FeedListUpdateEvent
@ -245,24 +244,20 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean { @UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
val itemId = item.itemId val itemId = item.itemId
when (itemId) { when (itemId) {
R.id.refresh_item -> { R.id.action_search -> {
FeedUpdateManager.runOnceOrAsk(requireContext()) (activity as MainActivity).loadChildFragment(SearchFragment.newInstance())
return true
}
R.id.subscriptions_filter -> {
SubscriptionsFilterDialog().show(childFragmentManager, "filter")
return true return true
} }
R.id.subscriptions_sort -> { R.id.subscriptions_sort -> {
FeedSortDialog.showDialog(requireContext()) FeedSortDialog.showDialog(requireContext())
return true return true
} }
R.id.action_search -> { R.id.subscriptions_filter -> {
(activity as MainActivity).loadChildFragment(SearchFragment.newInstance()) SubscriptionsFilterDialog().show(childFragmentManager, "filter")
return true return true
} }
R.id.action_statistics -> { R.id.refresh_item -> {
(activity as MainActivity).loadChildFragment(StatisticsFragment()) FeedUpdateManager.runOnceOrAsk(requireContext())
return true return true
} }
else -> return false else -> return false
@ -344,6 +339,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
} }
override fun onStartSelectMode() { override fun onStartSelectMode() {
speedDialView.visibility = View.VISIBLE
val feedsOnly: MutableList<NavDrawerData.FeedDrawerItem> = ArrayList<NavDrawerData.FeedDrawerItem>() val feedsOnly: MutableList<NavDrawerData.FeedDrawerItem> = ArrayList<NavDrawerData.FeedDrawerItem>()
for (item in feedListFiltered) { for (item in feedListFiltered) {
feedsOnly.add(item) feedsOnly.add(item)

View File

@ -63,7 +63,8 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() {
companion object { companion object {
private const val PREF_SWIPE_QUEUE = "prefSwipeQueue" private const val PREF_SWIPE_QUEUE = "prefSwipeQueue"
private const val PREF_SWIPE_INBOX = "prefSwipeInbox" // private const val PREF_SWIPE_INBOX = "prefSwipeInbox"
// private const val PREF_SWIPE_STATISTICS = "prefSwipeStatistics"
private const val PREF_SWIPE_EPISODES = "prefSwipeEpisodes" private const val PREF_SWIPE_EPISODES = "prefSwipeEpisodes"
private const val PREF_SWIPE_DOWNLOADS = "prefSwipeDownloads" private const val PREF_SWIPE_DOWNLOADS = "prefSwipeDownloads"
private const val PREF_SWIPE_FEED = "prefSwipeFeed" private const val PREF_SWIPE_FEED = "prefSwipeFeed"

View File

@ -1,36 +0,0 @@
package ac.mdiq.podcini.ui.fragment.swipeactions
import android.content.Context
import androidx.fragment.app.Fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler.markReadWithUndo
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
class RemoveFromInboxSwipeAction : SwipeAction {
override fun getId(): String {
return SwipeAction.REMOVE_FROM_INBOX
}
override fun getActionIcon(): Int {
return R.drawable.ic_check
}
override fun getActionColor(): Int {
return R.attr.icon_purple
}
override fun getTitle(context: Context): String {
return context.getString(R.string.remove_inbox_label)
}
override fun performAction(item: FeedItem, fragment: Fragment, filter: FeedItemFilter) {
if (item.isNew) {
markReadWithUndo(fragment, item, FeedItem.UNPLAYED, willRemove(filter, item))
}
}
override fun willRemove(filter: FeedItemFilter, item: FeedItem): Boolean {
return filter.showNew
}
}

View File

@ -25,7 +25,7 @@ interface SwipeAction {
companion object { companion object {
const val ADD_TO_QUEUE: String = "ADD_TO_QUEUE" const val ADD_TO_QUEUE: String = "ADD_TO_QUEUE"
const val REMOVE_FROM_INBOX: String = "REMOVE_FROM_INBOX" // const val REMOVE_FROM_INBOX: String = "REMOVE_FROM_INBOX"
const val START_DOWNLOAD: String = "START_DOWNLOAD" const val START_DOWNLOAD: String = "START_DOWNLOAD"
const val MARK_FAV: String = "MARK_FAV" const val MARK_FAV: String = "MARK_FAV"
const val TOGGLE_PLAYED: String = "MARK_PLAYED" const val TOGGLE_PLAYED: String = "MARK_PLAYED"

View File

@ -213,8 +213,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
@JvmField @JvmField
val swipeActions: List<SwipeAction> = Collections.unmodifiableList( val swipeActions: List<SwipeAction> = Collections.unmodifiableList(
listOf(AddToQueueSwipeAction(), RemoveFromInboxSwipeAction(), listOf(AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(),
StartDownloadSwipeAction(), MarkFavoriteSwipeAction(),
TogglePlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(), TogglePlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(),
DeleteSwipeAction(), RemoveFromHistorySwipeAction()) DeleteSwipeAction(), RemoveFromHistorySwipeAction())
) )

View File

@ -34,6 +34,7 @@ object NotificationUtils {
createChannelError(context), createChannelError(context),
createChannelSyncError(context), createChannelSyncError(context),
createChannelEpisodeNotification(context)) createChannelEpisodeNotification(context))
mNotificationManager.createNotificationChannelsCompat(channels) mNotificationManager.createNotificationChannelsCompat(channels)
} }

View File

@ -33,20 +33,20 @@ object FeedMenuHandler {
val context = fragment.requireContext() val context = fragment.requireContext()
if (menuItemId == R.id.rename_folder_item) { if (menuItemId == R.id.rename_folder_item) {
RenameItemDialog(fragment.activity as Activity, selectedFeed).show() RenameItemDialog(fragment.activity as Activity, selectedFeed).show()
} else if (menuItemId == R.id.remove_all_inbox_item) { // } else if (menuItemId == R.id.remove_all_inbox_item) {
val dialog: ConfirmationDialog = object : ConfirmationDialog(fragment.activity as Activity, // val dialog: ConfirmationDialog = object : ConfirmationDialog(fragment.activity as Activity,
R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) { // R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) {
@OptIn(UnstableApi::class) @SuppressLint("CheckResult") // @OptIn(UnstableApi::class) @SuppressLint("CheckResult")
override fun onConfirmButtonPressed(clickedDialog: DialogInterface) { // override fun onConfirmButtonPressed(clickedDialog: DialogInterface) {
clickedDialog.dismiss() // clickedDialog.dismiss()
Observable.fromCallable(Callable { DBWriter.removeFeedNewFlag(selectedFeed.id) } as Callable<Future<*>>) // Observable.fromCallable(Callable { DBWriter.removeFeedNewFlag(selectedFeed.id) } as Callable<Future<*>>)
.subscribeOn(Schedulers.io()) // .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) // .observeOn(AndroidSchedulers.mainThread())
.subscribe({ callback.run() }, // .subscribe({ callback.run() },
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) // { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
} // }
} // }
dialog.createNewDialog().show() // dialog.createNewDialog().show()
} else if (menuItemId == R.id.edit_tags) { } else if (menuItemId == R.id.edit_tags) {
if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed.preferences!!)) if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed.preferences!!))
.show(fragment.childFragmentManager, TagSettingsDialog.TAG) .show(fragment.childFragmentManager, TagSettingsDialog.TAG)

View File

@ -4,6 +4,7 @@ package ac.mdiq.podcini.ui.statistics
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.PagerFragmentBinding import ac.mdiq.podcini.databinding.PagerFragmentBinding
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.common.PagedToolbarFragment import ac.mdiq.podcini.ui.common.PagedToolbarFragment
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment
@ -17,6 +18,7 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
@ -33,29 +35,29 @@ import org.greenrobot.eventbus.EventBus
* Displays the 'statistics' screen * Displays the 'statistics' screen
*/ */
class StatisticsFragment : PagedToolbarFragment() { class StatisticsFragment : PagedToolbarFragment() {
private var tabLayout: TabLayout? = null private lateinit var tabLayout: TabLayout
private var viewPager: ViewPager2? = null private lateinit var viewPager: ViewPager2
private var toolbar: MaterialToolbar? = null private lateinit var toolbar: MaterialToolbar
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
val binding = PagerFragmentBinding.inflate(inflater) val binding = PagerFragmentBinding.inflate(inflater)
// val rootView = inflater.inflate(R.layout.pager_fragment, container, false)
viewPager = binding.viewpager viewPager = binding.viewpager
toolbar = binding.toolbar toolbar = binding.toolbar
toolbar?.title = getString(R.string.statistics_label) toolbar.title = getString(R.string.statistics_label)
toolbar?.inflateMenu(R.menu.statistics) toolbar.inflateMenu(R.menu.statistics)
toolbar?.setNavigationOnClickListener { parentFragmentManager.popBackStack() } toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
viewPager?.adapter = StatisticsPagerAdapter(this) (activity as MainActivity).setupToolbarToggle(toolbar, false)
viewPager.adapter = StatisticsPagerAdapter(this)
// Give the TabLayout the ViewPager // Give the TabLayout the ViewPager
tabLayout = binding.slidingTabs tabLayout = binding.slidingTabs
if (toolbar != null && viewPager != null) super.setupPagedToolbar(toolbar!!, viewPager!!) super.setupPagedToolbar(toolbar, viewPager)
if (tabLayout == null || viewPager == null) return null
TabLayoutMediator(tabLayout!!, viewPager!!) { tab: TabLayout.Tab, position: Int -> TabLayoutMediator(tabLayout, viewPager) { tab: TabLayout.Tab, position: Int ->
when (position) { when (position) {
POS_SUBSCRIPTIONS -> tab.setText(R.string.subscriptions_label) POS_SUBSCRIPTIONS -> tab.setText(R.string.subscriptions_label)
POS_YEARS -> tab.setText(R.string.years_statistics_label) POS_YEARS -> tab.setText(R.string.years_statistics_label)

View File

@ -34,6 +34,8 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.ui.adapter.actionbutton.ItemActionButton import ac.mdiq.podcini.ui.adapter.actionbutton.ItemActionButton
import ac.mdiq.podcini.ui.common.CircularProgressBar import ac.mdiq.podcini.ui.common.CircularProgressBar
import ac.mdiq.podcini.ui.common.ThemeUtils import ac.mdiq.podcini.ui.common.ThemeUtils
import ac.mdiq.podcini.util.Converter
import android.widget.LinearLayout
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
import kotlin.math.max import kotlin.math.max
@ -56,7 +58,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
private val position: TextView private val position: TextView
private val duration: TextView private val duration: TextView
private val size: TextView private val size: TextView
val isInbox: ImageView
@JvmField @JvmField
val isInQueue: ImageView val isInQueue: ImageView
private val isVideo: ImageView private val isVideo: ImageView
@ -71,6 +72,8 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
private val leftPadding: View private val leftPadding: View
@JvmField @JvmField
val coverHolder: CardView val coverHolder: CardView
@JvmField
val infoCard: LinearLayout
private var item: FeedItem? = null private var item: FeedItem? = null
@ -84,7 +87,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
progressBar = binding.progressBar progressBar = binding.progressBar
isInQueue = binding.ivInPlaylist isInQueue = binding.ivInPlaylist
isVideo = binding.ivIsVideo isVideo = binding.ivIsVideo
isInbox = binding.statusInbox
isFavorite = binding.isFavorite isFavorite = binding.isFavorite
size = binding.size size = binding.size
separatorIcons = binding.separatorIcons separatorIcons = binding.separatorIcons
@ -92,6 +94,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
secondaryActionButton = binding.secondaryActionButton.root secondaryActionButton = binding.secondaryActionButton.root
secondaryActionIcon = binding.secondaryActionButton.secondaryActionIcon secondaryActionIcon = binding.secondaryActionButton.secondaryActionIcon
coverHolder = binding.coverHolder coverHolder = binding.coverHolder
infoCard = binding.infoCard
leftPadding = binding.leftPadding leftPadding = binding.leftPadding
itemView.tag = this itemView.tag = this
} }
@ -107,7 +110,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
} }
pubDate.text = DateFormatter.formatAbbrev(activity, item.getPubDate()) pubDate.text = DateFormatter.formatAbbrev(activity, item.getPubDate())
pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate())) pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate()))
isInbox.visibility = if (item.isNew) View.VISIBLE else View.GONE
isFavorite.visibility = if (item.isTagged(FeedItem.TAG_FAVORITE)) View.VISIBLE else View.GONE isFavorite.visibility = if (item.isTagged(FeedItem.TAG_FAVORITE)) View.VISIBLE else View.GONE
isInQueue.visibility = if (item.isTagged(FeedItem.TAG_QUEUE)) View.VISIBLE else View.GONE isInQueue.visibility = if (item.isTagged(FeedItem.TAG_QUEUE)) View.VISIBLE else View.GONE
container.alpha = if (item.isPlayed()) 0.5f else 1.0f container.alpha = if (item.isPlayed()) 0.5f else 1.0f
@ -210,7 +212,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
item = FeedItem() item = FeedItem()
container.alpha = 0.1f container.alpha = 0.1f
secondaryActionIcon.setImageDrawable(null) secondaryActionIcon.setImageDrawable(null)
isInbox.visibility = View.VISIBLE
isVideo.visibility = View.GONE isVideo.visibility = View.GONE
isFavorite.visibility = View.GONE isFavorite.visibility = View.GONE
isInQueue.visibility = View.GONE isInQueue.visibility = View.GONE
@ -235,9 +236,10 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
} }
private fun updateDuration(event: PlaybackPositionEvent) { private fun updateDuration(event: PlaybackPositionEvent) {
if (feedItem?.media != null) { val media = feedItem?.media
feedItem!!.media!!.setPosition(event.position) if (media != null) {
feedItem!!.media!!.setDuration(event.duration) media.setPosition(event.position)
media.setDuration(event.duration)
} }
val currentPosition: Int = event.position val currentPosition: Int = event.position
val timeDuration: Int = event.duration val timeDuration: Int = event.duration
@ -248,9 +250,9 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
return return
} }
if (UserPreferences.shouldShowRemainingTime()) { if (UserPreferences.shouldShowRemainingTime()) {
duration.text = (if (remainingTime > 0) "-" else "") + ac.mdiq.podcini.util.Converter.getDurationStringLong(remainingTime) duration.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime)
} else { } else {
duration.text = ac.mdiq.podcini.util.Converter.getDurationStringLong(timeDuration) duration.text = Converter.getDurationStringLong(timeDuration)
} }
} }
@ -271,11 +273,9 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
* Hides the separator dot between icons and text if there are no icons. * Hides the separator dot between icons and text if there are no icons.
*/ */
fun hideSeparatorIfNecessary() { fun hideSeparatorIfNecessary() {
val hasIcons = isInbox.visibility == View.VISIBLE || val hasIcons = isInQueue.visibility == View.VISIBLE ||
isInQueue.visibility == View.VISIBLE ||
isVideo.visibility == View.VISIBLE || isVideo.visibility == View.VISIBLE ||
isFavorite.visibility == View.VISIBLE || isFavorite.visibility == View.VISIBLE
isInbox.visibility == View.VISIBLE
separatorIcons.visibility = if (hasIcons) View.VISIBLE else View.GONE separatorIcons.visibility = if (hasIcons) View.VISIBLE else View.GONE
} }

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3,9l4,-4l4,4m-4,-4v14"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/colorPrimary"
android:strokeLineCap="round"/>
<path
android:pathData="M21,15l-4,4l-4,-4m4,4v-14"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/colorPrimary"
android:strokeLineCap="round"/>
</vector>

View File

@ -3,5 +3,6 @@
android:width="24dp" android:width="24dp"
android:viewportHeight="24" android:viewportHeight="24"
android:viewportWidth="24"> android:viewportWidth="24">
<path android:fillColor="?attr/action_icon_color" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/> <path android:fillColor="?attr/action_icon_color"
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector> </vector>

View File

@ -3,5 +3,6 @@
android:height="48dp" android:height="48dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path android:fillColor="?attr/action_icon_color" android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/> <path android:fillColor="?attr/action_icon_color"
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
</vector> </vector>

View File

@ -3,5 +3,6 @@
android:height="48dp" android:height="48dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path android:fillColor="#ffffff" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/> <path android:fillColor="#ffffff"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector> </vector>

View File

@ -3,5 +3,6 @@
android:height="48dp" android:height="48dp"
android:viewportHeight="24.0" android:viewportHeight="24.0"
android:viewportWidth="24.0"> android:viewportWidth="24.0">
<path android:fillColor="#ffffff" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/> <path android:fillColor="#ffffff"
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
</vector> </vector>

View File

@ -75,7 +75,6 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
tools:text="1:06:29" /> tools:text="1:06:29" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<LinearLayout <LinearLayout

View File

@ -17,8 +17,9 @@
android:gravity="start" android:gravity="start"
android:text="Title" android:text="Title"
android:textStyle="bold" android:textStyle="bold"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="15sp"/> android:textSize="13sp"/>
<ac.mdiq.podcini.ui.view.ChapterSeekBar <ac.mdiq.podcini.ui.view.ChapterSeekBar
android:id="@+id/sbPosition" android:id="@+id/sbPosition"
@ -97,7 +98,8 @@
android:padding="8dp" android:padding="8dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_play_48dp" app:srcCompat="@drawable/ic_play_48dp"
tools:srcCompat="@drawable/ic_play_48dp" /> tools:srcCompat="@drawable/ic_play_48dp"
app:tint="@color/medium_gray"/>
<ImageButton <ImageButton
android:id="@+id/butRev" android:id="@+id/butRev"
@ -112,7 +114,8 @@
android:contentDescription="@string/rewind_label" android:contentDescription="@string/rewind_label"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fast_rewind" app:srcCompat="@drawable/ic_fast_rewind"
tools:srcCompat="@drawable/ic_fast_rewind" /> tools:srcCompat="@drawable/ic_fast_rewind"
app:tint="@color/medium_gray"/>
<TextView <TextView
android:id="@+id/txtvRev" android:id="@+id/txtvRev"
@ -140,8 +143,8 @@
android:layout_toLeftOf="@id/butRev" android:layout_toLeftOf="@id/butRev"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/playback_speed" android:contentDescription="@string/playback_speed"
app:foregroundColor="?attr/action_icon_color" app:foregroundColor="@color/medium_gray"
tools:srcCompat="@drawable/ic_playback_speed" /> tools:srcCompat="@drawable/ic_playback_speed"/>
<TextView <TextView
android:id="@+id/txtvPlaybackSpeed" android:id="@+id/txtvPlaybackSpeed"
@ -171,7 +174,8 @@
android:contentDescription="@string/fast_forward_label" android:contentDescription="@string/fast_forward_label"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_fast_forward" app:srcCompat="@drawable/ic_fast_forward"
tools:srcCompat="@drawable/ic_fast_forward" /> tools:srcCompat="@drawable/ic_fast_forward"
app:tint="@color/medium_gray"/>
<TextView <TextView
android:id="@+id/txtvFF" android:id="@+id/txtvFF"
@ -201,7 +205,8 @@
android:contentDescription="@string/skip_episode_label" android:contentDescription="@string/skip_episode_label"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_skip_48dp" app:srcCompat="@drawable/ic_skip_48dp"
tools:srcCompat="@drawable/ic_skip_48dp" /> tools:srcCompat="@drawable/ic_skip_48dp"
app:tint="@color/medium_gray"/>
</RelativeLayout> </RelativeLayout>

View File

@ -87,6 +87,7 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<LinearLayout <LinearLayout
android:id="@+id/info_card"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
@ -104,13 +105,6 @@
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical">
<ImageView
android:id="@+id/statusInbox"
android:layout_width="14sp"
android:layout_height="14sp"
android:contentDescription="@string/is_inbox_label"
app:srcCompat="@drawable/ic_inbox" />
<ImageView <ImageView
android:id="@+id/ivIsVideo" android:id="@+id/ivIsVideo"
android:layout_width="14sp" android:layout_width="14sp"

View File

@ -34,6 +34,7 @@
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<LinearLayout <LinearLayout
android:id="@+id/info_card"
android:layout_width="0.dp" android:layout_width="0.dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
custom:showAsAction="always"
android:title="@string/search_label"/>
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
<item
android:id="@+id/homesettings_items"
android:icon="@drawable/ic_settings"
android:menuCategory="container"
android:title="@string/configure_home"
custom:showAsAction="never"/>
</menu>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
custom:showAsAction="always"
android:title="@string/search_label"/>
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
<item
android:id="@+id/inbox_sort"
android:title="@string/sort" />
<item
android:id="@+id/remove_all_inbox_item"
android:title="@string/remove_all_inbox_label"
android:menuCategory="container"
custom:showAsAction="collapseActionView"
android:icon="@drawable/ic_check"/>
</menu>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <!-- <item-->
android:id="@+id/remove_all_inbox_item" <!-- android:id="@+id/remove_all_inbox_item"-->
android:menuCategory="container" <!-- android:menuCategory="container"-->
android:title="@string/remove_all_inbox_label" /> <!-- android:title="@string/remove_all_inbox_label" />-->
<item <item
android:id="@+id/edit_tags" android:id="@+id/edit_tags"

View File

@ -6,10 +6,16 @@
android:icon="@drawable/ic_search" android:icon="@drawable/ic_search"
custom:showAsAction="always" custom:showAsAction="always"
android:title="@string/search_label"/> android:title="@string/search_label"/>
<item
android:id="@+id/subscriptions_sort"
android:title="@string/sort"
android:icon="@drawable/arrows_sort"
custom:showAsAction="always" />
<item <item
android:id="@+id/action_statistics" android:id="@+id/action_statistics"
android:icon="@drawable/ic_chart_box" android:icon="@drawable/ic_chart_box"
android:title="@string/statistics_label" android:title="@string/statistics_label"
android:visible="false"
custom:showAsAction="always" /> custom:showAsAction="always" />
<item <item
android:id="@+id/refresh_item" android:id="@+id/refresh_item"
@ -19,9 +25,6 @@
<item <item
android:id="@+id/subscriptions_filter" android:id="@+id/subscriptions_filter"
android:title="@string/filter" android:title="@string/filter"
custom:showAsAction="never" /> android:visible="false"
<item custom:showAsAction="never"/>
android:id="@+id/subscriptions_sort"
android:title="@string/sort"
custom:showAsAction="never" />
</menu> </menu>

View File

@ -195,9 +195,8 @@
<item>@string/queue_label</item> <item>@string/queue_label</item>
<item>@string/episodes_label</item> <item>@string/episodes_label</item>
<item>@string/downloads_label</item> <item>@string/downloads_label</item>
<!-- <item>@string/inbox_label</item>-->
<item>@string/playback_history_label</item> <item>@string/playback_history_label</item>
<!-- <item>@string/statistics_label</item>--> <item>@string/statistics_label</item>
<item>@string/add_feed_label</item> <item>@string/add_feed_label</item>
</string-array> </string-array>

View File

@ -5,9 +5,9 @@
android:key="prefSwipeQueue" android:key="prefSwipeQueue"
android:title="@string/queue_label"/> android:title="@string/queue_label"/>
<Preference <!-- <Preference-->
android:key="prefSwipeInbox" <!-- android:key="prefSwipeInbox"-->
android:title="@string/inbox_label"/> <!-- android:title="@string/inbox_label"/>-->
<Preference <Preference
android:key="prefSwipeEpisodes" android:key="prefSwipeEpisodes"
@ -21,6 +21,10 @@
android:key="prefSwipeHistory" android:key="prefSwipeHistory"
android:title="@string/playback_history_label"/> android:title="@string/playback_history_label"/>
<Preference
android:key="prefSwipeStatistics"
android:title="@string/statistics_label"/>
<Preference <Preference
android:key="prefSwipeFeed" android:key="prefSwipeFeed"
android:title="@string/individual_subscription"/> android:title="@string/individual_subscription"/>

View File

@ -33,3 +33,17 @@
* improvement on player UI * improvement on player UI
* episode description now on first page of player popup page * episode description now on first page of player popup page
* localization updates * localization updates
## 4.2.1
* Statistics moved to the drawer
* tuned down color of player controller
* Subscriptions menu adjustment
* Subscriptions filter is disabled for now
* more null safety tuning
* fixed the refresh bug related to permissions
* long-press operation has changed
* long-press on title area would be the same as a click
* click on an icon allows operation on the single item
* long-press on an icon would allow for multi-select

View File

@ -1,2 +1,2 @@
Version 4.1.0 sees the removal of the InBox feature, improvements on player UI, and localization updates Version 4.2.0 sees the removal of the InBox feature, improvements on player UI, and localization updates

View File

@ -0,0 +1,13 @@
Version 4.2.1 brings several changes:
* Statistics moved to the drawer
* tuned down color of player controller
* Subscriptions menu adjustment
* Subscriptions filter is disabled for now
* more null safety tuning
* fixed the refresh bug related to permissions
* long-press operation has changed
* long-press on title area would be the same as a click
* click on an icon allows operation on the single item
* long-press on an icon would allow for multi-select

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 128 KiB

BIN
images/icon 114x114.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB