5.3.0 commit
This commit is contained in:
parent
ef5fe709ba
commit
c49af77af8
|
@ -17,7 +17,7 @@ Compared to AntennaPod this project:
|
|||
2. Plays in `AudioOffloadMode`, kind to device battery,
|
||||
3. Is purely `Kotlin` based and mono-modular,
|
||||
4. Targets Android 14 with updated dependencies,
|
||||
5. Outfits with Viewbinding and modern image library Coil,
|
||||
5. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava, and SharedFlow replacing EventBus,
|
||||
6. Boasts new UI's including streamlined drawer, subscriptions view and player controller,
|
||||
7. Accepts podcast as well as plain RSS and YouTube feeds,
|
||||
8. Offers Readability and Text-to-Speech for RSS contents,
|
||||
|
@ -72,6 +72,7 @@ The project aims to improve efficiency and provide more useful and user-friendly
|
|||
* Sort dialog no longer dims the main view
|
||||
* in episode list view, if episode has no media, TTS button is shown for fetching transcript (if not exist) and then generating audio file from the transcript. TTS audio files are playable in the same way as local media (with speed setting, pause and rewind/forward)
|
||||
* Subscriptions view has sorting by "Unread publication date"
|
||||
* History view shows time of last play, and allows filters and sorts
|
||||
|
||||
### Podcast/Episode
|
||||
|
||||
|
@ -100,7 +101,7 @@ The project aims to improve efficiency and provide more useful and user-friendly
|
|||
### Security and reliability
|
||||
|
||||
* Disabled `usesCleartextTraffic`, so that all content transmission is more private and secure
|
||||
* Settings/Preferences can now to exported and imported
|
||||
* Settings/Preferences can now be exported and imported
|
||||
|
||||
For more details of the changes, see the [Changelog](changelog.md)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
plugins {
|
||||
id('com.android.application')
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
// id 'kotlin-kapt'
|
||||
id 'com.google.devtools.ksp'
|
||||
id('com.github.triplet.play') version '3.8.3' apply false
|
||||
}
|
||||
|
@ -159,8 +159,8 @@ android {
|
|||
// Version code schema (not used):
|
||||
// "1.2.3-beta4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 3020144
|
||||
versionName "5.2.1"
|
||||
versionCode 3020145
|
||||
versionName "5.3.0"
|
||||
|
||||
def commit = ""
|
||||
try {
|
||||
|
@ -232,9 +232,7 @@ dependencies {
|
|||
}
|
||||
}
|
||||
|
||||
// doesn't work with ksp??
|
||||
kapt "androidx.annotation:annotation:1.8.0"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.8.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation "androidx.fragment:fragment-ktx:1.6.2"
|
||||
|
@ -266,8 +264,8 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||
implementation 'com.squareup.okio:okio:3.9.0'
|
||||
|
||||
implementation "org.greenrobot:eventbus:3.3.1"
|
||||
kapt "org.greenrobot:eventbus-annotation-processor:3.3.1"
|
||||
// implementation "org.greenrobot:eventbus:3.3.1"
|
||||
// kapt "org.greenrobot:eventbus-annotation-processor:3.3.1"
|
||||
|
||||
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
|
@ -316,11 +314,11 @@ dependencies {
|
|||
playApi 'com.google.android.gms:play-services-cast-framework:21.4.0'
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg('eventBusIndex', 'ac.mdiq.podcini.ApEventBusIndex')
|
||||
}
|
||||
}
|
||||
//kapt {
|
||||
// arguments {
|
||||
// arg('eventBusIndex', 'ac.mdiq.podcini.ApEventBusIndex')
|
||||
// }
|
||||
//}
|
||||
|
||||
if (project.hasProperty("podciniPlayPublisherCredentials")) {
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
package de.test.podcini.service.playback
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.setShakeToReset
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.setVibrate
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager
|
||||
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.setShakeToReset
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.setVibrate
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.deleteDatabase
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.init
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
|
@ -31,6 +34,9 @@ import java.util.concurrent.TimeUnit
|
|||
*/
|
||||
@LargeTest
|
||||
class PlaybackServiceTaskManagerTest {
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
deleteDatabase()
|
||||
|
@ -205,19 +211,29 @@ class PlaybackServiceTaskManagerTest {
|
|||
val TIMEOUT = 2 * TIME
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val timerReceiver: Any = object : Any() {
|
||||
@Subscribe
|
||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent?) {
|
||||
private fun procFlowEvents() {
|
||||
scope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> sleepTimerUpdate(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sleepTimerUpdate(event: FlowEvent.SleepTimerUpdatedEvent?) {
|
||||
if (countDownLatch.count == 0L) {
|
||||
Assert.fail()
|
||||
}
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(timerReceiver)
|
||||
// EventBus.getDefault().register(timerReceiver)
|
||||
val pstm = PlaybackServiceTaskManager(c, defaultPSTM)
|
||||
pstm.setSleepTimer(TIME)
|
||||
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
EventBus.getDefault().unregister(timerReceiver)
|
||||
// EventBus.getDefault().unregister(timerReceiver)
|
||||
pstm.shutdown()
|
||||
}
|
||||
|
||||
|
@ -230,8 +246,17 @@ class PlaybackServiceTaskManagerTest {
|
|||
val TIMEOUT = 2 * TIME
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
val timerReceiver: Any = object : Any() {
|
||||
@Subscribe
|
||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
||||
private fun procFlowEvents() {
|
||||
scope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> sleepTimerUpdate(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fun sleepTimerUpdate(event: FlowEvent.SleepTimerUpdatedEvent) {
|
||||
when {
|
||||
event.isOver -> {
|
||||
countDownLatch.countDown()
|
||||
|
@ -243,12 +268,12 @@ class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
}
|
||||
val pstm = PlaybackServiceTaskManager(c, defaultPSTM)
|
||||
EventBus.getDefault().register(timerReceiver)
|
||||
// EventBus.getDefault().register(timerReceiver)
|
||||
pstm.setSleepTimer(TIME)
|
||||
pstm.disableSleepTimer()
|
||||
Assert.assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS))
|
||||
pstm.shutdown()
|
||||
EventBus.getDefault().unregister(timerReceiver)
|
||||
// EventBus.getDefault().unregister(timerReceiver)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
package de.test.podcini.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.setQueue
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.deleteDatabase
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.deleteDatabase
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import de.test.podcini.util.service.download.HTTPBin
|
||||
import de.test.podcini.util.syndication.feedgenerator.Rss2Generator
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
@ -193,8 +192,8 @@ class UITestUtils(private val context: Context) {
|
|||
adapter.setCompleteFeed(*hostedFeeds.toTypedArray<Feed>())
|
||||
adapter.setQueue(queue)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(hostedFeeds))
|
||||
EventBus.getDefault().post(setQueue(queue))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(hostedFeeds))
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.setQueue(queue))
|
||||
}
|
||||
|
||||
fun setMediaFileName(filename: String) {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package de.test.podcini.util.event
|
||||
|
||||
import ac.mdiq.podcini.util.event.FeedItemEvent
|
||||
import io.reactivex.functions.Consumer
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
/**
|
||||
* Test helpers to listen [FeedItemEvent] and handle them accordingly
|
||||
*
|
||||
*/
|
||||
class FeedItemEventListener {
|
||||
private val events: MutableList<FeedItemEvent> = ArrayList()
|
||||
|
||||
@Subscribe
|
||||
fun onEvent(event: FeedItemEvent) {
|
||||
events.add(event)
|
||||
}
|
||||
|
||||
fun getEvents(): List<FeedItemEvent> {
|
||||
return events
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Provides an listener subscribing to [FeedItemEvent] that the callers can use
|
||||
*
|
||||
* Note: it uses RxJava's version of [Consumer] because it allows exceptions to be thrown.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun withFeedItemEventListener(consumer: Consumer<FeedItemEventListener?>) {
|
||||
val feedItemEventListener = FeedItemEventListener()
|
||||
try {
|
||||
EventBus.getDefault().register(feedItemEventListener)
|
||||
consumer.accept(feedItemEventListener)
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(feedItemEventListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,13 @@
|
|||
package ac.mdiq.podcini
|
||||
|
||||
import ac.mdiq.podcini.preferences.PreferenceUpgrader
|
||||
import ac.mdiq.podcini.ui.activity.SplashActivity
|
||||
import ac.mdiq.podcini.util.SPAUtil
|
||||
import ac.mdiq.podcini.util.config.ApplicationCallbacksImpl
|
||||
import ac.mdiq.podcini.util.config.ClientConfig
|
||||
import ac.mdiq.podcini.util.config.ClientConfigurator
|
||||
import ac.mdiq.podcini.util.error.CrashReportWriter
|
||||
import ac.mdiq.podcini.util.error.RxJavaErrorHandlerSetup
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
|
@ -9,15 +17,6 @@ import com.google.android.material.color.DynamicColors
|
|||
import com.joanzapata.iconify.Iconify
|
||||
import com.joanzapata.iconify.fonts.FontAwesomeModule
|
||||
import com.joanzapata.iconify.fonts.MaterialModule
|
||||
import ac.mdiq.podcini.ui.activity.SplashActivity
|
||||
import ac.mdiq.podcini.util.config.ApplicationCallbacksImpl
|
||||
import ac.mdiq.podcini.util.config.ClientConfig
|
||||
import ac.mdiq.podcini.util.config.ClientConfigurator
|
||||
import ac.mdiq.podcini.util.error.CrashReportWriter
|
||||
import ac.mdiq.podcini.util.error.RxJavaErrorHandlerSetup
|
||||
import ac.mdiq.podcini.preferences.PreferenceUpgrader
|
||||
import ac.mdiq.podcini.util.SPAUtil
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
/** Main application class. */
|
||||
class PodciniApp : Application() {
|
||||
|
@ -50,12 +49,12 @@ class PodciniApp : Application() {
|
|||
Iconify.with(MaterialModule())
|
||||
|
||||
SPAUtil.sendSPAppsQueryFeedsIntent(this)
|
||||
EventBus.builder()
|
||||
.addIndex(ApEventBusIndex())
|
||||
// .addIndex(ApCoreEventBusIndex())
|
||||
.logNoSubscriberMessages(false)
|
||||
.sendNoSubscriberEvent(false)
|
||||
.installDefaultEventBus()
|
||||
// EventBus.builder()
|
||||
// .addIndex(ApEventBusIndex())
|
||||
//// .addIndex(ApCoreEventBusIndex())
|
||||
// .logNoSubscriberMessages(false)
|
||||
// .sendNoSubscriberEvent(false)
|
||||
// .installDefaultEventBus()
|
||||
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package ac.mdiq.podcini.feed
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder
|
||||
import org.apache.commons.lang3.builder.ToStringStyle
|
||||
|
||||
class FeedEvent(private val action: Action, @JvmField val feedId: Long) {
|
||||
enum class Action {
|
||||
FILTER_CHANGED,
|
||||
SORT_ORDER_CHANGED
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("action", action)
|
||||
.append("feedId", feedId)
|
||||
.toString()
|
||||
}
|
||||
}
|
|
@ -12,10 +12,11 @@ import java.util.*
|
|||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class CombinedSearcher : PodcastSearcher {
|
||||
|
||||
override fun search(query: String): Single<List<PodcastSearchResult?>?> {
|
||||
val disposables = ArrayList<Disposable>()
|
||||
val singleResults: MutableList<List<PodcastSearchResult?>?> = ArrayList(
|
||||
Collections.nCopies<List<PodcastSearchResult?>?>(PodcastSearcherRegistry.searchProviders.size, null))
|
||||
val singleResults: MutableList<List<PodcastSearchResult?>?> =
|
||||
ArrayList(Collections.nCopies<List<PodcastSearchResult?>?>(PodcastSearcherRegistry.searchProviders.size, null))
|
||||
val latch = CountDownLatch(PodcastSearcherRegistry.searchProviders.size)
|
||||
for (i in PodcastSearcherRegistry.searchProviders.indices) {
|
||||
val searchProviderInfo = PodcastSearcherRegistry.searchProviders[i]
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
package ac.mdiq.podcini.net.download
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.util.Log
|
||||
import androidx.work.*
|
||||
import androidx.work.Constraints.Builder
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.net.download.service.FeedUpdateWorker
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isFeedRefreshAllowed
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isNetworkRestricted
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isVpnOverWifi
|
||||
import ac.mdiq.podcini.util.NetworkUtils.networkAvailable
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.work.*
|
||||
import androidx.work.Constraints.Builder
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object FeedUpdateManager {
|
||||
|
@ -72,7 +71,7 @@ object FeedUpdateManager {
|
|||
Logd(TAG, "Run auto update immediately in background.")
|
||||
when {
|
||||
feed != null && feed.isLocalFeed -> runOnce(context, feed)
|
||||
!networkAvailable() -> EventBus.getDefault().post(MessageEvent(context.getString(R.string.download_error_no_connection)))
|
||||
!networkAvailable() -> EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.download_error_no_connection)))
|
||||
isFeedRefreshAllowed -> runOnce(context, feed)
|
||||
else -> confirmMobileRefresh(context, feed)
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package ac.mdiq.podcini.net.download.service
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import androidx.work.Constraints.Builder
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import androidx.work.*
|
||||
import androidx.work.Constraints.Builder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -39,21 +40,36 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
|
|||
DBWriter.deleteFeedMediaOfItem(context, media.id) // Remove partially downloaded file
|
||||
val tag = WORK_TAG_EPISODE_URL + media.download_url
|
||||
val future: Future<List<WorkInfo>> = WorkManager.getInstance(context).getWorkInfosByTag(tag)
|
||||
Observable.fromFuture(future)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(
|
||||
{ workInfos: List<WorkInfo> ->
|
||||
for (info in workInfos) {
|
||||
if (info.tags.contains(WORK_DATA_WAS_QUEUED)) {
|
||||
if (media.item != null) DBWriter.removeQueueItem(context, false, media.item!!)
|
||||
}
|
||||
// Observable.fromFuture(future)
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(Schedulers.io())
|
||||
// .subscribe(
|
||||
// { workInfos: List<WorkInfo> ->
|
||||
// for (info in workInfos) {
|
||||
// if (info.tags.contains(WORK_DATA_WAS_QUEUED)) {
|
||||
// if (media.item != null) DBWriter.removeQueueItem(context, false, media.item!!)
|
||||
// }
|
||||
// }
|
||||
// WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
// }, { exception: Throwable ->
|
||||
// WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
// exception.printStackTrace()
|
||||
// })
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val workInfoList = future.get() // Wait for the completion of the future operation and retrieve the result
|
||||
workInfoList.forEach { workInfo ->
|
||||
if (workInfo.tags.contains(WORK_DATA_WAS_QUEUED)) {
|
||||
if (media.item != null) DBWriter.removeQueueItem(context, false, media.item!!)
|
||||
}
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
}, { exception: Throwable ->
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
exception.printStackTrace()
|
||||
})
|
||||
}
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
} catch (exception: Throwable) {
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(tag)
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelAll(context: Context) {
|
||||
|
|
|
@ -13,7 +13,8 @@ import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
|
|||
import ac.mdiq.podcini.ui.utils.NotificationUtils
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.config.ClientConfigurator
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
|
@ -29,7 +30,6 @@ import androidx.work.WorkerParameters
|
|||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -179,7 +179,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
|
|||
val retrying = !isLastRunAttempt && !isImmediateFail
|
||||
if (episodeTitle.length > 20) episodeTitle = episodeTitle.substring(0, 19) + "…"
|
||||
|
||||
EventBus.getDefault().post(MessageEvent(applicationContext.getString(
|
||||
EventFlow.postEvent(FlowEvent.MessageEvent(applicationContext.getString(
|
||||
if (retrying) R.string.download_error_retrying else R.string.download_error_not_retrying,
|
||||
episodeTitle), { ctx: Context -> MainActivityStarter(ctx).withDownloadLogsOpen().start() }, applicationContext.getString(R.string.download_error_details)))
|
||||
}
|
||||
|
@ -197,10 +197,11 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
|
|||
}
|
||||
|
||||
private fun sendErrorNotification(title: String) {
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) {
|
||||
sendMessage(title, false)
|
||||
return
|
||||
}
|
||||
// TODO: need to get number of subscribers in SharedFlow
|
||||
// if (EventBus.getDefault().hasSubscriberForEvent(FlowEvent.MessageEvent::class.java)) {
|
||||
// sendMessage(title, false)
|
||||
// return
|
||||
// }
|
||||
|
||||
val builder = NotificationCompat.Builder(applicationContext, NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR)
|
||||
builder.setTicker(applicationContext.getString(R.string.download_report_title))
|
||||
|
|
|
@ -10,12 +10,12 @@ import ac.mdiq.podcini.storage.model.download.DownloadError
|
|||
import ac.mdiq.podcini.storage.model.download.DownloadResult
|
||||
import ac.mdiq.podcini.util.ChapterUtils
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.util.Log
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
|
@ -69,7 +69,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
|
|||
// so we do it after the enclosing media has been updated above,
|
||||
// to ensure subscribers will get the updated FeedMedia as well
|
||||
DBWriter.persistFeedItem(item).get()
|
||||
if (broadcastUnreadStateUpdate) EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "MediaHandlerThread was interrupted")
|
||||
|
|
|
@ -2,6 +2,10 @@ package ac.mdiq.podcini.net.sync
|
|||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
object LockingAsyncExecutor {
|
||||
|
@ -21,14 +25,26 @@ object LockingAsyncExecutor {
|
|||
lock.unlock()
|
||||
}
|
||||
} else {
|
||||
Completable.fromRunnable {
|
||||
lock.lock()
|
||||
try {
|
||||
runnable.run()
|
||||
} finally {
|
||||
lock.unlock()
|
||||
// Completable.fromRunnable {
|
||||
// lock.lock()
|
||||
// try {
|
||||
// runnable.run()
|
||||
// } finally {
|
||||
// lock.unlock()
|
||||
// }
|
||||
// }.subscribeOn(Schedulers.io()).subscribe()
|
||||
|
||||
val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
lock.lock()
|
||||
try {
|
||||
runnable.run()
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}.subscribeOn(Schedulers.io()).subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,7 @@ import ac.mdiq.podcini.ui.utils.NotificationUtils
|
|||
import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.LongList
|
||||
import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.SyncServiceEvent
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
|
@ -44,8 +42,12 @@ import androidx.core.app.NotificationCompat
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.work.*
|
||||
import androidx.work.Constraints.Builder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
|
@ -72,11 +74,11 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
|
||||
activeSyncProvider.logout()
|
||||
clearErrorNotifications()
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_success))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_success))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(true)
|
||||
return Result.success()
|
||||
} catch (e: Exception) {
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_error))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_error))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(false)
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
|
||||
|
@ -97,7 +99,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
private fun syncSubscriptions(syncServiceImpl: ISyncService) {
|
||||
Logd(TAG, "syncSubscriptions called")
|
||||
val lastSync = SynchronizationSettings.lastSubscriptionSynchronizationTimestamp
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_subscriptions))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_subscriptions))
|
||||
val localSubscriptions: List<String> = getFeedListDownloadUrls()
|
||||
val subscriptionChanges = syncServiceImpl.getSubscriptionChanges(lastSync)
|
||||
var newTimeStamp = subscriptionChanges?.timestamp?:0L
|
||||
|
@ -151,21 +153,32 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
|
||||
private fun waitForDownloadServiceCompleted() {
|
||||
Logd(TAG, "waitForDownloadServiceCompleted called")
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_wait_for_downloads))
|
||||
try {
|
||||
while (true) {
|
||||
Thread.sleep(1000)
|
||||
val event = EventBus.getDefault().getStickyEvent(FeedUpdateRunningEvent::class.java)
|
||||
if (event == null || !event.isFeedUpdateRunning) return
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_wait_for_downloads))
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
scope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedUpdateRunningEvent -> if (!event.isFeedUpdateRunning) return@collectLatest
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
return@launch
|
||||
}
|
||||
scope.cancel()
|
||||
// try {
|
||||
// while (true) {
|
||||
// Thread.sleep(1000)
|
||||
// val event = EventBus.getDefault().getStickyEvent(FlowEvent.FeedUpdateRunningEvent::class.java)
|
||||
// if (event == null || !event.isFeedUpdateRunning) return
|
||||
// }
|
||||
// } catch (e: InterruptedException) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
}
|
||||
|
||||
fun getEpisodeActions(syncServiceImpl: ISyncService) : Pair<Long, Long> {
|
||||
val lastSync = SynchronizationSettings.lastEpisodeActionSynchronizationTimestamp
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_episodes_download))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_episodes_download))
|
||||
val getResponse = syncServiceImpl.getEpisodeActionChanges(lastSync)
|
||||
val newTimeStamp = getResponse?.timestamp?:0L
|
||||
val remoteActions = getResponse?.episodeActions?: listOf()
|
||||
|
@ -175,10 +188,10 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
|
||||
open fun pushEpisodeActions(syncServiceImpl: ISyncService, lastSync: Long, newTimeStamp_: Long): Long {
|
||||
var newTimeStamp = newTimeStamp_
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_episodes_upload))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_episodes_upload))
|
||||
val queuedEpisodeActions: MutableList<EpisodeAction> = synchronizationQueueStorage.queuedEpisodeActions
|
||||
if (lastSync == 0L) {
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_upload_played))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_upload_played))
|
||||
val readItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PLAYED), SortOrder.DATE_NEW_OLD)
|
||||
Logd(TAG, "First sync. Upload state for all " + readItems.size + " played episodes")
|
||||
for (item in readItems) {
|
||||
|
@ -272,10 +285,11 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
Logd(TAG, "Skipping sync error notification because of user setting")
|
||||
return
|
||||
}
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) {
|
||||
EventBus.getDefault().post(MessageEvent(description))
|
||||
return
|
||||
}
|
||||
// TODO:
|
||||
// if (EventBus.getDefault().hasSubscriberForEvent(FlowEvent.MessageEvent::class.java)) {
|
||||
// EventFlow.postEvent(FlowEvent.MessageEvent(description))
|
||||
// return
|
||||
// }
|
||||
|
||||
val intent = applicationContext.packageManager.getLaunchIntentForPackage(
|
||||
applicationContext.packageName)
|
||||
|
@ -335,7 +349,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
|
|||
} else {
|
||||
// Give it some time, so other possible actions can be queued.
|
||||
builder.setInitialDelay(20L, TimeUnit.SECONDS)
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_started))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_started))
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
package ac.mdiq.podcini.net.sync.nextcloud
|
||||
|
||||
import ac.mdiq.podcini.net.sync.HostnameParser
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import ac.mdiq.podcini.net.sync.HostnameParser
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
@ -42,27 +46,51 @@ class NextcloudLoginFlow(private val httpClient: OkHttpClient, private val rawHo
|
|||
poll()
|
||||
return
|
||||
}
|
||||
startDisposable = Observable.fromCallable {
|
||||
val url = URI(hostname.scheme, null, hostname.host, hostname.port, hostname.subfolder + "/index.php/login/v2", null, null).toURL()
|
||||
val result = doRequest(url, "")
|
||||
val loginUrl = result.getString("login")
|
||||
this.token = result.getJSONObject("poll").getString("token")
|
||||
this.endpoint = result.getJSONObject("poll").getString("endpoint")
|
||||
loginUrl
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ result: String? ->
|
||||
// startDisposable = Observable.fromCallable {
|
||||
// val url = URI(hostname.scheme, null, hostname.host, hostname.port, hostname.subfolder + "/index.php/login/v2", null, null).toURL()
|
||||
// val result = doRequest(url, "")
|
||||
// val loginUrl = result.getString("login")
|
||||
// this.token = result.getJSONObject("poll").getString("token")
|
||||
// this.endpoint = result.getJSONObject("poll").getString("endpoint")
|
||||
// loginUrl
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe(
|
||||
// { result: String? ->
|
||||
// val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(result))
|
||||
// context.startActivity(browserIntent)
|
||||
// poll()
|
||||
// }, { error: Throwable ->
|
||||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// this.token = null
|
||||
// this.endpoint = null
|
||||
// callback.onNextcloudAuthError(error.localizedMessage)
|
||||
// })
|
||||
|
||||
val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val url = URI(hostname.scheme, null, hostname.host, hostname.port, hostname.subfolder + "/index.php/login/v2", null, null).toURL()
|
||||
val result = doRequest(url, "")
|
||||
val loginUrl = result.getString("login")
|
||||
token = result.getJSONObject("poll").getString("token")
|
||||
endpoint = result.getJSONObject("poll").getString("endpoint")
|
||||
loginUrl
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(result))
|
||||
context.startActivity(browserIntent)
|
||||
poll()
|
||||
}, { error: Throwable ->
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
this.token = null
|
||||
this.endpoint = null
|
||||
callback.onNextcloudAuthError(error.localizedMessage)
|
||||
})
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
token = null
|
||||
endpoint = null
|
||||
callback.onNextcloudAuthError(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun poll() {
|
||||
|
|
|
@ -16,7 +16,8 @@ import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
|||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.SyncServiceEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
|
@ -24,7 +25,6 @@ import androidx.core.content.ContextCompat.getString
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.work.*
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.json.JSONArray
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
@ -53,7 +53,7 @@ import kotlin.math.min
|
|||
val lastSync = SynchronizationSettings.lastEpisodeActionSynchronizationTimestamp
|
||||
val newTimeStamp = pushEpisodeActions(this, 0L, System.currentTimeMillis())
|
||||
SynchronizationSettings.setLastEpisodeActionSynchronizationAttemptTimestamp(newTimeStamp)
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "50"))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "50"))
|
||||
sendToPeer("AllSent", "AllSent")
|
||||
|
||||
var receivedBye = false
|
||||
|
@ -63,8 +63,8 @@ import kotlin.math.min
|
|||
} catch (e: SocketTimeoutException) {
|
||||
Log.e("Guest", getString(context, R.string.sync_error_host_not_respond))
|
||||
logout()
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_error, getString(context, R.string.sync_error_host_not_respond)))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_error, getString(context, R.string.sync_error_host_not_respond)))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(false)
|
||||
return Result.failure()
|
||||
}
|
||||
|
@ -77,13 +77,13 @@ import kotlin.math.min
|
|||
} catch (e: SocketTimeoutException) {
|
||||
Log.e("Host", getString(context, R.string.sync_error_guest_not_respond))
|
||||
logout()
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_error, getString(context, R.string.sync_error_guest_not_respond)))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_error, getString(context, R.string.sync_error_guest_not_respond)))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(false)
|
||||
return Result.failure()
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "50"))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "50"))
|
||||
// TODO: not using lastSync
|
||||
val lastSync = SynchronizationSettings.lastEpisodeActionSynchronizationTimestamp
|
||||
val newTimeStamp = pushEpisodeActions(this, 0L, System.currentTimeMillis())
|
||||
|
@ -92,15 +92,15 @@ import kotlin.math.min
|
|||
}
|
||||
} else {
|
||||
logout()
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_error, "Login failure"))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_error, "Login failure"))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(false)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
logout()
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_success))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "100"))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_success))
|
||||
SynchronizationSettings.setLastSynchronizationAttemptSuccess(true)
|
||||
return Result.success()
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ import kotlin.math.min
|
|||
|
||||
@OptIn(UnstableApi::class) override fun login() {
|
||||
Logd(TAG, "serverIp: $hostIp serverPort: $hostPort $isGuest")
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "2"))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "2"))
|
||||
if (!isPortInUse(hostPort)) {
|
||||
if (isGuest) {
|
||||
val maxTries = 120
|
||||
|
@ -159,7 +159,7 @@ import kotlin.math.min
|
|||
Log.w(TAG, "port $hostPort in use, ignored")
|
||||
loginFail = true
|
||||
}
|
||||
EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "5"))
|
||||
EventFlow.postEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_in_progress, "5"))
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun isPortInUse(port: Int): Boolean {
|
||||
|
@ -236,12 +236,12 @@ import kotlin.math.min
|
|||
|
||||
override fun pushEpisodeActions(syncServiceImpl: ISyncService, lastSync: Long, newTimeStamp_: Long): Long {
|
||||
var newTimeStamp = newTimeStamp_
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_episodes_upload))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_episodes_upload))
|
||||
val queuedEpisodeActions: MutableList<EpisodeAction> = synchronizationQueueStorage.queuedEpisodeActions
|
||||
Logd(TAG, "pushEpisodeActions queuedEpisodeActions: ${queuedEpisodeActions.size}")
|
||||
|
||||
if (lastSync == 0L) {
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_upload_played))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_upload_played))
|
||||
// only push downloaded items
|
||||
val pausedItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PAUSED), SortOrder.DATE_NEW_OLD)
|
||||
val readItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PLAYED), SortOrder.DATE_NEW_OLD)
|
||||
|
@ -368,7 +368,7 @@ import kotlin.math.min
|
|||
|
||||
// Give it some time, so other possible actions can be queued.
|
||||
builder.setInitialDelay(20L, TimeUnit.SECONDS)
|
||||
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_started))
|
||||
EventFlow.postStickyEvent(FlowEvent.SyncServiceEvent(R.string.sync_status_started))
|
||||
|
||||
val workRequest: OneTimeWorkRequest = builder.setInitialDelay(0L, TimeUnit.SECONDS).build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(hostIp_, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
|
|
|
@ -13,9 +13,8 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
|||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackServiceEvent
|
||||
import ac.mdiq.podcini.util.event.playback.SpeedChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
|
@ -23,10 +22,10 @@ import android.util.Log
|
|||
import android.util.Pair
|
||||
import android.view.SurfaceHolder
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Communicates with the playback service. GUI classes should use this class to
|
||||
|
@ -64,16 +63,22 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
@Synchronized
|
||||
fun init() {
|
||||
if (!eventsRegistered) {
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
eventsRegistered = true
|
||||
}
|
||||
if (PlaybackService.isRunning) initServiceRunning()
|
||||
else updatePlayButtonShowsPlay(true)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackServiceEvent) {
|
||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_STARTED) init()
|
||||
private fun procFlowEvents() {
|
||||
activity.lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED) init()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -118,7 +123,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
released = true
|
||||
|
||||
if (eventsRegistered) {
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
eventsRegistered = false
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +319,8 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
if (media is FeedMedia) {
|
||||
media!!.setPosition(time)
|
||||
DBWriter.persistFeedItem((media as FeedMedia).item)
|
||||
EventBus.getDefault().post(PlaybackPositionEvent(time, media!!.getDuration()))
|
||||
EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(time, media!!.getDuration()))
|
||||
// EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(time, media!!.getDuration()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +390,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
|
|||
if (playbackService != null) playbackService!!.setSpeed(speed, codeArray)
|
||||
else {
|
||||
UserPreferences.setPlaybackSpeed(speed)
|
||||
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
||||
EventFlow.postEvent(FlowEvent.SpeedChangedEvent(speed))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,8 @@ import ac.mdiq.podcini.storage.model.playback.Playable
|
|||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.NetworkUtils.wasDownloadBlocked
|
||||
import ac.mdiq.podcini.util.config.ClientConfig
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.playback.SpeedChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.UiModeManager
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
|
@ -49,7 +48,6 @@ import androidx.media3.extractor.mp3.Mp3Extractor
|
|||
import androidx.media3.ui.DefaultTrackNameProvider
|
||||
import androidx.media3.ui.TrackNameProvider
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.Runnable
|
||||
|
@ -87,6 +85,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
private var mediaSource: MediaSource? = null
|
||||
private var playbackParameters: PlaybackParameters
|
||||
|
||||
private var bufferedPercentagePrev = 0
|
||||
|
||||
private val formats: List<Format>
|
||||
get() {
|
||||
val formats: MutableList<Format> = arrayListOf()
|
||||
|
@ -297,11 +297,11 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
setPlayerStatus(PlayerStatus.ERROR, null)
|
||||
EventBus.getDefault().postSticky(PlayerErrorEvent(e.localizedMessage ?: ""))
|
||||
EventFlow.postStickyEvent(FlowEvent.PlayerErrorEvent(e.localizedMessage ?: ""))
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
setPlayerStatus(PlayerStatus.ERROR, null)
|
||||
EventBus.getDefault().postSticky(PlayerErrorEvent(e.localizedMessage ?: ""))
|
||||
EventFlow.postStickyEvent(FlowEvent.PlayerErrorEvent(e.localizedMessage ?: ""))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,7 +509,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
override fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
|
||||
EventBus.getDefault().post(SpeedChangedEvent(speed))
|
||||
EventFlow.postEvent(FlowEvent.SpeedChangedEvent(speed))
|
||||
Logd(TAG, "setPlaybackParams speed=$speed pitch=${playbackParameters.pitch} skipSilence=$skipSilence")
|
||||
playbackParameters = PlaybackParameters(speed, playbackParameters.pitch)
|
||||
exoPlayer!!.skipSilenceEnabled = skipSilence
|
||||
|
@ -679,7 +679,10 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
while (true) {
|
||||
delay(bufferUpdateInterval)
|
||||
withContext(Dispatchers.Main) {
|
||||
bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage)
|
||||
if (bufferedPercentagePrev != exoPlayer!!.bufferedPercentage) {
|
||||
bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage)
|
||||
bufferedPercentagePrev = exoPlayer!!.bufferedPercentage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -754,14 +757,14 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
audioSeekCompleteListener = Runnable { this.genericSeekCompleteListener() }
|
||||
bufferingUpdateListener = Consumer<Int> { percent: Int ->
|
||||
when (percent) {
|
||||
BUFFERING_STARTED -> EventBus.getDefault().post(BufferUpdateEvent.started())
|
||||
BUFFERING_ENDED -> EventBus.getDefault().post(BufferUpdateEvent.ended())
|
||||
else -> EventBus.getDefault().post(BufferUpdateEvent.progressUpdate(0.01f * percent))
|
||||
BUFFERING_STARTED -> EventFlow.postEvent(FlowEvent.BufferUpdateEvent.started())
|
||||
BUFFERING_ENDED -> EventFlow.postEvent(FlowEvent.BufferUpdateEvent.ended())
|
||||
else -> EventFlow.postEvent(FlowEvent.BufferUpdateEvent.progressUpdate(0.01f * percent))
|
||||
}
|
||||
}
|
||||
audioErrorListener = Consumer<String> { message: String ->
|
||||
Log.e(TAG, "PlayerErrorEvent: $message")
|
||||
EventBus.getDefault().postSticky(PlayerErrorEvent(message))
|
||||
EventFlow.postStickyEvent(FlowEvent.PlayerErrorEvent(message))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,12 +57,8 @@ import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed
|
|||
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.playback.*
|
||||
import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent
|
||||
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
|
@ -93,11 +89,9 @@ import com.google.common.util.concurrent.Futures
|
|||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
import kotlin.concurrent.Volatile
|
||||
import kotlin.math.max
|
||||
|
@ -197,7 +191,7 @@ class PlaybackService : MediaSessionService() {
|
|||
registerReceiver(headsetDisconnected, IntentFilter(Intent.ACTION_HEADSET_PLUG))
|
||||
registerReceiver(bluetoothStateUpdated, IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
|
||||
registerReceiver(audioBecomingNoisy, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
taskManager = PlaybackServiceTaskManager(this, taskManagerCallback)
|
||||
|
||||
recreateMediaSessionIfNeeded()
|
||||
|
@ -207,7 +201,7 @@ class PlaybackService : MediaSessionService() {
|
|||
recreateMediaPlayer()
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_STARTED))
|
||||
EventFlow.postEvent(FlowEvent.PlaybackServiceEvent(FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED))
|
||||
}
|
||||
|
||||
fun recreateMediaSessionIfNeeded() {
|
||||
|
@ -277,7 +271,7 @@ class PlaybackService : MediaSessionService() {
|
|||
unregisterReceiver(bluetoothStateUpdated)
|
||||
unregisterReceiver(audioBecomingNoisy)
|
||||
taskManager.shutdown()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
fun isServiceReady(): Boolean {
|
||||
|
@ -469,10 +463,11 @@ class PlaybackService : MediaSessionService() {
|
|||
|
||||
@SuppressLint("LaunchActivityFromNotification")
|
||||
private fun displayStreamingNotAllowedNotification(originalIntent: Intent) {
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) {
|
||||
EventBus.getDefault().post(MessageEvent(getString(R.string.confirm_mobile_streaming_notification_message)))
|
||||
return
|
||||
}
|
||||
// TODO
|
||||
// if (EventBus.getDefault().hasSubscriberForEvent(FlowEvent.MessageEvent::class.java)) {
|
||||
// EventFlow.postEvent(FlowEvent.MessageEvent(getString(R.string.confirm_mobile_streaming_notification_message)))
|
||||
// return
|
||||
// }
|
||||
|
||||
val intentAllowThisTime = Intent(originalIntent)
|
||||
intentAllowThisTime.setAction(PlaybackServiceConstants.EXTRA_ALLOW_STREAM_THIS_TIME)
|
||||
|
@ -666,7 +661,8 @@ class PlaybackService : MediaSessionService() {
|
|||
override fun positionSaverTick() {
|
||||
if (currentPosition != previousPosition) {
|
||||
// Log.d(TAG, "positionSaverTick currentPosition: $currentPosition, currentPlaybackSpeed: $currentPlaybackSpeed")
|
||||
EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, duration))
|
||||
EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(currentPosition, duration))
|
||||
// EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(currentPosition, duration))
|
||||
skipEndingIfNecessary()
|
||||
saveCurrentPosition(true, null, Playable.INVALID_TIME)
|
||||
previousPosition = currentPosition
|
||||
|
@ -718,7 +714,7 @@ class PlaybackService : MediaSessionService() {
|
|||
|
||||
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING && autoEnable() && autoEnableByTime && !sleepTimerActive()) {
|
||||
setSleepTimer(timerMillis())
|
||||
EventBus.getDefault().post(MessageEvent(getString(R.string.sleep_timer_enabled_label), { disableSleepTimer() }, getString(R.string.undo)))
|
||||
EventFlow.postEvent(FlowEvent.MessageEvent(getString(R.string.sleep_timer_enabled_label), { disableSleepTimer() }, getString(R.string.undo)))
|
||||
}
|
||||
// loadQueueForMediaSession()
|
||||
}
|
||||
|
@ -784,16 +780,29 @@ class PlaybackService : MediaSessionService() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun playerError(event: PlayerErrorEvent?) {
|
||||
private fun procFlowEvents() {
|
||||
scope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.PlayerErrorEvent -> playerError(event)
|
||||
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> sleepTimerUpdate(event)
|
||||
is FlowEvent.VolumeAdaptionChangedEvent -> volumeAdaptionChanged(event)
|
||||
is FlowEvent.SpeedPresetChangedEvent -> onSpeedPresetChanged(event)
|
||||
is FlowEvent.SkipIntroEndingChangedEvent -> skipIntroEndingPresetChanged(event)
|
||||
is FlowEvent.StartPlayEvent -> currentitem = event.item
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun playerError(event: FlowEvent.PlayerErrorEvent) {
|
||||
if (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK)
|
||||
mediaPlayer!!.pause(abandonFocus = true, reinit = false)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
||||
fun bufferUpdate(event: FlowEvent.BufferUpdateEvent) {
|
||||
if (event.hasEnded()) {
|
||||
val playable = playable
|
||||
if (this.playable is FeedMedia && playable!!.getDuration() <= 0 && (mediaPlayer?.getDuration()?:0) > 0) {
|
||||
|
@ -804,9 +813,7 @@ class PlaybackService : MediaSessionService() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
||||
fun sleepTimerUpdate(event: FlowEvent.SleepTimerUpdatedEvent) {
|
||||
when {
|
||||
event.isOver -> {
|
||||
mediaPlayer?.pause(abandonFocus = true, reinit = true)
|
||||
|
@ -856,7 +863,7 @@ class PlaybackService : MediaSessionService() {
|
|||
writeNoMediaPlaying()
|
||||
return null
|
||||
}
|
||||
EventBus.getDefault().post(StartPlayEvent(nextItem))
|
||||
EventFlow.postEvent(FlowEvent.StartPlayEvent(nextItem))
|
||||
return nextItem.media
|
||||
}
|
||||
|
||||
|
@ -1157,20 +1164,16 @@ class PlaybackService : MediaSessionService() {
|
|||
private val shutdownReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (TextUtils.equals(intent.action, PlaybackServiceConstants.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
|
||||
EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN))
|
||||
EventFlow.postEvent(FlowEvent.PlaybackServiceEvent(FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN))
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun volumeAdaptionChanged(event: VolumeAdaptionChangedEvent) {
|
||||
fun volumeAdaptionChanged(event: FlowEvent.VolumeAdaptionChangedEvent) {
|
||||
val playbackVolumeUpdater = PlaybackVolumeUpdater()
|
||||
if (mediaPlayer != null) playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, event.feedId, event.volumeAdaptionSetting)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun onSpeedPresetChanged(event: SpeedPresetChangedEvent) {
|
||||
fun onSpeedPresetChanged(event: FlowEvent.SpeedPresetChangedEvent) {
|
||||
val item = (playable as? FeedMedia)?.item ?: currentitem
|
||||
if (item?.feed?.id == event.feedId) {
|
||||
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) setSpeed(getPlaybackSpeed(playable!!.getMediaType()))
|
||||
|
@ -1178,9 +1181,7 @@ class PlaybackService : MediaSessionService() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun skipIntroEndingPresetChanged(event: SkipIntroEndingChangedEvent) {
|
||||
fun skipIntroEndingPresetChanged(event: FlowEvent.SkipIntroEndingChangedEvent) {
|
||||
val item = (playable as? FeedMedia)?.item ?: currentitem
|
||||
// if (playable is FeedMedia) {
|
||||
if (item?.feed?.id == event.feedId) {
|
||||
|
@ -1194,12 +1195,6 @@ class PlaybackService : MediaSessionService() {
|
|||
// }
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEvenStartPlay(event: StartPlayEvent) {
|
||||
Logd(TAG, "onEvenStartPlay ${event.item.title}")
|
||||
currentitem = event.item
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
mediaPlayer?.resume()
|
||||
taskManager.restartSleepTimer()
|
||||
|
@ -1244,7 +1239,7 @@ class PlaybackService : MediaSessionService() {
|
|||
feedPreferences.feedPlaybackSpeed = speed
|
||||
Logd(TAG, "setSpeed ${feed.title} $speed")
|
||||
DBWriter.persistFeedPreferences(feedPreferences)
|
||||
EventBus.getDefault().post(SpeedPresetChangedEvent(feedPreferences.feedPlaybackSpeed, feed.id))
|
||||
EventFlow.postEvent(FlowEvent.SpeedPresetChangedEvent(feedPreferences.feedPlaybackSpeed, feed.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1288,7 +1283,8 @@ class PlaybackService : MediaSessionService() {
|
|||
|
||||
fun seekTo(t: Int) {
|
||||
mediaPlayer?.seekTo(t)
|
||||
EventBus.getDefault().post(PlaybackPositionEvent(t, duration))
|
||||
EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(t, duration))
|
||||
// EventFlow.postEvent(FlowEvent.PlaybackPositionEvent(t, duration))
|
||||
}
|
||||
|
||||
fun setAudioTrack(track: Int) {
|
||||
|
|
|
@ -6,7 +6,8 @@ import ac.mdiq.podcini.ui.widget.WidgetUpdater
|
|||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.ChapterUtils
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -16,7 +17,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -149,7 +149,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb
|
|||
if (isSleepTimerActive) sleepTimerFuture!!.cancel(true)
|
||||
sleepTimer = SleepTimer(waitingTime)
|
||||
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS)
|
||||
EventBus.getDefault().post(SleepTimerUpdatedEvent.justEnabled(waitingTime))
|
||||
EventFlow.postEvent(FlowEvent.SleepTimerUpdatedEvent.justEnabled(waitingTime))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,7 +263,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb
|
|||
override fun run() {
|
||||
Logd(TAG, "Starting SleepTimer")
|
||||
var lastTick = System.currentTimeMillis()
|
||||
EventBus.getDefault().post(SleepTimerUpdatedEvent.updated(timeLeft))
|
||||
EventFlow.postEvent(FlowEvent.SleepTimerUpdatedEvent.updated(timeLeft))
|
||||
while (timeLeft > 0) {
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVAL)
|
||||
|
@ -277,7 +277,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb
|
|||
timeLeft -= now - lastTick
|
||||
lastTick = now
|
||||
|
||||
EventBus.getDefault().post(SleepTimerUpdatedEvent.updated(timeLeft))
|
||||
EventFlow.postEvent(FlowEvent.SleepTimerUpdatedEvent.updated(timeLeft))
|
||||
if (timeLeft < NOTIFICATION_THRESHOLD) {
|
||||
Logd(TAG, "Sleep timer is about to expire")
|
||||
if (SleepTimerPreferences.vibrate() && !hasVibrated) {
|
||||
|
@ -304,7 +304,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb
|
|||
}
|
||||
|
||||
fun restart() {
|
||||
EventBus.getDefault().post(SleepTimerUpdatedEvent.cancelled())
|
||||
EventFlow.postEvent(FlowEvent.SleepTimerUpdatedEvent.cancelled())
|
||||
setSleepTimer(waitingTime)
|
||||
shakeListener?.pause()
|
||||
shakeListener = null
|
||||
|
@ -314,7 +314,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb
|
|||
sleepTimerFuture!!.cancel(true)
|
||||
shakeListener?.pause()
|
||||
|
||||
EventBus.getDefault().post(SleepTimerUpdatedEvent.cancelled())
|
||||
EventFlow.postEvent(FlowEvent.SleepTimerUpdatedEvent.cancelled())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@ import ac.mdiq.podcini.storage.model.feed.FeedPreferences
|
|||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.storage.model.playback.Playable
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.PlayerStatusEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
/**
|
||||
* Provides access to preferences set by the playback service. A private
|
||||
|
@ -24,7 +24,7 @@ import org.greenrobot.eventbus.EventBus
|
|||
class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListener {
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
if (PREF_CURRENT_PLAYER_STATUS == key) EventBus.getDefault().post(PlayerStatusEvent())
|
||||
if (PREF_CURRENT_PLAYER_STATUS == key) EventFlow.postEvent(FlowEvent.PlayerStatusEvent())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -56,6 +56,7 @@ object UserPreferences {
|
|||
const val PREF_QUEUE_KEEP_SORTED_ORDER: String = "prefQueueKeepSortedOrder"
|
||||
const val PREF_NEW_EPISODES_ACTION: String = "prefNewEpisodesAction" // not used
|
||||
private const val PREF_DOWNLOADS_SORTED_ORDER = "prefDownloadSortedOrder"
|
||||
private const val PREF_HISTORY_SORTED_ORDER = "prefHistorySortedOrder"
|
||||
private const val PREF_INBOX_SORTED_ORDER = "prefInboxSortedOrder"
|
||||
|
||||
// Episode
|
||||
|
@ -835,16 +836,23 @@ object UserPreferences {
|
|||
}
|
||||
|
||||
// @JvmStatic
|
||||
// var inboxSortedOrder: SortOrder?
|
||||
// var historySortedOrder: SortOrder?
|
||||
// /**
|
||||
// * Returns the sort order for the downloads.
|
||||
// */
|
||||
// get() {
|
||||
// val sortOrderStr = prefs.getString(PREF_INBOX_SORTED_ORDER, "" + SortOrder.DATE_NEW_OLD.code)
|
||||
// val sortOrderStr = prefs.getString(PREF_HISTORY_SORTED_ORDER, "" + SortOrder.PLAYED_DATE_NEW_OLD.code)
|
||||
// return SortOrder.fromCodeString(sortOrderStr)
|
||||
// }
|
||||
// /**
|
||||
// * Sets the sort order for the downloads.
|
||||
// */
|
||||
// set(sortOrder) {
|
||||
// prefs.edit().putString(PREF_INBOX_SORTED_ORDER, "" + sortOrder!!.code).apply()
|
||||
// prefs.edit().putString(PREF_HISTORY_SORTED_ORDER, "" + sortOrder!!.code).apply()
|
||||
// }
|
||||
|
||||
// @JvmStatic
|
||||
|
||||
@JvmStatic
|
||||
var subscriptionsFilter: SubscriptionsFilter
|
||||
get() {
|
||||
val value = prefs.getString(PREF_FILTER_FEED, "")
|
||||
|
|
|
@ -2,6 +2,7 @@ package ac.mdiq.podcini.preferences.fragments
|
|||
|
||||
import ac.mdiq.podcini.PodciniApp.Companion.forceRestart
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.DatabaseTransporter
|
||||
import ac.mdiq.podcini.storage.PreferencesTransporter
|
||||
import ac.mdiq.podcini.storage.asynctask.DocumentFileExportWorker
|
||||
|
@ -12,6 +13,8 @@ import ac.mdiq.podcini.storage.export.html.HtmlWriter
|
|||
import ac.mdiq.podcini.storage.export.opml.OpmlWriter
|
||||
import ac.mdiq.podcini.ui.activity.OpmlImportActivity
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
|
@ -29,6 +32,7 @@ import androidx.annotation.StringRes
|
|||
import androidx.core.app.ShareCompat.IntentBuilder
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -37,6 +41,10 @@ import io.reactivex.Completable
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -245,38 +253,80 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
progressDialog!!.show()
|
||||
disposable = Completable.fromAction { DatabaseTransporter.importBackup(uri, requireContext()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
showDatabaseImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
// disposable = Completable.fromAction { DatabaseTransporter.importBackup(uri, requireContext()) }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({
|
||||
// showDatabaseImportSuccessDialog()
|
||||
// progressDialog!!.dismiss()
|
||||
// }, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
DatabaseTransporter.importBackup(uri, requireContext())
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
showDatabaseImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showExportErrorDialog(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restorePreferencesResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data?.data == null) return
|
||||
val uri = result.data!!.data!!
|
||||
progressDialog!!.show()
|
||||
disposable = Completable.fromAction { PreferencesTransporter.importBackup(uri, requireContext()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
showDatabaseImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
// disposable = Completable.fromAction { PreferencesTransporter.importBackup(uri, requireContext()) }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({
|
||||
// showDatabaseImportSuccessDialog()
|
||||
// progressDialog!!.dismiss()
|
||||
// }, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
PreferencesTransporter.importBackup(uri, requireContext())
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
showDatabaseImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showExportErrorDialog(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun backupDatabaseResult(uri: Uri?) {
|
||||
if (uri == null) return
|
||||
progressDialog!!.show()
|
||||
disposable = Completable.fromAction { DatabaseTransporter.exportToDocument(uri, requireContext()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
showExportSuccessSnackbar(uri, "application/x-sqlite3")
|
||||
progressDialog!!.dismiss()
|
||||
}, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
// disposable = Completable.fromAction { DatabaseTransporter.exportToDocument(uri, requireContext()) }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({
|
||||
// showExportSuccessSnackbar(uri, "application/x-sqlite3")
|
||||
// progressDialog!!.dismiss()
|
||||
// }, { error: Throwable -> this.showExportErrorDialog(error) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
DatabaseTransporter.exportToDocument(uri, requireContext())
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
showExportSuccessSnackbar(uri, "application/x-sqlite3")
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showExportErrorDialog(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseOpmlImportPathResult(uri: Uri?) {
|
||||
|
|
|
@ -6,7 +6,8 @@ import ac.mdiq.podcini.preferences.UsageStatistics.doNotAskAgain
|
|||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.dialog.*
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -16,7 +17,6 @@ import androidx.media3.common.util.UnstableApi
|
|||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
@ -61,7 +61,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
findPreference<Preference>(PREF_PLAYBACK_PREFER_STREAMING)!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, _: Any? ->
|
||||
// Update all visible lists to reflect new streaming action button
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
// User consciously decided whether to prefer the streaming button, disable suggestion to change that
|
||||
doNotAskAgain(UsageStatistics.ACTION_STREAM)
|
||||
true
|
||||
|
|
|
@ -7,8 +7,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
|
|||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.dialog.DrawerPreferencesDialog
|
||||
import ac.mdiq.podcini.ui.dialog.FeedSortDialog
|
||||
import ac.mdiq.podcini.util.event.PlayerStatusEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Build
|
||||
|
@ -19,7 +19,6 @@ import androidx.preference.Preference
|
|||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class UserInterfacePreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
|
@ -45,8 +44,8 @@ class UserInterfacePreferencesFragment : PreferenceFragmentCompat() {
|
|||
|
||||
findPreference<Preference>(UserPreferences.PREF_SHOW_TIME_LEFT)?.setOnPreferenceChangeListener { _: Preference?, newValue: Any? ->
|
||||
setShowRemainTimeSetting(newValue as Boolean?)
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventBus.getDefault().post(PlayerStatusEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.PlayerStatusEvent())
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,47 +1,37 @@
|
|||
package ac.mdiq.podcini.preferences.fragments.about
|
||||
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import android.R.color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.ListFragment
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class DevelopersFragment : ListFragment() {
|
||||
private var developersLoader: Disposable? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
listView.divider = null
|
||||
listView.setSelector(color.transparent)
|
||||
|
||||
developersLoader = Single.create { emitter: SingleEmitter<ArrayList<SimpleIconListAdapter.ListItem>?> ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val developers = ArrayList<SimpleIconListAdapter.ListItem>()
|
||||
val reader = BufferedReader(InputStreamReader(requireContext().assets.open("developers.csv"), "UTF-8"))
|
||||
var line: String
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
var line = ""
|
||||
while ((reader.readLine()?.also { line = it }) != null) {
|
||||
val info = line.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
developers.add(SimpleIconListAdapter.ListItem(info[0], info[2], "https://avatars2.githubusercontent.com/u/" + info[1] + "?s=60&v=4"))
|
||||
}
|
||||
emitter.onSuccess(developers)
|
||||
withContext(Dispatchers.Main) {
|
||||
listAdapter = SimpleIconListAdapter(requireContext(), developers) }
|
||||
}.invokeOnCompletion { throwable ->
|
||||
if (throwable != null) Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ developers: ArrayList<SimpleIconListAdapter.ListItem>? ->
|
||||
if (developers != null) listAdapter = SimpleIconListAdapter(requireContext(), developers)
|
||||
}, { error: Throwable -> Toast.makeText(context, error.message, Toast.LENGTH_LONG).show() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
developersLoader?.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
package ac.mdiq.podcini.preferences.fragments.about
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ListView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.ListFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
class LicensesFragment : ListFragment() {
|
||||
private var licensesLoader: Disposable? = null
|
||||
private val licenses = ArrayList<LicenseItem>()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
listView.divider = null
|
||||
|
||||
licensesLoader = Single.create { emitter: SingleEmitter<ArrayList<LicenseItem>?> ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
licenses.clear()
|
||||
val stream = requireContext().assets.open("licenses.xml")
|
||||
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
|
@ -40,14 +38,14 @@ class LicensesFragment : ListFragment() {
|
|||
String.format("By %s, %s license", lib.getNamedItem("author").textContent, lib.getNamedItem("license").textContent),
|
||||
"", lib.getNamedItem("website").textContent, lib.getNamedItem("licenseText").textContent))
|
||||
}
|
||||
emitter.onSuccess(licenses)
|
||||
withContext(Dispatchers.Main) {
|
||||
listAdapter = SimpleIconListAdapter(requireContext(), licenses)
|
||||
}
|
||||
}.invokeOnCompletion { throwable ->
|
||||
if (throwable!= null) {
|
||||
Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ developers: ArrayList<LicenseItem>? -> if (developers != null) listAdapter = SimpleIconListAdapter(requireContext(), developers) },
|
||||
{ error: Throwable -> Toast.makeText(context, error.message, Toast.LENGTH_LONG).show() }
|
||||
)
|
||||
}
|
||||
|
||||
private class LicenseItem(title: String, subtitle: String, imageUrl: String, val licenseUrl: String, val licenseTextFile: String)
|
||||
|
@ -72,8 +70,8 @@ class LicensesFragment : ListFragment() {
|
|||
try {
|
||||
val reader = BufferedReader(InputStreamReader(requireContext().assets.open(licenseTextFile), "UTF-8"))
|
||||
val licenseText = StringBuilder()
|
||||
var line: String?
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
var line = ""
|
||||
while ((reader.readLine()?.also { line = it }) != null) {
|
||||
licenseText.append(line).append("\n")
|
||||
}
|
||||
|
||||
|
@ -85,11 +83,6 @@ class LicensesFragment : ListFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
licensesLoader?.dispose()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.licenses)
|
||||
|
|
|
@ -1,47 +1,40 @@
|
|||
package ac.mdiq.podcini.preferences.fragments.about
|
||||
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import android.R.color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.ListFragment
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class SpecialThanksFragment : ListFragment() {
|
||||
private var translatorsLoader: Disposable? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
listView.divider = null
|
||||
listView.setSelector(color.transparent)
|
||||
|
||||
translatorsLoader = Single.create { emitter: SingleEmitter<ArrayList<SimpleIconListAdapter.ListItem>?> ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val translators = ArrayList<SimpleIconListAdapter.ListItem>()
|
||||
val reader = BufferedReader(InputStreamReader(requireContext().assets.open("special_thanks.csv"), "UTF-8"))
|
||||
var line: String
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
var line = ""
|
||||
while ((reader.readLine()?.also { line = it }) != null) {
|
||||
val info = line.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
translators.add(SimpleIconListAdapter.ListItem(info[0], info[1], info[2]))
|
||||
}
|
||||
emitter.onSuccess(translators)
|
||||
withContext(Dispatchers.Main) {
|
||||
listAdapter = SimpleIconListAdapter(requireContext(), translators)
|
||||
}
|
||||
}.invokeOnCompletion { throwable ->
|
||||
if (throwable!= null) {
|
||||
Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ translators: ArrayList<SimpleIconListAdapter.ListItem>? ->
|
||||
if (translators != null) listAdapter = SimpleIconListAdapter(requireContext(), translators)
|
||||
}, { error: Throwable -> Toast.makeText(context, error.message, Toast.LENGTH_LONG).show() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
translatorsLoader?.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,43 +6,37 @@ import android.view.View
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.ListFragment
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleIconListAdapter
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class TranslatorsFragment : ListFragment() {
|
||||
private var translatorsLoader: Disposable? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
listView.divider = null
|
||||
listView.setSelector(color.transparent)
|
||||
|
||||
translatorsLoader = Single.create { emitter: SingleEmitter<ArrayList<SimpleIconListAdapter.ListItem>?> ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val translators = ArrayList<SimpleIconListAdapter.ListItem>()
|
||||
val reader = BufferedReader(InputStreamReader(requireContext().assets.open("translators.csv"), "UTF-8"))
|
||||
var line: String
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
var line = ""
|
||||
while ((reader.readLine()?.also { line = it }) != null) {
|
||||
val info = line.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
translators.add(SimpleIconListAdapter.ListItem(info[0], info[1], ""))
|
||||
}
|
||||
emitter.onSuccess(translators)
|
||||
withContext(Dispatchers.Main) {
|
||||
listAdapter = SimpleIconListAdapter(requireContext(), translators) }
|
||||
}.invokeOnCompletion { throwable ->
|
||||
if (throwable != null) Toast.makeText(context, throwable.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ translators: ArrayList<SimpleIconListAdapter.ListItem>? ->
|
||||
if (translators != null) listAdapter = SimpleIconListAdapter(requireContext(), translators)
|
||||
},
|
||||
{ error: Throwable -> Toast.makeText(context, error.message, Toast.LENGTH_LONG).show() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
translatorsLoader?.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.TextView
|
||||
import android.widget.ViewFlipper
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
|
@ -109,25 +109,48 @@ class GpodderAuthenticationFragment : DialogFragment() {
|
|||
txtvError.visibility = View.GONE
|
||||
val inputManager = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputManager.hideSoftInputFromWindow(login.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
Completable.fromAction {
|
||||
service?.setCredentials(usernameStr, passwordStr)
|
||||
service?.login()
|
||||
if (service != null) devices = service!!.devices
|
||||
this@GpodderAuthenticationFragment.username = usernameStr
|
||||
this@GpodderAuthenticationFragment.password = passwordStr
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
|
||||
// Completable.fromAction {
|
||||
// service?.setCredentials(usernameStr, passwordStr)
|
||||
// service?.login()
|
||||
// if (service != null) devices = service!!.devices
|
||||
// this@GpodderAuthenticationFragment.username = usernameStr
|
||||
// this@GpodderAuthenticationFragment.password = passwordStr
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({
|
||||
// login.isEnabled = true
|
||||
// progressBar.visibility = View.GONE
|
||||
// advance()
|
||||
// }, { error: Throwable ->
|
||||
// login.isEnabled = true
|
||||
// progressBar.visibility = View.GONE
|
||||
// txtvError.text = error.cause!!.message
|
||||
// txtvError.visibility = View.VISIBLE
|
||||
// })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
service?.setCredentials(usernameStr, passwordStr)
|
||||
service?.login()
|
||||
if (service != null) devices = service!!.devices
|
||||
this@GpodderAuthenticationFragment.username = usernameStr
|
||||
this@GpodderAuthenticationFragment.password = passwordStr
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
login.isEnabled = true
|
||||
progressBar.visibility = View.GONE
|
||||
advance()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
login.isEnabled = true
|
||||
progressBar.visibility = View.GONE
|
||||
advance()
|
||||
}, { error: Throwable ->
|
||||
login.isEnabled = true
|
||||
progressBar.visibility = View.GONE
|
||||
txtvError.text = error.cause!!.message
|
||||
txtvError.text = e.cause!!.message
|
||||
txtvError.visibility = View.VISIBLE
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,23 +189,43 @@ class GpodderAuthenticationFragment : DialogFragment() {
|
|||
txtvError.visibility = View.GONE
|
||||
deviceName.isEnabled = false
|
||||
|
||||
Observable.fromCallable {
|
||||
val deviceId = generateDeviceId(deviceNameStr)
|
||||
service!!.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE)
|
||||
GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ device: GpodnetDevice? ->
|
||||
progBarCreateDevice.visibility = View.GONE
|
||||
selectedDevice = device
|
||||
advance()
|
||||
}, { error: Throwable ->
|
||||
// Observable.fromCallable {
|
||||
// val deviceId = generateDeviceId(deviceNameStr)
|
||||
// service!!.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE)
|
||||
// GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0)
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ device: GpodnetDevice? ->
|
||||
// progBarCreateDevice.visibility = View.GONE
|
||||
// selectedDevice = device
|
||||
// advance()
|
||||
// }, { error: Throwable ->
|
||||
// deviceName.isEnabled = true
|
||||
// progBarCreateDevice.visibility = View.GONE
|
||||
// txtvError.text = error.message
|
||||
// txtvError.visibility = View.VISIBLE
|
||||
// })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val device = withContext(Dispatchers.IO) {
|
||||
val deviceId = generateDeviceId(deviceNameStr)
|
||||
service!!.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE)
|
||||
GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
progBarCreateDevice.visibility = View.GONE
|
||||
selectedDevice = device
|
||||
advance()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
deviceName.isEnabled = true
|
||||
progBarCreateDevice.visibility = View.GONE
|
||||
txtvError.text = error.message
|
||||
txtvError.text = e.message
|
||||
txtvError.visibility = View.VISIBLE
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateDeviceName(): String {
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
package ac.mdiq.podcini.preferences.fragments.synchronization
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.AlertdialogSyncProviderChooserBinding
|
||||
import ac.mdiq.podcini.net.sync.SyncService
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationCredentials
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationProviderViewData
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -12,24 +24,13 @@ import android.widget.ImageView
|
|||
import android.widget.ListAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.AlertdialogSyncProviderChooserBinding
|
||||
import ac.mdiq.podcini.ui.activity.PreferenceActivity
|
||||
import ac.mdiq.podcini.net.sync.SyncService
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationCredentials
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationProviderViewData
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
|
||||
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
|
||||
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
||||
import ac.mdiq.podcini.util.event.SyncServiceEvent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SynchronizationPreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
|
@ -43,17 +44,27 @@ class SynchronizationPreferencesFragment : PreferenceFragmentCompat() {
|
|||
super.onStart()
|
||||
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.synchronization_pref)
|
||||
updateScreen()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
(activity as PreferenceActivity).supportActionBar!!.subtitle = ""
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun syncStatusChanged(event: SyncServiceEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun syncStatusChanged(event: FlowEvent.SyncServiceEvent) {
|
||||
if (!isProviderConnected && !wifiSyncEnabledKey) return
|
||||
|
||||
updateScreen()
|
||||
|
|
|
@ -7,23 +7,23 @@ import ac.mdiq.podcini.net.sync.SynchronizationSettings.setWifiSyncEnabled
|
|||
import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.hostPort
|
||||
import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.startInstantSync
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.SyncServiceEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Dialog
|
||||
import android.content.Context.WIFI_SERVICE
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
@OptIn(UnstableApi::class) class WifiAuthenticationFragment : DialogFragment() {
|
||||
|
@ -67,7 +67,7 @@ import java.util.*
|
|||
isGuest = false
|
||||
SynchronizationCredentials.hostport = portNum
|
||||
}
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
return dialog.create()
|
||||
}
|
||||
|
||||
|
@ -93,8 +93,18 @@ import java.util.*
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun syncStatusChanged(event: SyncServiceEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun syncStatusChanged(event: FlowEvent.SyncServiceEvent) {
|
||||
when (event.messageResId) {
|
||||
R.string.sync_status_error -> {
|
||||
Toast.makeText(requireContext(), event.message, Toast.LENGTH_LONG).show()
|
||||
|
|
|
@ -12,6 +12,7 @@ import ac.mdiq.podcini.storage.model.download.DownloadResult
|
|||
import ac.mdiq.podcini.storage.model.feed.*
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter.Companion.unfiltered
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedPreferences.Companion.TAG_ROOT
|
||||
import ac.mdiq.podcini.util.FeedItemPermutors
|
||||
import ac.mdiq.podcini.util.FeedItemPermutors.getPermutor
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.LongList
|
||||
|
@ -19,6 +20,7 @@ import ac.mdiq.podcini.util.comparator.DownloadResultComparator
|
|||
import ac.mdiq.podcini.util.comparator.PlaybackCompletionDateComparator
|
||||
import android.database.Cursor
|
||||
import android.util.Log
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
|
@ -369,7 +371,7 @@ object DBReader {
|
|||
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPlaybackHistory(offset: Int, limit: Int): List<FeedItem> {
|
||||
fun getPlaybackHistory(offset: Int, limit: Int, start: Long = 0L, end: Long = Date().time, sortOrder: SortOrder = SortOrder.PLAYED_DATE_NEW_OLD): List<FeedItem> {
|
||||
Logd(TAG, "getPlaybackHistory() called")
|
||||
|
||||
val adapter = getInstance()
|
||||
|
@ -378,7 +380,7 @@ object DBReader {
|
|||
var mediaCursor: Cursor? = null
|
||||
var itemCursor: Cursor? = null
|
||||
try {
|
||||
mediaCursor = adapter.getCompletedMediaCursor(offset, limit)
|
||||
mediaCursor = adapter.getPlayedMediaCursor(offset, limit, start, end)
|
||||
val itemIds = arrayOfNulls<String>(mediaCursor.count)
|
||||
var i = 0
|
||||
while (i < itemIds.size && mediaCursor.moveToPosition(i)) {
|
||||
|
@ -389,7 +391,7 @@ object DBReader {
|
|||
itemCursor = adapter.getFeedItemCursor(itemIds.filterNotNull().toTypedArray())
|
||||
val items = extractItemlistFromCursor(adapter, itemCursor).toMutableList()
|
||||
loadAdditionalFeedItemListData(items)
|
||||
items.sortWith(PlaybackCompletionDateComparator())
|
||||
getPermutor(sortOrder).reorder(items)
|
||||
return items
|
||||
} finally {
|
||||
mediaCursor?.close()
|
||||
|
|
|
@ -18,15 +18,13 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
|
|||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.comparator.FeedItemPubdateComparator
|
||||
import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
|
||||
|
@ -90,8 +88,8 @@ import java.util.concurrent.*
|
|||
media.setDownloaded(false)
|
||||
media.setFile_url(null)
|
||||
DBWriter.persistFeedMedia(media)
|
||||
if (media.item != null) EventBus.getDefault().post(updated(media.item!!))
|
||||
EventBus.getDefault().post(MessageEvent(context.getString(R.string.error_file_not_found)))
|
||||
if (media.item != null) EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(media.item!!))
|
||||
EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.error_file_not_found)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,8 +136,8 @@ import java.util.concurrent.*
|
|||
return getFeed(feed.id)
|
||||
} else {
|
||||
val feeds = getFeedList()
|
||||
for (f in feeds) {
|
||||
if (f.identifyingValue == feed.identifyingValue) {
|
||||
for (f in feeds.toList()) {
|
||||
if (f != null && f.identifyingValue == feed.identifyingValue) {
|
||||
f.items = getFeedItemList(f).toMutableList()
|
||||
return f
|
||||
}
|
||||
|
@ -331,8 +329,8 @@ import java.util.concurrent.*
|
|||
|
||||
adapter.close()
|
||||
|
||||
if (savedFeed != null) EventBus.getDefault().post(FeedListUpdateEvent(savedFeed))
|
||||
else EventBus.getDefault().post(FeedListUpdateEvent(emptyList()))
|
||||
if (savedFeed != null) EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(savedFeed))
|
||||
else EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(emptyList<Long>()))
|
||||
|
||||
return resultFeed
|
||||
}
|
||||
|
|
|
@ -1,6 +1,35 @@
|
|||
package ac.mdiq.podcini.storage
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.feed.LocalFeedUpdater.updateFeed
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.net.sync.model.EpisodeAction
|
||||
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
|
||||
import ac.mdiq.podcini.playback.service.PlaybackServiceConstants
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isQueueKeepSorted
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.queueKeepSortedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeed
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedItem
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedItemList
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedMedia
|
||||
import ac.mdiq.podcini.storage.DBReader.getQueue
|
||||
import ac.mdiq.podcini.storage.DBReader.getQueueIDList
|
||||
import ac.mdiq.podcini.storage.DBTasks.autodownloadUndownloadedItems
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
|
||||
import ac.mdiq.podcini.storage.model.download.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.feed.*
|
||||
import ac.mdiq.podcini.util.FeedItemPermutors.getPermutor
|
||||
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.LongList
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import ac.mdiq.podcini.util.showStackTrace
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
@ -9,43 +38,9 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import ac.mdiq.podcini.feed.FeedEvent
|
||||
import ac.mdiq.podcini.feed.LocalFeedUpdater.updateFeed
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
|
||||
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying
|
||||
import ac.mdiq.podcini.playback.service.PlaybackServiceConstants
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeed
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedItem
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedItemList
|
||||
import ac.mdiq.podcini.storage.DBReader.getFeedMedia
|
||||
import ac.mdiq.podcini.storage.DBReader.getQueue
|
||||
import ac.mdiq.podcini.storage.DBReader.getQueueIDList
|
||||
import ac.mdiq.podcini.storage.DBTasks.autodownloadUndownloadedItems
|
||||
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
|
||||
import ac.mdiq.podcini.util.FeedItemPermutors.getPermutor
|
||||
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
|
||||
import ac.mdiq.podcini.util.LongList
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.added
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.cleared
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.irreversibleRemoved
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.moved
|
||||
import ac.mdiq.podcini.util.event.QueueEvent.Companion.removed
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackHistoryEvent
|
||||
import ac.mdiq.podcini.storage.model.download.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.feed.*
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.net.sync.model.EpisodeAction
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isQueueKeepSorted
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.queueKeepSortedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.showStackTrace
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
|
@ -112,7 +107,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.close()
|
||||
item.setMedia(null)
|
||||
persistFeedItem(item)
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
if (result && item != null && shouldDeleteRemoveFromQueue()) removeQueueItemSynchronous(context, false, item.id)
|
||||
}
|
||||
|
@ -128,7 +123,7 @@ import java.util.concurrent.TimeUnit
|
|||
// Local feed
|
||||
val documentFile = DocumentFile.fromSingleUri(context, Uri.parse(media.getFile_url()))
|
||||
if (documentFile == null || !documentFile.exists() || !documentFile.delete()) {
|
||||
EventBus.getDefault().post(MessageEvent(context.getString(R.string.delete_local_failed)))
|
||||
EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.delete_local_failed)))
|
||||
return false
|
||||
}
|
||||
media.setFile_url(null)
|
||||
|
@ -138,8 +133,8 @@ import java.util.concurrent.TimeUnit
|
|||
// delete downloaded media file
|
||||
val mediaFile = File(url)
|
||||
if (mediaFile.exists() && !mediaFile.delete()) {
|
||||
val evt = MessageEvent(context.getString(R.string.delete_failed))
|
||||
EventBus.getDefault().post(evt)
|
||||
val evt = FlowEvent.MessageEvent(context.getString(R.string.delete_failed))
|
||||
EventFlow.postEvent(evt)
|
||||
return false
|
||||
}
|
||||
media.setDownloaded(false)
|
||||
|
@ -171,7 +166,7 @@ import java.util.concurrent.TimeUnit
|
|||
.currentTimestamp()
|
||||
.build()
|
||||
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action)
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
@ -200,7 +195,7 @@ import java.util.concurrent.TimeUnit
|
|||
if (!feed.isLocalFeed && feed.download_url != null)
|
||||
SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.download_url!!)
|
||||
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feed))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feed))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,13 +237,13 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.close()
|
||||
|
||||
for (item in removedFromQueue) {
|
||||
EventBus.getDefault().post(irreversibleRemoved(item))
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.irreversibleRemoved(item))
|
||||
}
|
||||
|
||||
// we assume we also removed download log entries for the feed or its media files.
|
||||
// especially important if download or refresh failed, as the user should not be able
|
||||
// to retry these
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.DownloadLogEvent())
|
||||
|
||||
val backupManager = BackupManager(context)
|
||||
backupManager.dataChanged()
|
||||
|
@ -263,7 +258,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.clearPlaybackHistory()
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.HistoryEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,7 +271,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.clearDownloadLog()
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.DownloadLogEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,7 +298,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedMediaPlaybackCompletionDate(media)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(PlaybackHistoryEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.HistoryEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +316,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setDownloadStatus(status)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.DownloadLogEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,8 +345,8 @@ import java.util.concurrent.TimeUnit
|
|||
queue.add(index, item)
|
||||
adapter.setQueue(queue)
|
||||
item.addTag(FeedItem.TAG_QUEUE)
|
||||
EventBus.getDefault().post(added(item, index))
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.added(item, index))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
if (item.isNew) markItemPlayed(FeedItem.UNPLAYED, item.id)
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +404,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
var queueModified = false
|
||||
val markAsUnplayedIds = LongList()
|
||||
val events: MutableList<QueueEvent> = ArrayList()
|
||||
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
|
||||
val updatedItems: MutableList<FeedItem> = ArrayList()
|
||||
val positionCalculator =
|
||||
ItemEnqueuePositionCalculator(enqueueLocation)
|
||||
|
@ -420,7 +415,7 @@ import java.util.concurrent.TimeUnit
|
|||
val item = getFeedItem(itemId)
|
||||
if (item != null) {
|
||||
queue.add(insertPosition, item)
|
||||
events.add(added(item, insertPosition))
|
||||
events.add(FlowEvent.QueueEvent.added(item, insertPosition))
|
||||
|
||||
item.addTag(FeedItem.TAG_QUEUE)
|
||||
updatedItems.add(item)
|
||||
|
@ -434,9 +429,9 @@ import java.util.concurrent.TimeUnit
|
|||
applySortOrder(queue, events)
|
||||
adapter.setQueue(queue)
|
||||
for (event in events) {
|
||||
EventBus.getDefault().post(event)
|
||||
EventFlow.postEvent(event)
|
||||
}
|
||||
EventBus.getDefault().post(updated(updatedItems))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(updatedItems))
|
||||
if (markAsUnplayed && markAsUnplayedIds.size() > 0) markItemPlayed(FeedItem.UNPLAYED, *markAsUnplayedIds.toArray())
|
||||
}
|
||||
adapter.close()
|
||||
|
@ -451,7 +446,7 @@ import java.util.concurrent.TimeUnit
|
|||
* @param queue The queue to be sorted.
|
||||
* @param events Replaces the events by a single SORT event if the list has to be sorted automatically.
|
||||
*/
|
||||
private fun applySortOrder(queue: MutableList<FeedItem>, events: MutableList<QueueEvent>) {
|
||||
private fun applySortOrder(queue: MutableList<FeedItem>, events: MutableList<FlowEvent.QueueEvent>) {
|
||||
// queue is not in keep sorted mode, there's nothing to do
|
||||
if (!isQueueKeepSorted) return
|
||||
|
||||
|
@ -466,7 +461,7 @@ import java.util.concurrent.TimeUnit
|
|||
}
|
||||
// Replace ADDED events by a single SORTED event
|
||||
events.clear()
|
||||
events.add(QueueEvent.sorted(queue))
|
||||
events.add(FlowEvent.QueueEvent.sorted(queue))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,7 +474,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.clearQueue()
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(cleared())
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.cleared())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,7 +505,7 @@ import java.util.concurrent.TimeUnit
|
|||
val queue = getQueue(adapter).toMutableList()
|
||||
|
||||
var queueModified = false
|
||||
val events: MutableList<QueueEvent> = ArrayList()
|
||||
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
|
||||
val updatedItems: MutableList<FeedItem> = ArrayList()
|
||||
for (itemId in itemIds) {
|
||||
val position = indexInItemList(queue, itemId)
|
||||
|
@ -523,7 +518,7 @@ import java.util.concurrent.TimeUnit
|
|||
}
|
||||
queue.removeAt(position)
|
||||
item.removeTag(FeedItem.TAG_QUEUE)
|
||||
events.add(removed(item))
|
||||
events.add(FlowEvent.QueueEvent.removed(item))
|
||||
updatedItems.add(item)
|
||||
queueModified = true
|
||||
} else {
|
||||
|
@ -533,9 +528,9 @@ import java.util.concurrent.TimeUnit
|
|||
if (queueModified) {
|
||||
adapter.setQueue(queue)
|
||||
for (event in events) {
|
||||
EventBus.getDefault().post(event)
|
||||
EventFlow.postEvent(event)
|
||||
}
|
||||
EventBus.getDefault().post(updated(updatedItems))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(updatedItems))
|
||||
} else Log.w(TAG, "Queue was not modified by call to removeQueueItem")
|
||||
|
||||
adapter.close()
|
||||
|
@ -552,8 +547,8 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.addFavoriteItem(item)
|
||||
adapter.close()
|
||||
item.addTag(FeedItem.TAG_FAVORITE)
|
||||
EventBus.getDefault().post(FavoritesEvent())
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FavoritesEvent())
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -563,8 +558,8 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.removeFavoriteItem(item)
|
||||
adapter.close()
|
||||
item.removeTag(FeedItem.TAG_FAVORITE)
|
||||
EventBus.getDefault().post(FavoritesEvent())
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FavoritesEvent())
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -634,7 +629,7 @@ import java.util.concurrent.TimeUnit
|
|||
val item: FeedItem = queue.removeAt(from)
|
||||
queue.add(to, item)
|
||||
adapter.setQueue(queue)
|
||||
if (broadcastUpdate) EventBus.getDefault().post(moved(item, to))
|
||||
if (broadcastUpdate) EventFlow.postEvent(FlowEvent.QueueEvent.moved(item, to))
|
||||
}
|
||||
} else Log.e(TAG, "moveQueueItemHelper: Could not load queue")
|
||||
|
||||
|
@ -678,7 +673,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItemRead(played, *itemIds)
|
||||
adapter.close()
|
||||
if (broadcastUpdate) EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
if (broadcastUpdate) EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -701,7 +696,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItemRead(played, itemId, mediaId, resetMediaPosition)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -716,7 +711,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED, feedId)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,7 +725,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItems(FeedItem.NEW, FeedItem.UNPLAYED)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,7 +761,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.storeFeedItemlist(items)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(updated(items))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(items))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -819,7 +814,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setSingleFeedItem(item)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -848,7 +843,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedPreferences(preferences)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(preferences.feedID))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(preferences.feedID))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -876,7 +871,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feedId))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedId))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -886,7 +881,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedCustomTitle(feed.id, feed.getCustomTitle())
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(feed))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feed))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -910,7 +905,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
permutor.reorder(queue)
|
||||
adapter.setQueue(queue)
|
||||
if (broadcastUpdate) EventBus.getDefault().post(QueueEvent.sorted(queue))
|
||||
if (broadcastUpdate) EventFlow.postEvent(FlowEvent.QueueEvent.sorted(queue))
|
||||
adapter.close()
|
||||
}
|
||||
}
|
||||
|
@ -928,7 +923,7 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItemFilter(feedId, filterValues)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId))
|
||||
EventFlow.postEvent(FlowEvent.FeedEvent(FlowEvent.FeedEvent.Action.FILTER_CHANGED, feedId))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -942,22 +937,31 @@ import java.util.concurrent.TimeUnit
|
|||
adapter.open()
|
||||
adapter.setFeedItemSortOrder(feedId, sortOrder)
|
||||
adapter.close()
|
||||
EventBus.getDefault().post(FeedEvent(FeedEvent.Action.SORT_ORDER_CHANGED, feedId))
|
||||
EventFlow.postEvent(FlowEvent.FeedEvent(FlowEvent.FeedEvent.Action.SORT_ORDER_CHANGED, feedId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the statistics in DB
|
||||
*/
|
||||
fun resetStatistics(): Future<*> {
|
||||
return runOnDbThread {
|
||||
// fun resetStatistics(): Future<*> {
|
||||
// return runOnDbThread {
|
||||
// val adapter = getInstance()
|
||||
// adapter.open()
|
||||
// adapter.resetAllMediaPlayedDuration()
|
||||
// adapter.close()
|
||||
// }
|
||||
// }
|
||||
|
||||
suspend fun resetStatistics(): Unit = withContext(Dispatchers.IO) {
|
||||
val result = async {
|
||||
val adapter = getInstance()
|
||||
adapter.open()
|
||||
adapter.resetAllMediaPlayedDuration()
|
||||
adapter.close()
|
||||
}
|
||||
result.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit to the DB thread only if caller is not already on the DB thread. Otherwise,
|
||||
* just execute synchronously
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.nio.charset.Charset
|
|||
* Writes an OPML file into the export directory in the background.
|
||||
*/
|
||||
class ExportWorker private constructor(private val exportWriter: ExportWriter, private val output: File, private val context: Context) {
|
||||
|
||||
constructor(exportWriter: ExportWriter, context: Context) : this(exportWriter, File(getDataFolder(EXPORT_DIR),
|
||||
DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension()), context)
|
||||
|
||||
|
|
|
@ -697,6 +697,15 @@ class PodDBAdapter private constructor() {
|
|||
val completedMediaLength: Long
|
||||
get() = DatabaseUtils.queryNumEntries(db, TABLE_NAME_FEED_MEDIA, "$KEY_PLAYBACK_COMPLETION_DATE> 0")
|
||||
|
||||
fun getPlayedMediaCursor(offset: Int, limit: Int, start: Long = 0, end: Long = Date().time): Cursor {
|
||||
require(limit >= 0) { "Limit must be >= 0" }
|
||||
|
||||
return db.query(TABLE_NAME_FEED_MEDIA, null,
|
||||
String.format(Locale.US, "%s > %d AND %s <= % d", KEY_LAST_PLAYED_TIME, start, KEY_LAST_PLAYED_TIME, end),
|
||||
null, null,
|
||||
null, String.format(Locale.US, "%s DESC LIMIT %d, %d", KEY_LAST_PLAYED_TIME, offset, limit))
|
||||
}
|
||||
|
||||
fun getSingleFeedMediaCursor(id: Long): Cursor {
|
||||
val query = ("SELECT $KEYS_FEED_MEDIA FROM $TABLE_NAME_FEED_MEDIA WHERE $KEY_ID=$id")
|
||||
return db.rawQuery(query, null)
|
||||
|
|
|
@ -14,6 +14,12 @@ object FeedItemSortQuery {
|
|||
SortOrder.EPISODE_TITLE_Z_A -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_TITLE + " " + "DESC"
|
||||
SortOrder.DATE_OLD_NEW -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_PUBDATE + " " + "ASC"
|
||||
SortOrder.DATE_NEW_OLD -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_PUBDATE + " " + "DESC"
|
||||
|
||||
SortOrder.PLAYED_DATE_OLD_NEW -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_LAST_PLAYED_TIME + " " + "ASC"
|
||||
SortOrder.PLAYED_DATE_NEW_OLD -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_LAST_PLAYED_TIME + " " + "DESC"
|
||||
SortOrder.COMPLETED_DATE_OLD_NEW -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE + " " + "ASC"
|
||||
SortOrder.COMPLETED_DATE_NEW_OLD -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE + " " + "DESC"
|
||||
|
||||
SortOrder.DURATION_SHORT_LONG -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DURATION + " " + "ASC"
|
||||
SortOrder.DURATION_LONG_SHORT -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DURATION + " " + "DESC"
|
||||
SortOrder.SIZE_SMALL_LARGE -> PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_SIZE + " " + "ASC"
|
||||
|
|
|
@ -14,9 +14,13 @@ enum class SortOrder(@JvmField val code: Int, @JvmField val scope: Scope) {
|
|||
EPISODE_FILENAME_Z_A(8, Scope.INTRA_FEED),
|
||||
SIZE_SMALL_LARGE(9, Scope.INTRA_FEED),
|
||||
SIZE_LARGE_SMALL(10, Scope.INTRA_FEED),
|
||||
PLAYED_DATE_OLD_NEW(11, Scope.INTRA_FEED),
|
||||
PLAYED_DATE_NEW_OLD(12, Scope.INTRA_FEED),
|
||||
COMPLETED_DATE_OLD_NEW(13, Scope.INTRA_FEED),
|
||||
COMPLETED_DATE_NEW_OLD(14, Scope.INTRA_FEED),
|
||||
|
||||
FEED_TITLE_A_Z(101, Scope.INTER_FEED),
|
||||
FEED_TITLE_Z_A(102, Scope.INTER_FEED),
|
||||
|
||||
RANDOM(103, Scope.INTER_FEED),
|
||||
SMART_SHUFFLE_OLD_NEW(104, Scope.INTER_FEED),
|
||||
SMART_SHUFFLE_NEW_OLD(105, Scope.INTER_FEED);
|
||||
|
|
|
@ -7,11 +7,11 @@ import ac.mdiq.podcini.storage.DBTasks
|
|||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.playback.StartPlayEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
|
||||
override fun getLabel(): Int {
|
||||
|
@ -35,7 +35,7 @@ class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start()
|
||||
EventBus.getDefault().post(StartPlayEvent(item))
|
||||
EventFlow.postEvent(FlowEvent.StartPlayEvent(item))
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
}
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
package ac.mdiq.podcini.ui.actions.actionbutton
|
||||
|
||||
import android.content.Context
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.preferences.UsageStatistics
|
||||
import ac.mdiq.podcini.preferences.UsageStatistics.logAction
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.ui.dialog.StreamingConfirmationDialog
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
import ac.mdiq.podcini.storage.model.playback.RemoteMedia
|
||||
import ac.mdiq.podcini.util.event.playback.StartPlayEvent
|
||||
import android.util.Log
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import ac.mdiq.podcini.ui.dialog.StreamingConfirmationDialog
|
||||
import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
||||
class StreamActionButton(item: FeedItem) : ItemActionButton(item) {
|
||||
override fun getLabel(): Int {
|
||||
|
@ -40,7 +39,7 @@ class StreamActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
.shouldStreamThisTime(true)
|
||||
.callEvenIfRunning(true)
|
||||
.start()
|
||||
EventBus.getDefault().post(StartPlayEvent(item))
|
||||
EventFlow.postEvent(FlowEvent.StartPlayEvent(item))
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.actions.actionbutton
|
|||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.getMediafilePath
|
||||
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.getMediafilename
|
||||
import ac.mdiq.podcini.util.AudioMediaOperation.mergeAudios
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.storage.DBWriter.persistFeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
|
@ -11,9 +10,11 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
|||
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.tts
|
||||
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.ttsReady
|
||||
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.ttsWorking
|
||||
import ac.mdiq.podcini.util.AudioMediaOperation.mergeAudios
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.NetworkUtils.fetchHtmlSource
|
||||
import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.speech.tts.TextToSpeech.getMaxSpeechInputLength
|
||||
|
@ -26,7 +27,6 @@ import androidx.core.text.HtmlCompat
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import kotlinx.coroutines.*
|
||||
import net.dankito.readability4j.Readability4J
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
@ -52,7 +52,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
}
|
||||
processing = 0.01f
|
||||
item.setBuilding()
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
ioScope.launch {
|
||||
if (item.transcript == null) {
|
||||
runBlocking {
|
||||
|
@ -66,12 +66,12 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
}
|
||||
} else readerText = HtmlCompat.fromHtml(item.transcript!!, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
processing = 0.1f
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
if (!readerText.isNullOrEmpty()) {
|
||||
while (!ttsReady) Thread.sleep(100)
|
||||
|
||||
processing = 0.15f
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
while (ttsWorking) Thread.sleep(100)
|
||||
ttsWorking = true
|
||||
if (item.feed?.language != null) {
|
||||
|
@ -123,10 +123,10 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
i++
|
||||
while (i-j > 0) Thread.sleep(100)
|
||||
processing = 0.15f + 0.7f * startIndex / readerText!!.length
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
processing = 0.85f
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
if (status == TextToSpeech.SUCCESS) {
|
||||
mergeAudios(parts.toTypedArray(), mediaFile.absolutePath, null)
|
||||
|
||||
|
@ -157,7 +157,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) {
|
|||
|
||||
item.setPlayed(false)
|
||||
processing = 1f
|
||||
EventBus.getDefault().post(updated(item))
|
||||
EventFlow.postEvent(FlowEvent.FeedItemEvent.updated(item))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package ac.mdiq.podcini.ui.actions.swipeactions
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
|
||||
import ac.mdiq.podcini.ui.fragment.*
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import androidx.core.graphics.ColorUtils
|
||||
|
@ -11,15 +19,7 @@ import androidx.media3.common.util.UnstableApi
|
|||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.annimon.stream.Stream
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
|
||||
import ac.mdiq.podcini.ui.fragment.*
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.event.SwipeActionsChangedEvent
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
@ -90,7 +90,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
|
|||
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
|
||||
override fun onCall() {
|
||||
this@SwipeActions.reloadPreference()
|
||||
EventBus.getDefault().post(SwipeActionsChangedEvent())
|
||||
EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,9 +25,8 @@ import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
|||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getDrawableFromAttr
|
||||
import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
|
||||
import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
|
@ -57,6 +56,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
|
@ -69,10 +69,9 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCa
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
|
@ -196,7 +195,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().postSticky(FeedUpdateRunningEvent(isRefreshingFeeds))
|
||||
EventFlow.postStickyEvent(FlowEvent.FeedUpdateRunningEvent(isRefreshingFeeds))
|
||||
}
|
||||
WorkManager.getInstance(this)
|
||||
.getWorkInfosByTagLiveData(DownloadServiceInterface.WORK_TAG)
|
||||
|
@ -232,7 +231,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
updatedEpisodes[downloadUrl] = DownloadStatus(status, progress)
|
||||
}
|
||||
DownloadServiceInterface.get()?.setCurrentDownloads(updatedEpisodes)
|
||||
EventBus.getDefault().postSticky(EpisodeDownloadEvent(updatedEpisodes))
|
||||
EventFlow.postStickyEvent(FlowEvent.EpisodeDownloadEvent(updatedEpisodes))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,7 +482,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
|
||||
public override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
RatingDialog.init(this)
|
||||
|
||||
val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
|
||||
|
@ -517,7 +516,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
|
@ -558,10 +557,19 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: MessageEvent) {
|
||||
Logd(TAG, "onEvent($event)")
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.MessageEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEventMainThread(event: FlowEvent.MessageEvent) {
|
||||
Logd(TAG, "onEvent($event)")
|
||||
val snackbar = showSnackbarAbovePlayer(event.message, Snackbar.LENGTH_LONG)
|
||||
if (event.action != null) snackbar.setAction(event.actionText) { event.action.accept(this) }
|
||||
}
|
||||
|
@ -674,7 +682,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
|
||||
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
var customKeyCode: Int? = null
|
||||
EventBus.getDefault().post(event)
|
||||
EventFlow.postEvent(event)
|
||||
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_P -> customKeyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|
||||
|
|
|
@ -30,12 +30,12 @@ import android.widget.Toast
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.io.Reader
|
||||
|
@ -62,57 +62,84 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
setContentView(binding.root)
|
||||
|
||||
binding.feedlist.choiceMode = ListView.CHOICE_MODE_MULTIPLE
|
||||
binding.feedlist.onItemClickListener =
|
||||
OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
|
||||
val checked = binding.feedlist.checkedItemPositions
|
||||
var checkedCount = 0
|
||||
for (i in 0 until checked.size()) {
|
||||
if (checked.valueAt(i)) checkedCount++
|
||||
}
|
||||
if (listAdapter != null) {
|
||||
if (checkedCount == listAdapter!!.count) {
|
||||
selectAll.setVisible(false)
|
||||
deselectAll.setVisible(true)
|
||||
} else {
|
||||
deselectAll.setVisible(false)
|
||||
selectAll.setVisible(true)
|
||||
}
|
||||
binding.feedlist.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
|
||||
val checked = binding.feedlist.checkedItemPositions
|
||||
var checkedCount = 0
|
||||
for (i in 0 until checked.size()) {
|
||||
if (checked.valueAt(i)) checkedCount++
|
||||
}
|
||||
if (listAdapter != null) {
|
||||
if (checkedCount == listAdapter!!.count) {
|
||||
selectAll.setVisible(false)
|
||||
deselectAll.setVisible(true)
|
||||
} else {
|
||||
deselectAll.setVisible(false)
|
||||
selectAll.setVisible(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.butCancel.setOnClickListener {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
binding.butConfirm.setOnClickListener {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
Completable.fromAction {
|
||||
val checked = binding.feedlist.checkedItemPositions
|
||||
for (i in 0 until checked.size()) {
|
||||
if (!checked.valueAt(i)) continue
|
||||
// Completable.fromAction {
|
||||
// val checked = binding.feedlist.checkedItemPositions
|
||||
// for (i in 0 until checked.size()) {
|
||||
// if (!checked.valueAt(i)) continue
|
||||
//
|
||||
// if (!readElements.isNullOrEmpty()) {
|
||||
// val element = readElements!![checked.keyAt(i)]
|
||||
// val feed = Feed(element.xmlUrl, null, if (element.text != null) element.text else "Unknown podcast")
|
||||
// feed.items = mutableListOf()
|
||||
// DBTasks.updateFeed(this, feed, false)
|
||||
// }
|
||||
// }
|
||||
// runOnce(this)
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe(
|
||||
// {
|
||||
// binding.progressBar.visibility = View.GONE
|
||||
// val intent = Intent(this@OpmlImportActivity, MainActivity::class.java)
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
// startActivity(intent)
|
||||
// finish()
|
||||
// }, { e: Throwable ->
|
||||
// e.printStackTrace()
|
||||
// binding.progressBar.visibility = View.GONE
|
||||
// Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
|
||||
// })
|
||||
|
||||
if (!readElements.isNullOrEmpty()) {
|
||||
val element = readElements!![checked.keyAt(i)]
|
||||
val feed = Feed(element.xmlUrl, null, if (element.text != null) element.text else "Unknown podcast")
|
||||
feed.items = mutableListOf()
|
||||
DBTasks.updateFeed(this, feed, false)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
val checked = binding.feedlist.checkedItemPositions
|
||||
for (i in 0 until checked.size()) {
|
||||
if (!checked.valueAt(i)) continue
|
||||
|
||||
if (!readElements.isNullOrEmpty()) {
|
||||
val element = readElements!![checked.keyAt(i)]
|
||||
val feed = Feed(element.xmlUrl, null, if (element.text != null) element.text else "Unknown podcast")
|
||||
feed.items = mutableListOf()
|
||||
DBTasks.updateFeed(this@OpmlImportActivity, feed, false)
|
||||
}
|
||||
}
|
||||
runOnce(this@OpmlImportActivity)
|
||||
}
|
||||
binding.progressBar.visibility = View.GONE
|
||||
val intent = Intent(this@OpmlImportActivity, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
binding.progressBar.visibility = View.GONE
|
||||
Toast.makeText(this@OpmlImportActivity, (e.message ?: "Import error"), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
runOnce(this)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
binding.progressBar.visibility = View.GONE
|
||||
val intent = Intent(this@OpmlImportActivity, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}, { e: Throwable ->
|
||||
e.printStackTrace()
|
||||
binding.progressBar.visibility = View.GONE
|
||||
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
|
||||
})
|
||||
}
|
||||
|
||||
var uri = intent.data
|
||||
|
@ -203,38 +230,83 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
private fun startImport() {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
Observable.fromCallable {
|
||||
val opmlFileStream = contentResolver.openInputStream(uri!!)
|
||||
val bomInputStream = BOMInputStream(opmlFileStream)
|
||||
val bom = bomInputStream.bom
|
||||
val charsetName = if (bom == null) "UTF-8" else bom.charsetName
|
||||
val reader: Reader = InputStreamReader(bomInputStream, charsetName)
|
||||
val opmlReader = OpmlReader()
|
||||
val result = opmlReader.readDocument(reader)
|
||||
reader.close()
|
||||
result
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ result: ArrayList<OpmlElement>? ->
|
||||
// Observable.fromCallable {
|
||||
// val opmlFileStream = contentResolver.openInputStream(uri!!)
|
||||
// val bomInputStream = BOMInputStream(opmlFileStream)
|
||||
// val bom = bomInputStream.bom
|
||||
// val charsetName = if (bom == null) "UTF-8" else bom.charsetName
|
||||
// val reader: Reader = InputStreamReader(bomInputStream, charsetName)
|
||||
// val opmlReader = OpmlReader()
|
||||
// val result = opmlReader.readDocument(reader)
|
||||
// reader.close()
|
||||
// result
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe(
|
||||
// { result: ArrayList<OpmlElement>? ->
|
||||
// binding.progressBar.visibility = View.GONE
|
||||
// Logd(TAG, "Parsing was successful")
|
||||
// readElements = result
|
||||
// listAdapter = ArrayAdapter(this@OpmlImportActivity, android.R.layout.simple_list_item_multiple_choice, titleList)
|
||||
// binding.feedlist.adapter = listAdapter
|
||||
// }, { e: Throwable ->
|
||||
// Logd(TAG, Log.getStackTraceString(e))
|
||||
// val message = if (e.message == null) "" else e.message!!
|
||||
// if (message.lowercase().contains("permission")) {
|
||||
// val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
// if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
// requestPermission()
|
||||
// return@subscribe
|
||||
// }
|
||||
// }
|
||||
// binding.progressBar.visibility = View.GONE
|
||||
// val alert = MaterialAlertDialogBuilder(this)
|
||||
// alert.setTitle(R.string.error_label)
|
||||
// val userReadable = getString(R.string.opml_reader_error)
|
||||
// val details = e.message
|
||||
// val total = """
|
||||
// $userReadable
|
||||
//
|
||||
// $details
|
||||
// """.trimIndent()
|
||||
// val errorMessage = SpannableString(total)
|
||||
// errorMessage.setSpan(ForegroundColorSpan(-0x77777778), userReadable.length, total.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// alert.setMessage(errorMessage)
|
||||
// alert.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> finish() }
|
||||
// alert.show()
|
||||
// })
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val opmlFileStream = contentResolver.openInputStream(uri!!)
|
||||
val bomInputStream = BOMInputStream(opmlFileStream)
|
||||
val bom = bomInputStream.bom
|
||||
val charsetName = if (bom == null) "UTF-8" else bom.charsetName
|
||||
val reader: Reader = InputStreamReader(bomInputStream, charsetName)
|
||||
val opmlReader = OpmlReader()
|
||||
val result = opmlReader.readDocument(reader)
|
||||
reader.close()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
Logd(TAG, "Parsing was successful")
|
||||
readElements = result
|
||||
listAdapter = ArrayAdapter(this@OpmlImportActivity, android.R.layout.simple_list_item_multiple_choice, titleList)
|
||||
binding.feedlist.adapter = listAdapter
|
||||
}, { e: Throwable ->
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Logd(TAG, Log.getStackTraceString(e))
|
||||
val message = if (e.message == null) "" else e.message!!
|
||||
if (message.lowercase().contains("permission")) {
|
||||
val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
val permission = ActivityCompat.checkSelfPermission(this@OpmlImportActivity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermission()
|
||||
return@subscribe
|
||||
return@withContext
|
||||
}
|
||||
}
|
||||
binding.progressBar.visibility = View.GONE
|
||||
val alert = MaterialAlertDialogBuilder(this)
|
||||
val alert = MaterialAlertDialogBuilder(this@OpmlImportActivity)
|
||||
alert.setTitle(R.string.error_label)
|
||||
val userReadable = getString(R.string.opml_reader_error)
|
||||
val details = e.message
|
||||
|
@ -248,7 +320,9 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
alert.setMessage(errorMessage)
|
||||
alert.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> finish() }
|
||||
alert.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -6,7 +6,8 @@ import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme
|
|||
import ac.mdiq.podcini.preferences.fragments.*
|
||||
import ac.mdiq.podcini.preferences.fragments.synchronization.SynchronizationPreferencesFragment
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
|
@ -16,14 +17,14 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
|
||||
|
@ -144,12 +145,12 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -157,8 +158,18 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
_binding = null
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: MessageEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.MessageEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEventMainThread(event: FlowEvent.MessageEvent) {
|
||||
Logd(FRAGMENT_TAG, "onEvent($event)")
|
||||
val s = Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG)
|
||||
if (event.action != null) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
|
@ -41,7 +42,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
|||
@Volatile
|
||||
private var listItems: List<Feed> = listOf()
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -138,7 +139,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
|||
// binding.list.adapter = adapter
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ac.mdiq.podcini.ui.activity
|
||||
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter
|
||||
import ac.mdiq.podcini.util.error.CrashReportWriter
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
@ -7,12 +9,10 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import ac.mdiq.podcini.util.error.CrashReportWriter
|
||||
import ac.mdiq.podcini.storage.database.PodDBAdapter
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.CompletableEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Shows the Podcini logo while waiting for the main activity to start.
|
||||
|
@ -24,24 +24,44 @@ class SplashActivity : Activity() {
|
|||
val content = findViewById<View>(android.R.id.content)
|
||||
content.viewTreeObserver.addOnPreDrawListener { false } // Keep splash screen active
|
||||
|
||||
Completable.create { subscriber: CompletableEmitter ->
|
||||
// Trigger schema updates
|
||||
PodDBAdapter.getInstance().open()
|
||||
PodDBAdapter.getInstance().close()
|
||||
subscriber.onComplete()
|
||||
// Completable.create { subscriber: CompletableEmitter ->
|
||||
// // Trigger schema updates
|
||||
// PodDBAdapter.getInstance().open()
|
||||
// PodDBAdapter.getInstance().close()
|
||||
// subscriber.onComplete()
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({
|
||||
// val intent = Intent(this@SplashActivity, MainActivity::class.java)
|
||||
// startActivity(intent)
|
||||
// overridePendingTransition(0, 0)
|
||||
// finish()
|
||||
// }, { error: Throwable ->
|
||||
// error.printStackTrace()
|
||||
// CrashReportWriter.write(error)
|
||||
// Toast.makeText(this, error.localizedMessage, Toast.LENGTH_LONG).show()
|
||||
// finish()
|
||||
// })
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.IO)
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
PodDBAdapter.getInstance().open()
|
||||
PodDBAdapter.getInstance().close()
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(this@SplashActivity, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
overridePendingTransition(0, 0)
|
||||
finish()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
CrashReportWriter.write(e)
|
||||
Toast.makeText(this@SplashActivity, e.localizedMessage, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
val intent = Intent(this@SplashActivity, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
overridePendingTransition(0, 0)
|
||||
finish()
|
||||
}, { error: Throwable ->
|
||||
error.printStackTrace()
|
||||
CrashReportWriter.write(error)
|
||||
Toast.makeText(this, error.localizedMessage, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,8 @@ import ac.mdiq.podcini.util.FeedItemUtil.getLinkWithFallback
|
|||
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.ShareUtils.hasLinkToShare
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackServiceEvent
|
||||
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
|
@ -36,11 +34,11 @@ import android.util.Log
|
|||
import android.view.*
|
||||
import android.view.MenuItem.SHOW_AS_ACTION_NEVER
|
||||
import android.widget.EditText
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Activity for playing video files.
|
||||
|
@ -137,7 +135,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
|
||||
@UnstableApi
|
||||
override fun onStop() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
@ -148,7 +146,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
@UnstableApi
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
|
@ -168,24 +166,21 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
startActivity(newIntent)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
||||
if (event.isCancelled || event.wasJustEnabled()) supportInvalidateOptionsMenu()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) supportInvalidateOptionsMenu()
|
||||
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) finish()
|
||||
is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(this@VideoplayerActivity, event)
|
||||
is FlowEvent.MessageEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) finish()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMediaPlayerError(event: PlayerErrorEvent) {
|
||||
MediaPlayerErrorDialog.show(this, event)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: MessageEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.MessageEvent) {
|
||||
Logd(TAG, "onEvent($event)")
|
||||
val errorDialog = MaterialAlertDialogBuilder(this)
|
||||
errorDialog.setMessage(event.message)
|
||||
|
|
|
@ -21,7 +21,8 @@ import java.lang.ref.WeakReference
|
|||
open class EpisodeItemListAdapter(mainActivity: MainActivity) :
|
||||
SelectableAdapter<EpisodeItemViewHolder?>(mainActivity), View.OnCreateContextMenuListener {
|
||||
|
||||
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
||||
val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
|
||||
|
||||
private var episodes: List<FeedItem> = ArrayList()
|
||||
var longPressedItem: FeedItem? = null
|
||||
private var longPressedPosition: Int = 0 // used to init actionMode
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import android.os.Bundle
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
|
||||
class AllEpisodesFilterDialog : ItemFilterDialog() {
|
||||
override fun onFilterChanged(newFilterValues: Set<String>) {
|
||||
EventBus.getDefault().post(AllEpisodesFilterChangedEvent(newFilterValues))
|
||||
EventFlow.postEvent(FlowEvent.AllEpisodesFilterChangedEvent(newFilterValues))
|
||||
}
|
||||
|
||||
class AllEpisodesFilterChangedEvent(val filterValues: Set<String?>?)
|
||||
companion object {
|
||||
fun newInstance(filter: FeedItemFilter?): AllEpisodesFilterDialog {
|
||||
val dialog = AllEpisodesFilterDialog()
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.model.download.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import ac.mdiq.podcini.util.DownloadErrorLabel.from
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
@ -8,14 +16,6 @@ import android.view.View
|
|||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.util.DownloadErrorLabel.from
|
||||
import ac.mdiq.podcini.util.event.MessageEvent
|
||||
import ac.mdiq.podcini.storage.model.download.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class DownloadLogDetailsDialog(context: Context, status: DownloadResult) : MaterialAlertDialogBuilder(context) {
|
||||
init {
|
||||
|
@ -43,7 +43,7 @@ class DownloadLogDetailsDialog(context: Context, status: DownloadResult) : Mater
|
|||
val clipboard = getContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText(context.getString(R.string.download_error_details), messageFull)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
if (Build.VERSION.SDK_INT < 32) EventBus.getDefault().post(MessageEvent(context.getString(R.string.copied_to_clipboard)))
|
||||
if (Build.VERSION.SDK_INT < 32) EventFlow.postEvent(FlowEvent.MessageEvent(context.getString(R.string.copied_to_clipboard)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.feedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.setFeedOrder
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.feedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.setFeedOrder
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
object FeedSortDialog {
|
||||
fun showDialog(context: Context) {
|
||||
|
@ -24,7 +24,7 @@ object FeedSortDialog {
|
|||
if (selectedIndex != which) {
|
||||
setFeedOrder(entryValues[which])
|
||||
//Update subscriptions
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
d.dismiss()
|
||||
}
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.SortDialogBinding
|
||||
import ac.mdiq.podcini.databinding.SortDialogItemActiveBinding
|
||||
import ac.mdiq.podcini.databinding.SortDialogItemBinding
|
||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.FrameLayout
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.SortDialogBinding
|
||||
import ac.mdiq.podcini.databinding.SortDialogItemActiveBinding
|
||||
import ac.mdiq.podcini.databinding.SortDialogItemBinding
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import android.graphics.Color
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
|
||||
open class ItemSortDialog : BottomSheetDialogFragment() {
|
||||
protected var _binding: SortDialogBinding? = null
|
||||
|
@ -45,6 +42,8 @@ open class ItemSortDialog : BottomSheetDialogFragment() {
|
|||
onAddItem(R.string.feed_title, SortOrder.FEED_TITLE_A_Z, SortOrder.FEED_TITLE_Z_A, true)
|
||||
onAddItem(R.string.duration, SortOrder.DURATION_SHORT_LONG, SortOrder.DURATION_LONG_SHORT, true)
|
||||
onAddItem(R.string.date, SortOrder.DATE_OLD_NEW, SortOrder.DATE_NEW_OLD, false)
|
||||
onAddItem(R.string.last_played_date, SortOrder.PLAYED_DATE_OLD_NEW, SortOrder.PLAYED_DATE_NEW_OLD, false)
|
||||
onAddItem(R.string.completed_date, SortOrder.COMPLETED_DATE_OLD_NEW, SortOrder.COMPLETED_DATE_NEW_OLD, false)
|
||||
onAddItem(R.string.size, SortOrder.SIZE_SMALL_LARGE, SortOrder.SIZE_LARGE_SMALL, false)
|
||||
onAddItem(R.string.filename, SortOrder.EPISODE_FILENAME_A_Z, SortOrder.EPISODE_FILENAME_Z_A, true)
|
||||
onAddItem(R.string.random, SortOrder.RANDOM, SortOrder.RANDOM, true)
|
||||
|
@ -87,8 +86,7 @@ open class ItemSortDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
protected open fun onSelectionChanged() {
|
||||
}
|
||||
protected open fun onSelectionChanged() {}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
|
|
@ -9,13 +9,13 @@ import android.text.style.ForegroundColorSpan
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
object MediaPlayerErrorDialog {
|
||||
fun show(activity: Activity, event: PlayerErrorEvent) {
|
||||
fun show(activity: Activity, event: FlowEvent.PlayerErrorEvent) {
|
||||
val errorDialog = MaterialAlertDialogBuilder(activity)
|
||||
errorDialog.setTitle(R.string.error_label)
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ProxySettingsBinding
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.newBuilder
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.reinit
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.setProxyConfig
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig
|
||||
import ac.mdiq.podcini.storage.model.download.ProxyConfig
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
@ -10,23 +18,17 @@ import android.view.View
|
|||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ProxySettingsBinding
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.newBuilder
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.reinit
|
||||
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.setProxyConfig
|
||||
import ac.mdiq.podcini.storage.model.download.ProxyConfig
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.CompletableEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Credentials.basic
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Request.Builder
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
|
@ -43,7 +45,7 @@ class ProxyDialog(private val context: Context) {
|
|||
private lateinit var txtvMessage: TextView
|
||||
|
||||
private var testSuccessful = false
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
fun show(): Dialog {
|
||||
val content = View.inflate(context, R.layout.proxy_settings, null)
|
||||
|
@ -215,7 +217,7 @@ class ProxyDialog(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun test() {
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
if (!checkValidity()) {
|
||||
setTestRequired(true)
|
||||
return
|
||||
|
@ -227,58 +229,108 @@ class ProxyDialog(private val context: Context) {
|
|||
txtvMessage.setTextColor(textColorPrimary)
|
||||
txtvMessage.text = "{fa-circle-o-notch spin} $checking"
|
||||
txtvMessage.visibility = View.VISIBLE
|
||||
disposable = Completable.create { emitter: CompletableEmitter ->
|
||||
val type = spType.selectedItem as String
|
||||
val host = etHost.text.toString()
|
||||
val port = etPort.text.toString()
|
||||
val username = etUsername.text.toString()
|
||||
val password = etPassword.text.toString()
|
||||
var portValue = 8080
|
||||
if (port.isNotEmpty()) portValue = port.toInt()
|
||||
|
||||
val address: SocketAddress = InetSocketAddress.createUnresolved(host, portValue)
|
||||
val proxyType = Proxy.Type.valueOf(type.uppercase())
|
||||
val builder: OkHttpClient.Builder = newBuilder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.proxy(Proxy(proxyType, address))
|
||||
if (username.isNotEmpty()) {
|
||||
builder.proxyAuthenticator { _: Route?, response: Response ->
|
||||
val credentials = basic(username, password)
|
||||
response.request.newBuilder()
|
||||
.header("Proxy-Authorization", credentials)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
val client: OkHttpClient = builder.build()
|
||||
val request: Request = Builder().url("https://www.example.com").head().build()
|
||||
// disposable = Completable.create { emitter: CompletableEmitter ->
|
||||
// val type = spType.selectedItem as String
|
||||
// val host = etHost.text.toString()
|
||||
// val port = etPort.text.toString()
|
||||
// val username = etUsername.text.toString()
|
||||
// val password = etPassword.text.toString()
|
||||
// var portValue = 8080
|
||||
// if (port.isNotEmpty()) portValue = port.toInt()
|
||||
//
|
||||
// val address: SocketAddress = InetSocketAddress.createUnresolved(host, portValue)
|
||||
// val proxyType = Proxy.Type.valueOf(type.uppercase())
|
||||
// val builder: OkHttpClient.Builder = newBuilder()
|
||||
// .connectTimeout(10, TimeUnit.SECONDS)
|
||||
// .proxy(Proxy(proxyType, address))
|
||||
// if (username.isNotEmpty()) {
|
||||
// builder.proxyAuthenticator { _: Route?, response: Response ->
|
||||
// val credentials = basic(username, password)
|
||||
// response.request.newBuilder()
|
||||
// .header("Proxy-Authorization", credentials)
|
||||
// .build()
|
||||
// }
|
||||
// }
|
||||
// val client: OkHttpClient = builder.build()
|
||||
// val request: Request = Builder().url("https://www.example.com").head().build()
|
||||
// try {
|
||||
// client.newCall(request).execute().use { response ->
|
||||
// if (response.isSuccessful) {
|
||||
// emitter.onComplete()
|
||||
// } else {
|
||||
// emitter.onError(IOException(response.message))
|
||||
// }
|
||||
// }
|
||||
// } catch (e: IOException) {
|
||||
// emitter.onError(e)
|
||||
// }
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe(
|
||||
// {
|
||||
// txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_green))
|
||||
// val message = String.format("%s %s", "{fa-check}", context.getString(R.string.proxy_test_successful))
|
||||
// txtvMessage.text = message
|
||||
// setTestRequired(false)
|
||||
// },
|
||||
// { error: Throwable ->
|
||||
// error.printStackTrace()
|
||||
// txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_red))
|
||||
// val message = String.format("%s %s: %s", "{fa-close}", context.getString(R.string.proxy_test_failed), error.message)
|
||||
// txtvMessage.text = message
|
||||
// setTestRequired(true)
|
||||
// }
|
||||
// )
|
||||
|
||||
val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) {
|
||||
emitter.onComplete()
|
||||
} else {
|
||||
emitter.onError(IOException(response.message))
|
||||
val type = spType.selectedItem as String
|
||||
val host = etHost.text.toString()
|
||||
val port = etPort.text.toString()
|
||||
val username = etUsername.text.toString()
|
||||
val password = etPassword.text.toString()
|
||||
var portValue = 8080
|
||||
if (port.isNotEmpty()) portValue = port.toInt()
|
||||
|
||||
val address: SocketAddress = InetSocketAddress.createUnresolved(host, portValue)
|
||||
val proxyType = Proxy.Type.valueOf(type.uppercase())
|
||||
val builder: OkHttpClient.Builder = newBuilder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.proxy(Proxy(proxyType, address))
|
||||
if (username.isNotEmpty()) {
|
||||
builder.proxyAuthenticator { _: Route?, response: Response ->
|
||||
val credentials = basic(username, password)
|
||||
response.request.newBuilder()
|
||||
.header("Proxy-Authorization", credentials)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
emitter.onError(e)
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
val client: OkHttpClient = builder.build()
|
||||
val request: Request = Builder().url("https://www.example.com").head().build()
|
||||
try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException(response.message)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_green))
|
||||
val message = String.format("%s %s", "{fa-check}", context.getString(R.string.proxy_test_successful))
|
||||
txtvMessage.text = message
|
||||
setTestRequired(false)
|
||||
},
|
||||
{ error: Throwable ->
|
||||
error.printStackTrace()
|
||||
txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_red))
|
||||
val message = String.format("%s %s: %s", "{fa-close}", context.getString(R.string.proxy_test_failed), error.message)
|
||||
txtvMessage.text = message
|
||||
setTestRequired(true)
|
||||
}
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_red))
|
||||
val message = String.format("%s %s: %s", "{fa-close}", context.getString(R.string.proxy_test_failed), e.message)
|
||||
txtvMessage.text = message
|
||||
setTestRequired(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,11 @@ package ac.mdiq.podcini.ui.dialog
|
|||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.TimeDialogBinding
|
||||
import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent
|
||||
import ac.mdiq.podcini.playback.PlaybackController
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.disableSleepTimer
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.extendSleepTimer
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.setSleepTimer
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnable
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableFrom
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableTo
|
||||
|
@ -16,12 +20,9 @@ import ac.mdiq.podcini.preferences.SleepTimerPreferences.setVibrate
|
|||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.shakeToReset
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.timerMillis
|
||||
import ac.mdiq.podcini.preferences.SleepTimerPreferences.vibrate
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService
|
||||
import ac.mdiq.podcini.util.Converter.getDurationStringLong
|
||||
import ac.mdiq.podcini.playback.PlaybackController
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.disableSleepTimer
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.extendSleepTimer
|
||||
import ac.mdiq.podcini.playback.PlaybackController.Companion.setSleepTimer
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
|
@ -31,12 +32,12 @@ import android.view.View
|
|||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.*
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class SleepTimerDialog : DialogFragment() {
|
||||
|
@ -56,13 +57,13 @@ class SleepTimerDialog : DialogFragment() {
|
|||
override fun loadMediaInfo() {}
|
||||
}
|
||||
controller.init()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
@UnstableApi override fun onStop() {
|
||||
super.onStop()
|
||||
controller.release()
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
@UnstableApi override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
@ -191,9 +192,18 @@ class SleepTimerDialog : DialogFragment() {
|
|||
chAutoEnable.text = text
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun timerUpdated(event: SleepTimerUpdatedEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> timerUpdated(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun timerUpdated(event: FlowEvent.SleepTimerUpdatedEvent) {
|
||||
timeDisplay.visibility = if (event.isOver || event.isCancelled) View.GONE else View.VISIBLE
|
||||
timeSetup.visibility = if (event.isOver || event.isCancelled) View.VISIBLE else View.GONE
|
||||
time.text = getDurationStringLong(event.getTimeLeft().toInt())
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.FilterDialogBinding
|
||||
import ac.mdiq.podcini.databinding.FilterDialogRowBinding
|
||||
import ac.mdiq.podcini.feed.SubscriptionsFilterGroup
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.subscriptionsFilter
|
||||
import ac.mdiq.podcini.storage.model.feed.SubscriptionsFilter
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -13,15 +22,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.feed.SubscriptionsFilterGroup
|
||||
import ac.mdiq.podcini.databinding.FilterDialogBinding
|
||||
import ac.mdiq.podcini.databinding.FilterDialogRowBinding
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.storage.model.feed.SubscriptionsFilter
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.subscriptionsFilter
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
|
||||
class SubscriptionsFilterDialog : BottomSheetDialogFragment() {
|
||||
|
@ -111,7 +111,7 @@ class SubscriptionsFilterDialog : BottomSheetDialogFragment() {
|
|||
private fun updateFilter(filterValues: Set<String>) {
|
||||
val subscriptionsFilter = SubscriptionsFilter(filterValues.toTypedArray<String>())
|
||||
UserPreferences.subscriptionsFilter = subscriptionsFilter
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import ac.mdiq.podcini.storage.DBWriter
|
|||
import ac.mdiq.podcini.storage.model.feed.FeedPreferences
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
|
||||
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration
|
||||
import ac.mdiq.podcini.util.event.FeedTagsChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -23,7 +24,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.Serializable
|
||||
|
||||
class TagSettingsDialog : DialogFragment() {
|
||||
|
@ -81,7 +81,7 @@ class TagSettingsDialog : DialogFragment() {
|
|||
addTag(binding.newTagEditText.text.toString().trim { it <= ' ' })
|
||||
updatePreferencesTags(feedPreferencesList, commonTags)
|
||||
DBReader.buildTags()
|
||||
EventBus.getDefault().post(FeedTagsChangedEvent())
|
||||
EventFlow.postEvent(FlowEvent.FeedTagsChangedEvent())
|
||||
}
|
||||
dialog.setNegativeButton(R.string.cancel_label, null)
|
||||
return dialog.create()
|
||||
|
|
|
@ -11,7 +11,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.playbackSpeedArray
|
|||
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration
|
||||
import ac.mdiq.podcini.ui.view.PlaybackSpeedSeekBar
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.playback.SpeedChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -21,15 +22,15 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
|
@ -57,7 +58,7 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
super.onStart()
|
||||
controller = object : PlaybackController(requireActivity()) {
|
||||
override fun loadMediaInfo() {
|
||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
if (controller != null) updateSpeed(FlowEvent.SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
}
|
||||
|
||||
override fun onPlaybackServiceConnected() {
|
||||
|
@ -72,19 +73,29 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
controller?.init()
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
if (controller != null) updateSpeed(SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
procFlowEvents()
|
||||
if (controller != null) updateSpeed(FlowEvent.SpeedChangedEvent(controller!!.currentPlaybackSpeedMultiplier))
|
||||
}
|
||||
|
||||
@UnstableApi override fun onStop() {
|
||||
super.onStop()
|
||||
controller?.release()
|
||||
controller = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun updateSpeed(event: SpeedChangedEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SpeedChangedEvent -> updateSpeed(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSpeed(event: FlowEvent.SpeedChangedEvent) {
|
||||
speedSeekBar.updateSpeed(event.newSpeed)
|
||||
addCurrentSpeedChip.text = String.format(Locale.getDefault(), "%1$.2f", event.newSpeed)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.setVideoMode
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.feedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.setFeedOrder
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.setVideoMode
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
object VideoModeDialog {
|
||||
fun showDialog(context: Context) {
|
||||
|
|
|
@ -8,25 +8,26 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
|
|||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog
|
||||
import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent
|
||||
import ac.mdiq.podcini.ui.dialog.ItemSortDialog
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
/**
|
||||
* Shows all episodes (possibly filtered by user).
|
||||
*/
|
||||
class AllEpisodesFragment : BaseEpisodesListFragment() {
|
||||
@UnstableApi class AllEpisodesFragment : BaseEpisodesListFragment() {
|
||||
|
||||
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val root = super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
@ -42,6 +43,11 @@ class AllEpisodesFragment : BaseEpisodesListFragment() {
|
|||
return root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun loadData(): List<FeedItem> {
|
||||
return DBReader.getEpisodes(0, page * EPISODES_PER_PAGE, getFilter(), allEpisodesSortOrder)
|
||||
}
|
||||
|
@ -79,7 +85,7 @@ class AllEpisodesFragment : BaseEpisodesListFragment() {
|
|||
if (filter.contains(FeedItemFilter.IS_FAVORITE)) filter.remove(FeedItemFilter.IS_FAVORITE)
|
||||
else filter.add(FeedItemFilter.IS_FAVORITE)
|
||||
|
||||
onFilterChanged(AllEpisodesFilterChangedEvent(HashSet(filter)))
|
||||
onFilterChanged(FlowEvent.AllEpisodesFilterChangedEvent(HashSet(filter)))
|
||||
return true
|
||||
}
|
||||
R.id.episodes_sort -> {
|
||||
|
@ -90,8 +96,18 @@ class AllEpisodesFragment : BaseEpisodesListFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onFilterChanged(event: AllEpisodesFilterChangedEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.AllEpisodesFilterChangedEvent -> onFilterChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onFilterChanged(event: FlowEvent.AllEpisodesFilterChangedEvent) {
|
||||
prefFilterAllEpisodes = StringUtils.join(event.filterValues, ",")
|
||||
updateFilterUi()
|
||||
page = 1
|
||||
|
@ -117,14 +133,15 @@ class AllEpisodesFragment : BaseEpisodesListFragment() {
|
|||
}
|
||||
|
||||
override fun onAddItem(title: Int, ascending: SortOrder, descending: SortOrder, ascendingIsDefault: Boolean) {
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG)
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG
|
||||
|| ascending == SortOrder.PLAYED_DATE_OLD_NEW || ascending == SortOrder.COMPLETED_DATE_OLD_NEW)
|
||||
super.onAddItem(title, ascending, descending, ascendingIsDefault)
|
||||
}
|
||||
|
||||
override fun onSelectionChanged() {
|
||||
super.onSelectionChanged()
|
||||
allEpisodesSortOrder = sortOrder
|
||||
EventBus.getDefault().post(FeedListUpdateEvent(0))
|
||||
EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(0))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,9 +38,8 @@ import ac.mdiq.podcini.util.ChapterUtils
|
|||
import ac.mdiq.podcini.util.Converter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||
import ac.mdiq.podcini.util.event.FavoritesEvent
|
||||
import ac.mdiq.podcini.util.event.PlayerErrorEvent
|
||||
import ac.mdiq.podcini.util.event.playback.*
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
|
@ -57,6 +56,7 @@ import androidx.core.app.ShareCompat
|
|||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
|
@ -65,10 +65,10 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.max
|
||||
|
@ -94,7 +94,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
private lateinit var cardViewSeek: CardView
|
||||
private lateinit var txtvSeek: TextView
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
private var controller: PlaybackController? = null
|
||||
// private var disposable: Disposable? = null
|
||||
private var seekedToChapterStart = false
|
||||
|
@ -145,7 +145,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
cardViewSeek = binding.cardViewSeek
|
||||
txtvSeek = binding.txtvSeek
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -163,8 +162,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
_binding = null
|
||||
controller?.release()
|
||||
controller = null
|
||||
scope.cancel()
|
||||
EventBus.getDefault().unregister(this)
|
||||
// scope.cancel()
|
||||
|
||||
Logd(TAG, "Fragment destroyed")
|
||||
}
|
||||
|
||||
|
@ -187,12 +186,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
// updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN)
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
// private fun loadMediaInfo0(includingChapters: Boolean) {
|
||||
// Logd(TAG, "loadMediaInfo called")
|
||||
//
|
||||
|
@ -226,7 +219,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
val theMedia = controller?.getMedia() ?: return
|
||||
if (currentMedia == null || theMedia.getIdentifier() != currentMedia?.getIdentifier() || (includingChapters && !theMedia.chaptersLoaded())) {
|
||||
Logd(TAG, "loadMediaInfo loading details ${theMedia.getIdentifier()} chapter: $includingChapters")
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
val media: Playable = withContext(Dispatchers.IO) {
|
||||
theMedia.apply {
|
||||
if (includingChapters) ChapterUtils.loadChapters(this, requireContext(), false)
|
||||
|
@ -270,12 +263,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
setupOptionsMenu(currentMedia)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
|
||||
if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
retainInstance = true
|
||||
|
@ -283,6 +270,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
loadMediaInfo(false)
|
||||
}
|
||||
|
||||
|
@ -311,18 +299,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
// }
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun favoritesChanged(event: FavoritesEvent?) {
|
||||
loadMediaInfo(false)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun mediaPlayerError(event: PlayerErrorEvent) {
|
||||
MediaPlayerErrorDialog.show(activity as Activity, event)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEvenStartPlay(event: StartPlayEvent) {
|
||||
fun onEvenStartPlay(event: FlowEvent.StartPlayEvent) {
|
||||
Logd(TAG, "onEvenStartPlay ${event.item.title}")
|
||||
currentitem = event.item
|
||||
if (currentMedia?.getIdentifier() == null || currentitem!!.media?.getIdentifier() != currentMedia?.getIdentifier())
|
||||
|
@ -330,6 +307,25 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
(activity as MainActivity).setPlayerVisible(true)
|
||||
}
|
||||
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
Logd(TAG, "subscribing PositionFlowEvent")
|
||||
EventFlow.events.collectLatest { event ->
|
||||
// Logd(TAG, "PositionFlowEvent: ${event}")
|
||||
when (event) {
|
||||
is FlowEvent.PlaybackServiceEvent ->
|
||||
if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN)
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
is FlowEvent.StartPlayEvent -> onEvenStartPlay(event)
|
||||
is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event)
|
||||
is FlowEvent.FavoritesEvent -> loadMediaInfo(false)
|
||||
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (controller == null) return
|
||||
|
||||
|
@ -539,15 +535,26 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
// Logd(TAG, "PositionFlowEvent: ${event}")
|
||||
when (event) {
|
||||
is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate(event)
|
||||
is FlowEvent.SpeedChangedEvent -> updatePlaybackSpeedButton(event)
|
||||
is FlowEvent.PlaybackServiceEvent -> onPlaybackServiceChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
|
@ -619,20 +626,18 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
if (controller == null) return@OnClickListener
|
||||
showTimeLeft = !showTimeLeft
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
onPositionObserverUpdate(PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
onPositionObserverUpdate(FlowEvent.PlaybackPositionEvent(controller!!.position, controller!!.duration))
|
||||
})
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
|
||||
fun updatePlaybackSpeedButton(event: FlowEvent.SpeedChangedEvent) {
|
||||
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
|
||||
txtvPlaybackSpeed.text = speedStr
|
||||
butPlaybackSpeed.setSpeed(event.newSpeed)
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPositionObserverUpdate(event: PlaybackPositionEvent) {
|
||||
fun onPositionObserverUpdate(event: FlowEvent.PlaybackPositionEvent) {
|
||||
if (controller == null || controller!!.position == Playable.INVALID_TIME || controller!!.duration == Playable.INVALID_TIME) return
|
||||
|
||||
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
|
||||
|
@ -668,11 +673,10 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
|
||||
fun onPlaybackServiceChanged(event: FlowEvent.PlaybackServiceEvent) {
|
||||
when (event.action) {
|
||||
PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> (activity as MainActivity).setPlayerVisible(false)
|
||||
PlaybackServiceEvent.Action.SERVICE_STARTED -> (activity as MainActivity).setPlayerVisible(true)
|
||||
FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> (activity as MainActivity).setPlayerVisible(false)
|
||||
FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED -> (activity as MainActivity).setPlayerVisible(true)
|
||||
// PlaybackServiceEvent.Action.SERVICE_RESTARTED -> (activity as MainActivity).setPlayerVisible(true)
|
||||
}
|
||||
}
|
||||
|
@ -685,12 +689,14 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
@OptIn(UnstableApi::class) override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
|
||||
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
|
||||
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
|
||||
if (UserPreferences.speedforwardSpeed > 0.1f) txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed)
|
||||
else txtvSkip.visibility = View.GONE
|
||||
val media = controller?.getMedia() ?: return
|
||||
updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||
updatePlaybackSpeedButton(FlowEvent.SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)))
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
|
@ -717,7 +723,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
|
||||
episodeTitle.text = media.getEpisodeTitle()
|
||||
(activity as MainActivity).setPlayerVisible(true)
|
||||
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||
onPositionObserverUpdate(FlowEvent.PlaybackPositionEvent(media.getPosition(), media.getDuration()))
|
||||
|
||||
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
|
||||
val imgLocFB = ImageResourceUtils.getFallbackImageLocation(media)
|
||||
|
|
|
@ -20,8 +20,8 @@ import ac.mdiq.podcini.ui.view.LiftOnScrollListener
|
|||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.FeedItemUtil
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
|
@ -31,6 +31,7 @@ import android.widget.TextView
|
|||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.util.Pair
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
|
@ -39,16 +40,17 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
/**
|
||||
* Shows unread or recently published episodes
|
||||
*/
|
||||
abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
|
||||
@UnstableApi abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
|
||||
|
||||
@JvmField
|
||||
protected var page: Int = 1
|
||||
protected var isLoadingMore: Boolean = false
|
||||
|
@ -58,7 +60,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
var _binding: BaseEpisodesListFragmentBinding? = null
|
||||
protected val binding get() = _binding!!
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
lateinit var recyclerView: EpisodeItemListRecyclerView
|
||||
lateinit var emptyView: EmptyViewHandler
|
||||
|
@ -120,19 +122,8 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
FeedUpdateManager.runOnceOrAsk(requireContext())
|
||||
}
|
||||
|
||||
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)
|
||||
// }
|
||||
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
|
||||
this@BaseEpisodesListFragment.onContextItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
listAdapter.setOnSelectModeListener(this)
|
||||
recyclerView.adapter = listAdapter
|
||||
createListAdaptor()
|
||||
|
||||
progressBar = binding.progressBar
|
||||
progressBar.visibility = View.VISIBLE
|
||||
|
||||
|
@ -181,12 +172,31 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
true
|
||||
}
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
loadItems()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
open fun createListAdaptor() {
|
||||
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)
|
||||
// }
|
||||
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
|
||||
this@BaseEpisodesListFragment.onContextItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
listAdapter.setOnSelectModeListener(this)
|
||||
recyclerView.adapter = listAdapter
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
loadItems()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
registerForContextMenu(recyclerView)
|
||||
|
@ -198,10 +208,10 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
unregisterForContextMenu(recyclerView)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
//// disposable?.dispose()
|
||||
// }
|
||||
|
||||
@UnstableApi override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (super.onOptionsItemSelected(item)) return true
|
||||
|
@ -257,7 +267,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
// .subscribe({ listAdapter.endSelectMode() },
|
||||
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>())
|
||||
|
@ -322,7 +332,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
// recyclerView.post { isLoadingMore = false }
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val data = withContext(Dispatchers.IO) {
|
||||
loadMoreData(page)
|
||||
|
@ -348,8 +358,8 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
scope.cancel()
|
||||
EventBus.getDefault().unregister(this)
|
||||
// scope.cancel()
|
||||
|
||||
listAdapter.endSelectMode()
|
||||
}
|
||||
|
||||
|
@ -362,8 +372,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
speedDialView.visibility = View.GONE
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
|
||||
for (item in event.items) {
|
||||
val pos: Int = FeedItemUtil.indexOfItemWithId(episodes, item.id)
|
||||
|
@ -377,8 +386,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
// Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
|
||||
if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
|
||||
currentPlaying!!.notifyPlaybackPositionUpdated(event)
|
||||
|
@ -395,7 +403,6 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onKeyUp(event: KeyEvent) {
|
||||
if (!isAdded || !isVisible || !isMenuVisible) return
|
||||
|
||||
|
@ -406,32 +413,39 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
for (downloadUrl in event.urls) {
|
||||
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(episodes, downloadUrl)
|
||||
if (pos >= 0) listAdapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||
refreshSwipeTelltale()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
|
||||
is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> loadItems()
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedUpdateRunningEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.keyEvents.collectLatest { event ->
|
||||
onKeyUp(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshSwipeTelltale() {
|
||||
|
@ -465,7 +479,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val data = withContext(Dispatchers.IO) {
|
||||
Pair(loadData().toMutableList(), loadTotalItemCount())
|
||||
|
@ -502,11 +516,9 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
|
|||
|
||||
protected abstract fun getPrefName(): String
|
||||
|
||||
protected open fun updateToolbar() {
|
||||
}
|
||||
protected open fun updateToolbar() {}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedUpdateRunningEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedUpdateRunningEvent) {
|
||||
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ import ac.mdiq.podcini.ui.adapter.ChaptersListAdapter
|
|||
import ac.mdiq.podcini.util.ChapterUtils.getCurrentChapterIndex
|
||||
import ac.mdiq.podcini.util.ChapterUtils.loadChapters
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -23,18 +24,15 @@ import android.widget.Toast
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.MaybeEmitter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@UnstableApi
|
||||
class ChaptersFragment : AppCompatDialogFragment() {
|
||||
|
@ -46,7 +44,7 @@ class ChaptersFragment : AppCompatDialogFragment() {
|
|||
private lateinit var adapter: ChaptersListAdapter
|
||||
|
||||
private var controller: PlaybackController? = null
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
private var focusedChapter = -1
|
||||
private var media: Playable? = null
|
||||
|
||||
|
@ -100,27 +98,41 @@ class ChaptersFragment : AppCompatDialogFragment() {
|
|||
}
|
||||
}
|
||||
controller?.init()
|
||||
EventBus.getDefault().register(this)
|
||||
loadMediaInfo(false)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
loadMediaInfo(false)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
controller?.release()
|
||||
controller = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
disposable?.dispose()
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
//// disposable?.dispose()
|
||||
// }
|
||||
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
updateChapterSelection(getCurrentChapter(media), false)
|
||||
adapter.notifyTimeChanged(event.position.toLong())
|
||||
}
|
||||
|
@ -132,19 +144,31 @@ class ChaptersFragment : AppCompatDialogFragment() {
|
|||
}
|
||||
|
||||
private fun loadMediaInfo(forceRefresh: Boolean) {
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
|
||||
disposable = Maybe.create { emitter: MaybeEmitter<Any> ->
|
||||
val media = controller!!.getMedia()
|
||||
if (media != null) {
|
||||
loadChapters(media, requireContext(), forceRefresh)
|
||||
emitter.onSuccess(media)
|
||||
} else emitter.onComplete()
|
||||
// disposable = Maybe.create { emitter: MaybeEmitter<Any> ->
|
||||
// val media = controller!!.getMedia()
|
||||
// if (media != null) {
|
||||
// loadChapters(media, requireContext(), forceRefresh)
|
||||
// emitter.onSuccess(media)
|
||||
// } else emitter.onComplete()
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ media: Any -> onMediaChanged(media as Playable) },
|
||||
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
val media = withContext(Dispatchers.IO) {
|
||||
val media_ = controller!!.getMedia()
|
||||
if (media_ != null) loadChapters(media_, requireContext(), forceRefresh)
|
||||
media_
|
||||
}
|
||||
onMediaChanged(media as Playable)
|
||||
}.invokeOnCompletion { throwable ->
|
||||
if (throwable!= null) Logd(TAG, Log.getStackTraceString(throwable))
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ media: Any -> onMediaChanged(media as Playable) },
|
||||
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
}
|
||||
|
||||
private fun onMediaChanged(media: Playable) {
|
||||
|
|
|
@ -10,7 +10,8 @@ import ac.mdiq.podcini.storage.DBReader
|
|||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.adapter.OnlineFeedsAdapter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.DiscoveryDefaultUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
|
@ -26,12 +27,12 @@ import android.widget.AdapterView.OnItemClickListener
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
@ -60,7 +61,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
private var searchResults: List<PodcastSearchResult>? = null
|
||||
private var topList: List<PodcastSearchResult>? = null
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
private var countryCode: String? = "US"
|
||||
private var hidden = false
|
||||
|
@ -135,7 +136,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
|
||||
adapter = null
|
||||
|
@ -194,7 +195,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
// butRetry.visibility = View.VISIBLE
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val podcasts = withContext(Dispatchers.IO) {
|
||||
loader.loadToplist(country?:"", NUM_OF_TOP_PODCASTS, DBReader.getFeedList())
|
||||
|
@ -226,7 +227,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
hidden = item.isChecked
|
||||
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
|
||||
|
||||
EventBus.getDefault().post(DiscoveryDefaultUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent())
|
||||
loadToplist(countryCode)
|
||||
return true
|
||||
}
|
||||
|
@ -280,7 +281,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
|
||||
prefs.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
|
||||
|
||||
EventBus.getDefault().post(DiscoveryDefaultUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent())
|
||||
loadToplist(countryCode)
|
||||
}
|
||||
builder.setNegativeButton(R.string.cancel_label, null)
|
||||
|
|
|
@ -9,18 +9,21 @@ import ac.mdiq.podcini.ui.adapter.DownloadLogAdapter
|
|||
import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog
|
||||
import ac.mdiq.podcini.ui.view.EmptyViewHandler
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.DownloadLogEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Shows the download log
|
||||
|
@ -33,11 +36,11 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
|
|||
|
||||
private var downloadLog: List<DownloadResult> = ArrayList()
|
||||
// private var disposable: Disposable? = null
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
|
@ -57,14 +60,17 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
|
|||
binding.list.adapter = adapter
|
||||
binding.list.onItemClickListener = this
|
||||
binding.list.isNestedScrollingEnabled = true
|
||||
EventBus.getDefault().register(this)
|
||||
loadDownloadLog()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
@ -74,9 +80,15 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
|
|||
if (item is DownloadResult) DownloadLogDetailsDialog(requireContext(), item).show()
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onDownloadLogChanged(event: DownloadLogEvent?) {
|
||||
loadDownloadLog()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.DownloadLogEvent -> loadDownloadLog()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
|
@ -107,7 +119,7 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
|
|||
// }
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
DBReader.getDownloadLog()
|
||||
|
|
|
@ -25,8 +25,8 @@ import ac.mdiq.podcini.ui.view.LiftOnScrollListener
|
|||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.FeedItemUtil
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
|
@ -34,6 +34,7 @@ import android.widget.ProgressBar
|
|||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
|
@ -41,19 +42,16 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Displays all completed downloads and provides a button to delete them.
|
||||
*/
|
||||
class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
|
||||
@UnstableApi class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
|
||||
private var _binding: SimpleListFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
|
@ -148,20 +146,23 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
DownloadLogFragment().show(childFragmentManager, null)
|
||||
|
||||
addEmptyView()
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
loadItems()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
loadItems()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
_binding = null
|
||||
adapter.endSelectMode()
|
||||
toolbar.setOnMenuItemClickListener(null)
|
||||
|
@ -193,8 +194,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
val newRunningDownloads: MutableSet<String> = HashSet()
|
||||
for (url in event.urls) {
|
||||
if (DownloadServiceInterface.get()?.isDownloadingEpisode(url) == true) newRunningDownloads.add(url)
|
||||
|
@ -210,6 +210,28 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
}
|
||||
}
|
||||
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlayerStatusEvent, is FlowEvent.DownloadLogEvent, is FlowEvent.UnreadItemsUpdateEvent -> loadItems()
|
||||
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
val selectedItem: FeedItem? = adapter.longPressedItem
|
||||
if (selectedItem == null) {
|
||||
|
@ -229,8 +251,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
emptyView.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with: event = [$event]")
|
||||
|
||||
var i = 0
|
||||
|
@ -258,8 +279,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
refreshInfoBar()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
// Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
|
||||
if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
|
||||
currentPlaying!!.notifyPlaybackPositionUpdated(event)
|
||||
|
@ -277,26 +297,6 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
refreshInfoBar()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDownloadLogChanged(event: DownloadLogEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||
refreshSwipeTelltale()
|
||||
}
|
||||
|
||||
private fun refreshSwipeTelltale() {
|
||||
if (swipeActions.actions?.left != null) binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
|
||||
if (swipeActions.actions?.right != null) binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
|
||||
|
@ -337,8 +337,8 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// })
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
scope.launch {
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val sortOrder: SortOrder? = UserPreferences.downloadsSortedOrder
|
||||
|
@ -423,7 +423,9 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
}
|
||||
|
||||
override fun onAddItem(title: Int, ascending: SortOrder, descending: SortOrder, ascendingIsDefault: Boolean) {
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.EPISODE_TITLE_A_Z
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.PLAYED_DATE_OLD_NEW
|
||||
|| ascending == SortOrder.COMPLETED_DATE_OLD_NEW
|
||||
|| ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.EPISODE_TITLE_A_Z
|
||||
|| ascending == SortOrder.SIZE_SMALL_LARGE || ascending == SortOrder.FEED_TITLE_A_Z) {
|
||||
super.onAddItem(title, ascending, descending, ascendingIsDefault)
|
||||
}
|
||||
|
@ -432,7 +434,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To
|
|||
override fun onSelectionChanged() {
|
||||
super.onSelectionChanged()
|
||||
UserPreferences.downloadsSortedOrder = sortOrder
|
||||
EventBus.getDefault().post(DownloadLogEvent.listUpdated())
|
||||
EventFlow.postEvent(FlowEvent.DownloadLogEvent())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,8 @@ import ac.mdiq.podcini.util.Converter
|
|||
import ac.mdiq.podcini.util.DateFormatter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.PlaybackStatus
|
||||
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
|
||||
import ac.mdiq.podcini.util.event.FeedItemEvent
|
||||
import ac.mdiq.podcini.util.event.PlayerStatusEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Layout
|
||||
|
@ -41,6 +39,7 @@ import androidx.appcompat.widget.Toolbar
|
|||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
|
@ -51,20 +50,17 @@ import com.skydoves.balloon.ArrowOrientation
|
|||
import com.skydoves.balloon.ArrowOrientationRules
|
||||
import com.skydoves.balloon.Balloon
|
||||
import com.skydoves.balloon.BalloonAnimation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Displays information about an Episode (FeedItem) and actions.
|
||||
*/
|
||||
class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
@UnstableApi class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
private var _binding: EpisodeInfoFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
|
@ -172,7 +168,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
})
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
controller = object : PlaybackController(requireActivity()) {
|
||||
override fun loadMediaInfo() {
|
||||
// Do nothing
|
||||
|
@ -184,6 +179,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun showOnDemandConfigBalloon(offerStreaming: Boolean) {
|
||||
val isLocaleRtl = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL)
|
||||
val balloon: Balloon = Balloon.Builder(requireContext())
|
||||
|
@ -208,7 +208,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
positiveButton.setOnClickListener {
|
||||
UserPreferences.isStreamOverDownload = offerStreaming
|
||||
// Update all visible lists to reflect new streaming action button
|
||||
EventBus.getDefault().post(UnreadItemsUpdateEvent())
|
||||
EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
|
||||
(activity as MainActivity).showSnackbarAbovePlayer(R.string.on_demand_config_setting_changed, Snackbar.LENGTH_SHORT)
|
||||
balloon.dismiss()
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
super.onDestroyView()
|
||||
Logd(TAG, "onDestroyView")
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
controller?.release()
|
||||
// disposable?.dispose()
|
||||
root.removeView(webvDescription)
|
||||
|
@ -382,8 +382,28 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
(activity as MainActivity).loadChildFragment(fragment)
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlayerStatusEvent -> updateButtons()
|
||||
is FlowEvent.UnreadItemsUpdateEvent -> load()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with: event = [$event]")
|
||||
if (this.item == null) return
|
||||
for (item in event.items) {
|
||||
|
@ -394,23 +414,12 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
if (item == null || item!!.media == null) return
|
||||
if (!event.urls.contains(item!!.media!!.download_url)) return
|
||||
if (itemsLoaded && activity != null) updateButtons()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
load()
|
||||
}
|
||||
|
||||
// @UnstableApi private fun load0() {
|
||||
// disposable?.dispose()
|
||||
// if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE
|
||||
|
@ -434,8 +443,8 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE
|
||||
|
||||
Logd(TAG, "load() called")
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
scope.launch {
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val feedItem = item
|
||||
|
|
|
@ -3,22 +3,25 @@ package ac.mdiq.podcini.ui.fragment
|
|||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Shows all episodes (possibly filtered by user).
|
||||
*/
|
||||
class EpisodesListFragment : BaseEpisodesListFragment() {
|
||||
@UnstableApi class ExternalEpisodesListFragment : BaseEpisodesListFragment() {
|
||||
|
||||
private val episodeList: MutableList<FeedItem> = mutableListOf()
|
||||
|
||||
|
@ -40,6 +43,11 @@ class EpisodesListFragment : BaseEpisodesListFragment() {
|
|||
return root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
fun setEpisodes(episodeList_: MutableList<FeedItem>) {
|
||||
episodeList.clear()
|
||||
episodeList.addAll(episodeList_)
|
||||
|
@ -94,12 +102,15 @@ class EpisodesListFragment : BaseEpisodesListFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onFilterChanged(event: AllEpisodesFilterChangedEvent) {
|
||||
// prefFilterAllEpisodes = StringUtils.join(event.filterValues, ",")
|
||||
// updateFilterUi()
|
||||
page = 1
|
||||
// loadItems()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.AllEpisodesFilterChangedEvent -> page = 1
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFilterUi() {
|
||||
|
@ -143,8 +154,8 @@ class EpisodesListFragment : BaseEpisodesListFragment() {
|
|||
const val EXTRA_EPISODES: String = "episodes_list"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(episodes: MutableList<FeedItem>): EpisodesListFragment {
|
||||
val i = EpisodesListFragment()
|
||||
fun newInstance(episodes: MutableList<FeedItem>): ExternalEpisodesListFragment {
|
||||
val i = ExternalEpisodesListFragment()
|
||||
i.setEpisodes(episodes)
|
||||
return i
|
||||
}
|
|
@ -37,6 +37,7 @@ import androidx.appcompat.content.res.AppCompatResources
|
|||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.load
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
|
@ -285,8 +286,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
// .subscribe({ (activity as MainActivity).showSnackbarAbovePlayer(string.ok, Snackbar.LENGTH_SHORT) },
|
||||
// { error: Throwable -> (activity as MainActivity).showSnackbarAbovePlayer(error.localizedMessage, Snackbar.LENGTH_LONG) })
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
scope.launch {
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
requireActivity().contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
|
|
@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.fragment
|
|||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.FeedItemListFragmentBinding
|
||||
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
|
||||
import ac.mdiq.podcini.feed.FeedEvent
|
||||
import ac.mdiq.podcini.net.download.FeedUpdateManager
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
|
@ -25,8 +24,8 @@ import ac.mdiq.podcini.ui.utils.MoreContentListFooterUtil
|
|||
import ac.mdiq.podcini.ui.view.ToolbarIconTintManager
|
||||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.*
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
|
@ -40,6 +39,7 @@ import android.widget.Toast
|
|||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
|
@ -48,17 +48,15 @@ import com.joanzapata.iconify.Iconify
|
|||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Semaphore
|
||||
|
||||
/**
|
||||
* Displays a list of FeedItems.
|
||||
*/
|
||||
class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolbar.OnMenuItemClickListener,
|
||||
@UnstableApi class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolbar.OnMenuItemClickListener,
|
||||
SelectableAdapter.OnSelectModeListener {
|
||||
|
||||
private var _binding: FeedItemListFragmentBinding? = null
|
||||
|
@ -79,7 +77,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
// private var disposable: Disposable? = null
|
||||
|
||||
private val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
private val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -151,14 +149,12 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
})
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
|
||||
binding.swipeRefresh.setOnRefreshListener {
|
||||
FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
|
||||
}
|
||||
|
||||
loadItems()
|
||||
// loadItems()
|
||||
|
||||
// Init action UI (via a FAB Speed Dial)
|
||||
speedDialBinding.fabSD.overlayLayout = speedDialBinding.fabSDOverlay
|
||||
|
@ -184,6 +180,12 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
loadItems()
|
||||
}
|
||||
|
||||
private val semaphore = Semaphore(0)
|
||||
private fun initializeTTS(context: Context) {
|
||||
Logd(TAG, "starting TTS")
|
||||
|
@ -207,10 +209,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
super.onDestroyView()
|
||||
_binding = null
|
||||
_speedDialBinding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
// disposable?.dispose()
|
||||
ioScope.cancel()
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
adapter.endSelectMode()
|
||||
|
||||
tts?.stop()
|
||||
|
@ -298,14 +300,12 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEvent(event: FeedEvent) {
|
||||
fun onEvent(event: FlowEvent.FeedEvent) {
|
||||
Logd(TAG, "onEvent() called with: event = [$event]")
|
||||
if (event.feedId == feedID) loadItems()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
|
||||
if (feed == null || feed!!.items.isEmpty()) return
|
||||
|
||||
|
@ -323,8 +323,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]")
|
||||
if (feed == null || feed!!.items.isEmpty()) return
|
||||
|
||||
|
@ -334,8 +333,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
// Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
|
||||
if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem) currentPlaying!!.notifyPlaybackPositionUpdated(event)
|
||||
else {
|
||||
|
@ -351,17 +349,39 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun favoritesChanged(event: FavoritesEvent?) {
|
||||
Logd(TAG, "favoritesChanged called")
|
||||
loadItems()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.QueueEvent -> loadItems()
|
||||
is FlowEvent.FavoritesEvent -> loadItems()
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedEvent -> onEvent(event)
|
||||
is FlowEvent.PlayerStatusEvent -> loadItems()
|
||||
is FlowEvent.UnreadItemsUpdateEvent -> loadItems()
|
||||
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
|
||||
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedUpdateRunningEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.keyEvents.collectLatest { event ->
|
||||
onKeyUp(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onQueueChanged(event: QueueEvent?) {
|
||||
Logd(TAG, "onQueueChanged called")
|
||||
loadItems()
|
||||
}
|
||||
|
||||
override fun onStartSelectMode() {
|
||||
swipeActions.detach()
|
||||
|
@ -383,33 +403,14 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
swipeActions.attachTo(binding.recyclerView)
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
Logd(TAG, "onPlayerStatusChanged called")
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
Logd(TAG, "onUnreadItemsChanged called")
|
||||
loadItems()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent) {
|
||||
fun onFeedListChanged(event: FlowEvent.FeedListUpdateEvent) {
|
||||
if (feed != null && event.contains(feed!!)) {
|
||||
Logd(TAG, "onFeedListChanged called")
|
||||
loadItems()
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||
refreshSwipeTelltale()
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedUpdateRunningEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedUpdateRunningEvent) {
|
||||
nextPageLoader.setLoadingState(event.isFeedUpdateRunning)
|
||||
if (!event.isFeedUpdateRunning) nextPageLoader.root.visibility = View.GONE
|
||||
binding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
|
||||
|
@ -486,7 +487,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
// { error: Throwable -> error.printStackTrace() },
|
||||
// { DownloadLogFragment().show(childFragmentManager, null) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
val downloadResult = withContext(Dispatchers.IO) {
|
||||
val feedDownloadLog: List<DownloadResult> = DBReader.getFeedDownloadLog(feedID)
|
||||
if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) null else feedDownloadLog[0]
|
||||
|
@ -558,7 +559,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
feed = withContext(Dispatchers.IO) {
|
||||
val feed_ = loadData()
|
||||
|
@ -616,7 +617,6 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
return feed
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onKeyUp(event: KeyEvent) {
|
||||
if (!isAdded || !isVisible || !isMenuVisible) return
|
||||
|
||||
|
@ -648,7 +648,8 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
|
|||
}
|
||||
|
||||
override fun onAddItem(title: Int, ascending: SortOrder, descending: SortOrder, ascendingIsDefault: Boolean) {
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.RANDOM
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.PLAYED_DATE_OLD_NEW || ascending == SortOrder.COMPLETED_DATE_OLD_NEW
|
||||
|| ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.RANDOM
|
||||
|| ascending == SortOrder.EPISODE_TITLE_A_Z
|
||||
|| (requireArguments().getBoolean(ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) {
|
||||
super.onAddItem(title, ascending, descending, ascendingIsDefault)
|
||||
|
|
|
@ -17,9 +17,8 @@ import ac.mdiq.podcini.ui.dialog.EpisodeFilterDialog
|
|||
import ac.mdiq.podcini.ui.dialog.FeedPreferenceSkipDialog
|
||||
import ac.mdiq.podcini.ui.dialog.TagSettingsDialog
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent
|
||||
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
|
||||
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
@ -34,6 +33,7 @@ import android.widget.Toast
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
|
@ -42,7 +42,6 @@ import androidx.preference.SwitchPreferenceCompat
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
|
@ -50,7 +49,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
private var _binding: FeedsettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
|
@ -77,7 +76,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
// { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) },
|
||||
// {})
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
val feed = withContext(Dispatchers.IO) {
|
||||
DBReader.getFeed(feedId)
|
||||
}
|
||||
|
@ -98,13 +97,13 @@ class FeedSettingsFragment : Fragment() {
|
|||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
class FeedSettingsPreferenceFragment : PreferenceFragmentCompat() {
|
||||
private var feed: Feed? = null
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
private var feedPreferences: FeedPreferences? = null
|
||||
|
||||
|
@ -173,7 +172,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
// findPreference<Preference>(PREF_SCREEN)!!.isVisible = true
|
||||
// }, { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) }, {})
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
feed = withContext(Dispatchers.IO) {
|
||||
DBReader.getFeed(feedId)
|
||||
}
|
||||
|
@ -212,7 +211,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
|
@ -224,7 +223,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
feedPreferences!!.feedSkipIntro = skipIntro
|
||||
feedPreferences!!.feedSkipEnding = skipEnding
|
||||
DBWriter.persistFeedPreferences(feedPreferences!!)
|
||||
EventBus.getDefault().post(SkipIntroEndingChangedEvent(feedPreferences!!.feedSkipIntro, feedPreferences!!.feedSkipEnding, feed!!.id))
|
||||
EventFlow.postEvent(FlowEvent.SkipIntroEndingChangedEvent(feedPreferences!!.feedSkipIntro, feedPreferences!!.feedSkipEnding, feed!!.id))
|
||||
}
|
||||
}.show()
|
||||
false
|
||||
|
@ -256,7 +255,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
else viewBinding.seekBar.currentSpeed
|
||||
feedPreferences!!.feedPlaybackSpeed = newSpeed
|
||||
if (feedPreferences != null) DBWriter.persistFeedPreferences(feedPreferences!!)
|
||||
EventBus.getDefault().post(SpeedPresetChangedEvent(feedPreferences!!.feedPlaybackSpeed, feed!!.id))
|
||||
EventFlow.postEvent(FlowEvent.SpeedPresetChangedEvent(feedPreferences!!.feedPlaybackSpeed, feed!!.id))
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.show()
|
||||
|
@ -352,7 +351,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
DBWriter.persistFeedPreferences(feedPreferences!!)
|
||||
updateVolumeAdaptationValue()
|
||||
if (feed != null && feedPreferences!!.volumeAdaptionSetting != null)
|
||||
EventBus.getDefault().post(VolumeAdaptionChangedEvent(feedPreferences!!.volumeAdaptionSetting!!, feed!!.id))
|
||||
EventFlow.postEvent(FlowEvent.VolumeAdaptionChangedEvent(feedPreferences!!.volumeAdaptionSetting!!, feed!!.id))
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,8 @@ import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog
|
|||
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.QueueEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.R.attr
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
|
@ -38,16 +37,17 @@ import androidx.core.graphics.Insets
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlin.math.max
|
||||
|
||||
class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
@ -56,7 +56,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
|
||||
private var navDrawerData: NavDrawerData? = null
|
||||
private var flatItemList: List<NavDrawerData.FeedDrawerItem>? = null
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private lateinit var navAdapter: NavListAdapter
|
||||
|
||||
|
@ -117,33 +117,35 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
scope.cancel()
|
||||
|
||||
// scope.cancel()
|
||||
requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
loadData()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.FeedListUpdateEvent -> loadData()
|
||||
is FlowEvent.QueueEvent -> onQueueChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
loadData()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onQueueChanged(event: QueueEvent) {
|
||||
fun onQueueChanged(event: FlowEvent.QueueEvent) {
|
||||
Logd(TAG, "onQueueChanged($event)")
|
||||
// we are only interested in the number of queue items, not download status or position
|
||||
if (event.action == QueueEvent.Action.DELETED_MEDIA || event.action == QueueEvent.Action.SORTED || event.action == QueueEvent.Action.MOVED) return
|
||||
|
||||
if (event.action == FlowEvent.QueueEvent.Action.DELETED_MEDIA
|
||||
|| event.action == FlowEvent.QueueEvent.Action.SORTED
|
||||
|| event.action == FlowEvent.QueueEvent.Action.MOVED) return
|
||||
loadData()
|
||||
}
|
||||
|
||||
|
@ -252,7 +254,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
|
|||
}
|
||||
|
||||
private fun loadData() {
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
|
||||
|
|
|
@ -27,8 +27,8 @@ import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
|||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||
import ac.mdiq.podcini.util.DownloadErrorLabel.from
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import ac.mdiq.podcini.util.syndication.FeedDiscoverer
|
||||
import ac.mdiq.podcini.util.syndication.HtmlToPlainText
|
||||
import android.app.Dialog
|
||||
|
@ -50,19 +50,17 @@ import android.widget.Toast
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.load
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.jsoup.Jsoup
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -79,7 +77,7 @@ import kotlin.concurrent.Volatile
|
|||
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
|
||||
* and the activity will finish as soon as the error dialog is closed.
|
||||
*/
|
||||
class OnlineFeedViewFragment : Fragment() {
|
||||
@OptIn(UnstableApi::class) class OnlineFeedViewFragment : Fragment() {
|
||||
private var _binding: OnlineFeedviewFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
|
@ -98,7 +96,7 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
|
||||
private var dialog: Dialog? = null
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private var download: Disposable? = null
|
||||
private var parser: Disposable? = null
|
||||
|
@ -160,13 +158,13 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
isPaused = false
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
isPaused = true
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
if (downloader != null && !downloader!!.isFinished) downloader!!.cancel()
|
||||
if (dialog != null && dialog!!.isShowing) dialog!!.dismiss()
|
||||
}
|
||||
|
@ -294,7 +292,7 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
// .subscribe({ status: DownloadResult? -> if (request.destination != null) checkDownloadResult(status, request.destination) },
|
||||
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val status = withContext(Dispatchers.IO) {
|
||||
feeds = DBReader.getFeedList()
|
||||
|
@ -329,8 +327,26 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
@OptIn(UnstableApi::class) private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> handleUpdatedFeedStatus()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onFeedListChanged(event: FlowEvent.FeedListUpdateEvent) {
|
||||
// updater = Observable.fromCallable { DBReader.getFeedList() }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -340,7 +356,7 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
// handleUpdatedFeedStatus()
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }
|
||||
// )
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val feeds = withContext(Dispatchers.IO) {
|
||||
DBReader.getFeedList()
|
||||
|
@ -357,11 +373,6 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent?) {
|
||||
handleUpdatedFeedStatus()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun parseFeed(destination: String) {
|
||||
Logd(TAG, "Parsing feed")
|
||||
// parser = Maybe.fromCallable { doParseFeed(destination) }
|
||||
|
@ -381,7 +392,7 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
// Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(error))
|
||||
// }
|
||||
// })
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
doParseFeed(destination)
|
||||
|
@ -550,7 +561,7 @@ class OnlineFeedViewFragment : Fragment() {
|
|||
for (i in 0..<episodes.size) {
|
||||
episodes[i].id = 1234567890L + i
|
||||
}
|
||||
val fragment: Fragment = EpisodesListFragment.newInstance(episodes)
|
||||
val fragment: Fragment = ExternalEpisodesListFragment.newInstance(episodes)
|
||||
(activity as MainActivity).loadChildFragment(fragment)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,43 @@
|
|||
package ac.mdiq.podcini.ui.fragment
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBWriter
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackHistoryEvent
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
|
||||
import ac.mdiq.podcini.storage.model.feed.SortOrder
|
||||
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
|
||||
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
|
||||
import ac.mdiq.podcini.ui.dialog.ItemSortDialog
|
||||
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
||||
import ac.mdiq.podcini.ui.statistics.subscriptions.DatesFilterDialog
|
||||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.DateFormatter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.util.Log
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.annotation.OptIn
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
@UnstableApi class PlaybackHistoryFragment : BaseEpisodesListFragment() {
|
||||
|
||||
private var sortOrder : SortOrder = SortOrder.PLAYED_DATE_NEW_OLD
|
||||
private var startDate : Long = 0L
|
||||
private var endDate : Long = Date().time
|
||||
|
||||
class PlaybackHistoryFragment : BaseEpisodesListFragment() {
|
||||
override fun getFragmentTag(): String {
|
||||
return "PlaybackHistoryFragment"
|
||||
}
|
||||
|
||||
override fun getPrefName(): String {
|
||||
return "PlaybackHistoryFragment"
|
||||
}
|
||||
|
@ -38,51 +52,130 @@ class PlaybackHistoryFragment : BaseEpisodesListFragment() {
|
|||
emptyView.setIcon(R.drawable.ic_history)
|
||||
emptyView.setTitle(R.string.no_history_head_label)
|
||||
emptyView.setMessage(R.string.no_history_label)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
override fun createListAdaptor() {
|
||||
listAdapter = object : EpisodeItemListAdapter(activity as MainActivity) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeItemViewHolder {
|
||||
return object: EpisodeItemViewHolder(mainActivityRef.get()!!, parent) {
|
||||
override fun setPubDate(item: FeedItem) {
|
||||
val playDate = Date(item.media?.getLastPlayedTime()?:0L)
|
||||
pubDate.text = DateFormatter.formatAbbrev(activity, playDate)
|
||||
pubDate.setContentDescription(DateFormatter.formatForAccessibility(playDate))
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> this@PlaybackHistoryFragment.onContextItemSelected(item) }
|
||||
}
|
||||
}
|
||||
listAdapter.setOnSelectModeListener(this)
|
||||
recyclerView.adapter = listAdapter
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun getFilter(): FeedItemFilter {
|
||||
return FeedItemFilter.unfiltered()
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
if (super.onOptionsItemSelected(item)) return true
|
||||
|
||||
if (item.itemId == R.id.clear_history_item) {
|
||||
val conDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(), R.string.clear_history_label, R.string.clear_playback_history_msg) {
|
||||
override fun onConfirmButtonPressed(dialog: DialogInterface) {
|
||||
dialog.dismiss()
|
||||
DBWriter.clearPlaybackHistory()
|
||||
}
|
||||
when (item.itemId) {
|
||||
R.id.episodes_sort -> {
|
||||
HistorySortDialog().show(childFragmentManager.beginTransaction(), "SortDialog")
|
||||
return true
|
||||
}
|
||||
R.id.filter_items -> {
|
||||
val dialog = object: DatesFilterDialog(requireContext(), 0L) {
|
||||
override fun initParams() {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.add(Calendar.YEAR, -1) // subtract 1 year
|
||||
timeFilterFrom = calendar.timeInMillis
|
||||
showMarkPlayed = false
|
||||
}
|
||||
override fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed: Boolean) {
|
||||
EventFlow.postEvent(FlowEvent.HistoryEvent(sortOrder, timeFilterFrom, timeFilterTo))
|
||||
}
|
||||
}
|
||||
dialog.show()
|
||||
return true
|
||||
}
|
||||
R.id.clear_history_item -> {
|
||||
val conDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(), R.string.clear_history_label, R.string.clear_playback_history_msg) {
|
||||
override fun onConfirmButtonPressed(dialog: DialogInterface) {
|
||||
dialog.dismiss()
|
||||
DBWriter.clearPlaybackHistory()
|
||||
}
|
||||
}
|
||||
conDialog.createNewDialog().show()
|
||||
return true
|
||||
}
|
||||
conDialog.createNewDialog().show()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun updateToolbar() {
|
||||
// Not calling super, as we do not have a refresh button that could be updated
|
||||
toolbar.menu.findItem(R.id.episodes_sort).setVisible(episodes.isNotEmpty())
|
||||
toolbar.menu.findItem(R.id.filter_items).setVisible(episodes.isNotEmpty())
|
||||
toolbar.menu.findItem(R.id.clear_history_item).setVisible(episodes.isNotEmpty())
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onHistoryUpdated(event: PlaybackHistoryEvent?) {
|
||||
loadItems()
|
||||
updateToolbar()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.HistoryEvent -> {
|
||||
sortOrder = event.sortOrder
|
||||
if (event.startDate > 0) startDate = event.startDate
|
||||
endDate = event.endDate
|
||||
loadItems()
|
||||
updateToolbar()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadData(): List<FeedItem> {
|
||||
return DBReader.getPlaybackHistory(0, page * EPISODES_PER_PAGE)
|
||||
val hList = DBReader.getPlaybackHistory(0, page * EPISODES_PER_PAGE, startDate, endDate, sortOrder).toMutableList()
|
||||
// FeedItemPermutors.getPermutor(sortOrder).reorder(hList)
|
||||
return hList
|
||||
}
|
||||
|
||||
override fun loadMoreData(page: Int): List<FeedItem> {
|
||||
return DBReader.getPlaybackHistory((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE)
|
||||
val hList = DBReader.getPlaybackHistory((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, startDate, endDate, sortOrder).toMutableList()
|
||||
// FeedItemPermutors.getPermutor(sortOrder).reorder(hList)
|
||||
return hList
|
||||
}
|
||||
|
||||
override fun loadTotalItemCount(): Int {
|
||||
return DBReader.getPlaybackHistoryLength().toInt()
|
||||
}
|
||||
|
||||
class HistorySortDialog : ItemSortDialog() {
|
||||
override fun onAddItem(title: Int, ascending: SortOrder, descending: SortOrder, ascendingIsDefault: Boolean) {
|
||||
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.PLAYED_DATE_OLD_NEW
|
||||
|| ascending == SortOrder.COMPLETED_DATE_OLD_NEW
|
||||
|| ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.EPISODE_TITLE_A_Z
|
||||
|| ascending == SortOrder.SIZE_SMALL_LARGE || ascending == SortOrder.FEED_TITLE_A_Z) {
|
||||
super.onAddItem(title, ascending, descending, ascendingIsDefault)
|
||||
}
|
||||
}
|
||||
override fun onSelectionChanged() {
|
||||
super.onSelectionChanged()
|
||||
EventFlow.postEvent(FlowEvent.HistoryEvent(sortOrder?: SortOrder.PLAYED_DATE_NEW_OLD))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG: String = "PlaybackHistoryFragment"
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ import ac.mdiq.podcini.util.ChapterUtils
|
|||
import ac.mdiq.podcini.util.DateFormatter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.NetworkUtils.fetchHtmlSource
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.AnimatorSet
|
||||
|
@ -41,17 +42,20 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.dankito.readability4j.Readability4J
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
/**
|
||||
* Displays the description of a Playable object in a Webview.
|
||||
|
@ -68,7 +72,7 @@ class PlayerDetailsFragment : Fragment() {
|
|||
private var item: FeedItem? = null
|
||||
private var displayedChapterIndex = -1
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
private var cleanedNotes: String? = null
|
||||
// private var webViewLoader: Disposable? = null
|
||||
private var controller: PlaybackController? = null
|
||||
|
@ -114,6 +118,11 @@ class PlayerDetailsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
|
@ -172,7 +181,7 @@ class PlayerDetailsFragment : Fragment() {
|
|||
|
||||
private fun load() {
|
||||
val context = context ?: return
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (item == null) {
|
||||
media = controller?.getMedia()
|
||||
|
@ -443,8 +452,18 @@ class PlayerDetailsFragment : Fragment() {
|
|||
savePreference()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(media, event.position)
|
||||
if (newChapterIndex > -1 && newChapterIndex != displayedChapterIndex) {
|
||||
refreshChapterData(newChapterIndex)
|
||||
|
|
|
@ -28,8 +28,8 @@ import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
|||
import ac.mdiq.podcini.util.Converter
|
||||
import ac.mdiq.podcini.util.FeedItemUtil
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
|
@ -41,6 +41,7 @@ import android.widget.ProgressBar
|
|||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -51,16 +52,16 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Shows all items in the queue.
|
||||
*/
|
||||
class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
|
||||
@UnstableApi class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
|
||||
|
||||
private var _binding: QueueFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -81,7 +82,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
private var recyclerAdapter: QueueRecyclerAdapter? = null
|
||||
private var currentPlaying: EpisodeItemViewHolder? = null
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -175,14 +176,14 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
recyclerAdapter?.endSelectMode()
|
||||
true
|
||||
}
|
||||
loadItems(true)
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
loadItems(true)
|
||||
procFlowEvents()
|
||||
if (queue.isNotEmpty()) recyclerView.restoreScrollPosition(TAG)
|
||||
}
|
||||
|
||||
|
@ -196,36 +197,65 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
//// disposable?.dispose()
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: QueueEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.QueueEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlayerStatusEvent -> onPlayerStatusChanged(event)
|
||||
is FlowEvent.UnreadItemsUpdateEvent -> onUnreadItemsChanged(event)
|
||||
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
is FlowEvent.FeedUpdateRunningEvent -> swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.keyEvents.collectLatest { event ->
|
||||
onKeyUp(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEventMainThread(event: FlowEvent.QueueEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with QueueEvent event = [$event]")
|
||||
if (recyclerAdapter == null) {
|
||||
loadItems(true)
|
||||
return
|
||||
}
|
||||
when (event.action) {
|
||||
QueueEvent.Action.ADDED -> {
|
||||
FlowEvent.QueueEvent.Action.ADDED -> {
|
||||
if (event.item != null) queue.add(event.position, event.item)
|
||||
recyclerAdapter?.notifyItemInserted(event.position)
|
||||
}
|
||||
QueueEvent.Action.SET_QUEUE, QueueEvent.Action.SORTED -> {
|
||||
FlowEvent.QueueEvent.Action.SET_QUEUE, FlowEvent.QueueEvent.Action.SORTED -> {
|
||||
queue = event.items.toMutableList()
|
||||
recyclerAdapter?.updateItems(event.items)
|
||||
}
|
||||
QueueEvent.Action.REMOVED, QueueEvent.Action.IRREVERSIBLE_REMOVED -> {
|
||||
FlowEvent.QueueEvent.Action.REMOVED, FlowEvent.QueueEvent.Action.IRREVERSIBLE_REMOVED -> {
|
||||
if (event.item != null) {
|
||||
val position: Int = FeedItemUtil.indexOfItemWithId(queue.toList(), event.item.id)
|
||||
queue.removeAt(position)
|
||||
recyclerAdapter?.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
QueueEvent.Action.CLEARED -> {
|
||||
FlowEvent.QueueEvent.Action.CLEARED -> {
|
||||
queue.clear()
|
||||
recyclerAdapter?.updateItems(queue)
|
||||
}
|
||||
QueueEvent.Action.MOVED -> return
|
||||
QueueEvent.Action.ADDED_ITEMS -> return
|
||||
QueueEvent.Action.DELETED_MEDIA -> return
|
||||
FlowEvent.QueueEvent.Action.MOVED -> return
|
||||
FlowEvent.QueueEvent.Action.ADDED_ITEMS -> return
|
||||
FlowEvent.QueueEvent.Action.DELETED_MEDIA -> return
|
||||
}
|
||||
recyclerAdapter?.updateDragDropEnabled()
|
||||
refreshToolbarState()
|
||||
|
@ -233,8 +263,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
refreshInfoBar()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
|
||||
if (recyclerAdapter == null) {
|
||||
loadItems(true)
|
||||
|
@ -255,8 +284,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]")
|
||||
for (downloadUrl in event.urls) {
|
||||
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue.toList(), downloadUrl)
|
||||
|
@ -264,8 +292,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
// Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
|
||||
if (recyclerAdapter != null) {
|
||||
if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem) currentPlaying!!.notifyPlaybackPositionUpdated(event)
|
||||
|
@ -283,15 +310,13 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
fun onPlayerStatusChanged(event: FlowEvent.PlayerStatusEvent?) {
|
||||
Logd(TAG, "onPlayerStatusChanged() called with event = [$event]")
|
||||
loadItems(false)
|
||||
refreshToolbarState()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
fun onUnreadItemsChanged(event: FlowEvent.UnreadItemsUpdateEvent?) {
|
||||
// Sent when playback position is reset
|
||||
Logd(TAG, "onUnreadItemsChanged() called with event = [$event]")
|
||||
loadItems(false)
|
||||
|
@ -310,17 +335,11 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
//// }
|
||||
// }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
|
||||
refreshSwipeTelltale()
|
||||
}
|
||||
|
||||
private fun refreshSwipeTelltale() {
|
||||
if (swipeActions.actions?.left != null) binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
|
||||
if (swipeActions.actions?.right != null) binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onKeyUp(event: KeyEvent) {
|
||||
if (!isAdded || !isVisible || !isMenuVisible) return
|
||||
|
||||
|
@ -336,8 +355,8 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
_binding = null
|
||||
recyclerAdapter?.endSelectMode()
|
||||
recyclerAdapter = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
scope.cancel()
|
||||
|
||||
// scope.cancel()
|
||||
|
||||
toolbar.setOnMenuItemClickListener(null)
|
||||
toolbar.setOnLongClickListener(null)
|
||||
|
@ -349,11 +368,6 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
toolbar.menu?.findItem(R.id.queue_lock)?.setVisible(!keepSorted)
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedUpdateRunningEvent) {
|
||||
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
|
||||
}
|
||||
|
||||
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
val itemId = item.itemId
|
||||
when (itemId) {
|
||||
|
@ -502,7 +516,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
|
|||
// refreshInfoBar()
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
queue = withContext(Dispatchers.IO) { DBReader.getQueue().toMutableList() }
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
|
@ -9,7 +9,8 @@ import ac.mdiq.podcini.storage.DBReader
|
|||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.adapter.FeedDiscoverAdapter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.DiscoveryDefaultUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
|
@ -21,11 +22,12 @@ import android.view.ViewGroup
|
|||
import android.widget.*
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
||||
|
@ -33,7 +35,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
|||
private val binding get() = _binding!!
|
||||
|
||||
// private var disposable: Disposable? = null
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private lateinit var adapter: FeedDiscoverAdapter
|
||||
private lateinit var discoverGridLayout: GridView
|
||||
|
@ -75,22 +77,31 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
|||
adapter.updateData(dummies)
|
||||
loadToplist()
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
scope.cancel()
|
||||
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun onDiscoveryDefaultUpdateEvent(event: DiscoveryDefaultUpdateEvent?) {
|
||||
loadToplist()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.DiscoveryDefaultUpdateEvent -> loadToplist()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadToplist() {
|
||||
|
@ -149,7 +160,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
|||
// errorRetry.setOnClickListener { loadToplist() }
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val podcasts = withContext(Dispatchers.IO) {
|
||||
loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList())
|
||||
|
|
|
@ -22,8 +22,8 @@ import ac.mdiq.podcini.ui.view.LiftOnScrollListener
|
|||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
|
||||
import ac.mdiq.podcini.util.FeedItemUtil
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.*
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
|
@ -35,6 +35,7 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.ProgressBar
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -43,15 +44,15 @@ import com.google.android.material.chip.Chip
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Performs a search operation on all feeds or one specific feed and displays the search result.
|
||||
*/
|
||||
class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
||||
@UnstableApi class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
||||
private var _binding: SearchFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
|
@ -68,7 +69,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
private var results: MutableList<FeedItem> = mutableListOf()
|
||||
private var currentPlaying: EpisodeItemViewHolder? = null
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
private var lastQueryChange: Long = 0
|
||||
private var isOtherViewInFoucus = false
|
||||
|
@ -81,7 +82,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,6 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
emptyViewHandler.setIcon(R.drawable.ic_search)
|
||||
emptyViewHandler.setTitle(R.string.search_status_no_results)
|
||||
emptyViewHandler.setMessage(R.string.type_to_search)
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
chip = binding.feedTitleChip
|
||||
chip.setOnCloseIconClickListener {
|
||||
|
@ -171,10 +171,15 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
}
|
||||
|
||||
private fun setupToolbar(toolbar: MaterialToolbar) {
|
||||
|
@ -231,18 +236,28 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
return super.onContextItemSelected(item)
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
search()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> search()
|
||||
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
|
||||
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.EpisodeDownloadEvent -> onEventMainThread(event)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
search()
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedItemEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
|
||||
Logd(TAG, "onEventMainThread() called with: event = [$event]")
|
||||
|
||||
var i = 0
|
||||
|
@ -259,16 +274,14 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: EpisodeDownloadEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
|
||||
for (downloadUrl in event.urls) {
|
||||
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(results, downloadUrl)
|
||||
if (pos >= 0) adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent) {
|
||||
fun onEventMainThread(event: FlowEvent.PlaybackPositionEvent) {
|
||||
if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
|
||||
currentPlaying!!.notifyPlaybackPositionUpdated(event)
|
||||
else {
|
||||
|
@ -284,11 +297,6 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
}
|
||||
}
|
||||
|
||||
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
|
||||
search()
|
||||
}
|
||||
|
||||
@UnstableApi private fun searchWithProgressBar() {
|
||||
progressBar.visibility = View.VISIBLE
|
||||
emptyViewHandler.hide()
|
||||
|
@ -318,7 +326,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
|
|||
//
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val results = withContext(Dispatchers.IO) {
|
||||
performSearch()
|
||||
|
|
|
@ -19,10 +19,8 @@ import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog
|
|||
import ac.mdiq.podcini.ui.view.EmptyViewHandler
|
||||
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.FeedTagsChangedEvent
|
||||
import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent
|
||||
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
|
@ -32,6 +30,7 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.widget.*
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -40,10 +39,10 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
@ -70,7 +69,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
private var displayedFolder: String = ""
|
||||
private var displayUpArrow = false
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
private var feedList: List<NavDrawerData.FeedDrawerItem> = mutableListOf()
|
||||
private var feedListFiltered: List<NavDrawerData.FeedDrawerItem> = mutableListOf()
|
||||
|
@ -190,17 +189,21 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
true
|
||||
}
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
loadSubscriptions()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
scope.cancel()
|
||||
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
|
@ -230,9 +233,25 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
subscriptionAdapter.setItems(feedListFiltered)
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: FeedUpdateRunningEvent) {
|
||||
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
|
||||
is FlowEvent.UnreadItemsUpdateEvent -> loadSubscriptions()
|
||||
is FlowEvent.FeedTagsChangedEvent -> resetTags()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
EventFlow.stickyEvents.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.FeedUpdateRunningEvent -> swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
|
@ -295,7 +314,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
|
||||
|
@ -333,22 +352,11 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
|
|||
return FeedMenuHandler.onMenuItemClicked(this, item.itemId, feed) { this.loadSubscriptions() }
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedListChanged(event: FeedListUpdateEvent?) {
|
||||
fun onFeedListChanged(event: FlowEvent.FeedListUpdateEvent?) {
|
||||
DBReader.updateFeedList()
|
||||
loadSubscriptions()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
|
||||
loadSubscriptions()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onFeedTagsChanged(event: FeedTagsChangedEvent?) {
|
||||
resetTags()
|
||||
}
|
||||
|
||||
override fun onEndSelectMode() {
|
||||
speedDialView.close()
|
||||
speedDialView.visibility = View.GONE
|
||||
|
|
|
@ -25,8 +25,8 @@ import ac.mdiq.podcini.ui.view.ShownotesWebView
|
|||
import ac.mdiq.podcini.util.Converter.getDurationStringLong
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||
import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -41,12 +41,12 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.app.ActivityCompat.invalidateOptionsMenu
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import kotlinx.coroutines.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.lang.Runnable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@UnstableApi
|
||||
class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
||||
|
@ -63,7 +63,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
private val videoControlsHider = Handler(Looper.getMainLooper())
|
||||
private var showTimeLeft = false
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Main)
|
||||
// val scope = CoroutineScope(Dispatchers.Main)
|
||||
// private var disposable: Disposable? = null
|
||||
private var prog = 0f
|
||||
|
||||
|
@ -95,9 +95,8 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
|
||||
Logd(TAG, "updatePlayButtonShowsPlay called")
|
||||
binding.playButton.setIsShowPlay(showPlay)
|
||||
if (showPlay) {
|
||||
(activity as AppCompatActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
if (showPlay) (activity as AppCompatActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
else {
|
||||
(activity as AppCompatActivity).window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
setupVideoAspectRatio()
|
||||
if (videoSurfaceCreated && controller != null) {
|
||||
|
@ -121,7 +120,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
onPositionObserverUpdate()
|
||||
EventBus.getDefault().register(this)
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
|
@ -134,7 +133,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
|
||||
@UnstableApi
|
||||
override fun onStop() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
|
||||
super.onStop()
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) videoControlsHider.removeCallbacks(hideVideoControls)
|
||||
|
||||
|
@ -149,13 +148,23 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
_binding = null
|
||||
controller?.release()
|
||||
controller = null // prevent leak
|
||||
scope.cancel()
|
||||
// scope.cancel()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@Suppress("unused")
|
||||
fun bufferUpdate(event: BufferUpdateEvent) {
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)
|
||||
is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bufferUpdate(event: FlowEvent.BufferUpdateEvent) {
|
||||
when {
|
||||
event.hasStarted() -> binding.progressBar.visibility = View.VISIBLE
|
||||
event.hasEnded() -> binding.progressBar.visibility = View.INVISIBLE
|
||||
|
@ -227,7 +236,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
// Log.e(TAG, Log.getStackTraceString(error))
|
||||
// })
|
||||
|
||||
scope.launch {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
item = withContext(Dispatchers.IO) {
|
||||
loadInBackground()
|
||||
|
@ -504,11 +513,6 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
|||
binding.controlsContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(event: PlaybackPositionEvent?) {
|
||||
onPositionObserverUpdate()
|
||||
}
|
||||
|
||||
fun onPositionObserverUpdate() {
|
||||
if (controller == null) return
|
||||
|
||||
|
|
|
@ -5,12 +5,13 @@ 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.fragment.PagedToolbarFragment
|
||||
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
|
||||
import ac.mdiq.podcini.ui.fragment.PagedToolbarFragment
|
||||
import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment
|
||||
import ac.mdiq.podcini.ui.statistics.subscriptions.SubscriptionStatisticsFragment
|
||||
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment
|
||||
import ac.mdiq.podcini.util.event.StatisticsEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
@ -21,16 +22,16 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Displays the 'statistics' screen
|
||||
|
@ -103,11 +104,24 @@ class StatisticsFragment : PagedToolbarFragment() {
|
|||
.putLong(PREF_FILTER_TO, Long.MAX_VALUE)
|
||||
.apply()
|
||||
|
||||
val disposable = Completable.fromFuture(DBWriter.resetStatistics())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ EventBus.getDefault().post(StatisticsEvent()) },
|
||||
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
// val disposable = Completable.fromFuture(DBWriter.resetStatistics())
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ EventFlow.postEvent(FlowEvent.StatisticsEvent()) },
|
||||
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
DBWriter.resetStatistics()
|
||||
}
|
||||
// This runs on the Main thread
|
||||
EventFlow.postEvent(FlowEvent.StatisticsEvent())
|
||||
} catch (error: Throwable) {
|
||||
// This also runs on the Main thread
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StatisticsPagerAdapter internal constructor(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
|
|
|
@ -14,12 +14,16 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Displays the 'download statistics' screen
|
||||
|
@ -29,16 +33,14 @@ class DownloadStatisticsFragment : Fragment() {
|
|||
private var _binding: StatisticsFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
private lateinit var downloadStatisticsList: RecyclerView
|
||||
private lateinit var progressBar: ProgressBar
|
||||
private lateinit var listAdapter: DownloadStatisticsListAdapter
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = StatisticsFragmentBinding.inflate(inflater)
|
||||
downloadStatisticsList = binding.statisticsList
|
||||
progressBar = binding.progressBar
|
||||
|
@ -68,24 +70,41 @@ class DownloadStatisticsFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun loadStatistics() {
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
|
||||
disposable =
|
||||
Observable.fromCallable {
|
||||
// Filters do not matter here
|
||||
val statisticsData = DBReader.getStatistics(false, 0, Long.MAX_VALUE)
|
||||
statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
item2.totalDownloadSize.compareTo(item1.totalDownloadSize)
|
||||
// disposable = Observable.fromCallable {
|
||||
// // Filters do not matter here
|
||||
// val statisticsData = DBReader.getStatistics(false, 0, Long.MAX_VALUE)
|
||||
// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
// item2.totalDownloadSize.compareTo(item1.totalDownloadSize)
|
||||
// }
|
||||
// statisticsData
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ result: StatisticsResult ->
|
||||
// listAdapter.update(result.feedTime)
|
||||
// progressBar.visibility = View.GONE
|
||||
// downloadStatisticsList.visibility = View.VISIBLE
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val statisticsData = withContext(Dispatchers.IO) {
|
||||
val data = DBReader.getStatistics(false, 0, Long.MAX_VALUE)
|
||||
data.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
item2.totalDownloadSize.compareTo(item1.totalDownloadSize)
|
||||
}
|
||||
data
|
||||
}
|
||||
statisticsData
|
||||
listAdapter.update(statisticsData.feedTime)
|
||||
progressBar.visibility = View.GONE
|
||||
downloadStatisticsList.visibility = View.VISIBLE
|
||||
} catch (error: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ result: StatisticsResult ->
|
||||
listAdapter.update(result.feedTime)
|
||||
progressBar.visibility = View.GONE
|
||||
downloadStatisticsList.visibility = View.VISIBLE
|
||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package ac.mdiq.podcini.ui.statistics.feed
|
||||
|
||||
import ac.mdiq.podcini.databinding.FeedStatisticsBinding
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.StatisticsItem
|
||||
import ac.mdiq.podcini.util.Converter.shortLocalizedDuration
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.StatisticsItem
|
||||
import ac.mdiq.podcini.util.Converter.shortLocalizedDuration
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class FeedStatisticsFragment : Fragment() {
|
||||
|
@ -21,11 +22,9 @@ class FeedStatisticsFragment : Fragment() {
|
|||
private val binding get() = _binding!!
|
||||
|
||||
private var feedId: Long = 0
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
feedId = requireArguments().getLong(EXTRA_FEED_ID)
|
||||
_binding = FeedStatisticsBinding.inflate(inflater)
|
||||
|
||||
|
@ -43,33 +42,47 @@ class FeedStatisticsFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun loadStatistics() {
|
||||
disposable =
|
||||
Observable.fromCallable {
|
||||
val statisticsData = DBReader.getStatistics(true, 0, Long.MAX_VALUE)
|
||||
statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
java.lang.Long.compare(item2.timePlayed,
|
||||
item1.timePlayed)
|
||||
}
|
||||
// disposable = Observable.fromCallable {
|
||||
// val statisticsData = DBReader.getStatistics(true, 0, Long.MAX_VALUE)
|
||||
// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
// java.lang.Long.compare(item2.timePlayed,
|
||||
// item1.timePlayed)
|
||||
// }
|
||||
//
|
||||
// for (statisticsItem in statisticsData.feedTime) {
|
||||
// if (statisticsItem.feed.id == feedId) {
|
||||
// return@fromCallable statisticsItem
|
||||
// }
|
||||
// }
|
||||
// null
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ s: StatisticsItem? -> this.showStats(s) }, { obj: Throwable -> obj.printStackTrace() })
|
||||
|
||||
for (statisticsItem in statisticsData.feedTime) {
|
||||
if (statisticsItem.feed.id == feedId) {
|
||||
return@fromCallable statisticsItem
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val statisticsData = withContext(Dispatchers.IO) {
|
||||
val data = DBReader.getStatistics(true, 0, Long.MAX_VALUE)
|
||||
data.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
item2.timePlayed.compareTo(item1.timePlayed)
|
||||
}
|
||||
for (statisticsItem in data.feedTime) {
|
||||
if (statisticsItem.feed.id == feedId) return@withContext statisticsItem
|
||||
}
|
||||
null
|
||||
}
|
||||
null
|
||||
showStats(statisticsData)
|
||||
} catch (error: Throwable) {
|
||||
error.printStackTrace()
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ s: StatisticsItem? -> this.showStats(s) }, { obj: Throwable -> obj.printStackTrace() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStats(s: StatisticsItem?) {
|
||||
binding.startedTotalLabel.text = String.format(Locale.getDefault(), "%d / %d",
|
||||
s!!.episodesStarted, s.episodes)
|
||||
binding.timePlayedLabel.text =
|
||||
shortLocalizedDuration(requireContext(), s.timePlayed)
|
||||
binding.totalDurationLabel.text =
|
||||
shortLocalizedDuration(requireContext(), s.time)
|
||||
binding.startedTotalLabel.text = String.format(Locale.getDefault(), "%d / %d", s!!.episodesStarted, s.episodes)
|
||||
binding.timePlayedLabel.text = shortLocalizedDuration(requireContext(), s.timePlayed)
|
||||
binding.totalDurationLabel.text = shortLocalizedDuration(requireContext(), s.time)
|
||||
binding.onDeviceLabel.text = String.format(Locale.getDefault(), "%d", s.episodesDownloadCount)
|
||||
binding.spaceUsedLabel.text = Formatter.formatShortFileSize(context, s.totalDownloadSize)
|
||||
}
|
||||
|
@ -77,7 +90,7 @@ class FeedStatisticsFragment : Fragment() {
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package ac.mdiq.podcini.ui.statistics.subscriptions
|
||||
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.StatisticsFilterDialogBinding
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.util.Pair
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
abstract class DatesFilterDialog(private val context: Context, oldestDate: Long) {
|
||||
|
||||
protected var prefs: SharedPreferences? = null
|
||||
protected var includeMarkedAsPlayed: Boolean = false
|
||||
protected var timeFilterFrom: Long = 0L
|
||||
protected var timeFilterTo: Long = Date().time
|
||||
protected var showMarkPlayed = true
|
||||
|
||||
protected val filterDatesFrom: Pair<Array<String>, Array<Long>>
|
||||
protected val filterDatesTo: Pair<Array<String>, Array<Long>>
|
||||
|
||||
|
||||
init {
|
||||
initParams()
|
||||
filterDatesFrom = makeMonthlyList(oldestDate, false)
|
||||
filterDatesTo = makeMonthlyList(oldestDate, true)
|
||||
}
|
||||
|
||||
// set prefs, includeMarkedAsPlayed, timeFilterFrom, timeFilterTo
|
||||
abstract fun initParams()
|
||||
abstract fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed: Boolean = true)
|
||||
|
||||
fun show() {
|
||||
val binding = StatisticsFilterDialogBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setView(binding.root)
|
||||
builder.setTitle(R.string.filter)
|
||||
binding.includeMarkedCheckbox.setOnCheckedChangeListener { compoundButton: CompoundButton?, checked: Boolean ->
|
||||
binding.timeToSpinner.isEnabled = !checked
|
||||
binding.timeFromSpinner.isEnabled = !checked
|
||||
binding.pastYearButton.isEnabled = !checked
|
||||
binding.allTimeButton.isEnabled = !checked
|
||||
binding.dateSelectionContainer.alpha = if (checked) 0.5f else 1f
|
||||
}
|
||||
if (showMarkPlayed) {
|
||||
binding.includeMarkedCheckbox.isChecked = includeMarkedAsPlayed
|
||||
} else {
|
||||
binding.includeMarkedCheckbox.visibility = View.GONE
|
||||
binding.noticeMessage.visibility = View.GONE
|
||||
}
|
||||
|
||||
val adapterFrom = ArrayAdapter(context, android.R.layout.simple_spinner_item, filterDatesFrom.first)
|
||||
adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding.timeFromSpinner.adapter = adapterFrom
|
||||
for (i in filterDatesFrom.second.indices) {
|
||||
if (filterDatesFrom.second[i] >= timeFilterFrom) {
|
||||
binding.timeFromSpinner.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val adapterTo = ArrayAdapter(context, android.R.layout.simple_spinner_item, filterDatesTo.first)
|
||||
adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding.timeToSpinner.adapter = adapterTo
|
||||
for (i in filterDatesTo.second.indices) {
|
||||
if (filterDatesTo.second[i] >= timeFilterTo) {
|
||||
binding.timeToSpinner.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
binding.allTimeButton.setOnClickListener { v: View? ->
|
||||
binding.timeFromSpinner.setSelection(0)
|
||||
binding.timeToSpinner.setSelection(filterDatesTo.first.size - 1)
|
||||
}
|
||||
binding.pastYearButton.setOnClickListener { v: View? ->
|
||||
binding.timeFromSpinner.setSelection(max(0.0, (filterDatesFrom.first.size - 12).toDouble()).toInt())
|
||||
binding.timeToSpinner.setSelection(filterDatesTo.first.size - 2)
|
||||
}
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int ->
|
||||
includeMarkedAsPlayed = binding.includeMarkedCheckbox.isChecked
|
||||
if (includeMarkedAsPlayed) {
|
||||
// We do not know the date at which something was marked as played, so filtering does not make sense
|
||||
timeFilterFrom = 0
|
||||
timeFilterTo = Long.MAX_VALUE
|
||||
} else {
|
||||
timeFilterFrom = filterDatesFrom.second[binding.timeFromSpinner.selectedItemPosition]
|
||||
timeFilterTo = filterDatesTo.second[binding.timeToSpinner.selectedItemPosition]
|
||||
}
|
||||
callback(timeFilterFrom, timeFilterTo, includeMarkedAsPlayed)
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun makeMonthlyList(oldestDate: Long, inclusive: Boolean): Pair<Array<String>, Array<Long>> {
|
||||
val date = Calendar.getInstance()
|
||||
date.timeInMillis = oldestDate
|
||||
date[Calendar.HOUR_OF_DAY] = 0
|
||||
date[Calendar.MINUTE] = 0
|
||||
date[Calendar.SECOND] = 0
|
||||
date[Calendar.MILLISECOND] = 0
|
||||
date[Calendar.DAY_OF_MONTH] = 1
|
||||
val names = ArrayList<String>()
|
||||
val timestamps = ArrayList<Long>()
|
||||
val skeleton = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMM yyyy")
|
||||
val dateFormat = SimpleDateFormat(skeleton, Locale.getDefault())
|
||||
while (date.timeInMillis < System.currentTimeMillis()) {
|
||||
names.add(dateFormat.format(Date(date.timeInMillis)))
|
||||
if (!inclusive) timestamps.add(date.timeInMillis)
|
||||
|
||||
if (date[Calendar.MONTH] == Calendar.DECEMBER) {
|
||||
date[Calendar.MONTH] = Calendar.JANUARY
|
||||
date[Calendar.YEAR] = date[Calendar.YEAR] + 1
|
||||
} else date[Calendar.MONTH] = date[Calendar.MONTH] + 1
|
||||
|
||||
if (inclusive) timestamps.add(date.timeInMillis)
|
||||
}
|
||||
if (inclusive) {
|
||||
names.add(context.getString(R.string.statistics_today))
|
||||
timestamps.add(Long.MAX_VALUE)
|
||||
}
|
||||
return Pair(names.toTypedArray<String>(), timestamps.toTypedArray<Long>())
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.statistics.subscriptions
|
||||
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.StatisticsFilterDialogBinding
|
||||
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
||||
import ac.mdiq.podcini.util.event.StatisticsEvent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.util.Pair
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
class StatisticsFilterDialog(private val context: Context, oldestDate: Long) {
|
||||
private val prefs: SharedPreferences =
|
||||
context.getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
|
||||
private var includeMarkedAsPlayed: Boolean
|
||||
private var timeFilterFrom: Long
|
||||
private var timeFilterTo: Long
|
||||
private val filterDatesFrom: Pair<Array<String>, Array<Long>>
|
||||
private val filterDatesTo: Pair<Array<String>, Array<Long>>
|
||||
|
||||
init {
|
||||
includeMarkedAsPlayed = prefs.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
|
||||
timeFilterFrom = prefs.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
|
||||
timeFilterTo = prefs.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
|
||||
filterDatesFrom = makeMonthlyList(oldestDate, false)
|
||||
filterDatesTo = makeMonthlyList(oldestDate, true)
|
||||
}
|
||||
|
||||
fun show() {
|
||||
val dialogBinding = StatisticsFilterDialogBinding.inflate(
|
||||
LayoutInflater.from(context))
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setView(dialogBinding.root)
|
||||
builder.setTitle(R.string.filter)
|
||||
dialogBinding.includeMarkedCheckbox.setOnCheckedChangeListener { compoundButton: CompoundButton?, checked: Boolean ->
|
||||
dialogBinding.timeToSpinner.isEnabled = !checked
|
||||
dialogBinding.timeFromSpinner.isEnabled = !checked
|
||||
dialogBinding.pastYearButton.isEnabled = !checked
|
||||
dialogBinding.allTimeButton.isEnabled = !checked
|
||||
dialogBinding.dateSelectionContainer.alpha = if (checked) 0.5f else 1f
|
||||
}
|
||||
dialogBinding.includeMarkedCheckbox.isChecked = includeMarkedAsPlayed
|
||||
|
||||
|
||||
val adapterFrom = ArrayAdapter(context,
|
||||
android.R.layout.simple_spinner_item, filterDatesFrom.first)
|
||||
adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
dialogBinding.timeFromSpinner.adapter = adapterFrom
|
||||
for (i in filterDatesFrom.second.indices) {
|
||||
if (filterDatesFrom.second[i] >= timeFilterFrom) {
|
||||
dialogBinding.timeFromSpinner.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val adapterTo = ArrayAdapter(context,
|
||||
android.R.layout.simple_spinner_item, filterDatesTo.first)
|
||||
adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
dialogBinding.timeToSpinner.adapter = adapterTo
|
||||
for (i in filterDatesTo.second.indices) {
|
||||
if (filterDatesTo.second[i] >= timeFilterTo) {
|
||||
dialogBinding.timeToSpinner.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dialogBinding.allTimeButton.setOnClickListener { v: View? ->
|
||||
dialogBinding.timeFromSpinner.setSelection(0)
|
||||
dialogBinding.timeToSpinner.setSelection(filterDatesTo.first.size - 1)
|
||||
}
|
||||
dialogBinding.pastYearButton.setOnClickListener { v: View? ->
|
||||
dialogBinding.timeFromSpinner.setSelection(max(0.0, (filterDatesFrom.first.size - 12).toDouble())
|
||||
.toInt())
|
||||
dialogBinding.timeToSpinner.setSelection(filterDatesTo.first.size - 2)
|
||||
}
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int ->
|
||||
includeMarkedAsPlayed = dialogBinding.includeMarkedCheckbox.isChecked
|
||||
if (includeMarkedAsPlayed) {
|
||||
// We do not know the date at which something was marked as played, so filtering does not make sense
|
||||
timeFilterFrom = 0
|
||||
timeFilterTo = Long.MAX_VALUE
|
||||
} else {
|
||||
timeFilterFrom = filterDatesFrom.second[dialogBinding.timeFromSpinner.selectedItemPosition]
|
||||
timeFilterTo = filterDatesTo.second[dialogBinding.timeToSpinner.selectedItemPosition]
|
||||
}
|
||||
prefs.edit()
|
||||
.putBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed)
|
||||
.putLong(StatisticsFragment.PREF_FILTER_FROM, timeFilterFrom)
|
||||
.putLong(StatisticsFragment.PREF_FILTER_TO, timeFilterTo)
|
||||
.apply()
|
||||
EventBus.getDefault().post(StatisticsEvent())
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun makeMonthlyList(oldestDate: Long, inclusive: Boolean): Pair<Array<String>, Array<Long>> {
|
||||
val date = Calendar.getInstance()
|
||||
date.timeInMillis = oldestDate
|
||||
date[Calendar.HOUR_OF_DAY] = 0
|
||||
date[Calendar.MINUTE] = 0
|
||||
date[Calendar.SECOND] = 0
|
||||
date[Calendar.MILLISECOND] = 0
|
||||
date[Calendar.DAY_OF_MONTH] = 1
|
||||
val names = ArrayList<String>()
|
||||
val timestamps = ArrayList<Long>()
|
||||
val skeleton = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMM yyyy")
|
||||
val dateFormat = SimpleDateFormat(skeleton, Locale.getDefault())
|
||||
while (date.timeInMillis < System.currentTimeMillis()) {
|
||||
names.add(dateFormat.format(Date(date.timeInMillis)))
|
||||
if (!inclusive) {
|
||||
timestamps.add(date.timeInMillis)
|
||||
}
|
||||
if (date[Calendar.MONTH] == Calendar.DECEMBER) {
|
||||
date[Calendar.MONTH] = Calendar.JANUARY
|
||||
date[Calendar.YEAR] = date[Calendar.YEAR] + 1
|
||||
} else {
|
||||
date[Calendar.MONTH] = date[Calendar.MONTH] + 1
|
||||
}
|
||||
if (inclusive) {
|
||||
timestamps.add(date.timeInMillis)
|
||||
}
|
||||
}
|
||||
if (inclusive) {
|
||||
names.add(context.getString(R.string.statistics_today))
|
||||
timestamps.add(Long.MAX_VALUE)
|
||||
}
|
||||
return Pair(names.toTypedArray<String>(), timestamps.toTypedArray<Long>())
|
||||
}
|
||||
}
|
|
@ -4,25 +4,31 @@ package ac.mdiq.podcini.ui.statistics.subscriptions
|
|||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.StatisticsFragmentBinding
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem
|
||||
import ac.mdiq.podcini.storage.DBReader.StatisticsResult
|
||||
import ac.mdiq.podcini.storage.StatisticsItem
|
||||
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
||||
import ac.mdiq.podcini.util.event.StatisticsEvent
|
||||
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment
|
||||
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment.Companion
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.ProgressBar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
@ -33,7 +39,7 @@ class SubscriptionStatisticsFragment : Fragment() {
|
|||
private var _binding: StatisticsFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
private var statisticsResult: StatisticsResult? = null
|
||||
|
||||
private lateinit var feedStatisticsList: RecyclerView
|
||||
|
@ -45,31 +51,38 @@ class SubscriptionStatisticsFragment : Fragment() {
|
|||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = StatisticsFragmentBinding.inflate(inflater)
|
||||
feedStatisticsList = binding.statisticsList
|
||||
progressBar = binding.progressBar
|
||||
listAdapter = PlaybackStatisticsListAdapter(this)
|
||||
feedStatisticsList.setLayoutManager(LinearLayoutManager(context))
|
||||
feedStatisticsList.setAdapter(listAdapter)
|
||||
EventBus.getDefault().register(this)
|
||||
refreshStatistics()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun statisticsEvent(event: StatisticsEvent?) {
|
||||
refreshStatistics()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.StatisticsEvent -> refreshStatistics()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
|
@ -81,7 +94,23 @@ class SubscriptionStatisticsFragment : Fragment() {
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.statistics_filter) {
|
||||
if (statisticsResult != null) {
|
||||
StatisticsFilterDialog(requireContext(), statisticsResult!!.oldestDate).show()
|
||||
val dialog = object: DatesFilterDialog(requireContext(), statisticsResult!!.oldestDate) {
|
||||
override fun initParams() {
|
||||
prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
|
||||
includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
|
||||
timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
|
||||
timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
|
||||
}
|
||||
override fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed: Boolean) {
|
||||
prefs!!.edit()
|
||||
.putBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed)
|
||||
.putLong(StatisticsFragment.PREF_FILTER_FROM, timeFilterFrom)
|
||||
.putLong(StatisticsFragment.PREF_FILTER_TO, timeFilterTo)
|
||||
.apply()
|
||||
EventFlow.postEvent(FlowEvent.StatisticsEvent())
|
||||
}
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -95,34 +124,57 @@ class SubscriptionStatisticsFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun loadStatistics() {
|
||||
if (disposable != null) {
|
||||
disposable!!.dispose()
|
||||
}
|
||||
// disposable?.dispose()
|
||||
|
||||
val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
|
||||
val includeMarkedAsPlayed = prefs.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
|
||||
val timeFilterFrom = prefs.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
|
||||
val timeFilterTo = prefs.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
|
||||
disposable = Observable.fromCallable {
|
||||
val statisticsData = DBReader.getStatistics(
|
||||
includeMarkedAsPlayed, timeFilterFrom, timeFilterTo)
|
||||
statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
item2.timePlayed.compareTo(item1.timePlayed)
|
||||
}
|
||||
statisticsData
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ result: StatisticsResult ->
|
||||
statisticsResult = result
|
||||
|
||||
// disposable = Observable.fromCallable {
|
||||
// val statisticsData = DBReader.getStatistics(
|
||||
// includeMarkedAsPlayed, timeFilterFrom, timeFilterTo)
|
||||
// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
// item2.timePlayed.compareTo(item1.timePlayed)
|
||||
// }
|
||||
// statisticsData
|
||||
// }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ result: StatisticsResult ->
|
||||
// statisticsResult = result
|
||||
// // When "from" is "today", set it to today
|
||||
// listAdapter.setTimeFilter(includeMarkedAsPlayed, max(
|
||||
// min(timeFilterFrom.toDouble(), System.currentTimeMillis().toDouble()), result.oldestDate.toDouble())
|
||||
// .toLong(),
|
||||
// min(timeFilterTo.toDouble(), System.currentTimeMillis().toDouble()).toLong())
|
||||
// listAdapter.update(result.feedTime)
|
||||
// progressBar.visibility = View.GONE
|
||||
// feedStatisticsList.visibility = View.VISIBLE
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val statisticsData = withContext(Dispatchers.IO) {
|
||||
val data = DBReader.getStatistics(includeMarkedAsPlayed, timeFilterFrom, timeFilterTo)
|
||||
data.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem ->
|
||||
item2.timePlayed.compareTo(item1.timePlayed)
|
||||
}
|
||||
data
|
||||
}
|
||||
statisticsResult = statisticsData
|
||||
// When "from" is "today", set it to today
|
||||
listAdapter.setTimeFilter(includeMarkedAsPlayed, max(
|
||||
min(timeFilterFrom.toDouble(), System.currentTimeMillis().toDouble()), result.oldestDate.toDouble())
|
||||
.toLong(),
|
||||
listAdapter.setTimeFilter(includeMarkedAsPlayed,
|
||||
max(min(timeFilterFrom.toDouble(), System.currentTimeMillis().toDouble()), statisticsData.oldestDate.toDouble()).toLong(),
|
||||
min(timeFilterTo.toDouble(), System.currentTimeMillis().toDouble()).toLong())
|
||||
listAdapter.update(result.feedTime)
|
||||
listAdapter.update(statisticsData.feedTime)
|
||||
progressBar.visibility = View.GONE
|
||||
feedStatisticsList.visibility = View.VISIBLE
|
||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
} catch (error: Throwable) {
|
||||
// This also runs on the Main thread
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -5,7 +5,8 @@ import ac.mdiq.podcini.R
|
|||
import ac.mdiq.podcini.databinding.StatisticsFragmentBinding
|
||||
import ac.mdiq.podcini.storage.DBReader
|
||||
import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem
|
||||
import ac.mdiq.podcini.util.event.StatisticsEvent
|
||||
import ac.mdiq.podcini.util.event.EventFlow
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
|
@ -14,15 +15,13 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Displays the yearly statistics screen
|
||||
|
@ -31,7 +30,7 @@ class YearsStatisticsFragment : Fragment() {
|
|||
private var _binding: StatisticsFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var disposable: Disposable? = null
|
||||
// private var disposable: Disposable? = null
|
||||
|
||||
private lateinit var yearStatisticsList: RecyclerView
|
||||
private lateinit var progressBar: ProgressBar
|
||||
|
@ -46,22 +45,32 @@ class YearsStatisticsFragment : Fragment() {
|
|||
listAdapter = YearStatisticsListAdapter(requireContext())
|
||||
yearStatisticsList.layoutManager = LinearLayoutManager(context)
|
||||
yearStatisticsList.adapter = listAdapter
|
||||
EventBus.getDefault().register(this)
|
||||
refreshStatistics()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
EventBus.getDefault().unregister(this)
|
||||
disposable?.dispose()
|
||||
|
||||
// disposable?.dispose()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun statisticsEvent(event: StatisticsEvent?) {
|
||||
refreshStatistics()
|
||||
private fun procFlowEvents() {
|
||||
lifecycleScope.launch {
|
||||
EventFlow.events.collectLatest { event ->
|
||||
when (event) {
|
||||
is FlowEvent.StatisticsEvent -> refreshStatistics()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
|
@ -78,16 +87,30 @@ class YearsStatisticsFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun loadStatistics() {
|
||||
disposable?.dispose()
|
||||
// disposable?.dispose()
|
||||
|
||||
disposable = Observable.fromCallable { DBReader.getMonthlyTimeStatistics() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ result: List<MonthlyStatisticsItem> ->
|
||||
// disposable = Observable.fromCallable { DBReader.getMonthlyTimeStatistics() }
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe({ result: List<MonthlyStatisticsItem> ->
|
||||
// listAdapter.update(result)
|
||||
// progressBar.visibility = View.GONE
|
||||
// yearStatisticsList.visibility = View.VISIBLE
|
||||
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result: List<MonthlyStatisticsItem> = withContext(Dispatchers.IO) {
|
||||
DBReader.getMonthlyTimeStatistics()
|
||||
}
|
||||
listAdapter.update(result)
|
||||
progressBar.visibility = View.GONE
|
||||
yearStatisticsList.visibility = View.VISIBLE
|
||||
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
|
||||
} catch (error: Throwable) {
|
||||
// This also runs on the Main thread
|
||||
Log.e(TAG, Log.getStackTraceString(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -21,7 +21,6 @@ import ac.mdiq.podcini.databinding.FeeditemlistItemBinding
|
|||
import ac.mdiq.podcini.ui.adapter.CoverLoader
|
||||
import ac.mdiq.podcini.feed.util.ImageResourceUtils
|
||||
import ac.mdiq.podcini.net.download.MediaSizeLoader
|
||||
import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedMedia
|
||||
import ac.mdiq.podcini.storage.model.playback.MediaType
|
||||
|
@ -35,6 +34,7 @@ import ac.mdiq.podcini.ui.actions.actionbutton.TTSActionButton
|
|||
import ac.mdiq.podcini.ui.view.CircularProgressBar
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils
|
||||
import ac.mdiq.podcini.util.*
|
||||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
|
@ -45,7 +45,7 @@ import kotlin.math.max
|
|||
* Holds the view which shows FeedItems.
|
||||
*/
|
||||
@UnstableApi
|
||||
class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
|
||||
open class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
|
||||
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)) {
|
||||
|
||||
val binding: FeeditemlistItemBinding = FeeditemlistItemBinding.bind(itemView)
|
||||
|
@ -56,7 +56,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
private val placeholder: TextView = binding.txtvPlaceholder
|
||||
private val cover: ImageView = binding.imgvCover
|
||||
private val title: TextView = binding.txtvTitle
|
||||
private val pubDate: TextView = binding.txtvPubDate
|
||||
protected val pubDate: TextView = binding.txtvPubDate
|
||||
private val position: TextView = binding.txtvPosition
|
||||
private val duration: TextView = binding.txtvDuration
|
||||
private val size: TextView = binding.size
|
||||
|
@ -100,8 +100,9 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
leftPadding.contentDescription = item.title
|
||||
binding.playedMark.visibility = View.GONE
|
||||
}
|
||||
pubDate.text = DateFormatter.formatAbbrev(activity, item.getPubDate())
|
||||
pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate()))
|
||||
|
||||
setPubDate(item)
|
||||
|
||||
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.75f else 1.0f
|
||||
|
@ -152,6 +153,11 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
}
|
||||
}
|
||||
|
||||
open fun setPubDate(item: FeedItem) {
|
||||
pubDate.text = DateFormatter.formatAbbrev(activity, item.getPubDate())
|
||||
pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate()))
|
||||
}
|
||||
|
||||
private fun bind(media: FeedMedia) {
|
||||
isVideo.visibility = if (media.getMediaType() == MediaType.VIDEO) View.VISIBLE else View.GONE
|
||||
duration.visibility = if (media.getDuration() > 0) View.VISIBLE else View.GONE
|
||||
|
@ -246,7 +252,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateDuration(event: PlaybackPositionEvent) {
|
||||
private fun updateDuration(event: FlowEvent.PlaybackPositionEvent) {
|
||||
val media = feedItem?.media
|
||||
if (media != null) {
|
||||
media.setPosition(event.position)
|
||||
|
@ -270,7 +276,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
|
|||
val isCurrentlyPlayingItem: Boolean
|
||||
get() = item?.media != null && PlaybackStatus.isCurrentlyPlaying(item?.media)
|
||||
|
||||
fun notifyPlaybackPositionUpdated(event: PlaybackPositionEvent) {
|
||||
fun notifyPlaybackPositionUpdated(event: FlowEvent.PlaybackPositionEvent) {
|
||||
progressBar.progress = (100.0 * event.position / event.duration).toInt()
|
||||
position.text = Converter.getDurationStringLong(event.position)
|
||||
updateDuration(event)
|
||||
|
|
|
@ -14,10 +14,13 @@ import ac.mdiq.podcini.ui.activity.appstartintent.PlaybackSpeedActivityStarter
|
|||
import ac.mdiq.podcini.ui.activity.appstartintent.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.util.Converter.getDurationStringLong
|
||||
import ac.mdiq.podcini.util.TimeSpeedConverter
|
||||
import android.R.attr.bitmap
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
|
@ -30,8 +33,10 @@ import coil.request.SuccessResult
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
/**
|
||||
* Updates the state of the player widget.
|
||||
*/
|
||||
|
@ -99,10 +104,17 @@ object WidgetUpdater {
|
|||
})
|
||||
.size(iconSize, iconSize)
|
||||
.build()
|
||||
val result = (context.imageLoader.execute(request) as SuccessResult).drawable
|
||||
icon = (result as BitmapDrawable).bitmap
|
||||
if (icon != null) views.setImageViewBitmap(R.id.imgvCover, icon)
|
||||
else views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
||||
withContext(Dispatchers.Main) {
|
||||
val result = (context.imageLoader.execute(request) as SuccessResult).drawable
|
||||
icon = (result as BitmapDrawable).bitmap
|
||||
try {
|
||||
if (icon != null) views.setImageViewBitmap(R.id.imgvCover, icon)
|
||||
else views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, e.message?:"")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (tr1: Throwable) {
|
||||
Log.e(TAG, "Error loading the media icon for the widget", tr1)
|
||||
|
|
|
@ -44,6 +44,19 @@ object FeedItemPermutors {
|
|||
SortOrder.EPISODE_FILENAME_Z_A -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
itemLink(f2).compareTo(itemLink(f1))
|
||||
}
|
||||
SortOrder.PLAYED_DATE_OLD_NEW -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
playDate(f1).compareTo(playDate(f2))
|
||||
}
|
||||
SortOrder.PLAYED_DATE_NEW_OLD -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
playDate(f2).compareTo(playDate(f1))
|
||||
}
|
||||
SortOrder.COMPLETED_DATE_OLD_NEW -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
completeDate(f1).compareTo(completeDate(f2))
|
||||
}
|
||||
SortOrder.COMPLETED_DATE_NEW_OLD -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
completeDate(f2).compareTo(completeDate(f1))
|
||||
}
|
||||
|
||||
SortOrder.FEED_TITLE_A_Z -> comparator = Comparator { f1: FeedItem?, f2: FeedItem? ->
|
||||
feedTitle(f1).compareTo(feedTitle(f2))
|
||||
}
|
||||
|
@ -77,28 +90,35 @@ object FeedItemPermutors {
|
|||
|
||||
// Null-safe accessors
|
||||
private fun pubDate(item: FeedItem?): Date {
|
||||
return if (item?.pubDate != null) item.pubDate!! else Date(0)
|
||||
return item?.pubDate ?: Date(0)
|
||||
}
|
||||
|
||||
private fun playDate(item: FeedItem?): Long {
|
||||
return item?.media?.getLastPlayedTime() ?: 0
|
||||
}
|
||||
|
||||
private fun completeDate(item: FeedItem?): Date {
|
||||
return item?.media?.getPlaybackCompletionDate() ?: Date(0)
|
||||
}
|
||||
|
||||
private fun itemTitle(item: FeedItem?): String {
|
||||
return if (item?.title != null) item.title!!.lowercase(Locale.getDefault()) else ""
|
||||
return (item?.title ?: "").lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
private fun duration(item: FeedItem?): Int {
|
||||
return if (item?.media != null) item.media!!.getDuration() else 0
|
||||
return item?.media?.getDuration() ?: 0
|
||||
}
|
||||
|
||||
private fun size(item: FeedItem?): Long {
|
||||
return if (item?.media != null) item.media!!.size else 0
|
||||
return item?.media?.size ?: 0
|
||||
}
|
||||
|
||||
private fun itemLink(item: FeedItem?): String {
|
||||
return if (item?.link != null) item.link!!.lowercase(Locale.getDefault()) else ""
|
||||
return (item?.link ?: "").lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
private fun feedTitle(item: FeedItem?): String {
|
||||
Logd("permutors", "feedTitle ${item?.feed?.title}")
|
||||
return if (item?.feed?.title != null) item.feed!!.title!!.lowercase(Locale.getDefault()) else ""
|
||||
return (item?.feed?.title ?: "").lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -131,8 +131,8 @@ object NetworkUtils {
|
|||
val bufferedReader = BufferedReader(InputStreamReader(inputStream))
|
||||
|
||||
val stringBuilder = StringBuilder()
|
||||
var line: String?
|
||||
while (bufferedReader.readLine().also { line = it } != null) {
|
||||
var line = ""
|
||||
while (bufferedReader.readLine()?.also { line = it } != null) {
|
||||
stringBuilder.append(line)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package ac.mdiq.podcini.util.comparator
|
||||
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
|
||||
class PlaybackLastPlayedDateComparator : Comparator<FeedItem> {
|
||||
override fun compare(lhs: FeedItem, rhs: FeedItem): Int {
|
||||
if (lhs.media?.getLastPlayedTime() != null && rhs.media?.getLastPlayedTime() != null)
|
||||
return rhs.media!!.getLastPlayedTime().compareTo(lhs.media!!.getLastPlayedTime())
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
class DiscoveryDefaultUpdateEvent
|
|
@ -1,14 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
class DownloadLogEvent private constructor() {
|
||||
override fun toString(): String {
|
||||
return "DownloadLogEvent"
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun listUpdated(): DownloadLogEvent {
|
||||
return DownloadLogEvent()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
import ac.mdiq.podcini.storage.model.download.DownloadStatus
|
||||
|
||||
class EpisodeDownloadEvent(private val map: Map<String, DownloadStatus>) {
|
||||
val urls: Set<String>
|
||||
get() = map.keys
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
//TODO: need to specify ids
|
||||
class FavoritesEvent
|
|
@ -1,18 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
import ac.mdiq.podcini.storage.model.feed.FeedItem
|
||||
|
||||
|
||||
// TODO: this appears not being posted
|
||||
class FeedItemEvent(@JvmField val items: List<FeedItem>) {
|
||||
companion object {
|
||||
fun updated(items: List<FeedItem>): FeedItemEvent {
|
||||
return FeedItemEvent(items)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun updated(vararg items: FeedItem): FeedItemEvent {
|
||||
return FeedItemEvent(listOf(*items))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
import ac.mdiq.podcini.storage.model.feed.Feed
|
||||
|
||||
class FeedListUpdateEvent {
|
||||
private val feeds: MutableList<Long> = ArrayList()
|
||||
|
||||
constructor(feeds: List<Feed>) {
|
||||
for (feed in feeds) {
|
||||
this.feeds.add(feed.id)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(feed: Feed) {
|
||||
feeds.add(feed.id)
|
||||
}
|
||||
|
||||
constructor(feedId: Long) {
|
||||
feeds.add(feedId)
|
||||
}
|
||||
|
||||
fun contains(feed: Feed): Boolean {
|
||||
return feeds.contains(feed.id)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue