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:
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020103
versionName "4.2.0"
versionCode 3020104
versionName "4.2.1"
def commit = ""
try {

View File

@ -21,7 +21,6 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<supports-screens
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.DownloadResult
import ac.mdiq.podcini.storage.model.feed.Feed
import android.os.Build
import android.widget.Toast
import java.util.*
class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
@ -107,21 +109,24 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
}
@UnstableApi private fun refreshFeeds(toUpdate: MutableList<Feed>, force: Boolean) {
if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this.applicationContext,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
if (ActivityCompat.checkSelfPermission(this.applicationContext,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
notificationManager.notify(R.id.notification_updating_feeds, createNotification(toUpdate))
val feed = toUpdate[0]
try {
@ -187,9 +192,9 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont
newEpisodesNotification.showIfNeeded(applicationContext, feedSyncTask.savedFeed!!)
if (!request.source.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) {
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.os.Build
import android.util.Log
import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat
import androidx.media3.common.util.UnstableApi
import androidx.work.Data
@ -79,8 +80,8 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
e.printStackTrace()
result = Result.failure()
}
if (result == Result.failure() && downloader != null) {
FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination))
if (result == Result.failure() && downloader?.downloadRequest?.destination != null) {
FileUtils.deleteQuietly(File(downloader!!.downloadRequest.destination!!))
}
progressUpdaterThread.interrupt()
try {
@ -102,9 +103,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
override fun onStopped() {
super.onStopped()
if (downloader != null) {
downloader!!.cancel()
}
downloader?.cancel()
}
override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
@ -112,7 +111,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
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)
if (!dest.exists()) {
try {
@ -162,7 +161,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
if (status.reason == DownloadError.ERROR_HTTP_DATA_ERROR
&& status.reasonDetailed.toInt() == 416) {
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)
return retry3times()
}
@ -199,7 +198,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
EventBus.getDefault().post(MessageEvent(
applicationContext.getString(
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)))
}
@ -251,8 +250,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
applicationContext.resources.getQuantityString(R.plurals.downloads_left,
progressCopy.size, progressCopy.size)
}
val builder = NotificationCompat.Builder(applicationContext,
NotificationUtils.CHANNEL_ID_DOWNLOADING)
val builder = NotificationCompat.Builder(applicationContext, NotificationUtils.CHANNEL_ID_DOWNLOADING)
builder.setTicker(applicationContext.getString(R.string.download_notification_title_episodes))
.setContentTitle(applicationContext.getString(R.string.download_notification_title_episodes))
.setContentText(contentText)

View File

@ -1,6 +1,7 @@
package ac.mdiq.podcini.service.download
import ac.mdiq.podcini.R
import ac.mdiq.podcini.service.FeedUpdateWorker
import android.Manifest
import android.app.PendingIntent
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.FeedCounter
import ac.mdiq.podcini.storage.database.PodDBAdapter
import android.widget.Toast
class NewEpisodesNotification {
private var countersBefore: Map<Long, Int>? = null
@ -80,7 +82,7 @@ class NewEpisodesNotification {
.setAutoCancel(true)
.build()
if (ActivityCompat.checkSelfPermission(context,
if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(context,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
@ -89,6 +91,8 @@ class NewEpisodesNotification {
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
notificationManager.notify(NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS,
@ -117,7 +121,7 @@ class NewEpisodesNotification {
.setOnlyAlertOnce(true)
.setAutoCancel(true)
.build()
if (ActivityCompat.checkSelfPermission(context,
if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(context,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
@ -126,6 +130,8 @@ class NewEpisodesNotification {
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
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.showSkipOnFullNotification
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.VideoPlayerActivityStarter
import android.os.Build.VERSION_CODES
@ -246,7 +247,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
if (notificationBuilder.playerStatus == PlayerStatus.PLAYING) {
notificationBuilder.playerStatus = PlayerStatus.STOPPED
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) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
@ -255,6 +256,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
notificationManager.notify(R.id.notification_playing, notificationBuilder.build())
@ -576,7 +579,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
pendingIntentAlwaysAllow)
.setAutoCancel(true)
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) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
@ -585,6 +588,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
notificationManager.notify(R.id.notification_streaming_confirmation, builder.build())
@ -1303,7 +1308,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed)
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) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
@ -1312,6 +1317,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// 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
}
notificationManager.notify(R.id.notification_playing, notificationBuilder.build())

View File

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

View File

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

View File

@ -91,7 +91,7 @@ import java.util.concurrent.*
media.setDownloaded(false)
media.setFile_url(null)
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)))
}
@ -153,7 +153,8 @@ import java.util.concurrent.*
* Get a FeedItem by its identifying value.
*/
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)) {
return item
}
@ -166,7 +167,8 @@ import java.util.concurrent.*
* This is to work around podcasters breaking their GUIDs.
*/
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)) {
return item
}
@ -220,7 +222,7 @@ import java.util.concurrent.*
Log.d(TAG, "New feed has a higher page number.")
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")
savedFeed.preferences!!.updateFromOther(newFeed.preferences)
}
@ -240,7 +242,7 @@ import java.util.concurrent.*
if (!newFeed.isLocalFeed && possibleDuplicate != null && item !== possibleDuplicate) {
// Canonical episode is the first one returned (usually oldest)
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.
@ -259,7 +261,7 @@ import java.util.concurrent.*
if (oldItem != null) {
Log.d(TAG, "Repaired duplicate: $oldItem, $item")
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.
@ -381,11 +383,13 @@ import java.util.concurrent.*
fun searchFeedItems(feedID: Long, query: String): FutureTask<List<FeedItem>> {
return FutureTask(object : QueryTask<List<FeedItem>>() {
override fun execute(adapter: PodDBAdapter?) {
val searchResult = adapter!!.searchItems(feedID, query)
val items = extractItemlistFromCursor(searchResult)
loadAdditionalFeedItemListData(items)
setResult(items)
searchResult.close()
val searchResult = adapter?.searchItems(feedID, query)
if (searchResult != null) {
val items = extractItemlistFromCursor(searchResult)
loadAdditionalFeedItemListData(items)
setResult(items)
searchResult.close()
}
}
})
}
@ -394,15 +398,17 @@ import java.util.concurrent.*
fun searchFeeds(query: String): FutureTask<List<Feed>> {
return FutureTask(object : QueryTask<List<Feed>>() {
override fun execute(adapter: PodDBAdapter?) {
val cursor = adapter!!.searchFeeds(query)
val items: MutableList<Feed> = ArrayList()
if (cursor.moveToFirst()) {
do {
items.add(convert(cursor))
} while (cursor.moveToNext())
if (adapter != null) {
val cursor = adapter.searchFeeds(query)
val items: MutableList<Feed> = ArrayList()
if (cursor.moveToFirst()) {
do {
items.add(convert(cursor))
} while (cursor.moveToNext())
}
setResult(items)
cursor.close()
}
setResult(items)
cursor.close()
}
})
}

View File

@ -93,8 +93,9 @@ import java.util.concurrent.TimeUnit
if (media != null) {
val result = deleteFeedMediaSynchronous(context, media)
if (result && shouldDeleteRemoveFromQueue()) {
removeQueueItemSynchronous(context, false, media.getItem()!!.id)
val item = media.getItem()
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",
media.id, media.getEpisodeTitle(), media.isDownloaded()))
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
val documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getFile_url()))
if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
@ -113,9 +115,9 @@ import java.util.concurrent.TimeUnit
}
media.setFile_url(null)
localDelete = true
} else if (media.getFile_url() != null) {
} else if (url != null) {
// delete downloaded media file
val mediaFile = File(media.getFile_url()!!)
val mediaFile = File(url)
if (mediaFile.exists() && !mediaFile.delete()) {
val evt = MessageEvent(context.getString(R.string.delete_failed))
EventBus.getDefault().post(evt)
@ -138,13 +140,13 @@ import java.util.concurrent.TimeUnit
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()
if (item != null) {
val item = media.getItem()
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)
.currentTimestamp()
.build()
@ -287,14 +289,16 @@ import java.util.concurrent.TimeUnit
@JvmOverloads
fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> {
return runOnDbThread {
Log.d(TAG, "Adding item to playback history")
media!!.setPlaybackCompletionDate(date)
if (media != null) {
Log.d(TAG, "Adding item to playback history")
media.setPlaybackCompletionDate(date)
val adapter = getInstance()
adapter.open()
adapter.setFeedMediaPlaybackCompletionDate(media)
adapter.close()
EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated())
val adapter = getInstance()
adapter.open()
adapter.setFeedMediaPlaybackCompletionDate(media)
adapter.close()
EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated())
}
}
}
@ -305,11 +309,13 @@ import java.util.concurrent.TimeUnit
*/
fun addDownloadStatus(status: DownloadResult?): Future<*> {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
adapter.setDownloadStatus(status!!)
adapter.close()
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
if (status != null) {
val adapter = getInstance()
adapter.open()
adapter.setDownloadStatus(status)
adapter.close()
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
}
}
}
@ -466,9 +472,10 @@ import java.util.concurrent.TimeUnit
// do not shuffle the list on every change
return
}
val permutor = getPermutor(sortOrder!!)
permutor.reorder(queue)
if (sortOrder != null) {
val permutor = getPermutor(sortOrder)
permutor.reorder(queue)
}
// Replace ADDED events by a single SORTED event
events.clear()
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 broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/
fun moveQueueItemToBottom(itemId: Long,
broadcastUpdate: Boolean
): Future<*> {
fun moveQueueItemToBottom(itemId: Long, broadcastUpdate: Boolean): Future<*> {
return runOnDbThread {
val queueIdList = getQueueIDList()
val index = queueIdList.indexOf(itemId)
@ -678,10 +683,12 @@ import java.util.concurrent.TimeUnit
fun resetPagedFeedPage(feed: Feed?): Future<*> {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
adapter.resetPagedFeedPage(feed!!)
adapter.close()
if (feed != null) {
val adapter = getInstance()
adapter.open()
adapter.resetPagedFeedPage(feed)
adapter.close()
}
}
}
@ -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.
*/
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)
}
@ -834,10 +841,12 @@ import java.util.concurrent.TimeUnit
@JvmStatic
fun setFeedMediaPlaybackInformation(media: FeedMedia?): Future<*> {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
adapter.setFeedMediaPlaybackInformation(media!!)
adapter.close()
if (media != null) {
val adapter = getInstance()
adapter.open()
adapter.setFeedMediaPlaybackInformation(media)
adapter.close()
}
}
}
@ -850,11 +859,13 @@ import java.util.concurrent.TimeUnit
@JvmStatic
fun setFeedItem(item: FeedItem?): Future<*> {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
adapter.setSingleFeedItem(item!!)
adapter.close()
EventBus.getDefault().post(updated(item))
if (item != null) {
val adapter = getInstance()
adapter.open()
adapter.setSingleFeedItem(item)
adapter.close()
EventBus.getDefault().post(updated(item))
}
}
}
@ -893,7 +904,7 @@ import java.util.concurrent.TimeUnit
private fun indexInItemList(items: List<FeedItem?>, itemId: Long): Int {
for (i in items.indices) {
val item = items[i]
if (item!!.id == itemId) {
if (item?.id == itemId) {
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.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.media.AudioManager
import android.net.Uri
@ -38,6 +42,8 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.EditText
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.graphics.Insets
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.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.apache.commons.lang3.ArrayUtils
import org.greenrobot.eventbus.EventBus
@ -107,6 +114,11 @@ class MainActivity : CastEnabledActivity() {
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()
ViewCompat.setOnApplyWindowInsetsListener(mainView) { _: View?, insets: WindowInsetsCompat ->
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() {
super.onAttachedToWindow()
updateInsets()
@ -331,12 +359,12 @@ class MainActivity : CastEnabledActivity() {
val fragment: Fragment
when (tag) {
QueueFragment.TAG -> fragment = QueueFragment()
// InboxFragment.TAG -> fragment = InboxFragment()
AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
CompletedDownloadsFragment.TAG -> fragment = CompletedDownloadsFragment()
PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment()
AddFeedFragment.TAG -> fragment = AddFeedFragment()
SubscriptionFragment.TAG -> fragment = SubscriptionFragment()
StatisticsFragment.TAG -> fragment = StatisticsFragment()
else -> {
// default to subscriptions screen
fragment = SubscriptionFragment()
@ -451,6 +479,7 @@ class MainActivity : CastEnabledActivity() {
override fun onResume() {
super.onResume()
handleNavIntent()
RatingDialog.check()
@ -633,6 +662,7 @@ class MainActivity : CastEnabledActivity() {
"EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null)
"QUEUE" -> loadFragment(QueueFragment.TAG, null)
"SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null)
"STATISTCS" -> loadFragment(StatisticsFragment.TAG, null)
else -> {
showSnackbarAbovePlayer(getString(R.string.app_action_not_found)+feature, Snackbar.LENGTH_LONG)
return

View File

@ -71,7 +71,30 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
val item: FeedItem = episodes[pos]
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()
if (!inActionMode()) {
val ids: LongArray = FeedItemUtil.getIds(episodes)
@ -81,12 +104,6 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
toggleSelection(holder.bindingAdapterPosition)
}
}
holder.itemView.setOnCreateContextMenuListener(this)
holder.itemView.setOnLongClickListener {
longPressedItem = item
longPressedPosition = holder.bindingAdapterPosition
false
}
holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
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.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
@ -96,12 +97,11 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
private fun getDrawable(tag: String?): Int {
return when (tag) {
QueueFragment.TAG -> R.drawable.ic_playlist_play
// InboxFragment.TAG -> R.drawable.ic_inbox
AllEpisodesFragment.TAG -> R.drawable.ic_feed
CompletedDownloadsFragment.TAG -> R.drawable.ic_download
PlaybackHistoryFragment.TAG -> R.drawable.ic_history
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
else -> 0
}
@ -260,7 +260,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
}
private fun bindSectionDivider(holder: DividerHolder) {
val context = activity.get() ?: return
// val context = activity.get() ?: return
if (subscriptionsFilter.isEnabled && showSubscriptionList) {
holder.itemView.isEnabled = true

View File

@ -33,7 +33,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
if (!dragDropEnabled) {
holder.dragHandle.setVisibility(View.GONE)
holder.dragHandle.setOnTouchListener(null)
holder.coverHolder.setOnTouchListener(null)
// holder.coverHolder.setOnTouchListener(null)
} else {
holder.dragHandle.setVisibility(View.VISIBLE)
holder.dragHandle.setOnTouchListener { _: View?, event: MotionEvent ->
@ -59,7 +59,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
}
if (inActionMode()) {
holder.dragHandle.setOnTouchListener(null)
holder.coverHolder.setOnTouchListener(null)
// holder.coverHolder.setOnTouchListener(null)
}
holder.isInQueue.setVisibility(View.GONE)
@ -71,7 +71,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
// menu.findItem(R.id.multi_select).setVisible(true)
val keepSorted: Boolean = UserPreferences.isQueueKeepSorted
if (getItem(0)?.id === longPressedItem?.id || keepSorted) {
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) {
val drawerItem: NavDrawerData.FeedDrawerItem = listItems[position]
holder.bind(drawerItem)
holder.itemView.setOnCreateContextMenuListener(this)
if (inActionMode()) {
holder.selectCheckbox.visibility = View.VISIBLE
holder.selectView.visibility = View.VISIBLE
holder.selectCheckbox.setChecked((isSelected(position)))
holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
setSelected(holder.bindingAdapterPosition,
isChecked)
setSelected(holder.bindingAdapterPosition, isChecked)
}
holder.coverImage.alpha = 0.6f
holder.count.visibility = View.GONE
@ -71,13 +69,30 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
holder.coverImage.alpha = 1.0f
}
holder.itemView.setOnLongClickListener {
if (!inActionMode()) {
longPressedPosition = holder.bindingAdapterPosition
selectedItem = drawerItem
}
holder.coverImage.setOnCreateContextMenuListener(this)
holder.coverImage.setOnLongClickListener {
longPressedPosition = holder.bindingAdapterPosition
selectedItem = drawerItem
startSelectMode(longPressedPosition)
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 ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -95,8 +110,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
if (inActionMode()) {
holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
} else {
val fragment: Fragment = FeedItemlistFragment
.newInstance(drawerItem.feed.id)
val fragment: Fragment = FeedItemlistFragment.newInstance(drawerItem.feed.id)
mainActivityRef.get()?.loadChildFragment(fragment)
}
}
@ -114,13 +128,18 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
if (inActionMode() || selectedItem == null) {
if (selectedItem == null) {
return
}
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
inflater.inflate(R.menu.nav_feed_context, menu)
menu.findItem(R.id.multi_select).setVisible(true)
menu.setHeaderTitle(selectedItem?.title)
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)
// menu.findItem(R.id.multi_select).setVisible(true)
menu.setHeaderTitle(selectedItem?.title)
}
}
fun onContextItemSelected(item: MenuItem): Boolean {
@ -156,6 +175,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
val count: TextView = binding.countLabel
val coverImage: ImageView = binding.coverImage
val infoCard: LinearLayout = binding.infoCard
val selectView: FrameLayout = binding.selectContainer
val selectCheckbox: CheckBox = binding.selectCheckBox
private val card: CardView = binding.outerContainer

View File

@ -37,14 +37,6 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
var forFragment = ""
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 -> {
forFragment = context.getString(R.string.episodes_label)
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 -> {
forFragment = context.getString(R.string.downloads_label)
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()
}
FeedItemlistFragment.TAG -> {
@ -64,12 +55,11 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
forFragment = context.getString(R.string.queue_label)
keys = Stream.of(keys).filter { a: SwipeAction ->
(!a.getId().equals(SwipeAction.ADD_TO_QUEUE)
&& !a.getId().equals(SwipeAction.REMOVE_FROM_INBOX)
&& !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)) }.toList()
}
PlaybackHistoryFragment.TAG -> {
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 -> {}
}
@ -169,7 +159,6 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
view.container.alpha = 0.3f
view.secondaryActionButton.secondaryAction.visibility = View.GONE
view.dragHandle.visibility = View.GONE
view.statusInbox.visibility = View.GONE
view.txtvTitle.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_unread_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 {
override fun onMainActionSelected(): Boolean {
return false
@ -343,9 +343,9 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
}
// if (!inActionMode()) {
// menu.findItem(R.id.multi_select).setVisible(true)
// }
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
this@CompletedDownloadsFragment.onContextItemSelected(item)
}

View File

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

View File

@ -362,7 +362,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed != null && feed!!.isLocalFeed) {
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
updateToolbar()
}
@ -431,12 +431,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed != null && feed!!.itemFilter != null) {
val filter: FeedItemFilter? = feed!!.itemFilter
if (filter != null && filter.values.isNotEmpty()) {
viewBinding.header.txtvInformation.text = ("{md-info-outline} "
+ this.getString(R.string.filtered_label))
viewBinding.header.txtvInformation.text = ("{md-info-outline} " + this.getString(R.string.filtered_label))
Iconify.addIcons(viewBinding.header.txtvInformation)
viewBinding.header.txtvInformation.setOnClickListener {
FeedItemFilterDialog.newInstance(feed!!).show(
childFragmentManager, null)
FeedItemFilterDialog.newInstance(feed!!).show(childFragmentManager, null)
}
viewBinding.header.txtvInformation.visibility = View.VISIBLE
} else {
@ -571,14 +569,14 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
private inner class FeedItemListAdapter(mainActivity: MainActivity) : EpisodeItemListAdapter(mainActivity) {
@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?) {
super.onCreateContextMenu(menu, v, menuInfo)
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
}
// if (!inActionMode()) {
// menu.findItem(R.id.multi_select).setVisible(true)
// }
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
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.dialog.*
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import android.R.attr
import android.app.Activity
import android.content.Context
@ -159,18 +160,18 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
@OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean {
val itemId = item.itemId
when (itemId) {
R.id.remove_all_inbox_item -> {
val removeAllNewFlagsConfirmationDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(),
R.string.remove_all_inbox_label,
R.string.remove_all_inbox_confirmation_msg) {
@OptIn(UnstableApi::class) override fun onConfirmButtonPressed(dialog: DialogInterface) {
dialog.dismiss()
DBWriter.removeFeedNewFlag(feed.id)
}
}
removeAllNewFlagsConfirmationDialog.createNewDialog().show()
return true
}
// R.id.remove_all_inbox_item -> {
// val removeAllNewFlagsConfirmationDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(),
// R.string.remove_all_inbox_label,
// R.string.remove_all_inbox_confirmation_msg) {
// @OptIn(UnstableApi::class) override fun onConfirmButtonPressed(dialog: DialogInterface) {
// dialog.dismiss()
// DBWriter.removeFeedNewFlag(feed.id)
// }
// }
// removeAllNewFlagsConfirmationDialog.createNewDialog().show()
// return true
// }
R.id.edit_tags -> {
if (feed.preferences != null)
TagSettingsDialog.newInstance(listOf(feed.preferences!!)).show(childFragmentManager, TagSettingsDialog.TAG)
@ -378,9 +379,8 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
QueueFragment.TAG,
AllEpisodesFragment.TAG,
CompletedDownloadsFragment.TAG,
// InboxFragment.TAG,
PlaybackHistoryFragment.TAG,
// StatisticsFragment.TAG,
StatisticsFragment.TAG,
AddFeedFragment.TAG,
)

View File

@ -62,6 +62,7 @@ import java.util.*
* Shows all items in the queue.
*/
class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
private lateinit var infoBar: TextView
private lateinit var recyclerView: EpisodeItemListRecyclerView
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 {
super.onCreateView(inflater, container, savedInstanceState)
val binding = QueueFragmentBinding.inflate(inflater)
// val root: View = inflater.inflate(R.layout.queue_fragment, container, false)
Log.d(TAG, "fragment onCreateView")
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_unread_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 {
override fun onMainActionSelected(): Boolean {
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.menuhandler.FeedMenuHandler
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.LiftOnScrollListener
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
@ -245,24 +244,20 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
val itemId = item.itemId
when (itemId) {
R.id.refresh_item -> {
FeedUpdateManager.runOnceOrAsk(requireContext())
return true
}
R.id.subscriptions_filter -> {
SubscriptionsFilterDialog().show(childFragmentManager, "filter")
R.id.action_search -> {
(activity as MainActivity).loadChildFragment(SearchFragment.newInstance())
return true
}
R.id.subscriptions_sort -> {
FeedSortDialog.showDialog(requireContext())
return true
}
R.id.action_search -> {
(activity as MainActivity).loadChildFragment(SearchFragment.newInstance())
R.id.subscriptions_filter -> {
SubscriptionsFilterDialog().show(childFragmentManager, "filter")
return true
}
R.id.action_statistics -> {
(activity as MainActivity).loadChildFragment(StatisticsFragment())
R.id.refresh_item -> {
FeedUpdateManager.runOnceOrAsk(requireContext())
return true
}
else -> return false
@ -344,6 +339,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
}
override fun onStartSelectMode() {
speedDialView.visibility = View.VISIBLE
val feedsOnly: MutableList<NavDrawerData.FeedDrawerItem> = ArrayList<NavDrawerData.FeedDrawerItem>()
for (item in feedListFiltered) {
feedsOnly.add(item)

View File

@ -63,7 +63,8 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() {
companion object {
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_DOWNLOADS = "prefSwipeDownloads"
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 {
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 MARK_FAV: String = "MARK_FAV"
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
val swipeActions: List<SwipeAction> = Collections.unmodifiableList(
listOf(AddToQueueSwipeAction(), RemoveFromInboxSwipeAction(),
StartDownloadSwipeAction(), MarkFavoriteSwipeAction(),
listOf(AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(),
TogglePlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(),
DeleteSwipeAction(), RemoveFromHistorySwipeAction())
)

View File

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

View File

@ -33,20 +33,20 @@ object FeedMenuHandler {
val context = fragment.requireContext()
if (menuItemId == R.id.rename_folder_item) {
RenameItemDialog(fragment.activity as Activity, selectedFeed).show()
} else if (menuItemId == R.id.remove_all_inbox_item) {
val dialog: ConfirmationDialog = object : ConfirmationDialog(fragment.activity as Activity,
R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) {
@OptIn(UnstableApi::class) @SuppressLint("CheckResult")
override fun onConfirmButtonPressed(clickedDialog: DialogInterface) {
clickedDialog.dismiss()
Observable.fromCallable(Callable { DBWriter.removeFeedNewFlag(selectedFeed.id) } as Callable<Future<*>>)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ callback.run() },
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
}
dialog.createNewDialog().show()
// } else if (menuItemId == R.id.remove_all_inbox_item) {
// val dialog: ConfirmationDialog = object : ConfirmationDialog(fragment.activity as Activity,
// R.string.remove_all_inbox_label, R.string.remove_all_inbox_confirmation_msg) {
// @OptIn(UnstableApi::class) @SuppressLint("CheckResult")
// override fun onConfirmButtonPressed(clickedDialog: DialogInterface) {
// clickedDialog.dismiss()
// Observable.fromCallable(Callable { DBWriter.removeFeedNewFlag(selectedFeed.id) } as Callable<Future<*>>)
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ callback.run() },
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
// }
// }
// dialog.createNewDialog().show()
} else if (menuItemId == R.id.edit_tags) {
if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed.preferences!!))
.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.databinding.PagerFragmentBinding
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.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment
@ -17,6 +18,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import androidx.viewpager2.adapter.FragmentStateAdapter
@ -33,29 +35,29 @@ import org.greenrobot.eventbus.EventBus
* Displays the 'statistics' screen
*/
class StatisticsFragment : PagedToolbarFragment() {
private var tabLayout: TabLayout? = null
private var viewPager: ViewPager2? = null
private var toolbar: MaterialToolbar? = null
private lateinit var tabLayout: TabLayout
private lateinit var viewPager: ViewPager2
private lateinit var toolbar: MaterialToolbar
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
setHasOptionsMenu(true)
val binding = PagerFragmentBinding.inflate(inflater)
// val rootView = inflater.inflate(R.layout.pager_fragment, container, false)
viewPager = binding.viewpager
toolbar = binding.toolbar
toolbar?.title = getString(R.string.statistics_label)
toolbar?.inflateMenu(R.menu.statistics)
toolbar?.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
viewPager?.adapter = StatisticsPagerAdapter(this)
toolbar.title = getString(R.string.statistics_label)
toolbar.inflateMenu(R.menu.statistics)
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
(activity as MainActivity).setupToolbarToggle(toolbar, false)
viewPager.adapter = StatisticsPagerAdapter(this)
// Give the TabLayout the ViewPager
tabLayout = binding.slidingTabs
if (toolbar != null && viewPager != null) super.setupPagedToolbar(toolbar!!, viewPager!!)
if (tabLayout == null || viewPager == null) return null
super.setupPagedToolbar(toolbar, viewPager)
TabLayoutMediator(tabLayout!!, viewPager!!) { tab: TabLayout.Tab, position: Int ->
TabLayoutMediator(tabLayout, viewPager) { tab: TabLayout.Tab, position: Int ->
when (position) {
POS_SUBSCRIPTIONS -> tab.setText(R.string.subscriptions_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.common.CircularProgressBar
import ac.mdiq.podcini.ui.common.ThemeUtils
import ac.mdiq.podcini.util.Converter
import android.widget.LinearLayout
import io.reactivex.functions.Consumer
import kotlin.math.max
@ -56,7 +58,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
private val position: TextView
private val duration: TextView
private val size: TextView
val isInbox: ImageView
@JvmField
val isInQueue: ImageView
private val isVideo: ImageView
@ -71,6 +72,8 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
private val leftPadding: View
@JvmField
val coverHolder: CardView
@JvmField
val infoCard: LinearLayout
private var item: FeedItem? = null
@ -84,7 +87,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
progressBar = binding.progressBar
isInQueue = binding.ivInPlaylist
isVideo = binding.ivIsVideo
isInbox = binding.statusInbox
isFavorite = binding.isFavorite
size = binding.size
separatorIcons = binding.separatorIcons
@ -92,6 +94,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
secondaryActionButton = binding.secondaryActionButton.root
secondaryActionIcon = binding.secondaryActionButton.secondaryActionIcon
coverHolder = binding.coverHolder
infoCard = binding.infoCard
leftPadding = binding.leftPadding
itemView.tag = this
}
@ -107,7 +110,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
}
pubDate.text = DateFormatter.formatAbbrev(activity, 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
isInQueue.visibility = if (item.isTagged(FeedItem.TAG_QUEUE)) View.VISIBLE else View.GONE
container.alpha = if (item.isPlayed()) 0.5f else 1.0f
@ -210,7 +212,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
item = FeedItem()
container.alpha = 0.1f
secondaryActionIcon.setImageDrawable(null)
isInbox.visibility = View.VISIBLE
isVideo.visibility = View.GONE
isFavorite.visibility = View.GONE
isInQueue.visibility = View.GONE
@ -235,9 +236,10 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
}
private fun updateDuration(event: PlaybackPositionEvent) {
if (feedItem?.media != null) {
feedItem!!.media!!.setPosition(event.position)
feedItem!!.media!!.setDuration(event.duration)
val media = feedItem?.media
if (media != null) {
media.setPosition(event.position)
media.setDuration(event.duration)
}
val currentPosition: Int = event.position
val timeDuration: Int = event.duration
@ -248,9 +250,9 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
return
}
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 {
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.
*/
fun hideSeparatorIfNecessary() {
val hasIcons = isInbox.visibility == View.VISIBLE ||
isInQueue.visibility == View.VISIBLE ||
val hasIcons = isInQueue.visibility == View.VISIBLE ||
isVideo.visibility == View.VISIBLE ||
isFavorite.visibility == View.VISIBLE ||
isInbox.visibility == View.VISIBLE
isFavorite.visibility == View.VISIBLE
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:viewportHeight="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>

View File

@ -3,5 +3,6 @@
android:height="48dp"
android:viewportHeight="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>

View File

@ -3,5 +3,6 @@
android:height="48dp"
android:viewportHeight="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>

View File

@ -3,5 +3,6 @@
android:height="48dp"
android:viewportHeight="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>

View File

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

View File

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

View File

@ -87,6 +87,7 @@
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/info_card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
@ -104,13 +105,6 @@
android:orientation="horizontal"
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
android:id="@+id/ivIsVideo"
android:layout_width="14sp"

View File

@ -34,6 +34,7 @@
tools:src="@tools:sample/avatars" />
<LinearLayout
android:id="@+id/info_card"
android:layout_width="0.dp"
android:layout_height="match_parent"
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"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/remove_all_inbox_item"
android:menuCategory="container"
android:title="@string/remove_all_inbox_label" />
<!-- <item-->
<!-- android:id="@+id/remove_all_inbox_item"-->
<!-- android:menuCategory="container"-->
<!-- android:title="@string/remove_all_inbox_label" />-->
<item
android:id="@+id/edit_tags"

View File

@ -2,26 +2,29 @@
<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"/>
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
custom:showAsAction="always"
android:title="@string/search_label"/>
<item
android:id="@+id/action_statistics"
android:icon="@drawable/ic_chart_box"
android:title="@string/statistics_label"
custom:showAsAction="always" />
android:id="@+id/subscriptions_sort"
android:title="@string/sort"
android:icon="@drawable/arrows_sort"
custom:showAsAction="always" />
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
android:id="@+id/action_statistics"
android:icon="@drawable/ic_chart_box"
android:title="@string/statistics_label"
android:visible="false"
custom:showAsAction="always" />
<item
android:id="@+id/subscriptions_filter"
android:title="@string/filter"
custom:showAsAction="never" />
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
<item
android:id="@+id/subscriptions_sort"
android:title="@string/sort"
custom:showAsAction="never" />
android:id="@+id/subscriptions_filter"
android:title="@string/filter"
android:visible="false"
custom:showAsAction="never"/>
</menu>

View File

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

View File

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

View File

@ -32,4 +32,18 @@
* Removed InBox feature
* improvement on player UI
* 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