diff --git a/7_podcast.jpg b/7_podcast.jpg
deleted file mode 100644
index 36ba875c..00000000
Binary files a/7_podcast.jpg and /dev/null differ
diff --git a/README.md b/README.md
index 33bcfd23..65260872 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,15 @@ This is based on a fork from the popular project AntennaPod (
-
+
@@ -28,4 +32,4 @@ Podcini, same as its forked project AntennaPod, is licensed under the GNU Genera
New files and modifications in the project is copyrighted in 2024 by Xilin Jia.
-Original files from the forked project maintains copyrights of the AntennaPod team.
\ No newline at end of file
+Original contents from the forked project maintains copyrights of the AntennaPod team.
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 14a66bd2..f137841f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -22,8 +22,8 @@ android {
// Version code schema:
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
- versionCode 3020100
- versionName "3.2.5"
+ versionCode 3020101
+ versionName "4.0.1"
def commit = ""
try {
@@ -37,11 +37,20 @@ android {
}
buildConfigField "String", "COMMIT_HASH", ('"' + (commit.isEmpty() ? "Unknown commit" : commit) + '"')
- javaCompileOptions {
- annotationProcessorOptions {
- arguments = [eventBusIndex: 'ac.mdiq.podcini.ApEventBusIndex']
- }
+// javaCompileOptions {
+// annotationProcessorOptions {
+// arguments = [eventBusIndex: 'ac.mdiq.podcini.ApEventBusIndex']
+// }
+// }
+
+ if (project.hasProperty("podcastindexApiKey")) {
+ buildConfigField "String", "PODCASTINDEX_API_KEY", '"' + podcastindexApiKey + '"'
+ buildConfigField "String", "PODCASTINDEX_API_SECRET", '"' + podcastindexApiSecret + '"'
+ } else {
+ buildConfigField "String", "PODCASTINDEX_API_KEY", '"XTMMQGA2YZ4WJUBYY4HK"'
+ buildConfigField "String", "PODCASTINDEX_API_SECRET", '"XAaAhk4^2YBsTE33vdbwbZNj82ZRLABDDqFdKe7x"'
}
+
}
signingConfigs {
releaseConfig {
@@ -91,46 +100,35 @@ dependencies {
}
}
- implementation project(":core")
- implementation project(":event")
- implementation project(':model')
- implementation project(':net:common')
- implementation project(':net:discovery')
- implementation project(':net:download:service-interface')
- implementation project(':net:sync:gpoddernet')
- implementation project(':net:sync:model')
- implementation project(':parser:feed')
- implementation project(':playback:base')
- implementation project(':playback:cast')
- implementation project(':storage:database')
- implementation project(':storage:preferences')
- implementation project(':ui:app-start-intent')
- implementation project(':ui:common')
- implementation project(':ui:echo')
- implementation project(':ui:glide')
- implementation project(':ui:i18n')
- implementation project(':ui:statistics')
+ kapt "androidx.annotation:annotation:$annotationVersion"
- kapt "androidx.annotation:annotation:1.7.1"
-
- implementation "androidx.appcompat:appcompat:1.6.1"
+ implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
- implementation "androidx.core:core-ktx:$coreVersion"
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.media:media:$mediaVersion"
+ implementation "androidx.media3:media3-exoplayer:$media3Version"
+ implementation "androidx.media3:media3-ui:$media3Version"
+ implementation "androidx.media3:media3-datasource-okhttp:$media3Version"
implementation "androidx.media3:media3-common:$media3Version"
implementation "androidx.palette:palette-ktx:$paletteVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion"
implementation "androidx.viewpager2:viewpager2:$viewPager2Version"
implementation "androidx.work:work-runtime:$workManagerVersion"
+ implementation "androidx.core:core-splashscreen:1.0.1"
+ implementation 'androidx.documentfile:documentfile:1.0.1'
+
implementation "com.google.android.material:material:$googleMaterialVersion"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "commons-io:commons-io:$commonsioVersion"
implementation "org.jsoup:jsoup:$jsoupVersion"
+
implementation "com.github.bumptech.glide:glide:$glideVersion"
+ implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion@aar"
+ annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
+
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
implementation "com.squareup.okio:okio:$okioVersion"
@@ -152,7 +150,6 @@ dependencies {
playImplementation 'com.google.android.play:core-ktx:1.8.0'
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
- androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
androidTestImplementation 'com.nanohttpd:nanohttpd:2.1.1'
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
@@ -160,6 +157,33 @@ dependencies {
androidTestImplementation "androidx.test:runner:$runnerVersion"
androidTestImplementation "androidx.test:rules:$rulesVersion"
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
+
+ implementation "com.annimon:stream:$annimonStreamVersion"
+ implementation 'com.github.mfietz:fyydlin:v0.5.0'
+
+ // Non-free dependencies:
+
+ testImplementation "androidx.test:core:$testCoreVersion"
+ testImplementation "org.awaitility:awaitility:$awaitilityVersion"
+ testImplementation "junit:junit:$junitVersion"
+ testImplementation 'org.mockito:mockito-inline:5.2.0'
+ testImplementation "org.robolectric:robolectric:$robolectricVersion"
+ testImplementation 'javax.inject:javax.inject:1'
+
+
+ playImplementation 'com.google.android.gms:play-services-base:17.5.0'
+ freeImplementation 'org.conscrypt:conscrypt-android:2.5.2'
+
+ playApi 'androidx.mediarouter:mediarouter:1.6.0'
+ playApi "com.google.android.support:wearable:$wearableSupportVersion"
+ playApi 'com.google.android.gms:play-services-cast-framework:21.2.0'
+}
+
+kapt {
+ arguments {
+ arg('eventBusIndex', 'ac.mdiq.podcini.ApEventBusIndex')
+ }
}
if (project.hasProperty("podciniPlayPublisherCredentials")) {
diff --git a/app/src/androidTest/java/ac/test/podcini/EspressoTestUtils.kt b/app/src/androidTest/java/ac/test/podcini/EspressoTestUtils.kt
index 0725c1c8..4ef27b30 100644
--- a/app/src/androidTest/java/ac/test/podcini/EspressoTestUtils.kt
+++ b/app/src/androidTest/java/ac/test/podcini/EspressoTestUtils.kt
@@ -17,15 +17,15 @@ import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.core.service.playback.PlaybackService
-import ac.mdiq.podcini.dialog.RatingDialog
-import ac.mdiq.podcini.dialog.RatingDialog.saveRated
-import ac.mdiq.podcini.fragment.NavDrawerFragment
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.service.playback.PlaybackService
+import ac.mdiq.podcini.ui.dialog.RatingDialog
+import ac.mdiq.podcini.ui.dialog.RatingDialog.saveRated
+import ac.mdiq.podcini.ui.fragment.NavDrawerFragment
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 ac.mdiq.podcini.storage.preferences.UserPreferences
+import ac.mdiq.podcini.preferences.UserPreferences
import junit.framework.AssertionFailedError
import org.awaitility.Awaitility
import org.awaitility.core.ConditionTimeoutException
diff --git a/app/src/androidTest/java/ac/test/podcini/dialogs/ShareDialogTest.kt b/app/src/androidTest/java/ac/test/podcini/dialogs/ShareDialogTest.kt
index 369df5de..4b88f6b9 100644
--- a/app/src/androidTest/java/ac/test/podcini/dialogs/ShareDialogTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/dialogs/ShareDialogTest.kt
@@ -11,8 +11,8 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.fragment.AllEpisodesFragment
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment
import de.test.podcini.EspressoTestUtils
import de.test.podcini.NthMatcher
import de.test.podcini.ui.UITestUtils
diff --git a/app/src/androidTest/java/ac/test/podcini/playback/PlaybackTest.kt b/app/src/androidTest/java/ac/test/podcini/playback/PlaybackTest.kt
index 9fb74e31..7a3181c4 100644
--- a/app/src/androidTest/java/ac/test/podcini/playback/PlaybackTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/playback/PlaybackTest.kt
@@ -13,19 +13,19 @@ import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.core.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
-import ac.mdiq.podcini.core.receiver.MediaButtonReceiver.Companion.createIntent
-import ac.mdiq.podcini.core.storage.DBReader.getEpisodes
-import ac.mdiq.podcini.core.storage.DBReader.getFeedItem
-import ac.mdiq.podcini.core.storage.DBReader.getQueue
-import ac.mdiq.podcini.core.storage.DBReader.getQueueIDList
-import ac.mdiq.podcini.core.storage.DBWriter.clearQueue
-import ac.mdiq.podcini.core.util.playback.PlaybackController
-import ac.mdiq.podcini.model.feed.FeedItemFilter.Companion.unfiltered
-import ac.mdiq.podcini.model.feed.SortOrder
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
+import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createIntent
+import ac.mdiq.podcini.storage.DBReader.getEpisodes
+import ac.mdiq.podcini.storage.DBReader.getFeedItem
+import ac.mdiq.podcini.storage.DBReader.getQueue
+import ac.mdiq.podcini.storage.DBReader.getQueueIDList
+import ac.mdiq.podcini.storage.DBWriter.clearQueue
+import ac.mdiq.podcini.playback.PlaybackController
+import ac.mdiq.podcini.storage.model.feed.FeedItemFilter.Companion.unfiltered
+import ac.mdiq.podcini.storage.model.feed.SortOrder
import ac.mdiq.podcini.playback.base.PlayerStatus
-import ac.mdiq.podcini.storage.preferences.UserPreferences
+import ac.mdiq.podcini.preferences.UserPreferences
import de.test.podcini.EspressoTestUtils
import de.test.podcini.IgnoreOnCi
import de.test.podcini.ui.UITestUtils
@@ -44,7 +44,7 @@ class PlaybackTest {
var activityTestRule: ActivityTestRule = ActivityTestRule(MainActivity::class.java, false, false)
private var uiTestUtils: UITestUtils? = null
- protected var context: Context? = null
+ protected lateinit var context: Context
private var controller: PlaybackController? = null
@Before
@@ -54,7 +54,7 @@ class PlaybackTest {
EspressoTestUtils.clearPreferences()
EspressoTestUtils.clearDatabase()
- uiTestUtils = UITestUtils(context!!)
+ uiTestUtils = UITestUtils(context)
uiTestUtils!!.setup()
}
@@ -192,28 +192,28 @@ class PlaybackTest {
}
protected fun setContinuousPlaybackPreference(value: Boolean) {
- val prefs = PreferenceManager.getDefaultSharedPreferences(context!!)
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit()
}
protected fun setSkipKeepsEpisodePreference(value: Boolean) {
- val prefs = PreferenceManager.getDefaultSharedPreferences(context!!)
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putBoolean(UserPreferences.PREF_SKIP_KEEPS_EPISODE, value).commit()
}
protected fun setSmartMarkAsPlayedPreference(smartMarkAsPlayedSecs: Int) {
- val prefs = PreferenceManager.getDefaultSharedPreferences(context!!)
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit().putString(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS,
smartMarkAsPlayedSecs.toString(10))
.commit()
}
private fun skipEpisode() {
- context!!.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT))
+ context.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT))
}
protected fun pauseEpisode() {
- context!!.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE))
+ context.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE))
}
protected fun startLocalPlayback() {
diff --git a/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt b/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt
index 984b75a5..660ead29 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt
@@ -3,12 +3,12 @@ package de.test.podcini.service.download
import android.util.Log
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
-import ac.mdiq.podcini.core.service.download.Downloader
-import ac.mdiq.podcini.core.service.download.HttpDownloader
-import ac.mdiq.podcini.model.download.DownloadError
-import ac.mdiq.podcini.model.feed.FeedFile
+import ac.mdiq.podcini.service.download.Downloader
+import ac.mdiq.podcini.service.download.HttpDownloader
+import ac.mdiq.podcini.storage.model.download.DownloadError
+import ac.mdiq.podcini.storage.model.feed.FeedFile
import ac.mdiq.podcini.net.download.serviceinterface.DownloadRequest
-import ac.mdiq.podcini.storage.preferences.UserPreferences.init
+import ac.mdiq.podcini.preferences.UserPreferences.init
import de.test.podcini.util.service.download.HTTPBin
import org.junit.After
import org.junit.Assert
@@ -28,10 +28,11 @@ class HttpDownloaderTest {
@Throws(Exception::class)
fun tearDown() {
val contents = destDir!!.listFiles()
- for (f in contents) {
- Assert.assertTrue(f.delete())
+ if (contents != null) {
+ for (f in contents) {
+ Assert.assertTrue(f.delete())
+ }
}
-
httpServer!!.stop()
}
@@ -61,18 +62,18 @@ class HttpDownloaderTest {
private fun download(url: String?, title: String, expectedResult: Boolean, deleteExisting: Boolean = true,
username: String? = null, password: String? = null
- ): Downloader {
+ ): ac.mdiq.podcini.service.download.Downloader {
val feedFile: FeedFile = setupFeedFile(url, title, deleteExisting)
val request = DownloadRequest(
feedFile.getFile_url()!!, url!!, title, 0, feedFile.getTypeAsInt(),
username, password, null, false)
- val downloader: Downloader = HttpDownloader(request)
+ val downloader: ac.mdiq.podcini.service.download.Downloader = HttpDownloader(request)
downloader.call()
val status = downloader.result
Assert.assertNotNull(status)
Assert.assertEquals(expectedResult, status.isSuccessful)
// the file should not exist if the download has failed and deleteExisting was true
- Assert.assertTrue(!deleteExisting || File(feedFile.getFile_url()).exists() == expectedResult)
+ Assert.assertTrue(!deleteExisting || File(feedFile.getFile_url()!!).exists() == expectedResult)
return downloader
}
@@ -100,7 +101,7 @@ class HttpDownloaderTest {
fun testCancel() {
val url = httpServer!!.baseUrl + "/delay/3"
val feedFile = setupFeedFile(url, "delay", true)
- val downloader: Downloader = HttpDownloader(DownloadRequest(
+ val downloader: ac.mdiq.podcini.service.download.Downloader = HttpDownloader(DownloadRequest(
feedFile.getFile_url()!!, url, "delay", 0,
feedFile.getTypeAsInt(), null, null, null, false))
val t: Thread = object : Thread() {
@@ -122,7 +123,7 @@ class HttpDownloaderTest {
@Test
fun testDeleteOnFailShouldDelete() {
val downloader = download(url404, "testDeleteOnFailShouldDelete", false, true, null, null)
- Assert.assertFalse(File(downloader.downloadRequest.destination).exists())
+ Assert.assertFalse(File(downloader.downloadRequest.destination!!).exists())
}
@Test
@@ -133,7 +134,7 @@ class HttpDownloaderTest {
dest.delete()
Assert.assertTrue(dest.createNewFile())
val downloader = download(url404, filename, false, false, null, null)
- Assert.assertTrue(File(downloader.downloadRequest.destination).exists())
+ Assert.assertTrue(File(downloader.downloadRequest.destination!!).exists())
}
@Test
diff --git a/app/src/androidTest/java/ac/test/podcini/service/playback/CancelablePSMPCallback.kt b/app/src/androidTest/java/ac/test/podcini/service/playback/CancelablePSMPCallback.kt
index 2ad07073..09c38193 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/playback/CancelablePSMPCallback.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/playback/CancelablePSMPCallback.kt
@@ -1,7 +1,7 @@
package de.test.podcini.service.playback
-import ac.mdiq.podcini.model.playback.MediaType
-import ac.mdiq.podcini.model.playback.Playable
+import ac.mdiq.podcini.storage.model.playback.MediaType
+import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPCallback
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPInfo
diff --git a/app/src/androidTest/java/ac/test/podcini/service/playback/DefaultPSMPCallback.kt b/app/src/androidTest/java/ac/test/podcini/service/playback/DefaultPSMPCallback.kt
index ca1b62e6..86ad1cda 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/playback/DefaultPSMPCallback.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/playback/DefaultPSMPCallback.kt
@@ -1,7 +1,7 @@
package de.test.podcini.service.playback
-import ac.mdiq.podcini.model.playback.MediaType
-import ac.mdiq.podcini.model.playback.Playable
+import ac.mdiq.podcini.storage.model.playback.MediaType
+import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPCallback
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPInfo
diff --git a/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceMediaPlayerTest.kt b/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceMediaPlayerTest.kt
index d0eb9276..2494d61f 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceMediaPlayerTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceMediaPlayerTest.kt
@@ -3,9 +3,9 @@ package de.test.podcini.service.playback
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
-import ac.mdiq.podcini.core.service.playback.LocalPSMP
-import ac.mdiq.podcini.model.feed.*
-import ac.mdiq.podcini.model.playback.Playable
+import ac.mdiq.podcini.service.playback.LocalPSMP
+import ac.mdiq.podcini.storage.model.feed.*
+import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer
import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPInfo
import ac.mdiq.podcini.playback.base.PlayerStatus
diff --git a/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceTaskManagerTest.kt b/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceTaskManagerTest.kt
index 32caf647..b7b2ae7d 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceTaskManagerTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/playback/PlaybackServiceTaskManagerTest.kt
@@ -3,15 +3,15 @@ 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.core.preferences.SleepTimerPreferences.setShakeToReset
-import ac.mdiq.podcini.core.preferences.SleepTimerPreferences.setVibrate
-import ac.mdiq.podcini.core.service.playback.PlaybackServiceTaskManager
-import ac.mdiq.podcini.core.service.playback.PlaybackServiceTaskManager.PSTMCallback
-import ac.mdiq.podcini.core.widget.WidgetUpdater.WidgetState
-import ac.mdiq.podcini.event.playback.SleepTimerUpdatedEvent
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.playback.Playable
+import ac.mdiq.podcini.preferences.SleepTimerPreferences.setShakeToReset
+import ac.mdiq.podcini.preferences.SleepTimerPreferences.setVibrate
+import ac.mdiq.podcini.service.playback.PlaybackServiceTaskManager
+import ac.mdiq.podcini.service.playback.PlaybackServiceTaskManager.PSTMCallback
+import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
+import ac.mdiq.podcini.playback.event.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.storage.database.PodDBAdapter.Companion.deleteDatabase
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
diff --git a/app/src/androidTest/java/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt b/app/src/androidTest/java/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt
index 4c95f072..8dd62b0f 100644
--- a/app/src/androidTest/java/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/service/playback/SleepTimerPreferencesTest.kt
@@ -1,6 +1,6 @@
package de.test.podcini.service.playback
-import ac.mdiq.podcini.core.preferences.SleepTimerPreferences.isInTimeRange
+import ac.mdiq.podcini.preferences.SleepTimerPreferences.isInTimeRange
import org.junit.Assert
import org.junit.Test
diff --git a/app/src/androidTest/java/ac/test/podcini/storage/AutoDownloadTest.kt b/app/src/androidTest/java/ac/test/podcini/storage/AutoDownloadTest.kt
index 78cd6efa..0f2f0946 100644
--- a/app/src/androidTest/java/ac/test/podcini/storage/AutoDownloadTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/storage/AutoDownloadTest.kt
@@ -2,14 +2,14 @@ package de.test.podcini.storage
import android.content.Context
import androidx.test.core.app.ApplicationProvider
-import ac.mdiq.podcini.core.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
-import ac.mdiq.podcini.core.storage.AutomaticDownloadAlgorithm
-import ac.mdiq.podcini.core.storage.DBReader.getQueue
-import ac.mdiq.podcini.core.storage.DBTasks.setDownloadAlgorithm
-import ac.mdiq.podcini.core.util.playback.PlaybackServiceStarter
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isAllowMobileStreaming
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isFollowQueue
+import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
+import ac.mdiq.podcini.storage.AutomaticDownloadAlgorithm
+import ac.mdiq.podcini.storage.DBReader.getQueue
+import ac.mdiq.podcini.storage.DBTasks.setDownloadAlgorithm
+import ac.mdiq.podcini.playback.PlaybackServiceStarter
+import ac.mdiq.podcini.storage.model.feed.FeedItem
+import ac.mdiq.podcini.preferences.UserPreferences.isAllowMobileStreaming
+import ac.mdiq.podcini.preferences.UserPreferences.isFollowQueue
import de.test.podcini.EspressoTestUtils
import de.test.podcini.ui.UITestUtils
import org.awaitility.Awaitility
@@ -106,7 +106,7 @@ class AutoDownloadTest {
var currentlyPlayingAtDownload: Long = -1
private set
- override fun autoDownloadUndownloadedItems(context: Context?): Runnable? {
+ override fun autoDownloadUndownloadedItems(context: Context): Runnable? {
return Runnable {
if (currentlyPlayingAtDownload == -1L) {
currentlyPlayingAtDownload = currentlyPlayingFeedMediaId
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/FeedSettingsTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/FeedSettingsTest.kt
index c25ae273..9e2ca0e6 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/FeedSettingsTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/FeedSettingsTest.kt
@@ -8,8 +8,8 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.model.feed.Feed
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.storage.model.feed.Feed
import de.test.podcini.EspressoTestUtils
import org.hamcrest.Matchers
import org.junit.After
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt
index 31ebc286..c1aa6cf0 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt
@@ -8,7 +8,7 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
+import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.deleteDatabase
import de.test.podcini.EspressoTestUtils
import org.junit.After
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/NavigationDrawerTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/NavigationDrawerTest.kt
index 3806cf08..19caf9f4 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/NavigationDrawerTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/NavigationDrawerTest.kt
@@ -11,10 +11,10 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.activity.PreferenceActivity
-import ac.mdiq.podcini.fragment.*
-import ac.mdiq.podcini.storage.preferences.UserPreferences.hiddenDrawerItems
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.ui.activity.PreferenceActivity
+import ac.mdiq.podcini.ui.fragment.*
+import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import de.test.podcini.EspressoTestUtils
import de.test.podcini.NthMatcher
import org.hamcrest.Matchers
@@ -63,13 +63,6 @@ class NavigationDrawerTest {
hiddenDrawerItems = ArrayList()
activityRule.launchActivity(Intent())
- // home
- openNavDrawer()
- EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.home_label)).perform(ViewActions.click())
- Espresso.onView(ViewMatchers.isRoot())
- .perform(EspressoTestUtils.waitForView(Matchers.allOf(ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.toolbar)),
- ViewMatchers.withText(R.string.home_label)), 1000))
-
// queue
openNavDrawer()
EspressoTestUtils.onDrawerItem(ViewMatchers.withText(R.string.queue_label)).perform(ViewActions.click())
@@ -159,7 +152,7 @@ class NavigationDrawerTest {
@Test
fun testDrawerPreferencesUnhideSomeElements() {
- var hidden = listOf(PlaybackHistoryFragment.TAG, CompletedDownloadsFragment.TAG)
+ var hidden = listOf(PlaybackHistoryFragment.TAG, CompletedDownloadsFragment.TAG)
hiddenDrawerItems = hidden
activityRule.launchActivity(Intent())
openNavDrawer()
@@ -170,7 +163,7 @@ class NavigationDrawerTest {
Espresso.onView(ViewMatchers.withText(R.string.downloads_label)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withText(R.string.confirm_label)).perform(ViewActions.click())
- hidden = hiddenDrawerItems as List
+ hidden = hiddenDrawerItems?.filterNotNull()?: listOf()
Assert.assertEquals(2, hidden.size.toLong())
Assert.assertTrue(hidden.contains(QueueFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG))
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/PreferencesTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/PreferencesTest.kt
index 7ff5f647..2fcab376 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/PreferencesTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/PreferencesTest.kt
@@ -11,33 +11,33 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.PreferenceActivity
-import ac.mdiq.podcini.core.storage.APCleanupAlgorithm
-import ac.mdiq.podcini.core.storage.APNullCleanupAlgorithm
-import ac.mdiq.podcini.core.storage.APQueueCleanupAlgorithm
-import ac.mdiq.podcini.core.storage.EpisodeCleanupAlgorithmFactory.build
-import ac.mdiq.podcini.core.storage.ExceptFavoriteCleanupAlgorithm
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import ac.mdiq.podcini.storage.preferences.UserPreferences.EnqueueLocation
-import ac.mdiq.podcini.storage.preferences.UserPreferences.enqueueLocation
-import ac.mdiq.podcini.storage.preferences.UserPreferences.episodeCacheSize
-import ac.mdiq.podcini.storage.preferences.UserPreferences.fastForwardSecs
-import ac.mdiq.podcini.storage.preferences.UserPreferences.init
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isAutoDelete
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isAutoDeleteLocal
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isEnableAutodownload
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isEnableAutodownloadOnBattery
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isFollowQueue
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isPauseOnHeadsetDisconnect
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isPersistNotify
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isUnpauseOnBluetoothReconnect
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isUnpauseOnHeadsetReconnect
-import ac.mdiq.podcini.storage.preferences.UserPreferences.rewindSecs
-import ac.mdiq.podcini.storage.preferences.UserPreferences.shouldDeleteRemoveFromQueue
-import ac.mdiq.podcini.storage.preferences.UserPreferences.shouldPauseForFocusLoss
-import ac.mdiq.podcini.storage.preferences.UserPreferences.showNextChapterOnFullNotification
-import ac.mdiq.podcini.storage.preferences.UserPreferences.showPlaybackSpeedOnFullNotification
-import ac.mdiq.podcini.storage.preferences.UserPreferences.showSkipOnFullNotification
+import ac.mdiq.podcini.ui.activity.PreferenceActivity
+import ac.mdiq.podcini.storage.APCleanupAlgorithm
+import ac.mdiq.podcini.storage.APNullCleanupAlgorithm
+import ac.mdiq.podcini.storage.APQueueCleanupAlgorithm
+import ac.mdiq.podcini.storage.EpisodeCleanupAlgorithmFactory.build
+import ac.mdiq.podcini.storage.ExceptFavoriteCleanupAlgorithm
+import ac.mdiq.podcini.preferences.UserPreferences
+import ac.mdiq.podcini.preferences.UserPreferences.EnqueueLocation
+import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation
+import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
+import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
+import ac.mdiq.podcini.preferences.UserPreferences.init
+import ac.mdiq.podcini.preferences.UserPreferences.isAutoDelete
+import ac.mdiq.podcini.preferences.UserPreferences.isAutoDeleteLocal
+import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
+import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery
+import ac.mdiq.podcini.preferences.UserPreferences.isFollowQueue
+import ac.mdiq.podcini.preferences.UserPreferences.isPauseOnHeadsetDisconnect
+import ac.mdiq.podcini.preferences.UserPreferences.isPersistNotify
+import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnBluetoothReconnect
+import ac.mdiq.podcini.preferences.UserPreferences.isUnpauseOnHeadsetReconnect
+import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
+import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue
+import ac.mdiq.podcini.preferences.UserPreferences.shouldPauseForFocusLoss
+import ac.mdiq.podcini.preferences.UserPreferences.showNextChapterOnFullNotification
+import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification
+import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification
import de.test.podcini.EspressoTestUtils
import org.awaitility.Awaitility
import org.junit.Assert
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/QueueFragmentTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/QueueFragmentTest.kt
index f8d41d9c..c8d44a08 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/QueueFragmentTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/QueueFragmentTest.kt
@@ -7,8 +7,8 @@ import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.fragment.QueueFragment
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.ui.fragment.QueueFragment
import de.test.podcini.EspressoTestUtils
import de.test.podcini.NthMatcher
import org.hamcrest.CoreMatchers
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/TextOnlyFeedsTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/TextOnlyFeedsTest.kt
index c9cac468..e0744156 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/TextOnlyFeedsTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/TextOnlyFeedsTest.kt
@@ -8,7 +8,7 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
+import ac.mdiq.podcini.ui.activity.MainActivity
import de.test.podcini.EspressoTestUtils
import org.hamcrest.CoreMatchers
import org.junit.After
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/UITestUtils.kt b/app/src/androidTest/java/ac/test/podcini/ui/UITestUtils.kt
index ba72be17..479a633f 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/UITestUtils.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/UITestUtils.kt
@@ -3,11 +3,11 @@ package de.test.podcini.ui
import android.content.Context
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
-import ac.mdiq.podcini.event.FeedListUpdateEvent
-import ac.mdiq.podcini.event.QueueEvent.Companion.setQueue
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.feed.FeedMedia
+import ac.mdiq.podcini.util.event.FeedListUpdateEvent
+import ac.mdiq.podcini.util.event.QueueEvent.Companion.setQueue
+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 de.test.podcini.util.service.download.HTTPBin
@@ -193,7 +193,7 @@ class UITestUtils(private val context: Context) {
adapter.setCompleteFeed(*hostedFeeds.toTypedArray())
adapter.setQueue(queue)
adapter.close()
- EventBus.getDefault().post(FeedListUpdateEvent(hostedFeeds))
+ EventBus.getDefault().post(ac.mdiq.podcini.util.event.FeedListUpdateEvent(hostedFeeds))
EventBus.getDefault().post(setQueue(queue))
}
diff --git a/app/src/androidTest/java/ac/test/podcini/ui/UITestUtilsTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/UITestUtilsTest.kt
index f3e3a0c8..ba2239b5 100644
--- a/app/src/androidTest/java/ac/test/podcini/ui/UITestUtilsTest.kt
+++ b/app/src/androidTest/java/ac/test/podcini/ui/UITestUtilsTest.kt
@@ -2,7 +2,7 @@ package de.test.podcini.ui
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
-import ac.mdiq.podcini.model.feed.Feed
+import ac.mdiq.podcini.storage.model.feed.Feed
import org.junit.After
import org.junit.Assert
import org.junit.Before
@@ -41,7 +41,7 @@ class UITestUtilsTest {
for (feed in feeds) {
testUrlReachable(feed.download_url)
- for (item in feed.items!!) {
+ for (item in feed.items) {
if (item.hasMedia()) {
testUrlReachable(item.media!!.download_url)
}
@@ -68,14 +68,14 @@ class UITestUtilsTest {
for (feed in uiTestUtils!!.hostedFeeds) {
Assert.assertTrue(feed.id != 0L)
- for (item in feed.items!!) {
+ for (item in feed.items) {
Assert.assertTrue(item.id != 0L)
if (item.hasMedia()) {
Assert.assertTrue(item.media!!.id != 0L)
if (downloadEpisodes) {
Assert.assertTrue(item.media!!.isDownloaded())
Assert.assertNotNull(item.media!!.getFile_url())
- val file = File(item.media!!.getFile_url())
+ val file = File(item.media!!.getFile_url()!!)
Assert.assertTrue(file.exists())
}
}
diff --git a/app/src/androidTest/java/ac/test/podcini/util/event/FeedItemEventListener.kt b/app/src/androidTest/java/ac/test/podcini/util/event/FeedItemEventListener.kt
index a0e4d3ba..f5ea50e0 100644
--- a/app/src/androidTest/java/ac/test/podcini/util/event/FeedItemEventListener.kt
+++ b/app/src/androidTest/java/ac/test/podcini/util/event/FeedItemEventListener.kt
@@ -1,6 +1,6 @@
package de.test.podcini.util.event
-import ac.mdiq.podcini.event.FeedItemEvent
+import ac.mdiq.podcini.util.event.FeedItemEvent
import io.reactivex.functions.Consumer
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@@ -10,14 +10,14 @@ import org.greenrobot.eventbus.Subscribe
*
*/
class FeedItemEventListener {
- private val events: MutableList = ArrayList()
+ private val events: MutableList = ArrayList()
@Subscribe
- fun onEvent(event: FeedItemEvent) {
+ fun onEvent(event: ac.mdiq.podcini.util.event.FeedItemEvent) {
events.add(event)
}
- fun getEvents(): List {
+ fun getEvents(): List {
return events
}
diff --git a/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt b/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt
index 8391488a..687d3502 100644
--- a/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt
+++ b/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/FeedGenerator.kt
@@ -1,6 +1,6 @@
package de.test.podcini.util.syndication.feedgenerator
-import ac.mdiq.podcini.model.feed.Feed
+import ac.mdiq.podcini.storage.model.feed.Feed
import java.io.IOException
import java.io.OutputStream
diff --git a/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt b/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt
index 65d8fd54..4318807b 100644
--- a/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt
+++ b/app/src/androidTest/java/ac/test/podcini/util/syndication/feedgenerator/Rss2Generator.kt
@@ -1,9 +1,9 @@
package de.test.podcini.util.syndication.feedgenerator
import android.util.Xml
-import ac.mdiq.podcini.core.util.DateFormatter.formatRfc822Date
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.parser.feed.namespace.PodcastIndex
+import ac.mdiq.podcini.util.DateFormatter.formatRfc822Date
+import ac.mdiq.podcini.storage.model.feed.Feed
+import ac.mdiq.podcini.feed.parser.namespace.PodcastIndex
import de.test.podcini.util.syndication.feedgenerator.GeneratorUtil.addPaymentLink
import java.io.IOException
import java.io.OutputStream
@@ -56,15 +56,15 @@ class Rss2Generator : FeedGenerator {
}
val fundingList = feed.paymentLinks
- if (fundingList != null) {
+ if (fundingList.isNotEmpty()) {
for (funding in fundingList) {
addPaymentLink(xml, funding.url, true)
}
}
// Write FeedItem data
- if (feed.items != null) {
- for (item in feed.items!!) {
+ if (feed.items.isNotEmpty()) {
+ for (item in feed.items) {
xml.startTag(null, "item")
if (item.title != null) {
@@ -99,7 +99,7 @@ class Rss2Generator : FeedGenerator {
xml.attribute(null, "type", item.media!!.mime_type)
xml.endTag(null, "enclosure")
}
- if (fundingList != null) {
+ if (fundingList.isNotEmpty()) {
for (funding in fundingList) {
xml.startTag(PodcastIndex.NSTAG, "funding")
xml.attribute(PodcastIndex.NSTAG, "url", funding.url)
diff --git a/app/src/free/java/ac/mdiq/podcini/dialog/RatingDialog.kt b/app/src/free/java/ac/mdiq/podcini/dialog/RatingDialog.kt
index c6e17800..372025df 100644
--- a/app/src/free/java/ac/mdiq/podcini/dialog/RatingDialog.kt
+++ b/app/src/free/java/ac/mdiq/podcini/dialog/RatingDialog.kt
@@ -1,4 +1,4 @@
-package ac.mdiq.podcini.dialog
+package ac.mdiq.podcini.ui.dialog
import android.content.Context
import androidx.annotation.VisibleForTesting
diff --git a/net/ssl/src/free/java/ac/mdiq/podcini/net/ssl/SslProviderInstaller.kt b/app/src/free/java/ac/mdiq/podcini/net/ssl/SslProviderInstaller.kt
similarity index 100%
rename from net/ssl/src/free/java/ac/mdiq/podcini/net/ssl/SslProviderInstaller.kt
rename to app/src/free/java/ac/mdiq/podcini/net/ssl/SslProviderInstaller.kt
diff --git a/playback/cast/src/free/java/ac/mdiq/podcini/playback/cast/CastEnabledActivity.kt b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastEnabledActivity.kt
similarity index 100%
rename from playback/cast/src/free/java/ac/mdiq/podcini/playback/cast/CastEnabledActivity.kt
rename to app/src/free/java/ac/mdiq/podcini/playback/cast/CastEnabledActivity.kt
diff --git a/playback/cast/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
similarity index 100%
rename from playback/cast/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
rename to app/src/free/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt
diff --git a/app/src/free/java/ac/mdiq/podcini/playback/cast/CastStateListener.kt b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastStateListener.kt
new file mode 100644
index 00000000..e3972e1a
--- /dev/null
+++ b/app/src/free/java/ac/mdiq/podcini/playback/cast/CastStateListener.kt
@@ -0,0 +1,11 @@
+package ac.mdiq.podcini.playback.cast
+
+import android.content.Context
+
+open class CastStateListener(context: Context) {
+ fun destroy() {
+ }
+
+ open fun onSessionStartedOrEnded() {
+ }
+}
diff --git a/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt b/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt
new file mode 100644
index 00000000..4db068f7
--- /dev/null
+++ b/app/src/free/java/ac/mdiq/podcini/service/playback/WearMediaSession.kt
@@ -0,0 +1,17 @@
+package ac.mdiq.podcini.service.playback
+
+import android.support.v4.media.session.MediaSessionCompat
+import android.support.v4.media.session.PlaybackStateCompat
+
+internal object WearMediaSession {
+ /**
+ * Take a custom action builder and add no extras, because this is not the Play version of the app.
+ */
+ fun addWearExtrasToAction(actionBuilder: PlaybackStateCompat.CustomAction.Builder?) {
+ // no-op
+ }
+
+ fun mediaSessionSetExtraForWear(mediaSession: MediaSessionCompat?) {
+ // no-op
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index feb448d3..116456ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -32,7 +45,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:backupAgent=".core.backup.OpmlBackupAgent"
+ android:backupAgent=".storage.backup.OpmlBackupAgent"
android:restoreAnyVersion="true"
android:theme="@style/Theme.Podcini.Splash"
android:usesCleartextTraffic="true"
@@ -42,15 +55,51 @@
android:allowAudioPlaybackCapture="true"
android:networkSecurityConfig="@xml/network_security_config">
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -100,7 +149,7 @@
+ android:value="ac.mdiq.podcini.ui.activity.MainActivity"/>
@@ -157,7 +206,7 @@
@@ -170,7 +219,7 @@
@@ -196,22 +245,22 @@
+ android:value="ac.mdiq.podcini.ui.activity.PreferenceActivity"/>
+ android:value="ac.mdiq.podcini.ui.activity.MainActivity"/>
@@ -219,14 +268,14 @@
+ android:value="ac.mdiq.podcini.ui.activity.MainActivity"/>
@@ -336,7 +385,7 @@
-
+
+
+
+
+
+
+
diff --git a/core/src/main/assets/html-export-favorites-item-template.html b/app/src/main/assets/html-export-favorites-item-template.html
similarity index 100%
rename from core/src/main/assets/html-export-favorites-item-template.html
rename to app/src/main/assets/html-export-favorites-item-template.html
diff --git a/core/src/main/assets/html-export-feed-template.html b/app/src/main/assets/html-export-feed-template.html
similarity index 100%
rename from core/src/main/assets/html-export-feed-template.html
rename to app/src/main/assets/html-export-feed-template.html
diff --git a/core/src/main/assets/html-export-template.html b/app/src/main/assets/html-export-template.html
similarity index 100%
rename from core/src/main/assets/html-export-template.html
rename to app/src/main/assets/html-export-template.html
diff --git a/core/src/main/assets/shownotes-style.css b/app/src/main/assets/shownotes-style.css
similarity index 100%
rename from core/src/main/assets/shownotes-style.css
rename to app/src/main/assets/shownotes-style.css
diff --git a/app/src/main/java/ac/mdiq/podcini/PodciniApp.kt b/app/src/main/java/ac/mdiq/podcini/PodciniApp.kt
index b5412054..4e3d5ad2 100644
--- a/app/src/main/java/ac/mdiq/podcini/PodciniApp.kt
+++ b/app/src/main/java/ac/mdiq/podcini/PodciniApp.kt
@@ -9,15 +9,14 @@ 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.activity.SplashActivity
-import ac.mdiq.podcini.config.ApplicationCallbacksImpl
-import ac.mdiq.podcini.core.ApCoreEventBusIndex
-import ac.mdiq.podcini.core.ClientConfig
-import ac.mdiq.podcini.core.ClientConfigurator
-import ac.mdiq.podcini.error.CrashReportWriter
-import ac.mdiq.podcini.error.RxJavaErrorHandlerSetup
+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.spa.SPAUtil
+import ac.mdiq.podcini.util.SPAUtil
import org.greenrobot.eventbus.EventBus
/** Main application class. */
@@ -26,7 +25,8 @@ class PodciniApp : Application() {
override fun onCreate() {
super.onCreate()
ClientConfig.USER_AGENT = "Podcini/" + BuildConfig.VERSION_NAME
- ClientConfig.applicationCallbacks = ApplicationCallbacksImpl()
+ ClientConfig.applicationCallbacks =
+ ApplicationCallbacksImpl()
Thread.setDefaultUncaughtExceptionHandler(CrashReportWriter())
RxJavaErrorHandlerSetup.setupRxJavaErrorHandler()
@@ -53,7 +53,7 @@ class PodciniApp : Application() {
SPAUtil.sendSPAppsQueryFeedsIntent(this)
EventBus.builder()
.addIndex(ApEventBusIndex())
- .addIndex(ApCoreEventBusIndex())
+// .addIndex(ApCoreEventBusIndex())
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.installDefaultEventBus()
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/BugReportActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/BugReportActivity.kt
deleted file mode 100644
index 9826da00..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/BugReportActivity.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.DialogInterface
-import android.os.Build
-import android.os.Bundle
-import android.util.Log
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.widget.TextView
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ShareCompat.IntentBuilder
-import androidx.core.content.FileProvider
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTheme
-import ac.mdiq.podcini.core.util.IntentUtils.openInBrowser
-import ac.mdiq.podcini.error.CrashReportWriter
-import ac.mdiq.podcini.storage.preferences.UserPreferences.getDataFolder
-import org.apache.commons.io.IOUtils
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
-import java.nio.charset.Charset
-
-/**
- * Displays the 'crash report' screen
- */
-class BugReportActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTheme(this))
- super.onCreate(savedInstanceState)
- supportActionBar!!.setDisplayShowHomeEnabled(true)
- setContentView(R.layout.bug_report)
-
- var stacktrace = "No crash report recorded"
- try {
- val crashFile = CrashReportWriter.file
- if (crashFile.exists()) {
- stacktrace = IOUtils.toString(FileInputStream(crashFile), Charset.forName("UTF-8"))
- } else {
- Log.d(TAG, stacktrace)
- }
- } catch (e: IOException) {
- e.printStackTrace()
- }
-
- val crashDetailsTextView = findViewById(R.id.crash_report_logs)
- crashDetailsTextView.text = """
- ${CrashReportWriter.systemInfo}
-
- $stacktrace
- """.trimIndent()
-
- findViewById(R.id.btn_open_bug_tracker).setOnClickListener { v: View? ->
- openInBrowser(
- this@BugReportActivity, "https://github.com/XilinJia/Podcini/issues")
- }
-
- findViewById(R.id.btn_copy_log).setOnClickListener { v: View? ->
- val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
- val clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsTextView.text)
- clipboard.setPrimaryClip(clip)
- if (Build.VERSION.SDK_INT < 32) {
- Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard,
- Snackbar.LENGTH_SHORT).show()
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.bug_report_options, menu)
- return super.onCreateOptionsMenu(menu)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.export_logcat) {
- val alertBuilder = MaterialAlertDialogBuilder(this)
- alertBuilder.setMessage(R.string.confirm_export_log_dialog_message)
- alertBuilder.setPositiveButton(R.string.confirm_label) { dialog: DialogInterface, which: Int ->
- exportLog()
- dialog.dismiss()
- }
- alertBuilder.setNegativeButton(R.string.cancel_label, null)
- alertBuilder.show()
- return true
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun exportLog() {
- try {
- val filename = File(getDataFolder(null), "full-logs.txt")
- val cmd = "logcat -d -f " + filename.absolutePath
- Runtime.getRuntime().exec(cmd)
- //share file
- try {
- val authority = getString(R.string.provider_authority)
- val fileUri = FileProvider.getUriForFile(this, authority, filename)
-
- IntentBuilder(this)
- .setType("text/*")
- .addStream(fileUri)
- .setChooserTitle(R.string.share_file_label)
- .startChooser()
- } catch (e: Exception) {
- e.printStackTrace()
- val strResId = R.string.log_file_share_exception
- Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG)
- .show()
- }
- } catch (e: IOException) {
- e.printStackTrace()
- Snackbar.make(findViewById(android.R.id.content), e.message!!, Snackbar.LENGTH_LONG).show()
- }
- }
-
-
- companion object {
- private const val TAG = "BugReportActivity"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/MainActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/MainActivity.kt
deleted file mode 100644
index d6ebe7ea..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/MainActivity.kt
+++ /dev/null
@@ -1,682 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.Intent
-import android.content.res.Configuration
-import android.media.AudioManager
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.util.DisplayMetrics
-import android.util.Log
-import android.view.KeyEvent
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup.MarginLayoutParams
-import android.widget.EditText
-import androidx.appcompat.app.ActionBarDrawerToggle
-import androidx.core.graphics.Insets
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.drawerlayout.widget.DrawerLayout
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentContainerView
-import androidx.media3.common.util.UnstableApi
-import androidx.recyclerview.widget.RecyclerView
-import androidx.work.WorkInfo
-import androidx.work.WorkManager
-import com.bumptech.glide.Glide
-import com.google.android.material.appbar.MaterialToolbar
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
-import com.google.android.material.snackbar.Snackbar
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getNoTitleTheme
-import ac.mdiq.podcini.core.receiver.MediaButtonReceiver.Companion.createIntent
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.core.sync.queue.SynchronizationQueueSink
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager.restartUpdateAlarm
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager.runOnceOrAsk
-import ac.mdiq.podcini.dialog.RatingDialog
-import ac.mdiq.podcini.event.EpisodeDownloadEvent
-import ac.mdiq.podcini.event.FeedUpdateRunningEvent
-import ac.mdiq.podcini.event.MessageEvent
-import ac.mdiq.podcini.fragment.*
-import ac.mdiq.podcini.model.download.DownloadStatus
-import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
-import ac.mdiq.podcini.playback.cast.CastEnabledActivity
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import ac.mdiq.podcini.storage.preferences.UserPreferences.backButtonOpensDrawer
-import ac.mdiq.podcini.storage.preferences.UserPreferences.defaultPage
-import ac.mdiq.podcini.storage.preferences.UserPreferences.hiddenDrawerItems
-import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
-import ac.mdiq.podcini.ui.common.ThemeUtils.getDrawableFromAttr
-import ac.mdiq.podcini.ui.home.HomeFragment
-import ac.mdiq.podcini.view.LockableBottomSheetBehavior
-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
-
-/**
- * The activity that is shown when the user launches the app.
- */
-@UnstableApi
-class MainActivity : CastEnabledActivity() {
-// some device doesn't have a drawer
- private var drawerLayout: DrawerLayout? = null
-
- private lateinit var navDrawer: View
- private lateinit var dummyView : View
- lateinit var bottomSheet: LockableBottomSheetBehavior<*>
- private set
-
- private var drawerToggle: ActionBarDrawerToggle? = null
-
- @JvmField
- val recycledViewPool: RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
- private var lastTheme = 0
- private var navigationBarInsets = Insets.NONE
-
- @UnstableApi public override fun onCreate(savedInstanceState: Bundle?) {
- lastTheme = getNoTitleTheme(this)
- setTheme(lastTheme)
-
- DBReader.updateFeedList()
-
- if (savedInstanceState != null) {
- ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(KEY_GENERATED_VIEW_ID, 0))
- }
- WindowCompat.setDecorFitsSystemWindows(window, false)
- super.onCreate(savedInstanceState)
- setContentView(R.layout.main)
- recycledViewPool.setMaxRecycledViews(R.id.view_type_episode_item, 25)
-
- dummyView = object : View(this) {}
-
- drawerLayout = findViewById(R.id.drawer_layout)
- navDrawer = findViewById(R.id.navDrawerFragment)
- setNavDrawerSize()
-
- // Consume navigation bar insets - we apply them in setPlayerVisible()
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_view)) { v: View?, insets: WindowInsetsCompat ->
- navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
- updateInsets()
- WindowInsetsCompat.Builder(insets)
- .setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.NONE)
- .build()
- }
-
- val fm = supportFragmentManager
- if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) {
- if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) {
- loadFragment(defaultPage, null)
- } else {
- val lastFragment = NavDrawerFragment.getLastNavFragment(this)
- if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) {
- loadFragment(lastFragment, null)
- } else {
- try {
- loadFeedFragmentById(lastFragment.toInt().toLong(), null)
- } catch (e: NumberFormatException) {
- // it's not a number, this happens if we removed
- // a label from the NAV_DRAWER_TAGS
- // give them a nice default...
- loadFragment(HomeFragment.TAG, null)
- }
- }
- }
- }
-
- val transaction = fm.beginTransaction()
- val navDrawerFragment = NavDrawerFragment()
- transaction.replace(R.id.navDrawerFragment, navDrawerFragment, NavDrawerFragment.TAG)
- val audioPlayerFragment = AudioPlayerFragment()
- transaction.replace(R.id.audioplayerFragment, audioPlayerFragment, AudioPlayerFragment.TAG)
- transaction.commit()
-
- checkFirstLaunch()
- val bottomSheet = findViewById(R.id.audioplayerFragment)
- this.bottomSheet = BottomSheetBehavior.from(bottomSheet) as LockableBottomSheetBehavior<*>
- this.bottomSheet.isHideable = false
- this.bottomSheet.setBottomSheetCallback(bottomSheetCallback)
-
- restartUpdateAlarm(this, false)
- SynchronizationQueueSink.syncNowIfNotSyncedRecently()
-
- WorkManager.getInstance(this)
- .getWorkInfosByTagLiveData(FeedUpdateManager.WORK_TAG_FEED_UPDATE)
- .observe(this) { workInfos: List ->
- var isRefreshingFeeds = false
- for (workInfo in workInfos) {
- if (workInfo.state == WorkInfo.State.RUNNING) {
- isRefreshingFeeds = true
- } else if (workInfo.state == WorkInfo.State.ENQUEUED) {
- isRefreshingFeeds = true
- }
- }
- EventBus.getDefault().postSticky(FeedUpdateRunningEvent(isRefreshingFeeds))
- }
- WorkManager.getInstance(this)
- .getWorkInfosByTagLiveData(DownloadServiceInterface.WORK_TAG)
- .observe(this) { workInfos: List ->
- val updatedEpisodes: MutableMap = HashMap()
- for (workInfo in workInfos) {
- var downloadUrl: String? = null
- for (tag in workInfo.tags) {
- if (tag.startsWith(DownloadServiceInterface.WORK_TAG_EPISODE_URL)) {
- downloadUrl = tag.substring(DownloadServiceInterface.WORK_TAG_EPISODE_URL.length)
- }
- }
- if (downloadUrl == null) {
- continue
- }
- var status: Int
- status = when (workInfo.state) {
- WorkInfo.State.RUNNING -> {
- DownloadStatus.STATE_RUNNING
- }
- WorkInfo.State.ENQUEUED, WorkInfo.State.BLOCKED -> {
- DownloadStatus.STATE_QUEUED
- }
- else -> {
- DownloadStatus.STATE_COMPLETED
- }
- }
- var progress = workInfo.progress.getInt(DownloadServiceInterface.WORK_DATA_PROGRESS, -1)
- if (progress == -1 && status != DownloadStatus.STATE_COMPLETED) {
- status = DownloadStatus.STATE_QUEUED
- progress = 0
- }
- updatedEpisodes[downloadUrl] = DownloadStatus(status, progress)
- }
- DownloadServiceInterface.get()?.setCurrentDownloads(updatedEpisodes)
- EventBus.getDefault().postSticky(EpisodeDownloadEvent(updatedEpisodes))
- }
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- updateInsets()
- }
-
- /**
- * View.generateViewId stores the current ID in a static variable.
- * When the process is killed, the variable gets reset.
- * This makes sure that we do not get ID collisions
- * and therefore errors when trying to restore state from another view.
- */
- private fun ensureGeneratedViewIdGreaterThan(minimum: Int) {
- while (View.generateViewId() <= minimum) {
- // Generate new IDs
- }
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putInt(KEY_GENERATED_VIEW_ID, View.generateViewId())
- }
-
- private val bottomSheetCallback: BottomSheetCallback = @UnstableApi object : BottomSheetCallback() {
- override fun onStateChanged(view: View, state: Int) {
- if (state == BottomSheetBehavior.STATE_COLLAPSED) {
- onSlide(view,0.0f)
- } else if (state == BottomSheetBehavior.STATE_EXPANDED) {
- onSlide(view, 1.0f)
- }
- }
- override fun onSlide(view: View, slideOffset: Float) {
- val audioPlayer = supportFragmentManager
- .findFragmentByTag(AudioPlayerFragment.TAG) as AudioPlayerFragment?
- if (audioPlayer == null) {
- return
- }
-
- if (slideOffset == 0.0f) { //STATE_COLLAPSED
- audioPlayer.scrollToPage(AudioPlayerFragment.POS_COVER)
- }
-
- audioPlayer.fadePlayerToToolbar(slideOffset)
- }
- }
-
- fun setupToolbarToggle(toolbar: MaterialToolbar, displayUpArrow: Boolean) {
- if (drawerLayout != null) { // Tablet layout does not have a drawer
- if (drawerToggle != null) {
- drawerLayout!!.removeDrawerListener(drawerToggle!!)
- }
- drawerToggle = ActionBarDrawerToggle(this, drawerLayout, toolbar,
- R.string.drawer_open, R.string.drawer_close)
- drawerLayout!!.addDrawerListener(drawerToggle!!)
- drawerToggle!!.syncState()
- drawerToggle!!.isDrawerIndicatorEnabled = !displayUpArrow
- drawerToggle!!.toolbarNavigationClickListener = View.OnClickListener { v: View? -> supportFragmentManager.popBackStack() }
- } else if (!displayUpArrow) {
- toolbar.navigationIcon = null
- } else {
- toolbar.setNavigationIcon(getDrawableFromAttr(this, R.attr.homeAsUpIndicator))
- toolbar.setNavigationOnClickListener { v: View? -> supportFragmentManager.popBackStack() }
- }
- }
-
- override fun onDestroy() {
- super.onDestroy()
- drawerLayout?.removeDrawerListener(drawerToggle!!)
- }
-
- private fun checkFirstLaunch() {
- val prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE)
- if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
- restartUpdateAlarm(this, true)
-
- val edit = prefs.edit()
- edit.putBoolean(PREF_IS_FIRST_LAUNCH, false)
- edit.apply()
- }
- }
-
- val isDrawerOpen: Boolean
- get() = drawerLayout?.isDrawerOpen(navDrawer)?:false
-
- private fun updateInsets() {
- setPlayerVisible(findViewById(R.id.audioplayerFragment).visibility == View.VISIBLE)
- val playerHeight = resources.getDimension(R.dimen.external_player_height).toInt()
- bottomSheet.peekHeight = playerHeight + navigationBarInsets.bottom
- }
-
- fun setPlayerVisible(visible: Boolean) {
- bottomSheet.setLocked(!visible)
- if (visible) {
- bottomSheetCallback.onStateChanged(dummyView, bottomSheet.state) // Update toolbar visibility
- } else {
- bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
- }
- val mainView = findViewById(R.id.main_view)
- val params = mainView.layoutParams as MarginLayoutParams
- val externalPlayerHeight = resources.getDimension(R.dimen.external_player_height).toInt()
- params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
- navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
- mainView.layoutParams = params
- val playerView = findViewById(R.id.playerFragment)
- val playerParams = playerView.layoutParams as MarginLayoutParams
- playerParams.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
- playerView.layoutParams = playerParams
- findViewById(R.id.audioplayerFragment).visibility = if (visible) View.VISIBLE else View.GONE
- }
-
- fun loadFragment(tag: String?, args: Bundle?) {
- var tag = tag
- var args = args
- Log.d(TAG, "loadFragment(tag: $tag, args: $args)")
- val fragment: Fragment
- when (tag) {
- HomeFragment.TAG -> fragment = HomeFragment()
- QueueFragment.TAG -> fragment = QueueFragment()
- InboxFragment.TAG -> fragment = InboxFragment()
- AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
- CompletedDownloadsFragment.TAG -> fragment = CompletedDownloadsFragment()
- PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment()
- AddFeedFragment.TAG -> fragment = AddFeedFragment()
- SubscriptionFragment.TAG -> fragment = SubscriptionFragment()
- else -> {
- // default to home screen
- fragment = HomeFragment()
- tag = HomeFragment.TAG
- args = null
- }
- }
- if (args != null) {
- fragment.arguments = args
- }
- NavDrawerFragment.saveLastNavFragment(this, tag)
- loadFragment(fragment)
- }
-
- fun loadFeedFragmentById(feedId: Long, args: Bundle?) {
- val fragment: Fragment = FeedItemlistFragment.newInstance(feedId)
- if (args != null) {
- fragment.arguments = args
- }
- NavDrawerFragment.saveLastNavFragment(this, feedId.toString())
- loadFragment(fragment)
- }
-
- private fun loadFragment(fragment: Fragment) {
- val fragmentManager = supportFragmentManager
- // clear back stack
- for (i in 0 until fragmentManager.backStackEntryCount) {
- fragmentManager.popBackStack()
- }
- val t = fragmentManager.beginTransaction()
- t.replace(R.id.main_view, fragment, MAIN_FRAGMENT_TAG)
- fragmentManager.popBackStack()
- // TODO: we have to allow state loss here
- // since this function can get called from an AsyncTask which
- // could be finishing after our app has already committed state
- // and is about to get shutdown. What we *should* do is
- // not commit anything in an AsyncTask, but that's a bigger
- // change than we want now.
- t.commitAllowingStateLoss()
-
- // Tablet layout does not have a drawer
- drawerLayout?.closeDrawer(navDrawer)
- }
-
- @JvmOverloads
- fun loadChildFragment(fragment: Fragment, transition: TransitionEffect? = TransitionEffect.NONE) {
- val transaction = supportFragmentManager.beginTransaction()
-
- when (transition) {
- TransitionEffect.FADE -> transaction.setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
- TransitionEffect.SLIDE -> transaction.setCustomAnimations(
- R.anim.slide_right_in,
- R.anim.slide_left_out,
- R.anim.slide_left_in,
- R.anim.slide_right_out)
- TransitionEffect.NONE -> {}
- null -> {}
- }
- transaction
- .hide(supportFragmentManager.findFragmentByTag(MAIN_FRAGMENT_TAG)!!)
- .add(R.id.main_view, fragment, MAIN_FRAGMENT_TAG)
- .addToBackStack(null)
- .commit()
- }
-
- override fun onPostCreate(savedInstanceState: Bundle?) {
- super.onPostCreate(savedInstanceState)
- if (drawerToggle != null) { // Tablet layout does not have a drawer
- drawerToggle!!.syncState()
- }
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- if (drawerToggle != null) { // Tablet layout does not have a drawer
- drawerToggle!!.onConfigurationChanged(newConfig)
- }
- setNavDrawerSize()
- }
-
- private fun setNavDrawerSize() {
- if (drawerToggle == null) { // Tablet layout does not have a drawer
- return
- }
- val screenPercent = resources.getInteger(R.integer.nav_drawer_screen_size_percent) * 0.01f
- val width = (screenWidth * screenPercent).toInt()
- val maxWidth = resources.getDimension(R.dimen.nav_drawer_max_screen_size).toInt()
-
- navDrawer.layoutParams.width = min(width.toDouble(), maxWidth.toDouble()).toInt()
- }
-
- private val screenWidth: Int
- get() {
- val displayMetrics = DisplayMetrics()
- windowManager.defaultDisplay.getMetrics(displayMetrics)
- return displayMetrics.widthPixels
- }
-
- override fun onRestoreInstanceState(savedInstanceState: Bundle) {
- super.onRestoreInstanceState(savedInstanceState)
-
- if (bottomSheet.state == BottomSheetBehavior.STATE_EXPANDED) {
- bottomSheetCallback.onSlide(dummyView, 1.0f)
- }
- }
-
- public override fun onStart() {
- super.onStart()
- EventBus.getDefault().register(this)
- RatingDialog.init(this)
- }
-
- override fun onResume() {
- super.onResume()
- handleNavIntent()
- RatingDialog.check()
-
- if (lastTheme != getNoTitleTheme(this)) {
- finish()
- startActivity(Intent(this, MainActivity::class.java))
- }
- if (hiddenDrawerItems!!.contains(NavDrawerFragment.getLastNavFragment(this))) {
- loadFragment(defaultPage, null)
- }
- }
-
- @Deprecated("Deprecated in Java")
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- lastTheme = getNoTitleTheme(this) // Don't recreate activity when a result is pending
- }
-
- override fun onStop() {
- super.onStop()
- EventBus.getDefault().unregister(this)
- }
-
- override fun onTrimMemory(level: Int) {
- super.onTrimMemory(level)
- Glide.get(this).trimMemory(level)
- }
-
- override fun onLowMemory() {
- super.onLowMemory()
- Glide.get(this).clearMemory()
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (drawerToggle != null && drawerToggle!!.onOptionsItemSelected(item)) { // Tablet layout does not have a drawer
- return true
- } else if (item.itemId == android.R.id.home) {
- if (supportFragmentManager.backStackEntryCount > 0) {
- supportFragmentManager.popBackStack()
- }
- return true
- } else {
- return super.onOptionsItemSelected(item)
- }
- }
-
- @Deprecated("Deprecated in Java")
- override fun onBackPressed() {
- if (isDrawerOpen) {
- drawerLayout?.closeDrawer(navDrawer)
- } else if (bottomSheet.state == BottomSheetBehavior.STATE_EXPANDED) {
- bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
- } else if (supportFragmentManager.backStackEntryCount != 0) {
- super.onBackPressed()
- } else {
- val toPage = defaultPage
- if (NavDrawerFragment.getLastNavFragment(this) == toPage || UserPreferences.DEFAULT_PAGE_REMEMBER == toPage) {
- if (backButtonOpensDrawer()) {
- drawerLayout?.openDrawer(navDrawer)
- } else {
- super.onBackPressed()
- }
- } else {
- loadFragment(toPage, null)
- }
- }
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onEventMainThread(event: MessageEvent) {
- Log.d(TAG, "onEvent($event)")
-
- val snackbar = showSnackbarAbovePlayer(event.message, Snackbar.LENGTH_LONG)
- if (event.action != null) {
- snackbar.setAction(event.actionText) { v: View? -> event.action!!.accept(this) }
- }
- }
-
- private fun handleNavIntent() {
- Log.d(TAG, "handleNavIntent()")
- val intent = intent
- if (intent.hasExtra(EXTRA_FEED_ID)) {
- val feedId = intent.getLongExtra(EXTRA_FEED_ID, 0)
- val args = intent.getBundleExtra(MainActivityStarter.EXTRA_FRAGMENT_ARGS)
- if (feedId > 0) {
- val startedFromSearch = intent.getBooleanExtra(EXTRA_STARTED_FROM_SEARCH, false)
- val addToBackStack = intent.getBooleanExtra(EXTRA_ADD_TO_BACK_STACK, false)
- if (startedFromSearch || addToBackStack) {
- loadChildFragment(FeedItemlistFragment.newInstance(feedId))
- } else {
- loadFeedFragmentById(feedId, args)
- }
- }
- bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
- } else if (intent.hasExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG)) {
- val tag = intent.getStringExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG)
- val args = intent.getBundleExtra(MainActivityStarter.EXTRA_FRAGMENT_ARGS)
- if (tag != null) {
- loadFragment(tag, args)
- }
- bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
- } else if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false)) {
- bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
- bottomSheetCallback.onSlide(dummyView, 1.0f)
- } else {
- handleDeeplink(intent.data)
- }
-
- if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_DRAWER, false)) {
- drawerLayout?.open()
- }
- if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_DOWNLOAD_LOGS, false)) {
- DownloadLogFragment().show(supportFragmentManager, null)
- }
- if (intent.getBooleanExtra(EXTRA_REFRESH_ON_START, false)) {
- runOnceOrAsk(this)
- }
- // to avoid handling the intent twice when the configuration changes
- setIntent(Intent(this@MainActivity, MainActivity::class.java))
- }
-
- @SuppressLint("MissingSuperCall")
- override fun onNewIntent(intent: Intent) {
- super.onNewIntent(intent)
- setIntent(intent)
- handleNavIntent()
- }
-
- fun showSnackbarAbovePlayer(text: CharSequence?, duration: Int): Snackbar {
- val s: Snackbar
- if (bottomSheet.state == BottomSheetBehavior.STATE_COLLAPSED) {
- s = Snackbar.make(findViewById(R.id.main_view), text!!, duration)
- if (findViewById(R.id.audioplayerFragment).visibility == View.VISIBLE) {
- s.setAnchorView(findViewById(R.id.audioplayerFragment))
- }
- } else {
- s = Snackbar.make(findViewById(android.R.id.content), text!!, duration)
- }
- s.show()
- return s
- }
-
- fun showSnackbarAbovePlayer(text: Int, duration: Int): Snackbar {
- return showSnackbarAbovePlayer(resources.getText(text), duration)
- }
-
- /**
- * Handles the deep link incoming via App Actions.
- * Performs an in-app search or opens the relevant feature of the app
- * depending on the query.
- *
- * @param uri incoming deep link
- */
- private fun handleDeeplink(uri: Uri?) {
- if (uri == null || uri.path == null) {
- return
- }
- Log.d(TAG, "Handling deeplink: $uri")
- when (uri.path) {
- "/deeplink/search" -> {
- val query = uri.getQueryParameter("query") ?: return
-
- this.loadChildFragment(SearchFragment.newInstance(query))
- }
- "/deeplink/main" -> {
- val feature = uri.getQueryParameter("page") ?: return
- when (feature) {
- "DOWNLOADS" -> loadFragment(CompletedDownloadsFragment.TAG, null)
- "HISTORY" -> loadFragment(PlaybackHistoryFragment.TAG, null)
- "EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null)
- "QUEUE" -> loadFragment(QueueFragment.TAG, null)
- "SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null)
- else -> {
- showSnackbarAbovePlayer(getString(R.string.app_action_not_found)+feature, Snackbar.LENGTH_LONG)
- return
- }
- }
- }
- else -> {}
- }
- }
-
- //Hardware keyboard support
- override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
- val currentFocus = currentFocus
- if (currentFocus is EditText) {
- return super.onKeyUp(keyCode, event)
- }
-
- val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
- var customKeyCode: Int? = null
- EventBus.getDefault().post(event)
-
- when (keyCode) {
- KeyEvent.KEYCODE_P -> customKeyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
- KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_COMMA -> customKeyCode =
- KeyEvent.KEYCODE_MEDIA_REWIND
- KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_PERIOD -> customKeyCode =
- KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
- KeyEvent.KEYCODE_PLUS, KeyEvent.KEYCODE_W -> {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI)
- return true
- }
- KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_S -> {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI)
- return true
- }
- KeyEvent.KEYCODE_M -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_TOGGLE_MUTE, AudioManager.FLAG_SHOW_UI)
- return true
- }
- }
- if (customKeyCode != null) {
- sendBroadcast(createIntent(this, customKeyCode))
- return true
- }
- return super.onKeyUp(keyCode, event)
- }
-
- companion object {
- private const val TAG = "MainActivity"
- const val MAIN_FRAGMENT_TAG: String = "main"
-
- const val PREF_NAME: String = "MainActivityPrefs"
- const val PREF_IS_FIRST_LAUNCH: String = "prefMainActivityIsFirstLaunch"
-
- const val EXTRA_FEED_ID: String = "fragment_feed_id"
- const val EXTRA_REFRESH_ON_START: String = "refresh_on_start"
- const val EXTRA_STARTED_FROM_SEARCH: String = "started_from_search"
- const val EXTRA_ADD_TO_BACK_STACK: String = "add_to_back_stack"
- const val KEY_GENERATED_VIEW_ID: String = "generated_view_id"
-
- @JvmStatic
- fun getIntentToOpenFeed(context: Context, feedId: Long): Intent {
- val intent = Intent(context.applicationContext, MainActivity::class.java)
- intent.putExtra(EXTRA_FEED_ID, feedId)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- return intent
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/OnlineFeedViewActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/OnlineFeedViewActivity.kt
deleted file mode 100644
index 74a3c19f..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/OnlineFeedViewActivity.kt
+++ /dev/null
@@ -1,706 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.app.Dialog
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.graphics.LightingColorFilter
-import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.TextUtils
-import android.text.style.ForegroundColorSpan
-import android.util.Log
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.Toast
-import androidx.annotation.UiThread
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.NavUtils
-import androidx.media3.common.util.UnstableApi
-import com.bumptech.glide.Glide
-import com.bumptech.glide.request.RequestOptions
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.adapter.FeedItemlistDescriptionAdapter
-import ac.mdiq.podcini.core.feed.FeedUrlNotFoundException
-import ac.mdiq.podcini.core.preferences.PlaybackPreferences.Companion.currentlyPlayingMediaType
-import ac.mdiq.podcini.core.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTranslucentTheme
-import ac.mdiq.podcini.core.service.download.DownloadRequestCreator.create
-import ac.mdiq.podcini.core.service.download.Downloader
-import ac.mdiq.podcini.core.service.download.HttpDownloader
-import ac.mdiq.podcini.core.service.playback.PlaybackServiceInterface
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.core.storage.DBTasks
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.core.util.DownloadErrorLabel.from
-import ac.mdiq.podcini.core.util.IntentUtils.sendLocalBroadcast
-import ac.mdiq.podcini.core.util.syndication.FeedDiscoverer
-import ac.mdiq.podcini.core.util.syndication.HtmlToPlainText
-import ac.mdiq.podcini.databinding.EditTextDialogBinding
-import ac.mdiq.podcini.databinding.OnlinefeedviewActivityBinding
-import ac.mdiq.podcini.databinding.OnlinefeedviewHeaderBinding
-import ac.mdiq.podcini.dialog.AuthenticationDialog
-import ac.mdiq.podcini.event.EpisodeDownloadEvent
-import ac.mdiq.podcini.event.FeedListUpdateEvent
-import ac.mdiq.podcini.event.PlayerStatusEvent
-import ac.mdiq.podcini.model.download.DownloadError
-import ac.mdiq.podcini.model.download.DownloadResult
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.model.playback.RemoteMedia
-import ac.mdiq.podcini.net.common.UrlChecker.prepareUrl
-import ac.mdiq.podcini.net.discovery.CombinedSearcher
-import ac.mdiq.podcini.net.discovery.PodcastSearcherRegistry
-import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
-import ac.mdiq.podcini.parser.feed.FeedHandler
-import ac.mdiq.podcini.parser.feed.FeedHandlerResult
-import ac.mdiq.podcini.parser.feed.UnsupportedFeedtypeException
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isEnableAutodownload
-import ac.mdiq.podcini.ui.common.ThemeUtils.getColorFromAttr
-import ac.mdiq.podcini.ui.glide.FastBlurTransformation
-import io.reactivex.Maybe
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
-import io.reactivex.observers.DisposableMaybeObserver
-import io.reactivex.schedulers.Schedulers
-import org.apache.commons.lang3.StringUtils
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
-import java.io.File
-import java.io.IOException
-import kotlin.concurrent.Volatile
-
-/**
- * Downloads a feed from a feed URL and parses it. Subclasses can display the
- * feed object that was parsed. This activity MUST be started with a given URL
- * or an Exception will be thrown.
- *
- *
- * 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 OnlineFeedViewActivity : AppCompatActivity() {
- @Volatile
- private var feeds: List? = null
- private var selectedDownloadUrl: String? = null
- private var downloader: Downloader? = null
- private var username: String? = null
- private var password: String? = null
-
- private var isPaused = false
- private var didPressSubscribe = false
- private var isFeedFoundBySearch = false
-
- private var dialog: Dialog? = null
-
- private var download: Disposable? = null
- private var parser: Disposable? = null
- private var updater: Disposable? = null
-
- private lateinit var headerBinding: OnlinefeedviewHeaderBinding
- private lateinit var viewBinding: OnlinefeedviewActivityBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTranslucentTheme(this))
- super.onCreate(savedInstanceState)
-
- viewBinding = OnlinefeedviewActivityBinding.inflate(layoutInflater)
- setContentView(viewBinding.root)
-
- viewBinding.transparentBackground.setOnClickListener { v: View? -> finish() }
- viewBinding.closeButton.setOnClickListener { view: View? -> finish() }
- viewBinding.card.setOnClickListener(null)
- viewBinding.card.setCardBackgroundColor(getColorFromAttr(this, R.attr.colorSurface))
- headerBinding = OnlinefeedviewHeaderBinding.inflate(layoutInflater)
-
- var feedUrl: String? = null
- if (intent.hasExtra(ARG_FEEDURL)) {
- feedUrl = intent.getStringExtra(ARG_FEEDURL)
- } else if (TextUtils.equals(intent.action, Intent.ACTION_SEND)) {
- feedUrl = intent.getStringExtra(Intent.EXTRA_TEXT)
- } else if (TextUtils.equals(intent.action, Intent.ACTION_VIEW)) {
- feedUrl = intent.dataString
- }
-
- if (feedUrl == null) {
- Log.e(TAG, "feedUrl is null.")
- showNoPodcastFoundError()
- } else {
- Log.d(TAG, "Activity was started with url $feedUrl")
- setLoadingLayout()
- // Remove subscribeonandroid.com from feed URL in order to subscribe to the actual feed URL
- if (feedUrl.contains("subscribeonandroid.com")) {
- feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))".toRegex(), "")
- }
- if (savedInstanceState != null) {
- username = savedInstanceState.getString("username")
- password = savedInstanceState.getString("password")
- }
- lookupUrlAndDownload(feedUrl)
- }
- }
-
- private fun showNoPodcastFoundError() {
- runOnUiThread {
- MaterialAlertDialogBuilder(this@OnlineFeedViewActivity)
- .setNeutralButton(android.R.string.ok) { dialog: DialogInterface?, which: Int -> finish() }
- .setTitle(R.string.error_label)
- .setMessage(R.string.null_value_podcast_error)
- .setOnDismissListener { dialog1: DialogInterface? ->
- setResult(RESULT_ERROR)
- finish()
- }
- .show()
- }
- }
-
- /**
- * Displays a progress indicator.
- */
- private fun setLoadingLayout() {
- viewBinding.progressBar.visibility = View.VISIBLE
- viewBinding.feedDisplayContainer.visibility = View.GONE
- }
-
- override fun onStart() {
- super.onStart()
- isPaused = false
- EventBus.getDefault().register(this)
- }
-
- 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()
- }
- }
-
- public override fun onDestroy() {
- super.onDestroy()
- updater?.dispose()
- download?.dispose()
- parser?.dispose()
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putString("username", username)
- outState.putString("password", password)
- }
-
- private fun resetIntent(url: String) {
- val intent = Intent()
- intent.putExtra(ARG_FEEDURL, url)
- setIntent(intent)
- }
-
- override fun finish() {
- super.finish()
- overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
- }
-
- @UnstableApi override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) {
- val destIntent = Intent(this, MainActivity::class.java)
- if (NavUtils.shouldUpRecreateTask(this, destIntent)) {
- startActivity(destIntent)
- } else {
- NavUtils.navigateUpFromSameTask(this)
- }
- return true
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun lookupUrlAndDownload(url: String) {
- download = PodcastSearcherRegistry.lookupUrl(url)
- ?.subscribeOn(Schedulers.io())
- ?.observeOn(Schedulers.io())
- ?.subscribe({ url1: String -> this.startFeedDownload(url1) },
- { error: Throwable? ->
- if (error is FeedUrlNotFoundException) {
- tryToRetrieveFeedUrlBySearch(error)
- } else {
- showNoPodcastFoundError()
- Log.e(TAG, Log.getStackTraceString(error))
- }
- })
- }
-
- private fun tryToRetrieveFeedUrlBySearch(error: FeedUrlNotFoundException) {
- Log.d(TAG, "Unable to retrieve feed url, trying to retrieve feed url from search")
- val url = searchFeedUrlByTrackName(error.trackName, error.artistName)
- if (url != null) {
- Log.d(TAG, "Successfully retrieve feed url")
- isFeedFoundBySearch = true
- startFeedDownload(url)
- } else {
- showNoPodcastFoundError()
- Log.d(TAG, "Failed to retrieve feed url")
- }
- }
-
- private fun searchFeedUrlByTrackName(trackName: String, artistName: String): String? {
- val searcher = CombinedSearcher()
- val query = "$trackName $artistName"
- val results = searcher.search(query)?.blockingGet()
- if (results.isNullOrEmpty()) return null
- for (result in results) {
- if (result?.feedUrl != null && result.author != null &&
- result.author.equals(artistName, ignoreCase = true) &&
- result.title.equals(trackName, ignoreCase = true)) {
- return result.feedUrl
- }
- }
- return null
- }
-
- private fun startFeedDownload(url: String) {
- Log.d(TAG, "Starting feed download")
- selectedDownloadUrl = prepareUrl(url)
- val request = create(Feed(selectedDownloadUrl, null))
- .withAuthentication(username, password)
- .withInitiatedByUser(true)
- .build()
-
- download = Observable.fromCallable {
- feeds = DBReader.getFeedList()
- downloader = HttpDownloader(request)
- downloader?.call()
- downloader?.result
- }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ status: DownloadResult? -> if (request.destination != null) checkDownloadResult(status, request.destination!!) },
- { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
- }
-
- private fun checkDownloadResult(status: DownloadResult?, destination: String) {
- if (status == null) return
- if (status.isSuccessful) {
- parseFeed(destination)
- } else if (status.reason == DownloadError.ERROR_UNAUTHORIZED) {
- if (!isFinishing && !isPaused) {
- if (username != null && password != null) {
- Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show()
- }
- if (downloader?.downloadRequest?.source != null) {
- dialog = FeedViewAuthenticationDialog(this@OnlineFeedViewActivity,
- R.string.authentication_notification_title, downloader!!.downloadRequest.source!!).create()
- dialog?.show()
- }
- }
- } else {
- showErrorDialog(getString(from(status.reason)), status.reasonDetailed)
- }
- }
-
- @UnstableApi @Subscribe
- fun onFeedListChanged(event: FeedListUpdateEvent?) {
- updater = Observable.fromCallable { DBReader.getFeedList() }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { feeds: List? ->
- this@OnlineFeedViewActivity.feeds = feeds
- handleUpdatedFeedStatus()
- }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }
- )
- }
-
- @UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
- fun onEventMainThread(event: EpisodeDownloadEvent?) {
- handleUpdatedFeedStatus()
- }
-
- private fun parseFeed(destination: String) {
- Log.d(TAG, "Parsing feed")
- parser = Maybe.fromCallable { doParseFeed(destination) }
- .subscribeOn(Schedulers.computation())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribeWith(object : DisposableMaybeObserver() {
-
- @UnstableApi override fun onSuccess(result: FeedHandlerResult) {
- showFeedInformation(result.feed, result.alternateFeedUrls)
- }
-
- override fun onComplete() {
- // Ignore null result: We showed the discovery dialog.
- }
-
- override fun onError(error: Throwable) {
- showErrorDialog(error.message, "")
- Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error))
- }
- })
- }
-
- /**
- * Try to parse the feed.
- * @return The FeedHandlerResult if successful.
- * Null if unsuccessful but we started another attempt.
- * @throws Exception If unsuccessful but we do not know a resolution.
- */
- @Throws(Exception::class)
- private fun doParseFeed(destination: String): FeedHandlerResult? {
- val handler = FeedHandler()
- val feed = Feed(selectedDownloadUrl, null)
- feed.file_url = destination
- val destinationFile = File(destination)
- return try {
- handler.parseFeed(feed)
- } catch (e: UnsupportedFeedtypeException) {
- Log.d(TAG, "Unsupported feed type detected")
- if ("html".equals(e.rootElement, ignoreCase = true)) {
- if (selectedDownloadUrl != null) {
- val dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl!!)
- if (dialogShown) {
- null // Should not display an error message
- } else {
- throw UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html))
- }
- } else null
- } else {
- throw e
- }
- } catch (e: Exception) {
- Log.e(TAG, Log.getStackTraceString(e))
- throw e
- } finally {
- val rc = destinationFile.delete()
- Log.d(TAG, "Deleted feed source file. Result: $rc")
- }
- }
-
- /**
- * Called when feed parsed successfully.
- * This method is executed on the GUI thread.
- */
- @UnstableApi private fun showFeedInformation(feed: Feed, alternateFeedUrls: Map) {
- viewBinding.progressBar.visibility = View.GONE
- viewBinding.feedDisplayContainer.visibility = View.VISIBLE
- if (isFeedFoundBySearch) {
- val resId = R.string.no_feed_url_podcast_found_by_search
- Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show()
- }
-
- viewBinding.backgroundImage.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
-
- viewBinding.listView.addHeaderView(headerBinding.root)
- viewBinding.listView.setSelector(android.R.color.transparent)
- viewBinding.listView.adapter = FeedItemlistDescriptionAdapter(this, 0, feed.items)
-
- if (StringUtils.isNotBlank(feed.imageUrl)) {
- Glide.with(this)
- .load(feed.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.light_gray)
- .error(R.color.light_gray)
- .fitCenter()
- .dontAnimate())
- .into(viewBinding.coverImage)
- Glide.with(this)
- .load(feed.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.image_readability_tint)
- .error(R.color.image_readability_tint)
- .transform(FastBlurTransformation())
- .dontAnimate())
- .into(viewBinding.backgroundImage)
- }
-
- viewBinding.titleLabel.text = feed.title
- viewBinding.authorLabel.text = feed.author
- headerBinding.txtvDescription.text = HtmlToPlainText.getPlainText(feed.description?:"")
-
- viewBinding.subscribeButton.setOnClickListener { v: View? ->
- if (feedInFeedlist()) {
- openFeed()
- } else {
- DBTasks.updateFeed(this, feed, false)
- didPressSubscribe = true
- handleUpdatedFeedStatus()
- }
- }
-
- viewBinding.stopPreviewButton.setOnClickListener { v: View? ->
- writeNoMediaPlaying()
- sendLocalBroadcast(this, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE)
- }
-
- if (isEnableAutodownload) {
- val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
- viewBinding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
- }
-
- headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
- headerBinding.txtvDescription.setOnClickListener { v: View? ->
- if (headerBinding.txtvDescription.maxLines > DESCRIPTION_MAX_LINES_COLLAPSED) {
- headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
- } else {
- headerBinding.txtvDescription.maxLines = 2000
- }
- }
-
- if (alternateFeedUrls.isEmpty()) {
- viewBinding.alternateUrlsSpinner.visibility = View.GONE
- } else {
- viewBinding.alternateUrlsSpinner.visibility = View.VISIBLE
-
- val alternateUrlsList: MutableList = ArrayList()
- val alternateUrlsTitleList: MutableList = ArrayList()
-
- if (feed.download_url != null) alternateUrlsList.add(feed.download_url!!)
- alternateUrlsTitleList.add(feed.title)
-
-
- alternateUrlsList.addAll(alternateFeedUrls.keys)
- for (url in alternateFeedUrls.keys) {
- alternateUrlsTitleList.add(alternateFeedUrls[url])
- }
-
- val adapter: ArrayAdapter = object : ArrayAdapter(this,
- R.layout.alternate_urls_item, alternateUrlsTitleList) {
- override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
- // reusing the old view causes a visual bug on Android <= 10
- return super.getDropDownView(position, null, parent)
- }
- }
-
- adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item)
- viewBinding.alternateUrlsSpinner.adapter = adapter
- viewBinding.alternateUrlsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
- selectedDownloadUrl = alternateUrlsList[position]
- }
-
- override fun onNothingSelected(parent: AdapterView<*>?) {
- }
- }
- }
- handleUpdatedFeedStatus()
- }
-
- @UnstableApi private fun openFeed() {
- // feed.getId() is always 0, we have to retrieve the id from the feed list from
- // the database
- val intent = MainActivity.getIntentToOpenFeed(this, feedId)
- intent.putExtra(MainActivity.EXTRA_STARTED_FROM_SEARCH,
- getIntent().getBooleanExtra(MainActivity.EXTRA_STARTED_FROM_SEARCH, false))
- finish()
- startActivity(intent)
- }
-
- @UnstableApi private fun handleUpdatedFeedStatus() {
- val dli = DownloadServiceInterface.get()
- if (dli == null || selectedDownloadUrl == null) return
-
- if (dli.isDownloadingEpisode(selectedDownloadUrl!!)) {
- viewBinding.subscribeButton.isEnabled = false
- viewBinding.subscribeButton.setText(R.string.subscribing_label)
- } else if (feedInFeedlist()) {
- viewBinding.subscribeButton.isEnabled = true
- viewBinding.subscribeButton.setText(R.string.open_podcast)
- if (didPressSubscribe) {
- didPressSubscribe = false
-
- val feed1 = DBReader.getFeed(feedId)?: return
- val feedPreferences = feed1.preferences
- if (feedPreferences != null) {
- if (isEnableAutodownload) {
- val autoDownload = viewBinding.autoDownloadCheckBox.isChecked
- feedPreferences.autoDownload = autoDownload
-
- val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
- val editor = preferences.edit()
- editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload)
- editor.apply()
- }
- if (username != null) {
- feedPreferences.username = username
- feedPreferences.password = password
- }
- DBWriter.setFeedPreferences(feedPreferences)
- }
- openFeed()
- }
- } else {
- viewBinding.subscribeButton.isEnabled = true
- viewBinding.subscribeButton.setText(R.string.subscribe_label)
- if (isEnableAutodownload) {
- viewBinding.autoDownloadCheckBox.visibility = View.VISIBLE
- }
- }
- }
-
- private fun feedInFeedlist(): Boolean {
- return feedId != 0L
- }
-
- private val feedId: Long
- get() {
- if (feeds == null) {
- return 0
- }
- for (f in feeds!!) {
- if (f.download_url == selectedDownloadUrl) {
- return f.id
- }
- }
- return 0
- }
-
- @UiThread
- private fun showErrorDialog(errorMsg: String?, details: String) {
- if (!isFinishing && !isPaused) {
- val builder = MaterialAlertDialogBuilder(this)
- builder.setTitle(R.string.error_label)
- if (errorMsg != null) {
- val total = """
- $errorMsg
-
- $details
- """.trimIndent()
- val errorMessage = SpannableString(total)
- errorMessage.setSpan(ForegroundColorSpan(-0x77777778),
- errorMsg.length, total.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
- builder.setMessage(errorMessage)
- } else {
- builder.setMessage(R.string.download_error_error_unknown)
- }
- builder.setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> dialog.cancel() }
- if (intent.getBooleanExtra(ARG_WAS_MANUAL_URL, false)) {
- builder.setNeutralButton(R.string.edit_url_menu) { dialog: DialogInterface?, which: Int -> editUrl() }
- }
- builder.setOnCancelListener { dialog: DialogInterface? ->
- setResult(RESULT_ERROR)
- finish()
- }
- if (dialog != null && dialog!!.isShowing) {
- dialog!!.dismiss()
- }
- dialog = builder.show()
- }
- }
-
- private fun editUrl() {
- val builder = MaterialAlertDialogBuilder(this)
- builder.setTitle(R.string.edit_url_menu)
- val dialogBinding = EditTextDialogBinding.inflate(layoutInflater)
- if (downloader != null) {
- dialogBinding.urlEditText.setText(downloader!!.downloadRequest.source)
- }
- builder.setView(dialogBinding.root)
- builder.setPositiveButton(R.string.confirm_label) { dialog: DialogInterface?, which: Int ->
- setLoadingLayout()
- lookupUrlAndDownload(dialogBinding.urlEditText.text.toString())
- }
- builder.setNegativeButton(R.string.cancel_label) { dialog1: DialogInterface, which: Int -> dialog1.cancel() }
- builder.setOnCancelListener { dialog1: DialogInterface? ->
- setResult(RESULT_ERROR)
- finish()
- }
- builder.show()
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun playbackStateChanged(event: PlayerStatusEvent?) {
- val isPlayingPreview = currentlyPlayingMediaType == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA.toLong()
- viewBinding.stopPreviewButton.visibility = if (isPlayingPreview) View.VISIBLE else View.GONE
- }
-
- /**
- *
- * @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found).
- */
- private fun showFeedDiscoveryDialog(feedFile: File, baseUrl: String): Boolean {
- val fd = FeedDiscoverer()
- val urlsMap: Map
- try {
- urlsMap = fd.findLinks(feedFile, baseUrl)
- if (urlsMap.isEmpty()) {
- return false
- }
- } catch (e: IOException) {
- e.printStackTrace()
- return false
- }
-
- if (isPaused || isFinishing) {
- return false
- }
-
- val titles: MutableList = ArrayList()
-
- val urls: List = ArrayList(urlsMap.keys)
- for (url in urls) {
- titles.add(urlsMap[url])
- }
-
- if (urls.size == 1) {
- // Skip dialog and display the item directly
- resetIntent(urls[0])
- startFeedDownload(urls[0])
- return true
- }
-
- val adapter = ArrayAdapter(this@OnlineFeedViewActivity,
- R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles)
- val onClickListener = DialogInterface.OnClickListener { dialog: DialogInterface, which: Int ->
- val selectedUrl = urls[which]
- dialog.dismiss()
- resetIntent(selectedUrl)
- startFeedDownload(selectedUrl)
- }
-
- val ab = MaterialAlertDialogBuilder(this@OnlineFeedViewActivity)
- .setTitle(R.string.feeds_label)
- .setCancelable(true)
- .setOnCancelListener { dialog: DialogInterface? -> finish() }
- .setAdapter(adapter, onClickListener)
-
- runOnUiThread {
- if (dialog != null && dialog!!.isShowing) {
- dialog!!.dismiss()
- }
- dialog = ab.show()
- }
- return true
- }
-
- private inner class FeedViewAuthenticationDialog(context: Context?, titleRes: Int, private val feedUrl: String) :
- AuthenticationDialog(context, titleRes, true, username, password) {
- override fun onCancelled() {
- super.onCancelled()
- finish()
- }
-
- override fun onConfirmed(username: String, password: String) {
- this@OnlineFeedViewActivity.username = username
- this@OnlineFeedViewActivity.password = password
- startFeedDownload(feedUrl)
- }
- }
-
- companion object {
- const val ARG_FEEDURL: String = "arg.feedurl"
- const val ARG_WAS_MANUAL_URL: String = "manual_url"
- private const val RESULT_ERROR = 2
- private const val TAG = "OnlineFeedViewActivity"
- private const val PREFS = "OnlineFeedViewActivityPreferences"
- private const val PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload"
- private const val DESCRIPTION_MAX_LINES_COLLAPSED = 4
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/OpmlImportActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/OpmlImportActivity.kt
deleted file mode 100644
index 8ab8d028..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/OpmlImportActivity.kt
+++ /dev/null
@@ -1,270 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.Manifest
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.style.ForegroundColorSpan
-import android.util.Log
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.widget.AdapterView
-import android.widget.AdapterView.OnItemClickListener
-import android.widget.ArrayAdapter
-import android.widget.ListView
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ActivityCompat
-import androidx.media3.common.util.UnstableApi
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.export.opml.OpmlElement
-import ac.mdiq.podcini.core.export.opml.OpmlReader
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTheme
-import ac.mdiq.podcini.core.storage.DBTasks
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager.runOnce
-import ac.mdiq.podcini.databinding.OpmlSelectionBinding
-import ac.mdiq.podcini.model.feed.Feed
-import io.reactivex.Completable
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.schedulers.Schedulers
-import org.apache.commons.io.input.BOMInputStream
-import java.io.InputStreamReader
-import java.io.Reader
-
-/**
- * Activity for Opml Import.
- */
-class OpmlImportActivity : AppCompatActivity() {
- private var uri: Uri? = null
- private lateinit var viewBinding: OpmlSelectionBinding
- private lateinit var selectAll: MenuItem
- private lateinit var deselectAll: MenuItem
-
- private var listAdapter: ArrayAdapter? = null
- private var readElements: ArrayList? = null
-
- @UnstableApi override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTheme(this))
- super.onCreate(savedInstanceState)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- viewBinding = OpmlSelectionBinding.inflate(layoutInflater)
- setContentView(viewBinding.root)
-
- viewBinding.feedlist.choiceMode = ListView.CHOICE_MODE_MULTIPLE
- viewBinding.feedlist.onItemClickListener =
- OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
- val checked = viewBinding.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)
- }
- }
- }
- viewBinding.butCancel.setOnClickListener { v: View? ->
- setResult(RESULT_CANCELED)
- finish()
- }
- viewBinding.butConfirm.setOnClickListener { v: View? ->
- viewBinding.progressBar.visibility = View.VISIBLE
- Completable.fromAction {
- val checked = viewBinding.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(
- {
- viewBinding.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()
- viewBinding.progressBar.visibility = View.GONE
- Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
- })
- }
-
- var uri = intent.data
- if (uri != null && uri.toString().startsWith("/")) {
- uri = Uri.parse("file://$uri")
- } else {
- val extraText = intent.getStringExtra(Intent.EXTRA_TEXT)
- if (extraText != null) {
- uri = Uri.parse(extraText)
- }
- }
- importUri(uri)
- }
-
- fun importUri(uri: Uri?) {
- if (uri == null) {
- MaterialAlertDialogBuilder(this)
- .setMessage(R.string.opml_import_error_no_file)
- .setPositiveButton(android.R.string.ok, null)
- .show()
- return
- }
- this.uri = uri
- startImport()
- }
-
- private val titleList: List
- get() {
- val result: MutableList = ArrayList()
- if (!readElements.isNullOrEmpty()) {
- for (element in readElements!!) {
- if (element.text != null) result.add(element.text!!)
- }
- }
- return result
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- super.onCreateOptionsMenu(menu)
- val inflater = menuInflater
- inflater.inflate(R.menu.opml_selection_options, menu)
- selectAll = menu.findItem(R.id.select_all_item)
- deselectAll = menu.findItem(R.id.deselect_all_item)
- deselectAll.setVisible(false)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- val itemId = item.itemId
- when (itemId) {
- R.id.select_all_item -> {
- selectAll.setVisible(false)
- selectAllItems(true)
- deselectAll.setVisible(true)
- return true
- }
- R.id.deselect_all_item -> {
- deselectAll.setVisible(false)
- selectAllItems(false)
- selectAll.setVisible(true)
- return true
- }
- android.R.id.home -> {
- finish()
- }
- }
- return false
- }
-
- private fun selectAllItems(b: Boolean) {
- for (i in 0 until viewBinding.feedlist.count) {
- viewBinding.feedlist.setItemChecked(i, b)
- }
- }
-
- private fun requestPermission() {
- requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
- }
-
- private val requestPermissionLauncher =
- registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
- if (isGranted) {
- startImport()
- } else {
- MaterialAlertDialogBuilder(this)
- .setMessage(R.string.opml_import_ask_read_permission)
- .setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int -> requestPermission() }
- .setNegativeButton(R.string.cancel_label) { dialog: DialogInterface?, which: Int -> finish() }
- .show()
- }
- }
-
- /** Starts the import process. */
- private fun startImport() {
- viewBinding.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? ->
- viewBinding.progressBar.visibility = View.GONE
- Log.d(TAG, "Parsing was successful")
- readElements = result
- listAdapter = ArrayAdapter(this@OpmlImportActivity,
- android.R.layout.simple_list_item_multiple_choice,
- titleList)
- viewBinding.feedlist.adapter = listAdapter
- }, { e: Throwable ->
- Log.d(TAG, Log.getStackTraceString(e))
- val message = if (e.message == null) "" else e.message!!
- if (message.lowercase().contains("permission")
- && Build.VERSION.SDK_INT >= 23) {
- val permission = ActivityCompat.checkSelfPermission(this,
- Manifest.permission.READ_EXTERNAL_STORAGE)
- if (permission != PackageManager.PERMISSION_GRANTED) {
- requestPermission()
- return@subscribe
- }
- }
- viewBinding.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) { dialog: DialogInterface?, which: Int -> finish() }
- alert.show()
- })
- }
-
- companion object {
- private const val TAG = "OpmlImportBaseActivity"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/PlaybackSpeedDialogActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/PlaybackSpeedDialogActivity.kt
deleted file mode 100644
index 61007cfb..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/PlaybackSpeedDialogActivity.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.content.DialogInterface
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTranslucentTheme
-import ac.mdiq.podcini.dialog.VariableSpeedDialog
-
-class PlaybackSpeedDialogActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTranslucentTheme(this))
- super.onCreate(savedInstanceState)
- val speedDialog: VariableSpeedDialog = InnerVariableSpeedDialog()
- speedDialog.show(supportFragmentManager, null)
- }
-
- class InnerVariableSpeedDialog : VariableSpeedDialog() {
- override fun onDismiss(dialog: DialogInterface) {
- super.onDismiss(dialog)
- requireActivity().finish()
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/PreferenceActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/PreferenceActivity.kt
deleted file mode 100644
index f8dd98a3..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/PreferenceActivity.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.provider.Settings
-import android.util.Log
-import android.view.MenuItem
-import android.view.View
-import android.view.inputmethod.InputMethodManager
-import androidx.appcompat.app.AppCompatActivity
-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 ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTheme
-import ac.mdiq.podcini.databinding.SettingsActivityBinding
-import ac.mdiq.podcini.event.MessageEvent
-import ac.mdiq.podcini.fragment.preferences.*
-import ac.mdiq.podcini.fragment.preferences.synchronization.SynchronizationPreferencesFragment
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
-
-/**
- * PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
- * PreferenceController.
- */
-class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
- private lateinit var binding: SettingsActivityBinding
-
- @SuppressLint("CommitTransaction")
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTheme(this))
- super.onCreate(savedInstanceState)
-
- val ab = supportActionBar
- ab?.setDisplayHomeAsUpEnabled(true)
-
- binding = SettingsActivityBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- if (supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) == null) {
- supportFragmentManager.beginTransaction()
- .replace(binding.settingsContainer.id, MainPreferencesFragment(), FRAGMENT_TAG)
- .commit()
- }
- val intent = intent
- if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) {
- openScreen(R.xml.preferences_autodownload)
- }
- }
-
- private fun getPreferenceScreen(screen: Int): PreferenceFragmentCompat? {
- var prefFragment: PreferenceFragmentCompat? = null
-
- when (screen) {
- R.xml.preferences_user_interface -> {
- prefFragment = UserInterfacePreferencesFragment()
- }
- R.xml.preferences_downloads -> {
- prefFragment = DownloadsPreferencesFragment()
- }
- R.xml.preferences_import_export -> {
- prefFragment = ImportExportPreferencesFragment()
- }
- R.xml.preferences_autodownload -> {
- prefFragment = AutoDownloadPreferencesFragment()
- }
- R.xml.preferences_synchronization -> {
- prefFragment = SynchronizationPreferencesFragment()
- }
- R.xml.preferences_playback -> {
- prefFragment = PlaybackPreferencesFragment()
- }
- R.xml.preferences_notifications -> {
- prefFragment = NotificationPreferencesFragment()
- }
- R.xml.preferences_swipe -> {
- prefFragment = SwipePreferencesFragment()
- }
- }
- return prefFragment
- }
-
- @SuppressLint("CommitTransaction")
- fun openScreen(screen: Int): PreferenceFragmentCompat? {
- val fragment = getPreferenceScreen(screen)
- if (screen == R.xml.preferences_notifications && Build.VERSION.SDK_INT >= 26) {
- val intent = Intent()
- intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
- startActivity(intent)
- } else {
- supportFragmentManager.beginTransaction()
- .replace(binding.settingsContainer.id, fragment!!)
- .addToBackStack(getString(getTitleOfPage(screen)))
- .commit()
- }
-
-
- return fragment
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) {
- if (supportFragmentManager.backStackEntryCount == 0) {
- finish()
- } else {
- val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
- var view = currentFocus
- //If no view currently has focus, create a new one, just so we can grab a window token from it
- if (view == null) {
- view = View(this)
- }
- imm.hideSoftInputFromWindow(view.windowToken, 0)
- supportFragmentManager.popBackStack()
- }
- return true
- }
- return false
- }
-
- override fun onSearchResultClicked(result: SearchPreferenceResult) {
- when (val screen = result.resourceFile) {
- R.xml.feed_settings -> {
- val builder = MaterialAlertDialogBuilder(this)
- builder.setTitle(R.string.feed_settings_label)
- builder.setMessage(R.string.pref_feed_settings_dialog_msg)
- builder.setPositiveButton(android.R.string.ok, null)
- builder.show()
- }
- R.xml.preferences_notifications -> {
- openScreen(screen)
- }
- else -> {
- val fragment = openScreen(result.resourceFile)
- result.highlight(fragment)
- }
- }
- }
-
- override fun onStart() {
- super.onStart()
- EventBus.getDefault().register(this)
- }
-
- override fun onStop() {
- super.onStop()
- EventBus.getDefault().unregister(this)
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onEventMainThread(event: MessageEvent) {
- Log.d(FRAGMENT_TAG, "onEvent($event)")
- val s = Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG)
- if (event.action != null) {
- s.setAction(event.actionText) { v: View? -> event.action!!.accept(this) }
- }
- s.show()
- }
-
- companion object {
- private const val FRAGMENT_TAG = "tag_preferences"
- const val OPEN_AUTO_DOWNLOAD_SETTINGS: String = "OpenAutoDownloadSettings"
- @JvmStatic
- fun getTitleOfPage(preferences: Int): Int {
- when (preferences) {
- R.xml.preferences_downloads -> {
- return R.string.downloads_pref
- }
- R.xml.preferences_autodownload -> {
- return R.string.pref_automatic_download_title
- }
- R.xml.preferences_playback -> {
- return R.string.playback_pref
- }
- R.xml.preferences_import_export -> {
- return R.string.import_export_pref
- }
- R.xml.preferences_user_interface -> {
- return R.string.user_interface_label
- }
- R.xml.preferences_synchronization -> {
- return R.string.synchronization_pref
- }
- R.xml.preferences_notifications -> {
- return R.string.notification_pref_fragment
- }
- R.xml.feed_settings -> {
- return R.string.feed_settings_label
- }
- R.xml.preferences_swipe -> {
- return R.string.swipeactions_label
- }
- else -> return R.string.settings_label
- }
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/SelectSubscriptionActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/SelectSubscriptionActivity.kt
deleted file mode 100644
index d88a1ab7..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/SelectSubscriptionActivity.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity.Companion.EXTRA_FEED_ID
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.core.storage.NavDrawerData
-import ac.mdiq.podcini.databinding.SubscriptionSelectionActivityBinding
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import android.app.Activity
-import android.content.Intent
-import android.graphics.Bitmap
-import android.os.Bundle
-import android.util.Log
-import android.view.View
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.ListView
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.pm.ShortcutInfoCompat
-import androidx.core.content.pm.ShortcutManagerCompat
-import androidx.core.graphics.drawable.IconCompat
-import androidx.media3.common.util.UnstableApi
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.DataSource
-import com.bumptech.glide.load.engine.GlideException
-import com.bumptech.glide.request.RequestListener
-import com.bumptech.glide.request.RequestOptions
-import com.bumptech.glide.request.target.Target
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
-import io.reactivex.schedulers.Schedulers
-
-class SelectSubscriptionActivity : AppCompatActivity() {
- private var disposable: Disposable? = null
-
- @Volatile
- private var listItems: List? = null
-
- private lateinit var viewBinding: SubscriptionSelectionActivityBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(ThemeSwitcher.getTranslucentTheme(this))
- super.onCreate(savedInstanceState)
-
- viewBinding = SubscriptionSelectionActivityBinding.inflate(layoutInflater)
- setContentView(viewBinding.root)
- setSupportActionBar(viewBinding.toolbar)
- setTitle(R.string.shortcut_select_subscription)
-
- viewBinding.transparentBackground.setOnClickListener { v: View? -> finish() }
- viewBinding.card.setOnClickListener(null)
-
- loadSubscriptions()
-
- val checkedPosition = arrayOfNulls(1)
- viewBinding.list.choiceMode = ListView.CHOICE_MODE_SINGLE
- viewBinding.list.onItemClickListener =
- AdapterView.OnItemClickListener { listView: AdapterView<*>?, view1: View?, position: Int, rowId: Long ->
- checkedPosition[0] = position
- }
- viewBinding.shortcutBtn.setOnClickListener { view: View? ->
- if (checkedPosition[0] != null && Intent.ACTION_CREATE_SHORTCUT == intent.action) {
- getBitmapFromUrl(listItems!![checkedPosition[0]!!])
- }
- }
- }
-
- fun getFeedItems(items: List, result: MutableList): List {
- for (item in items) {
- if (item == null) continue
- if (item.type == NavDrawerData.DrawerItem.Type.TAG) {
- getFeedItems((item as NavDrawerData.TagDrawerItem).children, result)
- } else {
- val feed: Feed = (item as NavDrawerData.FeedDrawerItem).feed
- if (!result.contains(feed)) {
- result.add(feed)
- }
- }
- }
- return result
- }
-
- @UnstableApi private fun addShortcut(feed: Feed, bitmap: Bitmap?) {
- val intent = Intent(this, MainActivity::class.java)
- intent.setAction(Intent.ACTION_MAIN)
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
- intent.putExtra(EXTRA_FEED_ID, feed.id)
- val id = "subscription-" + feed.id
-
- val icon: IconCompat = if (bitmap != null) {
- IconCompat.createWithAdaptiveBitmap(bitmap)
- } else {
- IconCompat.createWithResource(this, R.drawable.ic_subscriptions_shortcut)
- }
-
- val shortcut: ShortcutInfoCompat = ShortcutInfoCompat.Builder(this, id)
- .setShortLabel(feed.title?:"")
- .setLongLabel(feed.feedTitle?:"")
- .setIntent(intent)
- .setIcon(icon)
- .build()
-
- setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
- finish()
- }
-
- private fun getBitmapFromUrl(feed: Feed) {
- val iconSize = (128 * resources.displayMetrics.density).toInt()
- Glide.with(this)
- .asBitmap()
- .load(feed.imageUrl)
- .apply(RequestOptions.overrideOf(iconSize, iconSize))
- .listener(object : RequestListener {
- @UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?,
- target: Target, isFirstResource: Boolean
- ): Boolean {
- addShortcut(feed, null)
- return true
- }
-
- @UnstableApi override fun onResourceReady(resource: Bitmap, model: Any,
- target: Target, dataSource: DataSource, isFirstResource: Boolean
- ): Boolean {
- addShortcut(feed, resource)
- return true
- }
- }).submit()
- }
-
- private fun loadSubscriptions() {
- disposable?.dispose()
-
- disposable = Observable.fromCallable {
- val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
- getFeedItems(data.items, ArrayList())
- }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { result: List ->
- listItems = result
- val titles = ArrayList()
- for (feed in result) {
- if (feed.title != null) titles.add(feed.title!!)
- }
- val adapter: ArrayAdapter = ArrayAdapter(this,
- R.layout.simple_list_item_multiple_choice_on_start, titles)
- viewBinding.list.adapter = adapter
- }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
- }
-
- companion object {
- private const val TAG = "SelectSubscription"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/SplashActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/SplashActivity.kt
deleted file mode 100644
index 17262236..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/SplashActivity.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import android.widget.Toast
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.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
-
-/**
- * Shows the Podcini logo while waiting for the main activity to start.
- */
-@SuppressLint("CustomSplashScreen")
-class SplashActivity : Activity() {
- @UnstableApi override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val content = findViewById(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()
- }
- .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()
- })
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/VideoplayerActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/VideoplayerActivity.kt
deleted file mode 100644
index 16d7cf25..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/VideoplayerActivity.kt
+++ /dev/null
@@ -1,787 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.isCasting
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.core.util.Converter.getDurationStringLong
-import ac.mdiq.podcini.core.util.FeedItemUtil.getLinkWithFallback
-import ac.mdiq.podcini.core.util.IntentUtils.openInBrowser
-import ac.mdiq.podcini.core.util.ShareUtils.hasLinkToShare
-import ac.mdiq.podcini.core.util.TimeSpeedConverter
-import ac.mdiq.podcini.core.util.gui.PictureInPictureUtil
-import ac.mdiq.podcini.core.util.playback.PlaybackController
-import ac.mdiq.podcini.databinding.VideoplayerActivityBinding
-import ac.mdiq.podcini.dialog.*
-import ac.mdiq.podcini.event.MessageEvent
-import ac.mdiq.podcini.event.PlayerErrorEvent
-import ac.mdiq.podcini.event.playback.BufferUpdateEvent
-import ac.mdiq.podcini.event.playback.PlaybackPositionEvent
-import ac.mdiq.podcini.event.playback.PlaybackServiceEvent
-import ac.mdiq.podcini.event.playback.SleepTimerUpdatedEvent
-import ac.mdiq.podcini.fragment.ChaptersFragment
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.feed.FeedMedia
-import ac.mdiq.podcini.model.playback.Playable
-import ac.mdiq.podcini.playback.base.PlayerStatus
-import ac.mdiq.podcini.playback.cast.CastEnabledActivity
-import ac.mdiq.podcini.storage.preferences.UserPreferences.fastForwardSecs
-import ac.mdiq.podcini.storage.preferences.UserPreferences.rewindSecs
-import ac.mdiq.podcini.storage.preferences.UserPreferences.setShowRemainTimeSetting
-import ac.mdiq.podcini.storage.preferences.UserPreferences.shouldShowRemainingTime
-import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
-import android.content.DialogInterface
-import android.content.Intent
-import android.graphics.PixelFormat
-import android.graphics.drawable.ColorDrawable
-import android.media.AudioManager
-import android.os.Build
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.*
-import android.view.View.OnTouchListener
-import android.view.animation.*
-import android.widget.EditText
-import android.widget.FrameLayout
-import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
-import androidx.interpolator.view.animation.FastOutSlowInInterpolator
-import androidx.media3.common.util.UnstableApi
-import com.bumptech.glide.Glide
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-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
-
-/**
- * Activity for playing video files.
- */
-@UnstableApi
-class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
-
- private lateinit var viewBinding: VideoplayerActivityBinding
-
- /**
- * True if video controls are currently visible.
- */
- private var videoControlsShowing = true
- private var videoSurfaceCreated = false
- private var destroyingDueToReload = false
- private var lastScreenTap: Long = 0
- private val videoControlsHider = Handler(Looper.getMainLooper())
- private var controller: PlaybackController? = null
- private var showTimeLeft = false
- private var isFavorite = false
- private var switchToAudioOnly = false
- private var disposable: Disposable? = null
- private var prog = 0f
-
- override fun onCreate(savedInstanceState: Bundle?) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
- // has to be called before setting layout content
- supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
- setTheme(R.style.Theme_Podcini_VideoPlayer)
- super.onCreate(savedInstanceState)
-
- Log.d(TAG, "onCreate()")
-
- window.setFormat(PixelFormat.TRANSPARENT)
- viewBinding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
- setContentView(viewBinding.root)
- setupView()
- supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000))
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
-
- @UnstableApi
- override fun onResume() {
- super.onResume()
- switchToAudioOnly = false
- if (isCasting) {
- val intent = getPlayerActivityIntent(this)
- if (intent.component!!.className != VideoplayerActivity::class.java.name) {
- destroyingDueToReload = true
- finish()
- startActivity(intent)
- }
- }
- }
-
- @UnstableApi
- override fun onStop() {
- controller?.release()
- controller = null // prevent leak
- disposable?.dispose()
-
- EventBus.getDefault().unregister(this)
- super.onStop()
- if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
- videoControlsHider.removeCallbacks(hideVideoControls)
- }
- // Controller released; we will not receive buffering updates
- viewBinding.progressBar.visibility = View.GONE
- }
-
- public override fun onUserLeaveHint() {
- if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
- compatEnterPictureInPicture()
- }
- }
-
- @UnstableApi
- override fun onStart() {
- super.onStart()
- controller = newPlaybackController()
- controller!!.init()
- loadMediaInfo()
- onPositionObserverUpdate()
- EventBus.getDefault().register(this)
- }
-
- @UnstableApi
- override fun onPause() {
- if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
- if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
- controller!!.pause()
- }
- }
- super.onPause()
- }
-
- override fun onTrimMemory(level: Int) {
- super.onTrimMemory(level)
- Glide.get(this).trimMemory(level)
- }
-
- override fun onLowMemory() {
- super.onLowMemory()
- Glide.get(this).clearMemory()
- }
-
- @UnstableApi
- private fun newPlaybackController(): PlaybackController {
- return object : PlaybackController(this@VideoplayerActivity) {
- override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
- viewBinding.playButton.setIsShowPlay(showPlay)
- if (showPlay) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- setupVideoAspectRatio()
- if (videoSurfaceCreated && controller != null) {
- Log.d(TAG, "Videosurface already created, setting videosurface now")
- controller!!.setVideoSurface(viewBinding.videoView.holder)
- }
- }
- }
-
- override fun loadMediaInfo() {
- this@VideoplayerActivity.loadMediaInfo()
- }
-
- override fun onPlaybackEnd() {
- finish()
- }
- }
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- @Suppress("unused")
- fun bufferUpdate(event: BufferUpdateEvent) {
- if (event.hasStarted()) {
- viewBinding.progressBar.visibility = View.VISIBLE
- } else if (event.hasEnded()) {
- viewBinding.progressBar.visibility = View.INVISIBLE
- } else {
- viewBinding.sbPosition.secondaryProgress = (event.progress * viewBinding.sbPosition.max).toInt()
- }
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- @Suppress("unused")
- fun sleepTimerUpdate(event: SleepTimerUpdatedEvent) {
- if (event.isCancelled || event.wasJustEnabled()) {
- supportInvalidateOptionsMenu()
- }
- }
-
- @UnstableApi
- private fun loadMediaInfo() {
- Log.d(TAG, "loadMediaInfo()")
- if (controller?.getMedia() == null) {
- return
- }
- if (controller!!.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) {
- Log.d(TAG, "Closing, no longer video")
- destroyingDueToReload = true
- finish()
- MainActivityStarter(this).withOpenPlayer().start()
- return
- }
- showTimeLeft = shouldShowRemainingTime()
- onPositionObserverUpdate()
- checkFavorite()
- val media = controller!!.getMedia()
- if (media != null) {
- supportActionBar!!.subtitle = media.getEpisodeTitle()
- supportActionBar!!.title = media.getFeedTitle()
- }
- }
-
- @UnstableApi
- private fun setupView() {
- showTimeLeft = shouldShowRemainingTime()
- Log.d("timeleft", if (showTimeLeft) "true" else "false")
- viewBinding.durationLabel.setOnClickListener { v: View? ->
- showTimeLeft = !showTimeLeft
- val media = controller?.getMedia() ?: return@setOnClickListener
-
- val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
- val length: String
- if (showTimeLeft) {
- val remainingTime = converter.convert(media.getDuration() - media.getPosition())
- length = "-" + getDurationStringLong(remainingTime)
- } else {
- val duration = converter.convert(media.getDuration())
- length = getDurationStringLong(duration)
- }
- viewBinding.durationLabel.text = length
-
- setShowRemainTimeSetting(showTimeLeft)
- Log.d("timeleft on click", if (showTimeLeft) "true" else "false")
- }
-
- viewBinding.sbPosition.setOnSeekBarChangeListener(this)
- viewBinding.rewindButton.setOnClickListener { v: View? -> onRewind() }
- viewBinding.rewindButton.setOnLongClickListener { v: View? ->
- SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
- SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
- true
- }
- viewBinding.playButton.setIsVideoScreen(true)
- viewBinding.playButton.setOnClickListener { v: View? -> onPlayPause() }
- viewBinding.fastForwardButton.setOnClickListener { v: View? -> onFastForward() }
- viewBinding.fastForwardButton.setOnLongClickListener { v: View? ->
- SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
- SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
- false
- }
- // To suppress touches directly below the slider
- viewBinding.bottomControlsContainer.setOnTouchListener { view: View?, motionEvent: MotionEvent? -> true }
- viewBinding.bottomControlsContainer.fitsSystemWindows = true
- viewBinding.videoView.holder.addCallback(surfaceHolderCallback)
- viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-
- setupVideoControlsToggler()
- window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN)
-
- viewBinding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
- viewBinding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
- viewBinding.videoView.setAvailableSize(
- viewBinding.videoPlayerContainer.width.toFloat(), viewBinding.videoPlayerContainer.height.toFloat())
- }
- }
-
- private val hideVideoControls = Runnable {
- if (videoControlsShowing) {
- Log.d(TAG, "Hiding video controls")
- supportActionBar?.hide()
- hideVideoControls(true)
- videoControlsShowing = false
- }
- }
-
- private val onVideoviewTouched = OnTouchListener { v: View, event: MotionEvent ->
- if (event.action != MotionEvent.ACTION_DOWN) {
- return@OnTouchListener false
- }
- if (PictureInPictureUtil.isInPictureInPictureMode(this)) {
- return@OnTouchListener true
- }
- videoControlsHider.removeCallbacks(hideVideoControls)
-
- if (System.currentTimeMillis() - lastScreenTap < 300) {
- if (event.x > v.measuredWidth / 2.0f) {
- onFastForward()
- showSkipAnimation(true)
- } else {
- onRewind()
- showSkipAnimation(false)
- }
- if (videoControlsShowing) {
- supportActionBar?.hide()
- hideVideoControls(false)
- videoControlsShowing = false
- }
- return@OnTouchListener true
- }
-
- toggleVideoControlsVisibility()
- if (videoControlsShowing) {
- setupVideoControlsToggler()
- }
-
- lastScreenTap = System.currentTimeMillis()
- true
- }
-
- private fun showSkipAnimation(isForward: Boolean) {
- val skipAnimation = AnimationSet(true)
- skipAnimation.addAnimation(ScaleAnimation(1f, 2f, 1f, 2f,
- Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f))
- skipAnimation.addAnimation(AlphaAnimation(1f, 0f))
- skipAnimation.fillAfter = false
- skipAnimation.duration = 800
-
- val params = viewBinding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
- if (isForward) {
- viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
- params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
- } else {
- viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
- params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
- }
-
- viewBinding.skipAnimationImage.visibility = View.VISIBLE
- viewBinding.skipAnimationImage.layoutParams = params
- viewBinding.skipAnimationImage.startAnimation(skipAnimation)
- skipAnimation.setAnimationListener(object : Animation.AnimationListener {
- override fun onAnimationStart(animation: Animation) {
- }
-
- override fun onAnimationEnd(animation: Animation) {
- viewBinding.skipAnimationImage.visibility = View.GONE
- }
-
- override fun onAnimationRepeat(animation: Animation) {
- }
- })
- }
-
- private fun setupVideoControlsToggler() {
- videoControlsHider.removeCallbacks(hideVideoControls)
- videoControlsHider.postDelayed(hideVideoControls, 2500)
- }
-
- @UnstableApi
- private fun setupVideoAspectRatio() {
- if (videoSurfaceCreated && controller != null) {
- val videoSize = controller!!.videoSize
- if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
- Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second)
- viewBinding.videoView.setVideoSize(videoSize.first, videoSize.second)
- } else {
- Log.e(TAG, "Could not determine video size")
- }
- }
- }
-
- private fun toggleVideoControlsVisibility() {
- if (videoControlsShowing) {
- supportActionBar?.hide()
- hideVideoControls(true)
- } else {
- supportActionBar?.show()
- showVideoControls()
- }
- videoControlsShowing = !videoControlsShowing
- }
-
- @UnstableApi
- fun onRewind() {
- if (controller == null) {
- return
- }
- val curr = controller!!.position
- controller!!.seekTo(curr - rewindSecs * 1000)
- setupVideoControlsToggler()
- }
-
- @UnstableApi
- fun onPlayPause() {
- if (controller == null) {
- return
- }
- controller!!.playPause()
- setupVideoControlsToggler()
- }
-
- @UnstableApi
- fun onFastForward() {
- if (controller == null) {
- return
- }
- val curr = controller!!.position
- controller!!.seekTo(curr + fastForwardSecs * 1000)
- setupVideoControlsToggler()
- }
-
- private val surfaceHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
- override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
- holder.setFixedSize(width, height)
- }
-
- @UnstableApi
- override fun surfaceCreated(holder: SurfaceHolder) {
- Log.d(TAG, "Videoview holder created")
- videoSurfaceCreated = true
- if (controller?.status == PlayerStatus.PLAYING) {
- controller!!.setVideoSurface(holder)
- }
- setupVideoAspectRatio()
- }
-
- override fun surfaceDestroyed(holder: SurfaceHolder) {
- Log.d(TAG, "Videosurface was destroyed")
- videoSurfaceCreated = false
- if (controller != null && !destroyingDueToReload && !switchToAudioOnly) {
- controller!!.notifyVideoSurfaceAbandoned()
- }
- }
- }
-
- private fun showVideoControls() {
- viewBinding.bottomControlsContainer.visibility = View.VISIBLE
- viewBinding.controlsContainer.visibility = View.VISIBLE
- val animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
- if (animation != null) {
- viewBinding.bottomControlsContainer.startAnimation(animation)
- viewBinding.controlsContainer.startAnimation(animation)
- }
- viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
- }
-
- private fun hideVideoControls(showAnimation: Boolean) {
- if (showAnimation) {
- val animation = AnimationUtils.loadAnimation(this, R.anim.fade_out)
- if (animation != null) {
- viewBinding.bottomControlsContainer.startAnimation(animation)
- viewBinding.controlsContainer.startAnimation(animation)
- }
- }
- window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
- or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
- viewBinding.bottomControlsContainer.fitsSystemWindows = true
-
- viewBinding.bottomControlsContainer.visibility = View.GONE
- viewBinding.controlsContainer.visibility = View.GONE
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onEventMainThread(event: PlaybackPositionEvent?) {
- onPositionObserverUpdate()
- }
-
- @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) {
- Log.d(TAG, "onEvent($event)")
- val errorDialog = MaterialAlertDialogBuilder(this)
- errorDialog.setMessage(event.message)
- errorDialog.setPositiveButton(event.actionText) { dialog: DialogInterface?, which: Int ->
- event.action?.accept(this)
- }
-
- errorDialog.show()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- super.onCreateOptionsMenu(menu)
- requestCastButton(menu)
- val inflater = menuInflater
- inflater.inflate(R.menu.mediaplayer, menu)
- return true
- }
-
- @UnstableApi
- override fun onPrepareOptionsMenu(menu: Menu): Boolean {
- super.onPrepareOptionsMenu(menu)
- if (controller == null) {
- return false
- }
- val media = controller!!.getMedia()
- val isFeedMedia = (media is FeedMedia)
-
- menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia) // FeedMedia implies it belongs to a Feed
-
- val hasWebsiteLink = getWebsiteLinkWithFallback(media) != null
- menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink)
-
- val isItemAndHasLink = isFeedMedia && hasLinkToShare((media as FeedMedia).getItem())
- val isItemHasDownloadLink = isFeedMedia && (media as FeedMedia?)?.download_url != null
- menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
-
- menu.findItem(R.id.add_to_favorites_item).setVisible(false)
- menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
- if (isFeedMedia) {
- menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite)
- menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite)
- }
-
- menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller!!.sleepTimerActive())
- menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller!!.sleepTimerActive())
-
- menu.findItem(R.id.player_switch_to_audio_only).setVisible(true)
-
- menu.findItem(R.id.audio_controls).setVisible(controller!!.audioTracks.size >= 2)
- menu.findItem(R.id.playback_speed).setVisible(true)
- menu.findItem(R.id.player_show_chapters).setVisible(true)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // some options option requires FeedItem
- when {
- item.itemId == R.id.player_switch_to_audio_only -> {
- switchToAudioOnly = true
- finish()
- return true
- }
- item.itemId == android.R.id.home -> {
- val intent = Intent(this@VideoplayerActivity, MainActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(intent)
- finish()
- return true
- }
- item.itemId == R.id.player_show_chapters -> {
- ChaptersFragment().show(supportFragmentManager, ChaptersFragment.TAG)
- return true
- }
- controller == null -> {
- return false
- }
- else -> {
- val media = controller?.getMedia() ?: return false
- val feedItem = getFeedItem(media) // some options option requires FeedItem
- if (item.itemId == R.id.add_to_favorites_item && feedItem != null) {
- DBWriter.addFavoriteItem(feedItem)
- isFavorite = true
- invalidateOptionsMenu()
- } else if (item.itemId == R.id.remove_from_favorites_item && feedItem != null) {
- DBWriter.removeFavoriteItem(feedItem)
- isFavorite = false
- invalidateOptionsMenu()
- } else if (item.itemId == R.id.disable_sleeptimer_item
- || item.itemId == R.id.set_sleeptimer_item) {
- SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
- } else if (item.itemId == R.id.audio_controls) {
- val dialog = PlaybackControlsDialog.newInstance()
- dialog.show(supportFragmentManager, "playback_controls")
- } else if (item.itemId == R.id.open_feed_item && feedItem != null) {
- val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId)
- startActivity(intent)
- } else if (item.itemId == R.id.visit_website_item) {
- val url = getWebsiteLinkWithFallback(media)
- if (url != null) openInBrowser(this@VideoplayerActivity, url)
- } else if (item.itemId == R.id.share_item && feedItem != null) {
- val shareDialog = ShareDialog.newInstance(feedItem)
- shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
- } else if (item.itemId == R.id.playback_speed) {
- VariableSpeedDialog().show(supportFragmentManager, null)
- } else {
- return false
- }
- return true
- }
- }
- }
-
- fun onPositionObserverUpdate() {
- if (controller == null) {
- return
- }
-
- val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
- val currentPosition = converter.convert(controller!!.position)
- val duration = converter.convert(controller!!.duration)
- val remainingTime = converter.convert(
- controller!!.duration - controller!!.position)
- // Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
- if (currentPosition == Playable.INVALID_TIME
- || duration == Playable.INVALID_TIME) {
- Log.w(TAG, "Could not react to position observer update because of invalid time")
- return
- }
- viewBinding.positionLabel.text = getDurationStringLong(currentPosition)
- if (showTimeLeft) {
- viewBinding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
- } else {
- viewBinding.durationLabel.text = getDurationStringLong(duration)
- }
- updateProgressbarPosition(currentPosition, duration)
- }
-
- private fun updateProgressbarPosition(position: Int, duration: Int) {
- Log.d(TAG, "updateProgressbarPosition($position, $duration)")
- val progress = (position.toFloat()) / duration
- viewBinding.sbPosition.progress = (progress * viewBinding.sbPosition.max).toInt()
- }
-
- override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
- if (controller == null) {
- return
- }
- if (fromUser) {
- prog = progress / (seekBar.max.toFloat())
- val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
- val position = converter.convert((prog * controller!!.duration).toInt())
- viewBinding.seekPositionLabel.text = getDurationStringLong(position)
- }
- }
-
- override fun onStartTrackingTouch(seekBar: SeekBar) {
- viewBinding.seekCardView.scaleX = .8f
- viewBinding.seekCardView.scaleY = .8f
- viewBinding.seekCardView.animate()
- .setInterpolator(FastOutSlowInInterpolator())
- .alpha(1f).scaleX(1f).scaleY(1f)
- .setDuration(200)
- .start()
- videoControlsHider.removeCallbacks(hideVideoControls)
- }
-
- override fun onStopTrackingTouch(seekBar: SeekBar) {
- if (controller != null) {
- controller!!.seekTo((prog * controller!!.duration).toInt())
- }
- viewBinding.seekCardView.scaleX = 1f
- viewBinding.seekCardView.scaleY = 1f
- viewBinding.seekCardView.animate()
- .setInterpolator(FastOutSlowInInterpolator())
- .alpha(0f).scaleX(.8f).scaleY(.8f)
- .setDuration(200)
- .start()
- setupVideoControlsToggler()
- }
-
- private fun checkFavorite() {
- val feedItem = getFeedItem(controller?.getMedia()) ?: return
- disposable?.dispose()
-
- disposable = Observable.fromCallable { DBReader.getFeedItem(feedItem.id) }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { item: FeedItem? ->
- if (item != null) {
- val isFav = item.isTagged(FeedItem.TAG_FAVORITE)
- if (isFavorite != isFav) {
- isFavorite = isFav
- invalidateOptionsMenu()
- }
- }
- }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
- }
-
- private fun compatEnterPictureInPicture() {
- if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- supportActionBar?.hide()
- hideVideoControls(false)
- enterPictureInPictureMode()
- }
- }
-
- //Hardware keyboard support
- override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
- val currentFocus = currentFocus
- if (currentFocus is EditText) {
- return super.onKeyUp(keyCode, event)
- }
-
- val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
-
- when (keyCode) {
- KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE -> {
- onPlayPause()
- toggleVideoControlsVisibility()
- return true
- }
- KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_COMMA -> {
- onRewind()
- showSkipAnimation(false)
- return true
- }
- KeyEvent.KEYCODE_K, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_PERIOD -> {
- onFastForward()
- showSkipAnimation(true)
- return true
- }
- KeyEvent.KEYCODE_F, KeyEvent.KEYCODE_ESCAPE -> {
- //Exit fullscreen mode
- onBackPressed()
- return true
- }
- KeyEvent.KEYCODE_I -> {
- compatEnterPictureInPicture()
- return true
- }
- KeyEvent.KEYCODE_PLUS, KeyEvent.KEYCODE_W -> {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI)
- return true
- }
- KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_S -> {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI)
- return true
- }
- KeyEvent.KEYCODE_M -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_TOGGLE_MUTE, AudioManager.FLAG_SHOW_UI)
- return true
- }
- }
- //Go to x% of video:
- if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
- controller?.seekTo((0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller!!.duration).toInt())
- return true
- }
- return super.onKeyUp(keyCode, event)
- }
-
- companion object {
- private const val TAG = "VideoplayerActivity"
-
- private fun getWebsiteLinkWithFallback(media: Playable?): String? {
- if (media == null) {
- return null
- } else if (!media.getWebsiteLink().isNullOrBlank()) {
- return media.getWebsiteLink()
- } else if (media is FeedMedia) {
- return getLinkWithFallback(media.getItem())
- }
- return null
- }
-
- private fun getFeedItem(playable: Playable?): FeedItem? {
- return if (playable is FeedMedia) {
- playable.getItem()
- } else {
- null
- }
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/activity/WidgetConfigActivity.kt b/app/src/main/java/ac/mdiq/podcini/activity/WidgetConfigActivity.kt
deleted file mode 100644
index f135f6d3..00000000
--- a/app/src/main/java/ac/mdiq/podcini/activity/WidgetConfigActivity.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package ac.mdiq.podcini.activity
-
-import android.appwidget.AppWidgetManager
-import android.content.Intent
-import android.graphics.Color
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import android.widget.CheckBox
-import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
-import android.widget.TextView
-import androidx.appcompat.app.AppCompatActivity
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.ThemeSwitcher.getTheme
-import ac.mdiq.podcini.core.receiver.PlayerWidget
-import ac.mdiq.podcini.core.widget.WidgetUpdaterWorker
-
-class WidgetConfigActivity : AppCompatActivity() {
- private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
-
- private lateinit var widgetPreview: View
- private lateinit var opacitySeekBar: SeekBar
- private lateinit var opacityTextView: TextView
- private lateinit var ckPlaybackSpeed: CheckBox
- private lateinit var ckRewind: CheckBox
- private lateinit var ckFastForward: CheckBox
- private lateinit var ckSkip: CheckBox
-
- override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(getTheme(this))
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_widget_config)
-
- val configIntent = intent
- val extras = configIntent.extras
- if (extras != null) {
- appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID)
- }
-
- val resultValue = Intent()
- resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
- setResult(RESULT_CANCELED, resultValue)
- if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
- finish()
- }
-
- opacityTextView = findViewById(R.id.widget_opacity_textView)
- opacitySeekBar = findViewById(R.id.widget_opacity_seekBar)
- widgetPreview = findViewById(R.id.widgetLayout)
- findViewById(R.id.butConfirm).setOnClickListener { v: View? -> confirmCreateWidget() }
- opacitySeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
- override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
- opacityTextView.text = seekBar.progress.toString() + "%"
- val color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
- widgetPreview.setBackgroundColor(color)
- }
-
- override fun onStartTrackingTouch(seekBar: SeekBar) {
- }
-
- override fun onStopTrackingTouch(seekBar: SeekBar) {
- }
- })
-
- widgetPreview.findViewById(R.id.txtNoPlaying).visibility = View.GONE
- val title = widgetPreview.findViewById(R.id.txtvTitle)
- title.visibility = View.VISIBLE
- title.setText(R.string.app_name)
- val progress = widgetPreview.findViewById(R.id.txtvProgress)
- progress.visibility = View.VISIBLE
- progress.setText(R.string.position_default_label)
-
- ckPlaybackSpeed = findViewById(R.id.ckPlaybackSpeed)
- ckPlaybackSpeed.setOnClickListener { v: View? -> displayPreviewPanel() }
- ckRewind = findViewById(R.id.ckRewind)
- ckRewind.setOnClickListener { v: View? -> displayPreviewPanel() }
- ckFastForward = findViewById(R.id.ckFastForward)
- ckFastForward.setOnClickListener { v: View? -> displayPreviewPanel() }
- ckSkip = findViewById(R.id.ckSkip)
- ckSkip.setOnClickListener { v: View? -> displayPreviewPanel() }
-
- setInitialState()
- }
-
- private fun setInitialState() {
- val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
- ckPlaybackSpeed.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, false)
- ckRewind.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, false)
- ckFastForward.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, false)
- ckSkip.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, false)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- val color = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, PlayerWidget.DEFAULT_COLOR)
- val opacity = Color.alpha(color) * 100 / 0xFF
-
- opacitySeekBar.setProgress(opacity, false)
- }
- displayPreviewPanel()
- }
-
- private fun displayPreviewPanel() {
- val showExtendedPreview =
- ckPlaybackSpeed.isChecked || ckRewind.isChecked || ckFastForward.isChecked || ckSkip.isChecked
- widgetPreview.findViewById(R.id.extendedButtonsContainer).visibility =
- if (showExtendedPreview) View.VISIBLE else View.GONE
- widgetPreview.findViewById(R.id.butPlay).visibility =
- if (showExtendedPreview) View.GONE else View.VISIBLE
- widgetPreview.findViewById(R.id.butPlaybackSpeed).visibility =
- if (ckPlaybackSpeed.isChecked) View.VISIBLE else View.GONE
- widgetPreview.findViewById(R.id.butFastForward).visibility =
- if (ckFastForward.isChecked) View.VISIBLE else View.GONE
- widgetPreview.findViewById(R.id.butSkip).visibility =
- if (ckSkip.isChecked) View.VISIBLE else View.GONE
- widgetPreview.findViewById(R.id.butRew).visibility =
- if (ckRewind.isChecked) View.VISIBLE else View.GONE
- }
-
- private fun confirmCreateWidget() {
- val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
-
- val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
- val editor = prefs.edit()
- editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor)
- editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.isChecked)
- editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked)
- editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked)
- editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked)
- editor.apply()
-
- val resultValue = Intent()
- resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
- setResult(RESULT_OK, resultValue)
- finish()
- WidgetUpdaterWorker.enqueueWork(this)
- }
-
- private fun getColorWithAlpha(color: Int, opacity: Int): Int {
- return Math.round(0xFF * (0.01 * opacity)).toInt() * 0x1000000 + (color and 0xffffff)
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/ChaptersListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/ChaptersListAdapter.kt
deleted file mode 100644
index e342bde7..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/ChaptersListAdapter.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.content.Context
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.RecyclerView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.resource.bitmap.FitCenter
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners
-import com.bumptech.glide.request.RequestOptions
-import com.google.android.material.elevation.SurfaceColors
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.adapter.ChaptersListAdapter.ChapterHolder
-import ac.mdiq.podcini.core.util.Converter.getDurationStringLocalized
-import ac.mdiq.podcini.core.util.Converter.getDurationStringLong
-import ac.mdiq.podcini.core.util.IntentUtils.openInBrowser
-import ac.mdiq.podcini.model.feed.Chapter
-import ac.mdiq.podcini.model.feed.EmbeddedChapterImage
-import ac.mdiq.podcini.model.playback.Playable
-import ac.mdiq.podcini.ui.common.CircularProgressBar
-import kotlin.math.max
-import kotlin.math.min
-
-class ChaptersListAdapter(private val context: Context, private val callback: Callback?) : RecyclerView.Adapter() {
-
- private var media: Playable? = null
- private var currentChapterIndex = -1
- private var currentChapterPosition: Long = -1
- private var hasImages = false
-
- fun setMedia(media: Playable) {
- this.media = media
- hasImages = false
- for (chapter in media.getChapters()) {
- if (!TextUtils.isEmpty(chapter.imageUrl)) {
- hasImages = true
- }
- }
- notifyDataSetChanged()
- }
-
- override fun onBindViewHolder(holder: ChapterHolder, position: Int) {
- val sc = getItem(position)?: return
- holder.title.text = sc.title
- holder.start.text = getDurationStringLong(sc.start.toInt())
- val duration = if (position + 1 < itemCount) {
- media!!.getChapters()[position + 1].start - sc.start
- } else {
- (media?.getDuration()?:0) - sc.start
- }
- holder.duration.text = context.getString(R.string.chapter_duration,
- getDurationStringLocalized(context, duration.toInt().toLong()))
-
- if (TextUtils.isEmpty(sc.link)) {
- holder.link.visibility = View.GONE
- } else {
- holder.link.visibility = View.VISIBLE
- holder.link.text = sc.link
- holder.link.setOnClickListener { v: View? ->
- if (sc.link!=null) openInBrowser(context, sc.link!!)
- }
- }
- holder.secondaryActionIcon.setImageResource(R.drawable.ic_play_48dp)
- holder.secondaryActionButton.contentDescription = context.getString(R.string.play_chapter)
- holder.secondaryActionButton.setOnClickListener { v: View? ->
- callback?.onPlayChapterButtonClicked(position)
- }
-
- if (position == currentChapterIndex) {
- val density = context.resources.displayMetrics.density
- holder.itemView.setBackgroundColor(SurfaceColors.getColorForElevation(context, 32 * density))
- var progress = ((currentChapterPosition - sc.start).toFloat()) / duration
- progress = max(progress.toDouble(), CircularProgressBar.MINIMUM_PERCENTAGE.toDouble()).toFloat()
- progress = min(progress.toDouble(), CircularProgressBar.MAXIMUM_PERCENTAGE.toDouble()).toFloat()
- holder.progressBar.setPercentage(progress, position)
- holder.secondaryActionIcon.setImageResource(R.drawable.ic_replay)
- } else {
- holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent))
- holder.progressBar.setPercentage(0f, null)
- }
-
- if (hasImages) {
- holder.image.visibility = View.VISIBLE
- if (TextUtils.isEmpty(sc.imageUrl)) {
- Glide.with(context).clear(holder.image)
- } else {
- if (media != null) Glide.with(context)
- .load(EmbeddedChapterImage.getModelFor(media!!, position))
- .apply(RequestOptions()
- .dontAnimate()
- .transform(FitCenter(), RoundedCorners((4 * context.resources.displayMetrics.density).toInt())))
- .into(holder.image)
- }
- } else {
- holder.image.visibility = View.GONE
- }
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChapterHolder {
- val inflater = LayoutInflater.from(context)
- return ChapterHolder(inflater.inflate(R.layout.simplechapter_item, parent, false))
- }
-
- override fun getItemCount(): Int {
- return media?.getChapters()?.size?:0
- }
-
- class ChapterHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val title: TextView = itemView.findViewById(R.id.txtvTitle)
- val start: TextView = itemView.findViewById(R.id.txtvStart)
- val link: TextView = itemView.findViewById(R.id.txtvLink)
- val duration: TextView = itemView.findViewById(R.id.txtvDuration)
- val image: ImageView = itemView.findViewById(R.id.imgvCover)
- val secondaryActionButton: View = itemView.findViewById(R.id.secondaryActionButton)
- val secondaryActionIcon: ImageView = itemView.findViewById(R.id.secondaryActionIcon)
- val progressBar: CircularProgressBar = itemView.findViewById(R.id.secondaryActionProgress)
- }
-
- fun notifyChapterChanged(newChapterIndex: Int) {
- currentChapterIndex = newChapterIndex
- currentChapterPosition = getItem(newChapterIndex)?.start?:0
- notifyDataSetChanged()
- }
-
- fun notifyTimeChanged(timeMs: Long) {
- currentChapterPosition = timeMs
- // Passing an argument prevents flickering.
- // See EpisodeItemListAdapter.notifyItemChangedCompat.
- notifyItemChanged(currentChapterIndex, "foo")
- }
-
- fun getItem(position: Int): Chapter? {
- val chapters = media?.getChapters()?: return null
- if (position < 0 || position >= chapters.size) return null
- return chapters[position]
- }
-
- interface Callback {
- fun onPlayChapterButtonClicked(position: Int)
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/CoverLoader.kt b/app/src/main/java/ac/mdiq/podcini/adapter/CoverLoader.kt
deleted file mode 100644
index 9394bc42..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/CoverLoader.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.graphics.drawable.Drawable
-import android.view.View
-import android.widget.ImageView
-import android.widget.TextView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.RequestBuilder
-import com.bumptech.glide.request.RequestOptions
-import com.bumptech.glide.request.target.CustomViewTarget
-import com.bumptech.glide.request.transition.Transition
-import java.lang.ref.WeakReference
-
-class CoverLoader(activity: MainActivity) {
- private var resource = 0
- private var uri: String? = null
- private var fallbackUri: String? = null
- private var imgvCover: ImageView? = null
- private var textAndImageCombined = false
- private var fallbackTitle: TextView? = null
-
- fun withUri(uri: String?): CoverLoader {
- this.uri = uri
- return this
- }
-
- fun withResource(resource: Int): CoverLoader {
- this.resource = resource
- return this
- }
-
- fun withFallbackUri(uri: String?): CoverLoader {
- fallbackUri = uri
- return this
- }
-
- fun withCoverView(coverView: ImageView): CoverLoader {
- imgvCover = coverView
- return this
- }
-
- fun withPlaceholderView(title: TextView): CoverLoader {
- this.fallbackTitle = title
- return this
- }
-
- /**
- * Set cover text and if it should be shown even if there is a cover image.
- * @param fallbackTitle Fallback title text
- * @param textAndImageCombined Show cover text even if there is a cover image?
- */
- fun withPlaceholderView(fallbackTitle: TextView?, textAndImageCombined: Boolean): CoverLoader {
- this.fallbackTitle = fallbackTitle
- this.textAndImageCombined = textAndImageCombined
- return this
- }
-
- fun load() {
- if (imgvCover == null) return
-
- val coverTarget = CoverTarget(fallbackTitle, imgvCover!!, textAndImageCombined)
-
- if (resource != 0) {
- Glide.with(imgvCover!!).clear(coverTarget)
- imgvCover!!.setImageResource(resource)
- CoverTarget.setTitleVisibility(fallbackTitle, textAndImageCombined)
- return
- }
-
- val options: RequestOptions = RequestOptions()
- .fitCenter()
- .dontAnimate()
-
- var builder: RequestBuilder = Glide.with(imgvCover!!)
- .`as`(Drawable::class.java)
- .load(uri)
- .apply(options)
-
- if (fallbackUri != null) {
- builder = builder.error(Glide.with(imgvCover!!)
- .`as`(Drawable::class.java)
- .load(fallbackUri)
- .apply(options))
- }
-
- builder.into(coverTarget)
- }
-
- internal class CoverTarget(fallbackTitle: TextView?,
- coverImage: ImageView,
- private val textAndImageCombined: Boolean
- ) : CustomViewTarget(coverImage) {
-
- private val fallbackTitle: WeakReference = WeakReference(fallbackTitle)
- private val cover: WeakReference = WeakReference(coverImage)
-
- override fun onLoadFailed(errorDrawable: Drawable?) {
- setTitleVisibility(fallbackTitle.get(), true)
- }
-
- override fun onResourceReady(resource: Drawable,
- transition: Transition?
- ) {
- val ivCover = cover.get()
- ivCover!!.setImageDrawable(resource)
- setTitleVisibility(fallbackTitle.get(), textAndImageCombined)
- }
-
- override fun onResourceCleared(placeholder: Drawable?) {
- val ivCover = cover.get()
- ivCover!!.setImageDrawable(placeholder)
- setTitleVisibility(fallbackTitle.get(), textAndImageCombined)
- }
-
- companion object {
- fun setTitleVisibility(fallbackTitle: TextView?, textAndImageCombined: Boolean) {
- fallbackTitle?.visibility = if (textAndImageCombined) View.VISIBLE else View.GONE
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/DataFolderAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/DataFolderAdapter.kt
deleted file mode 100644
index 6ea6972e..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/DataFolderAdapter.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.content.Context
-import android.text.format.Formatter
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ProgressBar
-import android.widget.RadioButton
-import android.widget.TextView
-import androidx.core.util.Consumer
-import androidx.recyclerview.widget.RecyclerView
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.util.StorageUtils.getFreeSpaceAvailable
-import ac.mdiq.podcini.core.util.StorageUtils.getTotalSpaceAvailable
-import ac.mdiq.podcini.storage.preferences.UserPreferences.getDataFolder
-import java.io.File
-
-class DataFolderAdapter(context: Context, selectionHandler: Consumer) : RecyclerView.Adapter() {
-
- private val selectionHandler: Consumer
- private val currentPath: String?
- private val entries: List
- private val freeSpaceString: String
-
- init {
- this.entries = getStorageEntries(context)
- this.currentPath = getCurrentPath()
- this.selectionHandler = selectionHandler
- this.freeSpaceString = context.getString(R.string.choose_data_directory_available_space)
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val entryView = inflater.inflate(R.layout.choose_data_folder_dialog_entry, parent, false)
- return ViewHolder(entryView)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val storagePath = entries[position]
- val context = holder.root.context
- val freeSpace = Formatter.formatShortFileSize(context, storagePath.availableSpace)
- val totalSpace = Formatter.formatShortFileSize(context, storagePath.totalSpace)
-
- holder.path.text = storagePath.shortPath
- holder.size.text = String.format(freeSpaceString, freeSpace, totalSpace)
- holder.progressBar.progress = storagePath.usagePercentage
- val selectListener = View.OnClickListener { v: View? ->
- selectionHandler.accept(
- storagePath.fullPath)
- }
- holder.root.setOnClickListener(selectListener)
- holder.radioButton.setOnClickListener(selectListener)
-
- if (storagePath.fullPath == currentPath) {
- holder.radioButton.toggle()
- }
- }
-
- override fun getItemCount(): Int {
- return entries.size
- }
-
- private fun getCurrentPath(): String? {
- val dataFolder = getDataFolder(null)
- return dataFolder?.absolutePath
- }
-
- private fun getStorageEntries(context: Context): List {
- val mediaDirs = context.getExternalFilesDirs(null)
- val entries: MutableList = ArrayList(mediaDirs.size)
- for (dir in mediaDirs) {
- if (!isWritable(dir)) {
- continue
- }
- entries.add(StoragePath(dir.absolutePath))
- }
- if (entries.isEmpty() && isWritable(context.filesDir)) {
- entries.add(StoragePath(context.filesDir.absolutePath))
- }
- return entries
- }
-
- private fun isWritable(dir: File?): Boolean {
- return dir != null && dir.exists() && dir.canRead() && dir.canWrite()
- }
-
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val root: View = itemView.findViewById(R.id.root)
- val path: TextView = itemView.findViewById(R.id.path)
- val size: TextView = itemView.findViewById(R.id.size)
- val radioButton: RadioButton = itemView.findViewById(R.id.radio_button)
- val progressBar: ProgressBar = itemView.findViewById(R.id.used_space)
- }
-
- internal class StoragePath(val fullPath: String) {
- val shortPath: String
- get() {
- val prefixIndex = fullPath.indexOf("Android")
- return if ((prefixIndex > 0)) fullPath.substring(0, prefixIndex) else fullPath
- }
-
- val availableSpace: Long
- get() = getFreeSpaceAvailable(fullPath)
-
- val totalSpace: Long
- get() = getTotalSpaceAvailable(fullPath)
-
- val usagePercentage: Int
- get() = 100 - (100 * availableSpace / totalSpace.toFloat()).toInt()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/DownloadLogAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/DownloadLogAdapter.kt
deleted file mode 100644
index 71046047..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/DownloadLogAdapter.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.app.Activity
-import android.text.format.DateUtils
-import android.util.Log
-import android.view.View
-import android.view.ViewGroup
-import android.widget.BaseAdapter
-import android.widget.Toast
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.adapter.actionbutton.DownloadActionButton
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.core.util.DownloadErrorLabel
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager
-import ac.mdiq.podcini.model.download.DownloadError
-import ac.mdiq.podcini.model.download.DownloadResult
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.model.feed.FeedMedia
-import ac.mdiq.podcini.ui.common.ThemeUtils
-import ac.mdiq.podcini.view.viewholder.DownloadLogItemViewHolder
-import androidx.media3.common.util.UnstableApi
-
-/**
- * Displays a list of DownloadStatus entries.
- */
-class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
- private var downloadLog: List = ArrayList()
-
- fun setDownloadLog(downloadLog: List) {
- this.downloadLog = downloadLog
- notifyDataSetChanged()
- }
-
- @UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val holder: DownloadLogItemViewHolder
- if (convertView == null) {
- holder = DownloadLogItemViewHolder(context, parent)
- holder.itemView.tag = holder
- } else {
- holder = convertView.tag as DownloadLogItemViewHolder
- }
- val item = getItem(position)
- if (item != null) bind(holder, item, position)
- return holder.itemView
- }
-
- @UnstableApi private fun bind(holder: DownloadLogItemViewHolder, status: DownloadResult, position: Int) {
- var statusText: String? = ""
- if (status.feedfileType == Feed.FEEDFILETYPE_FEED) {
- statusText += context.getString(R.string.download_type_feed)
- } else if (status.feedfileType == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- statusText += context.getString(R.string.download_type_media)
- }
- statusText += " · "
- statusText += DateUtils.getRelativeTimeSpanString(status.getCompletionDate().time,
- System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0)
- holder.status.text = statusText
-
- if (status.title.isNotEmpty()) {
- holder.title.text = status.title
- } else {
- holder.title.setText(R.string.download_log_title_unknown)
- }
-
- if (status.isSuccessful) {
- holder.icon.setTextColor(ThemeUtils.getColorFromAttr(context, R.attr.icon_green))
- holder.icon.text = "{fa-check-circle}"
- holder.icon.setContentDescription(context.getString(R.string.download_successful))
- holder.secondaryActionButton.visibility = View.INVISIBLE
- holder.reason.visibility = View.GONE
- holder.tapForDetails.visibility = View.GONE
- } else {
- if (status.reason == DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE) {
- holder.icon.setTextColor(ThemeUtils.getColorFromAttr(context, R.attr.icon_yellow))
- holder.icon.text = "{fa-exclamation-circle}"
- } else {
- holder.icon.setTextColor(ThemeUtils.getColorFromAttr(context, R.attr.icon_red))
- holder.icon.text = "{fa-times-circle}"
- }
- holder.icon.setContentDescription(context.getString(R.string.error_label))
- holder.reason.setText(DownloadErrorLabel.from(status.reason))
- holder.reason.visibility = View.VISIBLE
- holder.tapForDetails.visibility = View.VISIBLE
-
- if (newerWasSuccessful(position, status.feedfileType, status.feedfileId)) {
- holder.secondaryActionButton.visibility = View.INVISIBLE
- holder.secondaryActionButton.setOnClickListener(null)
- holder.secondaryActionButton.tag = null
- } else {
- holder.secondaryActionIcon.setImageResource(R.drawable.ic_refresh)
- holder.secondaryActionButton.visibility = View.VISIBLE
-
- if (status.feedfileType == Feed.FEEDFILETYPE_FEED) {
- holder.secondaryActionButton.setOnClickListener(View.OnClickListener setOnClickListener@{ v: View? ->
- holder.secondaryActionButton.visibility = View.INVISIBLE
- val feed: Feed? = DBReader.getFeed(status.feedfileId)
- if (feed == null) {
- Log.e(TAG, "Could not find feed for feed id: " + status.feedfileId)
- return@setOnClickListener
- }
- FeedUpdateManager.runOnce(context, feed)
- })
- } else if (status.feedfileType == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- holder.secondaryActionButton.setOnClickListener(View.OnClickListener { v: View? ->
- holder.secondaryActionButton.visibility = View.INVISIBLE
- val media: FeedMedia? = DBReader.getFeedMedia(status.feedfileId)
- if (media == null) {
- Log.e(TAG, "Could not find feed media for feed id: " + status.feedfileId)
- return@OnClickListener
- }
- if (media.getItem() != null) DownloadActionButton(media.getItem()!!).onClick(context)
- (context as MainActivity).showSnackbarAbovePlayer(
- R.string.status_downloading_label, Toast.LENGTH_SHORT)
- })
- }
- }
- }
- }
-
- private fun newerWasSuccessful(downloadStatusIndex: Int, feedTypeId: Int, id: Long): Boolean {
- for (i in 0 until downloadStatusIndex) {
- val status: DownloadResult = downloadLog[i]
- if (status.feedfileType == feedTypeId && status.feedfileId == id && status.isSuccessful) {
- return true
- }
- }
- return false
- }
-
- override fun getCount(): Int {
- return downloadLog.size
- }
-
- override fun getItem(position: Int): DownloadResult? {
- if (position in downloadLog.indices) {
- return downloadLog[position]
- }
- return null
- }
-
- override fun getItemId(position: Int): Long {
- return position.toLong()
- }
-
- companion object {
- private const val TAG = "DownloadLogAdapter"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/EpisodeItemListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/EpisodeItemListAdapter.kt
deleted file mode 100644
index 032bb9ce..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/EpisodeItemListAdapter.kt
+++ /dev/null
@@ -1,213 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.MainActivity
-import ac.mdiq.podcini.core.util.FeedItemUtil
-import ac.mdiq.podcini.fragment.ItemPagerFragment
-import ac.mdiq.podcini.menuhandler.FeedItemMenuHandler
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.ui.common.ThemeUtils
-import ac.mdiq.podcini.view.viewholder.EpisodeItemViewHolder
-import android.R.color
-import android.app.Activity
-import android.os.Build
-import android.view.*
-import androidx.media3.common.util.UnstableApi
-import androidx.recyclerview.widget.RecyclerView
-import org.apache.commons.lang3.ArrayUtils
-import java.lang.ref.WeakReference
-
-
-/**
- * List adapter for the list of new episodes.
- */
-open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapter(mainActivity),
- View.OnCreateContextMenuListener {
-
- private val mainActivityRef: WeakReference = WeakReference(mainActivity)
- private var episodes: List = ArrayList()
- var longPressedItem: FeedItem? = null
- var longPressedPosition: Int = 0 // used to init actionMode
- private var dummyViews = 0
-
- init {
- setHasStableIds(true)
- }
-
- fun setDummyViews(dummyViews: Int) {
- this.dummyViews = dummyViews
- notifyDataSetChanged()
- }
-
- fun updateItems(items: List) {
- episodes = items
- notifyDataSetChanged()
- updateTitle()
- }
-
- override fun getItemViewType(position: Int): Int {
- return R.id.view_type_episode_item
- }
-
- @UnstableApi override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeItemViewHolder {
- return EpisodeItemViewHolder(mainActivityRef.get()!!, parent)
- }
-
- @UnstableApi override fun onBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
- if (pos >= episodes.size || pos < 0) {
- beforeBindViewHolder(holder, pos)
- holder.bindDummy()
- afterBindViewHolder(holder, pos)
- holder.hideSeparatorIfNecessary()
- return
- }
-
- // Reset state of recycled views
- holder.coverHolder.visibility = View.VISIBLE
- holder.dragHandle.setVisibility(View.GONE)
-
- beforeBindViewHolder(holder, pos)
-
- val item: FeedItem = episodes[pos]
- holder.bind(item)
-
- holder.itemView.setOnClickListener { v: View? ->
- val activity: MainActivity? = mainActivityRef.get()
- if (!inActionMode()) {
- val ids: LongArray = FeedItemUtil.getIds(episodes)
- val position = ArrayUtils.indexOf(ids, item.id)
- activity?.loadChildFragment(ItemPagerFragment.newInstance(ids, position))
- } else {
- toggleSelection(holder.bindingAdapterPosition)
- }
- }
- holder.itemView.setOnCreateContextMenuListener(this)
- holder.itemView.setOnLongClickListener { v: View? ->
- longPressedItem = item
- longPressedPosition = holder.bindingAdapterPosition
- false
- }
- holder.itemView.setOnTouchListener(View.OnTouchListener { v: View?, e: MotionEvent ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
- longPressedItem = item
- longPressedPosition = holder.bindingAdapterPosition
- return@OnTouchListener false
- }
- }
- false
- })
-
- if (inActionMode()) {
- holder.secondaryActionButton.setOnClickListener(null)
- if (isSelected(pos)) {
- holder.itemView.setBackgroundColor(-0x78000000
- + (0xffffff and ThemeUtils.getColorFromAttr(mainActivityRef.get()!!, R.attr.colorAccent)))
- } else {
- holder.itemView.setBackgroundResource(color.transparent)
- }
- }
-
- afterBindViewHolder(holder, pos)
- holder.hideSeparatorIfNecessary()
- }
-
- protected open fun beforeBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
- }
-
- protected open fun afterBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
- }
-
- @UnstableApi override fun onViewRecycled(holder: EpisodeItemViewHolder) {
- super.onViewRecycled(holder)
- // Set all listeners to null. This is required to prevent leaking fragments that have set a listener.
- // Activity -> recycledViewPool -> EpisodeItemViewHolder -> Listener -> Fragment (can not be garbage collected)
- holder.itemView.setOnClickListener(null)
- holder.itemView.setOnCreateContextMenuListener(null)
- holder.itemView.setOnLongClickListener(null)
- holder.itemView.setOnTouchListener(null)
- holder.secondaryActionButton.setOnClickListener(null)
- holder.dragHandle.setOnTouchListener(null)
- holder.coverHolder.setOnTouchListener(null)
- }
-
- /**
- * [.notifyItemChanged] is final, so we can not override.
- * Calling [.notifyItemChanged] may bind the item to a new ViewHolder and execute a transition.
- * This causes flickering and breaks the download animation that stores the old progress in the View.
- * Instead, we tell the adapter to use partial binding by calling [.notifyItemChanged].
- * We actually ignore the payload and always do a full bind but calling the partial bind method ensures
- * that ViewHolders are always re-used.
- *
- * @param position Position of the item that has changed
- */
- fun notifyItemChangedCompat(position: Int) {
- notifyItemChanged(position, "foo")
- }
-
- override fun getItemId(position: Int): Long {
-// if (position >= episodes.size) {
-// return RecyclerView.NO_ID // Dummy views
-// }
-// val item = episodes[position]
-// return item.id ?: RecyclerView.NO_POSITION.toLong()
- return getItem(position)?.id ?: RecyclerView.NO_ID
- }
-
- override fun getItemCount(): Int {
- return dummyViews + episodes.size
- }
-
- protected fun getItem(index: Int): FeedItem? {
-// return episodes[index]
- return if (index in episodes.indices) episodes[index] else null
- }
-
- protected val activity: Activity?
- get() = mainActivityRef.get()
-
- @UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
- val inflater: MenuInflater = activity!!.menuInflater
- if (inActionMode()) {
- inflater.inflate(R.menu.multi_select_context_popup, menu)
- } else {
- if (longPressedItem == null) {
- return
- }
- inflater.inflate(R.menu.feeditemlist_context, menu)
- menu.setHeaderTitle(longPressedItem!!.title)
- FeedItemMenuHandler.onPrepareMenu(menu, longPressedItem, R.id.skip_episode_item)
- }
- }
-
- fun onContextItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.multi_select -> {
- startSelectMode(longPressedPosition)
- return true
- }
- R.id.select_all_above -> {
- setSelected(0, longPressedPosition, true)
- return true
- }
- R.id.select_all_below -> {
- shouldSelectLazyLoadedItems = true
- setSelected(longPressedPosition + 1, itemCount, true)
- return true
- }
- else -> return false
- }
- }
-
- val selectedItems: List
- get() {
- val items: MutableList = ArrayList()
- for (i in 0 until itemCount) {
- if (i < episodes.size && isSelected(i)) {
- val item = getItem(i)
- if (item != null) items.add(item)
- }
- }
- return items
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/FeedDiscoverAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/FeedDiscoverAdapter.kt
deleted file mode 100644
index 5ceb3be7..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/FeedDiscoverAdapter.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.BaseAdapter
-import android.widget.ImageView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.resource.bitmap.FitCenter
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners
-import com.bumptech.glide.request.RequestOptions
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.net.discovery.PodcastSearchResult
-import java.lang.ref.WeakReference
-
-class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() {
- private val mainActivityRef: WeakReference = WeakReference(mainActivity)
- private val data: MutableList = ArrayList()
-
- fun updateData(newData: List) {
- data.clear()
- data.addAll(newData)
- notifyDataSetChanged()
- }
-
- override fun getCount(): Int {
- return data.size
- }
-
- override fun getItem(position: Int): PodcastSearchResult? {
- return if (position in data.indices) data[position] else null
- }
-
- override fun getItemId(position: Int): Long {
- return 0
- }
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var convertView = convertView
- val holder: Holder
-
- if (convertView == null) {
- convertView = View.inflate(mainActivityRef.get(), R.layout.quick_feed_discovery_item, null)
- holder = Holder()
- holder.imageView = convertView.findViewById(R.id.discovery_cover)
- convertView.tag = holder
- } else {
- holder = convertView.tag as Holder
- }
-
- val podcast: PodcastSearchResult? = getItem(position)
- holder.imageView!!.contentDescription = podcast?.title
-
- Glide.with(mainActivityRef.get()!!)
- .load(podcast?.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.light_gray)
- .transform(FitCenter(), RoundedCorners((8 * mainActivityRef.get()!!.resources.displayMetrics.density).toInt()))
- .dontAnimate())
- .into(holder.imageView!!)
-
- return convertView!!
- }
-
- internal class Holder {
- var imageView: ImageView? = null
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/FeedItemlistDescriptionAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/FeedItemlistDescriptionAdapter.kt
deleted file mode 100644
index a679dc4f..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/FeedItemlistDescriptionAdapter.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
-import ac.mdiq.podcini.core.util.DateFormatter.formatAbbrev
-import ac.mdiq.podcini.core.util.NetworkUtils.isStreamingAllowed
-import ac.mdiq.podcini.core.util.playback.PlaybackServiceStarter
-import ac.mdiq.podcini.core.util.syndication.HtmlToPlainText
-import ac.mdiq.podcini.dialog.StreamingConfirmationDialog
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.playback.MediaType
-import ac.mdiq.podcini.model.playback.Playable
-import ac.mdiq.podcini.model.playback.RemoteMedia
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ArrayAdapter
-import android.widget.Button
-import android.widget.TextView
-import androidx.media3.common.util.UnstableApi
-
-/**
- * List adapter for showing a list of FeedItems with their title and description.
- */
-class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: List?) :
- ArrayAdapter(context, resource, objects!!) {
- @UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var convertView = convertView
- val holder: Holder
-
- val item = getItem(position)
-
- // Inflate layout
- if (convertView == null) {
- holder = Holder()
- val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
- convertView = inflater.inflate(R.layout.itemdescription_listitem, parent, false)
- holder.title = convertView.findViewById(R.id.txtvTitle)
- holder.pubDate = convertView.findViewById(R.id.txtvPubDate)
- holder.description = convertView.findViewById(R.id.txtvDescription)
- holder.preview = convertView.findViewById(R.id.butPreview)
-
- convertView.tag = holder
- } else {
- holder = convertView.tag as Holder
- }
-
- holder.title!!.text = item!!.title
- holder.pubDate!!.text = formatAbbrev(context, item.pubDate)
- if (item.description != null) {
- val description = HtmlToPlainText.getPlainText(item.description!!)
- .replace("\n".toRegex(), " ")
- .replace("\\s+".toRegex(), " ")
- .trim { it <= ' ' }
- holder.description!!.text = description
- holder.description!!.maxLines = MAX_LINES_COLLAPSED
- }
- holder.description!!.tag = false
- holder.preview!!.visibility = View.GONE
- holder.preview!!.setOnClickListener { v: View? ->
- if (item.media == null) {
- return@setOnClickListener
- }
- val playable: Playable = RemoteMedia(item)
- if (!isStreamingAllowed) {
- StreamingConfirmationDialog(context, playable).show()
- return@setOnClickListener
- }
-
- PlaybackServiceStarter(context, playable)
- .callEvenIfRunning(true)
- .start()
- if (playable.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, playable))
- }
- }
- convertView!!.setOnClickListener { v: View? ->
- if (holder.description!!.tag == true) {
- holder.description!!.maxLines = MAX_LINES_COLLAPSED
- holder.preview!!.visibility = View.GONE
- holder.description!!.tag = false
- } else {
- holder.description!!.maxLines = 30
- holder.description!!.tag = true
-
- holder.preview!!.visibility = if (item.media != null) View.VISIBLE else View.GONE
- holder.preview!!.setText(R.string.preview_episode)
- }
- }
- return convertView
- }
-
- internal class Holder {
- var title: TextView? = null
- var pubDate: TextView? = null
- var description: TextView? = null
- var preview: Button? = null
- }
-
- companion object {
- private const val MAX_LINES_COLLAPSED = 2
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalFeedListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalFeedListAdapter.kt
deleted file mode 100644
index f1cba636..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalFeedListAdapter.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.view.ContextMenu
-import android.view.MenuInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import androidx.annotation.StringRes
-import androidx.cardview.widget.CardView
-import androidx.media3.common.util.UnstableApi
-import androidx.recyclerview.widget.RecyclerView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.request.RequestOptions
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.fragment.FeedItemlistFragment
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.ui.common.SquareImageView
-import java.lang.ref.WeakReference
-
-open class HorizontalFeedListAdapter(mainActivity: MainActivity) :
- RecyclerView.Adapter(), View.OnCreateContextMenuListener {
-
- private val mainActivityRef: WeakReference = WeakReference(mainActivity)
- private val data: MutableList = ArrayList()
- private var dummyViews = 0
- var longPressedItem: Feed? = null
-
- @StringRes
- private var endButtonText = 0
- private var endButtonAction: Runnable? = null
-
- fun setDummyViews(dummyViews: Int) {
- this.dummyViews = dummyViews
- }
-
- fun updateData(newData: List?) {
- data.clear()
- data.addAll(newData!!)
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
- val convertView = View.inflate(mainActivityRef.get(), R.layout.horizontal_feed_item, null)
- return Holder(convertView)
- }
-
- @UnstableApi override fun onBindViewHolder(holder: Holder, position: Int) {
- if (position == itemCount - 1 && endButtonAction != null) {
- holder.cardView.visibility = View.GONE
- holder.actionButton.visibility = View.VISIBLE
- holder.actionButton.setText(endButtonText)
- holder.actionButton.setOnClickListener { v: View? -> endButtonAction!!.run() }
- return
- }
- holder.cardView.visibility = View.VISIBLE
- holder.actionButton.visibility = View.GONE
- if (position >= data.size) {
- holder.itemView.alpha = 0.1f
- Glide.with(mainActivityRef.get()!!).clear(holder.imageView)
- holder.imageView.setImageResource(R.color.medium_gray)
- return
- }
-
- holder.itemView.alpha = 1.0f
- val podcast: Feed = data[position]
- holder.imageView.setContentDescription(podcast.title)
- holder.imageView.setOnClickListener { v: View? ->
- mainActivityRef.get()?.loadChildFragment(FeedItemlistFragment.newInstance(podcast.id))
- }
-
- holder.imageView.setOnCreateContextMenuListener(this)
- holder.imageView.setOnLongClickListener { v: View? ->
- val currentItemPosition = holder.bindingAdapterPosition
- longPressedItem = data[currentItemPosition]
- false
- }
-
- Glide.with(mainActivityRef.get()!!)
- .load(podcast.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.light_gray)
- .fitCenter()
- .dontAnimate())
- .into(holder.imageView)
- }
-
- override fun getItemId(position: Int): Long {
- if (position >= data.size) {
- return RecyclerView.NO_ID // Dummy views
- }
- return data[position].id
- }
-
- override fun getItemCount(): Int {
- return dummyViews + data.size + (if ((endButtonAction == null)) 0 else 1)
- }
-
- override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
- val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
- if (longPressedItem == null) {
- return
- }
- inflater.inflate(R.menu.nav_feed_context, contextMenu)
- contextMenu.setHeaderTitle(longPressedItem!!.title)
- }
-
- fun setEndButton(@StringRes text: Int, action: Runnable?) {
- endButtonAction = action
- endButtonText = text
- notifyDataSetChanged()
- }
-
- class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- var imageView: SquareImageView = itemView.findViewById(R.id.discovery_cover)
- var cardView: CardView
- var actionButton: Button
-
- init {
- imageView.setDirection(SquareImageView.DIRECTION_HEIGHT)
- actionButton = itemView.findViewById(R.id.actionButton)
- cardView = itemView.findViewById(R.id.cardView)
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalItemListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalItemListAdapter.kt
deleted file mode 100644
index 8dfcdf7e..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/HorizontalItemListAdapter.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.view.ContextMenu
-import android.view.MenuInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.media3.common.util.UnstableApi
-import androidx.recyclerview.widget.RecyclerView
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.util.FeedItemUtil
-import ac.mdiq.podcini.fragment.ItemPagerFragment
-import ac.mdiq.podcini.menuhandler.FeedItemMenuHandler
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.view.viewholder.HorizontalItemViewHolder
-import org.apache.commons.lang3.ArrayUtils
-import java.lang.ref.WeakReference
-
-open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.Adapter(),
- View.OnCreateContextMenuListener {
-
- private val mainActivityRef: WeakReference = WeakReference(mainActivity)
- private var data: List = ArrayList()
- var longPressedItem: FeedItem? = null
- private var dummyViews = 0
-
- init {
- setHasStableIds(true)
- }
-
- fun setDummyViews(dummyViews: Int) {
- this.dummyViews = dummyViews
- }
-
- fun updateData(newData: List) {
- data = newData
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HorizontalItemViewHolder {
- return HorizontalItemViewHolder(mainActivityRef.get()!!, parent)
- }
-
- @UnstableApi override fun onBindViewHolder(holder: HorizontalItemViewHolder, position: Int) {
- if (position >= data.size) {
- holder.bindDummy()
- return
- }
-
- val item: FeedItem = data[position]
- holder.bind(item)
-
- holder.card.setOnCreateContextMenuListener(this)
- holder.card.setOnLongClickListener { v: View? ->
- longPressedItem = item
- false
- }
- holder.secondaryActionIcon.setOnCreateContextMenuListener(this)
- holder.secondaryActionIcon.setOnLongClickListener { v: View? ->
- longPressedItem = item
- false
- }
- holder.card.setOnClickListener { v: View? ->
- val activity: MainActivity? = mainActivityRef.get()
- if (activity != null) {
- val ids: LongArray = FeedItemUtil.getIds(data)
- val clickPosition = ArrayUtils.indexOf(ids, item.id)
- activity.loadChildFragment(ItemPagerFragment.newInstance(ids, clickPosition))
- }
- }
- }
-
- override fun getItemId(position: Int): Long {
- if (position in data.indices) {
- val item: FeedItem = data[position]
- return item.id
- }
- return RecyclerView.NO_ID // Dummy views
- }
-
- override fun getItemCount(): Int {
- return dummyViews + data.size
- }
-
- override fun onViewRecycled(holder: HorizontalItemViewHolder) {
- super.onViewRecycled(holder)
- // Set all listeners to null. This is required to prevent leaking fragments that have set a listener.
- // Activity -> recycledViewPool -> ViewHolder -> Listener -> Fragment (can not be garbage collected)
- holder.card.setOnClickListener(null)
- holder.card.setOnCreateContextMenuListener(null)
- holder.card.setOnLongClickListener(null)
- holder.secondaryActionIcon.setOnClickListener(null)
- holder.secondaryActionIcon.setOnCreateContextMenuListener(null)
- holder.secondaryActionIcon.setOnLongClickListener(null)
- }
-
- /**
- * [.notifyItemChanged] is final, so we can not override.
- * Calling [.notifyItemChanged] may bind the item to a new ViewHolder and execute a transition.
- * This causes flickering and breaks the download animation that stores the old progress in the View.
- * Instead, we tell the adapter to use partial binding by calling [.notifyItemChanged].
- * We actually ignore the payload and always do a full bind but calling the partial bind method ensures
- * that ViewHolders are always re-used.
- *
- * @param position Position of the item that has changed
- */
- fun notifyItemChangedCompat(position: Int) {
- notifyItemChanged(position, "foo")
- }
-
- @UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
- val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
- if (longPressedItem == null) return
-
- menu.clear()
- inflater.inflate(R.menu.feeditemlist_context, menu)
- menu.setHeaderTitle(longPressedItem!!.title)
- FeedItemMenuHandler.onPrepareMenu(menu, longPressedItem, R.id.skip_episode_item)
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/NavListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/NavListAdapter.kt
deleted file mode 100644
index f869de4d..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/NavListAdapter.kt
+++ /dev/null
@@ -1,376 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.app.Activity
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
-import android.os.Build
-import android.view.*
-import android.view.ContextMenu.ContextMenuInfo
-import android.view.View.OnCreateContextMenuListener
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.RelativeLayout
-import android.widget.TextView
-import androidx.annotation.DrawableRes
-import androidx.preference.PreferenceManager
-import androidx.recyclerview.widget.RecyclerView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.resource.bitmap.FitCenter
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners
-import com.bumptech.glide.request.RequestOptions
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.activity.PreferenceActivity
-import ac.mdiq.podcini.core.storage.NavDrawerData.*
-import ac.mdiq.podcini.fragment.*
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import ac.mdiq.podcini.storage.preferences.UserPreferences.episodeCacheSize
-import ac.mdiq.podcini.storage.preferences.UserPreferences.hiddenDrawerItems
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isEnableAutodownload
-import ac.mdiq.podcini.storage.preferences.UserPreferences.subscriptionsFilter
-import ac.mdiq.podcini.ui.home.HomeFragment
-import androidx.media3.common.util.UnstableApi
-import org.apache.commons.lang3.ArrayUtils
-import java.lang.ref.WeakReference
-import java.text.NumberFormat
-import java.util.*
-import kotlin.math.abs
-
-/**
- * BaseAdapter for the navigation drawer
- */
-class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
- RecyclerView.Adapter(), OnSharedPreferenceChangeListener {
-
- private val fragmentTags: MutableList = ArrayList()
- private val titles: Array = context.resources.getStringArray(R.array.nav_drawer_titles)
- private val activity = WeakReference(context)
- @JvmField
- var showSubscriptionList: Boolean = true
-
- init {
- loadItems()
-
- val prefs = PreferenceManager.getDefaultSharedPreferences(context)
- prefs.registerOnSharedPreferenceChangeListener(this)
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
- if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS == key) {
- loadItems()
- }
- }
-
- private fun loadItems() {
- val newTags: MutableList = ArrayList(listOf(*NavDrawerFragment.NAV_DRAWER_TAGS))
- val hiddenFragments = hiddenDrawerItems
- newTags.removeAll(hiddenFragments!!)
-
- if (newTags.contains(SUBSCRIPTION_LIST_TAG)) {
- // we never want SUBSCRIPTION_LIST_TAG to be in 'tags'
- // since it doesn't actually correspond to a position in the list, but is
- // a placeholder that indicates if we should show the subscription list in the
- // nav drawer at all.
- showSubscriptionList = true
- newTags.remove(SUBSCRIPTION_LIST_TAG)
- } else {
- showSubscriptionList = false
- }
-
- fragmentTags.clear()
- fragmentTags.addAll(newTags)
- notifyDataSetChanged()
- }
-
- fun getLabel(tag: String?): String {
- val index = ArrayUtils.indexOf(NavDrawerFragment.NAV_DRAWER_TAGS, tag)
- return titles[index]
- }
-
- @UnstableApi @DrawableRes
- private fun getDrawable(tag: String?): Int {
- return when (tag) {
- HomeFragment.TAG -> R.drawable.ic_home
- QueueFragment.TAG -> R.drawable.ic_playlist_play
- InboxFragment.TAG -> R.drawable.ic_inbox
- AllEpisodesFragment.TAG -> R.drawable.ic_feed
- CompletedDownloadsFragment.TAG -> R.drawable.ic_download
- PlaybackHistoryFragment.TAG -> R.drawable.ic_history
- SubscriptionFragment.TAG -> R.drawable.ic_subscriptions
- AddFeedFragment.TAG -> R.drawable.ic_add
- else -> 0
- }
- }
-
- fun getFragmentTags(): List {
- return Collections.unmodifiableList(fragmentTags)
- }
-
- override fun getItemCount(): Int {
- var baseCount = subscriptionOffset
- if (showSubscriptionList) {
- baseCount += itemAccess.count
- }
- return baseCount
- }
-
- override fun getItemId(position: Int): Long {
- val viewType = getItemViewType(position)
- return when (viewType) {
- VIEW_TYPE_SUBSCRIPTION -> {
- itemAccess.getItem(position - subscriptionOffset)?.id?:0
- }
- VIEW_TYPE_NAV -> {
- (-abs(fragmentTags[position].hashCode().toLong().toDouble()) - 1).toLong() // Folder IDs are >0
- }
- else -> {
- 0
- }
- }
- }
-
- override fun getItemViewType(position: Int): Int {
- return if (0 <= position && position < fragmentTags.size) {
- VIEW_TYPE_NAV
- } else if (position < subscriptionOffset) {
- VIEW_TYPE_SECTION_DIVIDER
- } else {
- VIEW_TYPE_SUBSCRIPTION
- }
- }
-
- val subscriptionOffset: Int
- get() = if (fragmentTags.size > 0) fragmentTags.size + 1 else 0
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
- val inflater = LayoutInflater.from(activity.get())
- return when (viewType) {
- VIEW_TYPE_NAV -> {
- NavHolder(inflater.inflate(R.layout.nav_listitem, parent, false))
- }
- VIEW_TYPE_SECTION_DIVIDER -> {
- DividerHolder(inflater.inflate(R.layout.nav_section_item, parent, false))
- }
- else -> {
- FeedHolder(inflater.inflate(R.layout.nav_listitem, parent, false))
- }
- }
- }
-
- @UnstableApi override fun onBindViewHolder(holder: Holder, position: Int) {
- val viewType = getItemViewType(position)
-
- holder.itemView.setOnCreateContextMenuListener(null)
- when (viewType) {
- VIEW_TYPE_NAV -> {
- bindNavView(getLabel(fragmentTags[position]), position, holder as NavHolder)
- }
- VIEW_TYPE_SECTION_DIVIDER -> {
- bindSectionDivider(holder as DividerHolder)
- }
- else -> {
- val itemPos = position - subscriptionOffset
- val item = itemAccess.getItem(itemPos)
- if (item != null) {
- bindListItem(item, holder as FeedHolder)
- if (item.type == DrawerItem.Type.FEED) {
- bindFeedView(item as FeedDrawerItem, holder)
- } else {
- bindTagView(item as TagDrawerItem, holder)
- }
- }
- holder.itemView.setOnCreateContextMenuListener(itemAccess)
- }
- }
- if (viewType != VIEW_TYPE_SECTION_DIVIDER) {
- holder.itemView.isSelected = itemAccess.isSelected(position)
- holder.itemView.setOnClickListener { v: View? -> itemAccess.onItemClick(position) }
- holder.itemView.setOnLongClickListener { v: View? -> itemAccess.onItemLongClick(position) }
- holder.itemView.setOnTouchListener { v: View?, e: MotionEvent ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (e.isFromSource(InputDevice.SOURCE_MOUSE)
- && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
- itemAccess.onItemLongClick(position)
- return@setOnTouchListener false
- }
- }
- false
- }
- }
- }
-
- @UnstableApi private fun bindNavView(title: String, position: Int, holder: NavHolder) {
- val context = activity.get() ?: return
- holder.title.text = title
-
- // reset for re-use
- holder.count.visibility = View.GONE
- holder.count.setOnClickListener(null)
- holder.count.isClickable = false
-
- val tag = fragmentTags[position]
- when {
- tag == QueueFragment.TAG -> {
- val queueSize = itemAccess.queueSize
- if (queueSize > 0) {
- holder.count.text = NumberFormat.getInstance().format(queueSize.toLong())
- holder.count.visibility = View.VISIBLE
- }
- }
- tag == InboxFragment.TAG -> {
- val unreadItems = itemAccess.numberOfNewItems
- if (unreadItems > 0) {
- holder.count.text = NumberFormat.getInstance().format(unreadItems.toLong())
- holder.count.visibility = View.VISIBLE
- }
- }
- tag == SubscriptionFragment.TAG -> {
- val sum = itemAccess.feedCounterSum
- if (sum > 0) {
- holder.count.text = NumberFormat.getInstance().format(sum.toLong())
- holder.count.visibility = View.VISIBLE
- }
- }
- tag == CompletedDownloadsFragment.TAG && isEnableAutodownload -> {
- val epCacheSize = episodeCacheSize
- // don't count episodes that can be reclaimed
- val spaceUsed = (itemAccess.numberOfDownloadedItems
- - itemAccess.reclaimableItems)
- if (epCacheSize in 1..spaceUsed) {
- holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_alert, 0)
- holder.count.visibility = View.VISIBLE
- holder.count.setOnClickListener { v: View? ->
- MaterialAlertDialogBuilder(context)
- .setTitle(R.string.episode_cache_full_title)
- .setMessage(R.string.episode_cache_full_message)
- .setPositiveButton(android.R.string.ok, null)
- .setNeutralButton(R.string.open_autodownload_settings) { dialog: DialogInterface?, which: Int ->
- val intent = Intent(context, PreferenceActivity::class.java)
- intent.putExtra(PreferenceActivity.OPEN_AUTO_DOWNLOAD_SETTINGS, true)
- context.startActivity(intent)
- }
- .show()
- }
- }
- }
- }
-
- holder.image.setImageResource(getDrawable(fragmentTags[position]))
- }
-
- private fun bindSectionDivider(holder: DividerHolder) {
- val context = activity.get() ?: return
-
- if (subscriptionsFilter.isEnabled && showSubscriptionList) {
- holder.itemView.isEnabled = true
- holder.feedsFilteredMsg.visibility = View.VISIBLE
- } else {
- holder.itemView.isEnabled = false
- holder.feedsFilteredMsg.visibility = View.GONE
- }
- }
-
- private fun bindListItem(item: DrawerItem, holder: FeedHolder) {
- if (item.counter > 0) {
- holder.count.visibility = View.VISIBLE
- holder.count.text = NumberFormat.getInstance().format(item.counter.toLong())
- } else {
- holder.count.visibility = View.GONE
- }
- holder.title.text = item.title
- val padding = (activity.get()!!.resources.getDimension(R.dimen.thumbnail_length_navlist) / 2).toInt()
- holder.itemView.setPadding(item.layer * padding, 0, 0, 0)
- }
-
- private fun bindFeedView(drawerItem: FeedDrawerItem, holder: FeedHolder) {
- val feed = drawerItem.feed
- val context = activity.get() ?: return
-
- Glide.with(context)
- .load(feed.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.light_gray)
- .error(R.color.light_gray)
- .transform(FitCenter(),
- RoundedCorners((4 * context.resources.displayMetrics.density).toInt()))
- .dontAnimate())
- .into(holder.image)
-
- if (feed.hasLastUpdateFailed()) {
- val p = holder.title.layoutParams as RelativeLayout.LayoutParams
- p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure)
- holder.failure.visibility = View.VISIBLE
- } else {
- val p = holder.title.layoutParams as RelativeLayout.LayoutParams
- p.addRule(RelativeLayout.LEFT_OF, R.id.txtvCount)
- holder.failure.visibility = View.GONE
- }
- }
-
- private fun bindTagView(tag: TagDrawerItem, holder: FeedHolder) {
- val context = activity.get() ?: return
- if (tag.isOpen) {
- holder.count.visibility = View.GONE
- }
- Glide.with(context).clear(holder.image)
- holder.image.setImageResource(R.drawable.ic_tag)
- holder.failure.visibility = View.GONE
- }
-
- open class Holder(itemView: View) : RecyclerView.ViewHolder(itemView)
-
- internal class DividerHolder(itemView: View) : Holder(itemView) {
- val feedsFilteredMsg: LinearLayout = itemView.findViewById(R.id.nav_feeds_filtered_message)
- }
-
- internal class NavHolder(itemView: View) : Holder(itemView) {
- val image: ImageView = itemView.findViewById(R.id.imgvCover)
- val title: TextView = itemView.findViewById(R.id.txtvTitle)
- val count: TextView = itemView.findViewById(R.id.txtvCount)
- }
-
- internal class FeedHolder(itemView: View) : Holder(itemView) {
- val image: ImageView = itemView.findViewById(R.id.imgvCover)
- val title: TextView = itemView.findViewById(R.id.txtvTitle)
- val failure: ImageView = itemView.findViewById(R.id.itxtvFailure)
- val count: TextView = itemView.findViewById(R.id.txtvCount)
- }
-
- interface ItemAccess : OnCreateContextMenuListener {
- val count: Int
-
- fun getItem(position: Int): DrawerItem?
-
- fun isSelected(position: Int): Boolean
-
- val queueSize: Int
-
- val numberOfNewItems: Int
-
- val numberOfDownloadedItems: Int
-
- val reclaimableItems: Int
-
- val feedCounterSum: Int
-
- fun onItemClick(position: Int)
-
- fun onItemLongClick(position: Int): Boolean
-
- override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?)
- }
-
- companion object {
- const val VIEW_TYPE_NAV: Int = 0
- const val VIEW_TYPE_SECTION_DIVIDER: Int = 1
- private const val VIEW_TYPE_SUBSCRIPTION = 2
-
- /**
- * a tag used as a placeholder to indicate if the subscription list should be displayed or not
- * This tag doesn't correspond to any specific activity.
- */
- const val SUBSCRIPTION_LIST_TAG: String = "SubscriptionList"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/QueueRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/QueueRecyclerAdapter.kt
deleted file mode 100644
index 26ac335d..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/QueueRecyclerAdapter.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.annotation.SuppressLint
-import android.util.Log
-import android.view.ContextMenu
-import android.view.MenuInflater
-import android.view.MotionEvent
-import android.view.View
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.fragment.swipeactions.SwipeActions
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import ac.mdiq.podcini.view.viewholder.EpisodeItemViewHolder
-
-/**
- * List adapter for the queue.
- */
-open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeActions: SwipeActions) : EpisodeItemListAdapter(mainActivity) {
- private var dragDropEnabled: Boolean
-
- init {
- dragDropEnabled = !(UserPreferences.isQueueKeepSorted || UserPreferences.isQueueLocked)
- }
-
- fun updateDragDropEnabled() {
- dragDropEnabled = !(UserPreferences.isQueueKeepSorted || UserPreferences.isQueueLocked)
- notifyDataSetChanged()
- }
-
- @UnstableApi @SuppressLint("ClickableViewAccessibility")
- override fun afterBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
- if (!dragDropEnabled) {
- holder.dragHandle.setVisibility(View.GONE)
- holder.dragHandle.setOnTouchListener(null)
- holder.coverHolder.setOnTouchListener(null)
- } else {
- holder.dragHandle.setVisibility(View.VISIBLE)
- holder.dragHandle.setOnTouchListener { v1: View?, event: MotionEvent ->
- if (event.actionMasked == MotionEvent.ACTION_DOWN) {
- Log.d(TAG, "startDrag()")
- swipeActions.startDrag(holder)
- }
- false
- }
- holder.coverHolder.setOnTouchListener { v1, event ->
- if (event.actionMasked == MotionEvent.ACTION_DOWN) {
- val isLtr = holder.itemView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
- val factor = (if (isLtr) 1 else -1).toFloat()
- if (factor * event.x < factor * 0.5 * v1.width) {
- Log.d(TAG, "startDrag()")
- swipeActions.startDrag(holder)
- } else {
- Log.d(TAG, "Ignoring drag in right half of the image")
- }
- }
- false
- }
- }
- if (inActionMode()) {
- holder.dragHandle.setOnTouchListener(null)
- holder.coverHolder.setOnTouchListener(null)
- }
-
- holder.isInQueue.setVisibility(View.GONE)
- }
-
- @UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
- val inflater: MenuInflater = activity!!.getMenuInflater()
- inflater.inflate(R.menu.queue_context, menu)
- super.onCreateContextMenu(menu, v, menuInfo)
-
- if (!inActionMode()) {
- menu.findItem(R.id.multi_select).setVisible(true)
- val keepSorted: Boolean = UserPreferences.isQueueKeepSorted
- if (getItem(0)?.id === longPressedItem?.id || keepSorted) {
- menu.findItem(R.id.move_to_top_item).setVisible(false)
- }
- if (getItem(itemCount - 1)?.id === longPressedItem?.id || keepSorted) {
- menu.findItem(R.id.move_to_bottom_item).setVisible(false)
- }
- } else {
- menu.findItem(R.id.move_to_top_item).setVisible(false)
- menu.findItem(R.id.move_to_bottom_item).setVisible(false)
- }
- }
-
- companion object {
- private const val TAG = "QueueRecyclerAdapter"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/SelectableAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/SelectableAdapter.kt
deleted file mode 100644
index 8809eb93..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/SelectableAdapter.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.app.Activity
-import android.view.ActionMode
-import android.view.Menu
-import android.view.MenuItem
-import androidx.recyclerview.widget.RecyclerView
-import ac.mdiq.podcini.R
-
-/**
- * Used by Recyclerviews that need to provide ability to select items.
- */
-abstract class SelectableAdapter(private val activity: Activity) :
- RecyclerView.Adapter() {
-
- private var actionMode: ActionMode? = null
- private val selectedIds = HashSet()
- private var onSelectModeListener: OnSelectModeListener? = null
- var shouldSelectLazyLoadedItems: Boolean = false
- private var totalNumberOfItems = COUNT_AUTOMATICALLY
-
- fun startSelectMode(pos: Int) {
- if (inActionMode()) {
- endSelectMode()
- }
- onSelectModeListener?.onStartSelectMode()
-
- shouldSelectLazyLoadedItems = false
- selectedIds.clear()
- selectedIds.add(getItemId(pos))
- notifyDataSetChanged()
-
- actionMode = activity.startActionMode(object : ActionMode.Callback {
- override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
- val inflater = mode.menuInflater
- inflater.inflate(R.menu.multi_select_options, menu)
- return true
- }
-
- override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
- updateTitle()
- toggleSelectAllIcon(menu.findItem(R.id.select_toggle), false)
- return false
- }
-
- override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
- if (item.itemId == R.id.select_toggle) {
- val selectAll = selectedIds.size != itemCount
- shouldSelectLazyLoadedItems = selectAll
- setSelected(0, itemCount, selectAll)
- toggleSelectAllIcon(item, selectAll)
- updateTitle()
- return true
- }
- return false
- }
-
- override fun onDestroyActionMode(mode: ActionMode) {
- callOnEndSelectMode()
- actionMode = null
- shouldSelectLazyLoadedItems = false
- selectedIds.clear()
- notifyDataSetChanged()
- }
- })
- updateTitle()
- }
-
- /**
- * End action mode if currently in select mode, otherwise do nothing
- */
- fun endSelectMode() {
- if (inActionMode()) {
- callOnEndSelectMode()
- actionMode?.finish()
- }
- }
-
- fun isSelected(pos: Int): Boolean {
- return selectedIds.contains(getItemId(pos))
- }
-
- /**
- * Set the selected state of item at given position
- *
- * @param pos the position to select
- * @param selected true for selected state and false for unselected
- */
- open fun setSelected(pos: Int, selected: Boolean) {
- if (selected) {
- selectedIds.add(getItemId(pos))
- } else {
- selectedIds.remove(getItemId(pos))
- }
- updateTitle()
- }
-
- /**
- * Set the selected state of item for a given range
- *
- * @param startPos start position of range, inclusive
- * @param endPos end position of range, inclusive
- * @param selected indicates the selection state
- * @throws IllegalArgumentException if start and end positions are not valid
- */
- @Throws(IllegalArgumentException::class)
- fun setSelected(startPos: Int, endPos: Int, selected: Boolean) {
- var i = startPos
- while (i < endPos && i < itemCount) {
- setSelected(i, selected)
- i++
- }
- notifyItemRangeChanged(startPos, (endPos - startPos))
- }
-
- protected fun toggleSelection(pos: Int) {
- setSelected(pos, !isSelected(pos))
- notifyItemChanged(pos)
-
- if (selectedIds.size == 0) {
- endSelectMode()
- }
- }
-
- fun inActionMode(): Boolean {
- return actionMode != null
- }
-
- val selectedCount: Int
- get() = selectedIds.size
-
- private fun toggleSelectAllIcon(selectAllItem: MenuItem, allSelected: Boolean) {
- if (allSelected) {
- selectAllItem.setIcon(R.drawable.ic_select_none)
- selectAllItem.setTitle(R.string.deselect_all_label)
- } else {
- selectAllItem.setIcon(R.drawable.ic_select_all)
- selectAllItem.setTitle(R.string.select_all_label)
- }
- }
-
- fun updateTitle() {
- if (actionMode == null) {
- return
- }
- var totalCount = itemCount
- var selectedCount = selectedIds.size
- if (totalNumberOfItems != COUNT_AUTOMATICALLY) {
- totalCount = totalNumberOfItems
- if (shouldSelectLazyLoadedItems) {
- selectedCount += (totalNumberOfItems - itemCount)
- }
- }
- actionMode!!.title = activity.resources
- .getQuantityString(R.plurals.num_selected_label, selectedIds.size,
- selectedCount, totalCount)
- }
-
- fun setOnSelectModeListener(onSelectModeListener: OnSelectModeListener?) {
- this.onSelectModeListener = onSelectModeListener
- }
-
- private fun callOnEndSelectMode() {
- onSelectModeListener?.onEndSelectMode()
- }
-
- fun shouldSelectLazyLoadedItems(): Boolean {
- return shouldSelectLazyLoadedItems
- }
-
- /**
- * Sets the total number of items that could be lazy-loaded.
- * Can also be set to [.COUNT_AUTOMATICALLY] to simply use [.getItemCount]
- */
- fun setTotalNumberOfItems(totalNumberOfItems: Int) {
- this.totalNumberOfItems = totalNumberOfItems
- }
-
- interface OnSelectModeListener {
- fun onStartSelectMode()
-
- fun onEndSelectMode()
- }
-
- companion object {
- const val COUNT_AUTOMATICALLY: Int = -1
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/SimpleChipAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/SimpleChipAdapter.kt
deleted file mode 100644
index 008808a4..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/SimpleChipAdapter.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.chip.Chip
-import ac.mdiq.podcini.R
-
-abstract class SimpleChipAdapter(private val context: Context) : RecyclerView.Adapter() {
- init {
- setHasStableIds(true)
- }
-
- protected abstract fun getChips(): List
-
- protected abstract fun onRemoveClicked(position: Int)
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val chip = Chip(context)
- chip.isCloseIconVisible = true
- chip.setCloseIconResource(R.drawable.ic_delete)
- return ViewHolder(chip)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.chip.text = getChips()[position]
- holder.chip.setOnCloseIconClickListener { v: View? -> onRemoveClicked(position) }
- }
-
- override fun getItemCount(): Int {
- return getChips().size
- }
-
- override fun getItemId(position: Int): Long {
- return getChips()[position].hashCode().toLong()
- }
-
- class ViewHolder internal constructor(var chip: Chip) : RecyclerView.ViewHolder(chip)
-}
\ No newline at end of file
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/SimpleIconListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/SimpleIconListAdapter.kt
deleted file mode 100644
index 520c129f..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/SimpleIconListAdapter.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ArrayAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.engine.DiskCacheStrategy
-import com.bumptech.glide.request.RequestOptions
-import ac.mdiq.podcini.R
-
-/**
- * Displays a list of items that have a subtitle and an icon.
- */
-class SimpleIconListAdapter(private val context: Context,
- private val listItems: List
-) : ArrayAdapter(context, R.layout.simple_icon_list_item, listItems) {
-
- override fun getView(position: Int, view: View?, parent: ViewGroup): View {
- var view = view
- if (view == null) {
- view = View.inflate(context, R.layout.simple_icon_list_item, null)
- }
-
- val item: ListItem = listItems[position]
- (view!!.findViewById(R.id.title) as TextView).text = item.title
- (view.findViewById(R.id.subtitle) as TextView).text = item.subtitle
- Glide.with(context)
- .load(item.imageUrl)
- .apply(RequestOptions()
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .fitCenter()
- .dontAnimate())
- .into(((view.findViewById(R.id.icon) as ImageView)))
- return view
- }
-
- open class ListItem(val title: String, val subtitle: String, val imageUrl: String)
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/SubscriptionsRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/SubscriptionsRecyclerAdapter.kt
deleted file mode 100644
index fdd36973..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/SubscriptionsRecyclerAdapter.kt
+++ /dev/null
@@ -1,272 +0,0 @@
-package ac.mdiq.podcini.adapter
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.content.Context
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.view.*
-import android.widget.*
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.cardview.widget.CardView
-import androidx.fragment.app.Fragment
-import androidx.media3.common.util.UnstableApi
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.elevation.SurfaceColors
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.storage.NavDrawerData
-import ac.mdiq.podcini.fragment.FeedItemlistFragment
-import ac.mdiq.podcini.fragment.SubscriptionFragment
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import java.lang.ref.WeakReference
-import java.text.NumberFormat
-
-/**
- * Adapter for subscriptions
- */
-open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
- SelectableAdapter(mainActivity),
- View.OnCreateContextMenuListener {
-
- private val mainActivityRef: WeakReference = WeakReference(mainActivity)
- private var listItems: List
- private var selectedItem: NavDrawerData.DrawerItem? = null
- var longPressedPosition: Int = 0 // used to init actionMode
- private var columnCount = 3
-
- init {
- this.listItems = ArrayList()
- setHasStableIds(true)
- }
-
- fun setColumnCount(columnCount: Int) {
- this.columnCount = columnCount
- }
-
- fun getItem(position: Int): Any {
- return listItems[position]
- }
-
- fun getSelectedItem(): NavDrawerData.DrawerItem? {
- return selectedItem
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
- val itemView: View =
- LayoutInflater.from(mainActivityRef.get()).inflate(R.layout.subscription_item, parent, false)
- itemView.findViewById(R.id.titleLabel).visibility = if (viewType == COVER_WITH_TITLE) View.VISIBLE else View.GONE
- return SubscriptionViewHolder(itemView)
- }
-
- @UnstableApi override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
- val drawerItem: NavDrawerData.DrawerItem = listItems[position]
- val isFeed = drawerItem.type == NavDrawerData.DrawerItem.Type.FEED
- holder.bind(drawerItem)
- holder.itemView.setOnCreateContextMenuListener(this)
- if (inActionMode()) {
- if (isFeed) {
- holder.selectCheckbox.visibility = View.VISIBLE
- holder.selectView.visibility = View.VISIBLE
- }
- holder.selectCheckbox.setChecked((isSelected(position)))
- holder.selectCheckbox.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
- setSelected(holder.bindingAdapterPosition,
- isChecked)
- }
- if (holder.coverImage != null) holder.coverImage.alpha = 0.6f
- holder.count.visibility = View.GONE
- } else {
- holder.selectView.visibility = View.GONE
- if (holder.coverImage != null) holder.coverImage.alpha = 1.0f
- }
-
- holder.itemView.setOnLongClickListener { v: View? ->
- if (!inActionMode()) {
- if (isFeed) {
- longPressedPosition = holder.bindingAdapterPosition
- }
- selectedItem = drawerItem
- }
- false
- }
-
- holder.itemView.setOnTouchListener { v: View?, e: MotionEvent ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (e.isFromSource(InputDevice.SOURCE_MOUSE)
- && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
- if (!inActionMode()) {
- if (isFeed) {
- longPressedPosition = holder.bindingAdapterPosition
- }
- selectedItem = drawerItem
- }
- }
- }
- false
- }
- holder.itemView.setOnClickListener { v: View? ->
- if (isFeed) {
- if (inActionMode()) {
- holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition))
- } else {
- val fragment: Fragment = FeedItemlistFragment
- .newInstance((drawerItem as NavDrawerData.FeedDrawerItem).feed.id)
- mainActivityRef.get()?.loadChildFragment(fragment)
- }
- } else if (!inActionMode()) {
- val fragment: Fragment = SubscriptionFragment.newInstance(drawerItem.title)
- mainActivityRef.get()?.loadChildFragment(fragment)
- }
- }
- }
-
- override fun getItemCount(): Int {
- return listItems.size
- }
-
- override fun getItemId(position: Int): Long {
- if (position >= listItems.size) {
- return RecyclerView.NO_ID // Dummy views
- }
- return listItems[position].id
- }
-
- override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
- if (inActionMode() || selectedItem == null) {
- return
- }
- val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
- if (selectedItem?.type == NavDrawerData.DrawerItem.Type.FEED) {
- inflater.inflate(R.menu.nav_feed_context, menu)
- menu.findItem(R.id.multi_select).setVisible(true)
- } else {
- inflater.inflate(R.menu.nav_folder_context, menu)
- }
- menu.setHeaderTitle(selectedItem?.title)
- }
-
- fun onContextItemSelected(item: MenuItem): Boolean {
- if (item.itemId == R.id.multi_select) {
- startSelectMode(longPressedPosition)
- return true
- }
- return false
- }
-
- val selectedItems: List
- get() {
- val items = ArrayList()
- for (i in 0 until itemCount) {
- if (isSelected(i)) {
- val drawerItem: NavDrawerData.DrawerItem = listItems[i]
- if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
- val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
- items.add(feed)
- }
- }
- }
- return items
- }
-
- fun setItems(listItems: List) {
- this.listItems = listItems
- notifyDataSetChanged()
- }
-
- override fun setSelected(pos: Int, selected: Boolean) {
- val drawerItem: NavDrawerData.DrawerItem = listItems[pos]
- if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
- super.setSelected(pos, selected)
- }
- }
-
- override fun getItemViewType(position: Int): Int {
- return if (UserPreferences.shouldShowSubscriptionTitle()) COVER_WITH_TITLE else 0
- }
-
- inner class SubscriptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- private val title = itemView.findViewById(R.id.titleLabel)
- val coverImage: ImageView? = itemView.findViewById(R.id.coverImage)
- val count: TextView = itemView.findViewById(R.id.countViewPill)
- private val fallbackTitle: TextView = itemView.findViewById(R.id.fallbackTitleLabel)
- val selectView: FrameLayout = itemView.findViewById(R.id.selectContainer)
- val selectCheckbox: CheckBox = itemView.findViewById(R.id.selectCheckBox)
- private val card: CardView = itemView.findViewById(R.id.outerContainer)
- private val errorIcon: View = itemView.findViewById(R.id.errorIcon)
-
- fun bind(drawerItem: NavDrawerData.DrawerItem) {
- val drawable: Drawable? = AppCompatResources.getDrawable(selectView.context,
- R.drawable.ic_checkbox_background)
- selectView.background = drawable // Setting this in XML crashes API <= 21
- title.text = drawerItem.title
- fallbackTitle.text = drawerItem.title
- if (coverImage != null) coverImage.contentDescription = drawerItem.title
- if (drawerItem.counter > 0) {
- count.text = NumberFormat.getInstance().format(drawerItem.counter.toLong())
- count.visibility = View.VISIBLE
- } else {
- count.visibility = View.GONE
- }
-
- val coverLoader = CoverLoader(mainActivityRef.get()!!)
- val textAndImageCombined: Boolean
- if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
- val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
- textAndImageCombined = feed.isLocalFeed && feed.imageUrl != null && feed.imageUrl!!.startsWith(Feed.PREFIX_GENERATIVE_COVER)
- coverLoader.withUri(feed.imageUrl)
- errorIcon.visibility = if (feed.hasLastUpdateFailed()) View.VISIBLE else View.GONE
- } else {
- textAndImageCombined = true
- coverLoader.withResource(R.drawable.ic_tag)
- errorIcon.visibility = View.GONE
- }
- if (UserPreferences.shouldShowSubscriptionTitle()) {
- // No need for fallback title when already showing title
- fallbackTitle.visibility = View.GONE
- } else {
- coverLoader.withPlaceholderView(fallbackTitle, textAndImageCombined)
- }
- if (coverImage != null) coverLoader.withCoverView(coverImage)
- coverLoader.load()
-
- val density: Float = mainActivityRef.get()!!.resources.displayMetrics.density
- card.setCardBackgroundColor(SurfaceColors.getColorForElevation(mainActivityRef.get()!!, 1 * density))
-
- val textPadding = if (columnCount <= 3) 16 else 8
- title.setPadding(textPadding, textPadding, textPadding, textPadding)
- fallbackTitle.setPadding(textPadding, textPadding, textPadding, textPadding)
-
- var textSize = 14
- if (columnCount == 3) {
- textSize = 15
- } else if (columnCount == 2) {
- textSize = 16
- }
- title.textSize = textSize.toFloat()
- fallbackTitle.textSize = textSize.toFloat()
- }
- }
-
- class GridDividerItemDecorator : RecyclerView.ItemDecoration() {
-
- override fun getItemOffsets(outRect: Rect,
- view: View,
- parent: RecyclerView,
- state: RecyclerView.State
- ) {
- super.getItemOffsets(outRect, view, parent, state)
- val context = parent.context
- val insetOffset = convertDpToPixel(context, 1f).toInt()
- outRect[insetOffset, insetOffset, insetOffset] = insetOffset
- }
- }
-
- companion object {
- private const val COVER_WITH_TITLE = 1
-
- fun convertDpToPixel(context: Context, dp: Float): Float {
- return dp * context.resources.displayMetrics.density
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/CancelDownloadActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/CancelDownloadActionButton.kt
deleted file mode 100644
index d3715345..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/CancelDownloadActionButton.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import androidx.annotation.DrawableRes
-import androidx.annotation.StringRes
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isEnableAutodownload
-
-class CancelDownloadActionButton(item: FeedItem) : ItemActionButton(item) {
- @StringRes
- override fun getLabel(): Int {
- return R.string.cancel_download_label
- }
-
- @DrawableRes
- override fun getDrawable(): Int {
- return R.drawable.ic_cancel
- }
-
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media
- if (media != null) DownloadServiceInterface.get()?.cancel(context, media)
- if (isEnableAutodownload) {
- item.disableAutoDownload()
- DBWriter.setFeedItem(item)
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DeleteActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DeleteActionButton.kt
deleted file mode 100644
index 21e9fc9b..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DeleteActionButton.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.view.View
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.view.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary
-import androidx.media3.common.util.UnstableApi
-
-class DeleteActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.delete_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_delete
- }
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media ?: return
-
- showLocalFeedDeleteWarningIfNecessary(context, listOf(item)) { DBWriter.deleteFeedMediaOfItem(context, media.id) }
- }
-
- override val visibility: Int
- get() {
- if (item.media != null && (item.media!!.isDownloaded() || item.feed?.isLocalFeed == true)) {
- return View.VISIBLE
- }
-
- return View.INVISIBLE
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DownloadActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DownloadActionButton.kt
deleted file mode 100644
index 6ff94d43..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/DownloadActionButton.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.content.DialogInterface
-import android.view.View
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.UsageStatistics
-import ac.mdiq.podcini.core.preferences.UsageStatistics.logAction
-import ac.mdiq.podcini.core.util.NetworkUtils.isEpisodeDownloadAllowed
-import ac.mdiq.podcini.core.util.NetworkUtils.isNetworkRestricted
-import ac.mdiq.podcini.core.util.NetworkUtils.isVpnOverWifi
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.feed.FeedMedia
-import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
-
-class DownloadActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.download_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_download
- }
- override val visibility: Int
- get() = if (item.feed?.isLocalFeed == true) View.INVISIBLE else View.VISIBLE
-
- override fun onClick(context: Context) {
- val media = item.media
- if (media == null || shouldNotDownload(media)) {
- return
- }
-
- logAction(UsageStatistics.ACTION_DOWNLOAD)
-
- if (isEpisodeDownloadAllowed) {
- DownloadServiceInterface.get()?.downloadNow(context, item, false)
- } else {
- val builder = MaterialAlertDialogBuilder(context)
- .setTitle(R.string.confirm_mobile_download_dialog_title)
- .setPositiveButton(R.string.confirm_mobile_download_dialog_download_later
- ) { d: DialogInterface?, w: Int -> DownloadServiceInterface.get()?.downloadNow(context, item, false) }
- .setNeutralButton(R.string.confirm_mobile_download_dialog_allow_this_time
- ) { d: DialogInterface?, w: Int -> DownloadServiceInterface.get()?.downloadNow(context, item, true) }
- .setNegativeButton(R.string.cancel_label, null)
- if (isNetworkRestricted && isVpnOverWifi) {
- builder.setMessage(R.string.confirm_mobile_download_dialog_message_vpn)
- } else {
- builder.setMessage(R.string.confirm_mobile_download_dialog_message)
- }
-
- builder.show()
- }
- }
-
- private fun shouldNotDownload(media: FeedMedia): Boolean {
- if (media.download_url == null) return true
- val isDownloading = DownloadServiceInterface.get()?.isDownloadingEpisode(media.download_url!!)?:false
- return isDownloading || media.isDownloaded()
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/ItemActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/ItemActionButton.kt
deleted file mode 100644
index 0f2f3442..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/ItemActionButton.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.view.View
-import android.widget.ImageView
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.core.util.PlaybackStatus.isCurrentlyPlaying
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
-import ac.mdiq.podcini.storage.preferences.UserPreferences.isStreamOverDownload
-
-abstract class ItemActionButton internal constructor(@JvmField var item: FeedItem) {
- abstract fun getLabel(): Int
-
- abstract fun getDrawable(): Int
-
- abstract fun onClick(context: Context)
-
- open val visibility: Int
- get() = View.VISIBLE
-
- fun configure(button: View, icon: ImageView, context: Context) {
- button.visibility = visibility
- button.contentDescription = context.getString(getLabel())
- button.setOnClickListener { view: View? -> onClick(context) }
- icon.setImageResource(getDrawable())
- }
-
- @UnstableApi companion object {
- fun forItem(item: FeedItem): ItemActionButton {
- val media = item.media ?: return MarkAsPlayedActionButton(item)
-
- val isDownloadingMedia = when (media.download_url) {
- null -> false
- else -> DownloadServiceInterface.get()?.isDownloadingEpisode(media.download_url!!)?:false
- }
- return when {
- isCurrentlyPlaying(media) -> {
- PauseActionButton(item)
- }
- item.feed != null && item.feed!!.isLocalFeed -> {
- PlayLocalActionButton(item)
- }
- media.isDownloaded() -> {
- PlayActionButton(item)
- }
- isDownloadingMedia -> {
- CancelDownloadActionButton(item)
- }
- isStreamOverDownload -> {
- StreamActionButton(item)
- }
- else -> {
- DownloadActionButton(item)
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/MarkAsPlayedActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/MarkAsPlayedActionButton.kt
deleted file mode 100644
index ecfda4f5..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/MarkAsPlayedActionButton.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.view.View
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.model.feed.FeedItem
-import androidx.media3.common.util.UnstableApi
-
-class MarkAsPlayedActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return (if (item.hasMedia()) R.string.mark_read_label else R.string.mark_read_no_media_label)
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_check
- }
- @UnstableApi override fun onClick(context: Context) {
- if (!item.isPlayed()) {
- DBWriter.markItemPlayed(item, FeedItem.PLAYED, true)
- }
- }
-
- override val visibility: Int
- get() = if (item.isPlayed()) View.INVISIBLE else View.VISIBLE
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PauseActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PauseActionButton.kt
deleted file mode 100644
index 5860b11d..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PauseActionButton.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.view.KeyEvent
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.receiver.MediaButtonReceiver.Companion.createIntent
-import ac.mdiq.podcini.core.util.PlaybackStatus.isCurrentlyPlaying
-import ac.mdiq.podcini.model.feed.FeedItem
-
-class PauseActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.pause_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_pause
- }
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media ?: return
-
- if (isCurrentlyPlaying(media)) {
- context.sendBroadcast(createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE))
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayActionButton.kt
deleted file mode 100644
index 74300d04..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayActionButton.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
-import ac.mdiq.podcini.core.storage.DBTasks
-import ac.mdiq.podcini.core.util.playback.PlaybackServiceStarter
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.playback.MediaType
-
-class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.play_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_play_24dp
- }
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media ?: return
- if (!media.fileExists()) {
- DBTasks.notifyMissingFeedMediaFile(context, media)
- return
- }
- PlaybackServiceStarter(context, media)
- .callEvenIfRunning(true)
- .start()
-
- if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayLocalActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayLocalActionButton.kt
deleted file mode 100644
index abba22c8..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/PlayLocalActionButton.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
-import ac.mdiq.podcini.core.util.playback.PlaybackServiceStarter
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.playback.MediaType
-
-class PlayLocalActionButton(item: FeedItem?) : ItemActionButton(item!!) {
- override fun getLabel(): Int {
- return R.string.play_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_play_24dp
- }
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media ?: return
-
- PlaybackServiceStarter(context, media)
- .callEvenIfRunning(true)
- .start()
-
- if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/StreamActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/StreamActionButton.kt
deleted file mode 100644
index bce7bfd9..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/StreamActionButton.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import androidx.media3.common.util.UnstableApi
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.preferences.UsageStatistics
-import ac.mdiq.podcini.core.preferences.UsageStatistics.logAction
-import ac.mdiq.podcini.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
-import ac.mdiq.podcini.core.util.NetworkUtils.isStreamingAllowed
-import ac.mdiq.podcini.core.util.playback.PlaybackServiceStarter
-import ac.mdiq.podcini.dialog.StreamingConfirmationDialog
-import ac.mdiq.podcini.model.feed.FeedItem
-import ac.mdiq.podcini.model.playback.MediaType
-
-class StreamActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.stream_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_stream
- }
- @UnstableApi override fun onClick(context: Context) {
- val media = item.media ?: return
- logAction(UsageStatistics.ACTION_STREAM)
-
- if (!isStreamingAllowed) {
- StreamingConfirmationDialog(context, media).show()
- return
- }
- PlaybackServiceStarter(context, media)
- .callEvenIfRunning(true)
- .start()
-
- if (media.getMediaType() == MediaType.VIDEO) {
- context.startActivity(getPlayerActivityIntent(context, media))
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/VisitWebsiteActionButton.kt b/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/VisitWebsiteActionButton.kt
deleted file mode 100644
index 9705297d..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/actionbutton/VisitWebsiteActionButton.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package ac.mdiq.podcini.adapter.actionbutton
-
-import android.content.Context
-import android.view.View
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.util.IntentUtils.openInBrowser
-import ac.mdiq.podcini.model.feed.FeedItem
-
-class VisitWebsiteActionButton(item: FeedItem) : ItemActionButton(item) {
- override fun getLabel(): Int {
- return R.string.visit_website_label
- }
- override fun getDrawable(): Int {
- return R.drawable.ic_web
- }
- override fun onClick(context: Context) {
- if (item.link!= null) openInBrowser(context, item.link!!)
- }
-
- override val visibility: Int
- get() = if (item.link == null) View.INVISIBLE else View.VISIBLE
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/adapter/itunes/ItunesAdapter.kt b/app/src/main/java/ac/mdiq/podcini/adapter/itunes/ItunesAdapter.kt
deleted file mode 100644
index 88a986f0..00000000
--- a/app/src/main/java/ac/mdiq/podcini/adapter/itunes/ItunesAdapter.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package ac.mdiq.podcini.adapter.itunes
-
-import ac.mdiq.podcini.activity.MainActivity
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ArrayAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.engine.DiskCacheStrategy
-import com.bumptech.glide.load.resource.bitmap.FitCenter
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners
-import com.bumptech.glide.request.RequestOptions
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.net.discovery.PodcastSearchResult
-import androidx.media3.common.util.UnstableApi
-
-class ItunesAdapter(
- /**
- * Related Context
- */
- private val context: Context, objects: List
-) : ArrayAdapter(context, 0, objects) {
- /**
- * List holding the podcasts found in the search
- */
- private val data: List = objects
-
- @UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- //Current podcast
- val podcast: PodcastSearchResult = data[position]
-
- //ViewHolder
- val viewHolder: PodcastViewHolder
-
- //Resulting view
- val view: View
-
- //Handle view holder stuff
- if (convertView == null) {
- view = (context as MainActivity).layoutInflater.inflate(R.layout.itunes_podcast_listitem, parent, false)
- viewHolder = PodcastViewHolder(view)
- view.tag = viewHolder
- } else {
- view = convertView
- viewHolder = view.tag as PodcastViewHolder
- }
-
- // Set the title
- viewHolder.titleView.text = podcast.title
- if (podcast.author != null && podcast.author!!.trim { it <= ' ' }.isNotEmpty()) {
- viewHolder.authorView.text = podcast.author
- viewHolder.authorView.visibility = View.VISIBLE
- } else if (podcast.feedUrl != null && !podcast.feedUrl!!.contains("itunes.apple.com")) {
- viewHolder.authorView.text = podcast.feedUrl
- viewHolder.authorView.visibility = View.VISIBLE
- } else {
- viewHolder.authorView.visibility = View.GONE
- }
-
- //Update the empty imageView with the image from the feed
- Glide.with(context)
- .load(podcast.imageUrl)
- .apply(RequestOptions()
- .placeholder(R.color.light_gray)
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .transform(FitCenter(),
- RoundedCorners((4 * context.resources.displayMetrics.density).toInt()))
- .dontAnimate())
- .into(viewHolder.coverView)
-
- //Feed the grid view
- return view
- }
-
- /**
- * View holder object for the GridView
- */
- internal class PodcastViewHolder(view: View) {
- /**
- * ImageView holding the Podcast image
- */
- val coverView: ImageView = view.findViewById(R.id.imgvCover)
-
- /**
- * TextView holding the Podcast title
- */
- val titleView: TextView = view.findViewById(R.id.txtvTitle)
-
- val authorView: TextView = view.findViewById(R.id.txtvAuthor)
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/asynctask/DocumentFileExportWorker.kt b/app/src/main/java/ac/mdiq/podcini/asynctask/DocumentFileExportWorker.kt
deleted file mode 100644
index b45793db..00000000
--- a/app/src/main/java/ac/mdiq/podcini/asynctask/DocumentFileExportWorker.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package ac.mdiq.podcini.asynctask
-
-import android.content.Context
-import android.net.Uri
-import androidx.documentfile.provider.DocumentFile
-import ac.mdiq.podcini.core.export.ExportWriter
-import ac.mdiq.podcini.core.storage.DBReader
-import io.reactivex.Observable
-import io.reactivex.ObservableEmitter
-import java.io.IOException
-import java.io.OutputStream
-import java.io.OutputStreamWriter
-import java.nio.charset.Charset
-
-/**
- * Writes an OPML file into the user selected export directory in the background.
- */
-class DocumentFileExportWorker(private val exportWriter: ExportWriter,
- private val context: Context,
- private val outputFileUri: Uri
-) {
- fun exportObservable(): Observable {
- val output = DocumentFile.fromSingleUri(context, outputFileUri)
- return Observable.create { subscriber: ObservableEmitter ->
- var outputStream: OutputStream? = null
- var writer: OutputStreamWriter? = null
- try {
- if (output == null) throw IOException()
- val uri = output.uri
- outputStream = context.contentResolver.openOutputStream(uri, "wt")
- if (outputStream == null) throw IOException()
- writer = OutputStreamWriter(outputStream, Charset.forName("UTF-8"))
- exportWriter.writeDocument(DBReader.getFeedList(), writer, context)
- subscriber.onNext(output)
- } catch (e: IOException) {
- subscriber.onError(e)
- } finally {
- if (writer != null) {
- try {
- writer.close()
- } catch (e: IOException) {
- subscriber.onError(e)
- }
- }
- if (outputStream != null) {
- try {
- outputStream.close()
- } catch (e: IOException) {
- subscriber.onError(e)
- }
- }
- subscriber.onComplete()
- }
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/asynctask/ExportWorker.kt b/app/src/main/java/ac/mdiq/podcini/asynctask/ExportWorker.kt
deleted file mode 100644
index 02c9b5a6..00000000
--- a/app/src/main/java/ac/mdiq/podcini/asynctask/ExportWorker.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package ac.mdiq.podcini.asynctask
-
-import android.content.Context
-import android.util.Log
-import ac.mdiq.podcini.core.export.ExportWriter
-import ac.mdiq.podcini.core.storage.DBReader
-import ac.mdiq.podcini.storage.preferences.UserPreferences.getDataFolder
-import io.reactivex.Observable
-import io.reactivex.ObservableEmitter
-import java.io.File
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.OutputStreamWriter
-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)
-
- fun exportObservable(): Observable {
- if (output.exists()) {
- val success = output.delete()
- Log.w(TAG, "Overwriting previously exported file: $success")
- }
- return Observable.create { subscriber: ObservableEmitter ->
- var writer: OutputStreamWriter? = null
- try {
- writer = OutputStreamWriter(FileOutputStream(output), Charset.forName("UTF-8"))
- exportWriter.writeDocument(DBReader.getFeedList(), writer, context)
- subscriber.onNext(output)
- } catch (e: IOException) {
- subscriber.onError(e)
- } finally {
- if (writer != null) {
- try {
- writer.close()
- } catch (e: IOException) {
- subscriber.onError(e)
- }
- }
- subscriber.onComplete()
- }
- }
- }
-
- companion object {
- private const val EXPORT_DIR = "export/"
- private const val TAG = "ExportWorker"
- private const val DEFAULT_OUTPUT_NAME = "podcini-feeds"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/config/ApplicationCallbacksImpl.kt b/app/src/main/java/ac/mdiq/podcini/config/ApplicationCallbacksImpl.kt
deleted file mode 100644
index 2d56f9e3..00000000
--- a/app/src/main/java/ac/mdiq/podcini/config/ApplicationCallbacksImpl.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package ac.mdiq.podcini.config
-
-import android.app.Application
-import ac.mdiq.podcini.PodciniApp
-import ac.mdiq.podcini.core.ApplicationCallbacks
-
-
-class ApplicationCallbacksImpl : ApplicationCallbacks {
- override fun getApplicationInstance(): Application {
- return PodciniApp.getInstance()
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/AllEpisodesFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/AllEpisodesFilterDialog.kt
deleted file mode 100644
index b2e5753b..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/AllEpisodesFilterDialog.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.os.Bundle
-import ac.mdiq.podcini.model.feed.FeedItemFilter
-import org.greenrobot.eventbus.EventBus
-
-class AllEpisodesFilterDialog : ItemFilterDialog() {
- override fun onFilterChanged(newFilterValues: Set) {
- EventBus.getDefault().post(AllEpisodesFilterChangedEvent(newFilterValues))
- }
-
- class AllEpisodesFilterChangedEvent(val filterValues: Set?)
- companion object {
- fun newInstance(filter: FeedItemFilter?): AllEpisodesFilterDialog {
- val dialog = AllEpisodesFilterDialog()
- val arguments = Bundle()
- arguments.putSerializable(ARGUMENT_FILTER, filter)
- dialog.arguments = arguments
- return dialog
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/AuthenticationDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/AuthenticationDialog.kt
deleted file mode 100644
index da53f91d..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/AuthenticationDialog.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.content.DialogInterface
-import android.text.method.HideReturnsTransformationMethod
-import android.text.method.PasswordTransformationMethod
-import android.view.LayoutInflater
-import android.view.View
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.databinding.AuthenticationDialogBinding
-
-/**
- * Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
- */
-abstract class AuthenticationDialog(context: Context?, titleRes: Int, enableUsernameField: Boolean,
- usernameInitialValue: String?, passwordInitialValue: String?
-) : MaterialAlertDialogBuilder(
- context!!) {
- var passwordHidden: Boolean = true
-
- init {
- setTitle(titleRes)
- val viewBinding = AuthenticationDialogBinding.inflate(LayoutInflater.from(context))
- setView(viewBinding.root)
-
- viewBinding.usernameEditText.isEnabled = enableUsernameField
- if (usernameInitialValue != null) {
- viewBinding.usernameEditText.setText(usernameInitialValue)
- }
- if (passwordInitialValue != null) {
- viewBinding.passwordEditText.setText(passwordInitialValue)
- }
- viewBinding.showPasswordButton.setOnClickListener { v: View? ->
- if (passwordHidden) {
- viewBinding.passwordEditText.transformationMethod = HideReturnsTransformationMethod.getInstance()
- viewBinding.showPasswordButton.alpha = 1.0f
- } else {
- viewBinding.passwordEditText.transformationMethod = PasswordTransformationMethod.getInstance()
- viewBinding.showPasswordButton.alpha = 0.6f
- }
- passwordHidden = !passwordHidden
- }
-
- setOnCancelListener { dialog: DialogInterface? -> onCancelled() }
- setNegativeButton(R.string.cancel_label) { dialog: DialogInterface?, which: Int -> onCancelled() }
- setPositiveButton(R.string.confirm_label) { dialog: DialogInterface?, which: Int ->
- onConfirmed(viewBinding.usernameEditText.text.toString(),
- viewBinding.passwordEditText.text.toString())
- }
- }
-
- protected open fun onCancelled() {
- }
-
- protected abstract fun onConfirmed(username: String, password: String)
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/ChooseDataFolderDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/ChooseDataFolderDialog.kt
deleted file mode 100644
index 1dcdedce..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/ChooseDataFolderDialog.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.view.View
-import androidx.core.util.Consumer
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.adapter.DataFolderAdapter
-
-object ChooseDataFolderDialog {
- fun showDialog(context: Context?, handlerFunc: Consumer) {
- val content = View.inflate(context, R.layout.choose_data_folder_dialog, null)
- val dialog = MaterialAlertDialogBuilder(context!!)
- .setView(content)
- .setTitle(R.string.choose_data_directory)
- .setMessage(R.string.choose_data_directory_message)
- .setNegativeButton(R.string.cancel_label, null)
- .create()
- val recyclerView = content.findViewById(R.id.recyclerView)
- recyclerView.layoutManager = LinearLayoutManager(context)
- val adapter = DataFolderAdapter(context) { path: String? ->
- dialog.dismiss()
- handlerFunc.accept(path)
- }
- recyclerView.adapter = adapter
-
- if (adapter.itemCount != 0) {
- dialog.show()
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/DownloadLogDetailsDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/DownloadLogDetailsDialog.kt
deleted file mode 100644
index 93ea471a..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/DownloadLogDetailsDialog.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.os.Build
-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.core.storage.DBReader
-import ac.mdiq.podcini.core.util.DownloadErrorLabel.from
-import ac.mdiq.podcini.event.MessageEvent
-import ac.mdiq.podcini.model.download.DownloadResult
-import ac.mdiq.podcini.model.feed.Feed
-import ac.mdiq.podcini.model.feed.FeedMedia
-import org.greenrobot.eventbus.EventBus
-
-class DownloadLogDetailsDialog(context: Context, status: DownloadResult) : MaterialAlertDialogBuilder(context) {
- init {
- var url = "unknown"
- if (status.feedfileType == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
- val media = DBReader.getFeedMedia(status.feedfileId)
- if (media != null) {
- url = media.download_url?:""
- }
- } else if (status.feedfileType == Feed.FEEDFILETYPE_FEED) {
- val feed = DBReader.getFeed(status.feedfileId)
- if (feed != null) {
- url = feed.download_url?:""
- }
- }
-
- var message = context.getString(R.string.download_successful)
- if (!status.isSuccessful) {
- message = status.reasonDetailed
- }
-
- val messageFull = context.getString(R.string.download_log_details_message,
- context.getString(from(status.reason)), message, url)
- setTitle(R.string.download_error_details)
- setMessage(messageFull)
- setPositiveButton("OK", null)
- setNeutralButton(R.string.copy_to_clipboard) { dialog, which ->
- 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)))
- }
- }
- }
-
- override fun show(): AlertDialog {
- val dialog = super.show()
- (dialog.findViewById(R.id.message) as? TextView)?.setTextIsSelectable(true)
- return dialog
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/DrawerPreferencesDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/DrawerPreferencesDialog.kt
deleted file mode 100644
index 06d97029..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/DrawerPreferencesDialog.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.content.DialogInterface
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.fragment.NavDrawerFragment
-import ac.mdiq.podcini.storage.preferences.UserPreferences
-import ac.mdiq.podcini.storage.preferences.UserPreferences.defaultPage
-import ac.mdiq.podcini.storage.preferences.UserPreferences.hiddenDrawerItems
-
-object DrawerPreferencesDialog {
- fun show(context: Context, callback: Runnable?) {
- val hiddenDrawerItems = hiddenDrawerItems?.toMutableList()?: mutableListOf()
- val navTitles = context.resources.getStringArray(R.array.nav_drawer_titles)
- val checked = BooleanArray(NavDrawerFragment.NAV_DRAWER_TAGS.size)
- for (i in NavDrawerFragment.NAV_DRAWER_TAGS.indices) {
- val tag = NavDrawerFragment.NAV_DRAWER_TAGS[i]
- if (!hiddenDrawerItems.contains(tag)) {
- checked[i] = true
- }
- }
- val builder = MaterialAlertDialogBuilder(context)
- builder.setTitle(R.string.drawer_preferences)
- builder.setMultiChoiceItems(navTitles, checked) { dialog: DialogInterface?, which: Int, isChecked: Boolean ->
- if (isChecked) {
- hiddenDrawerItems.remove(NavDrawerFragment.NAV_DRAWER_TAGS[which])
- } else {
- hiddenDrawerItems.add(NavDrawerFragment.NAV_DRAWER_TAGS[which])
- }
- }
- builder.setPositiveButton(R.string.confirm_label) { dialog: DialogInterface?, which: Int ->
- UserPreferences.hiddenDrawerItems = hiddenDrawerItems
- if (hiddenDrawerItems.contains(defaultPage)) {
- for (tag in NavDrawerFragment.NAV_DRAWER_TAGS) {
- if (!hiddenDrawerItems.contains(tag)) {
- defaultPage = tag
- break
- }
- }
- }
- callback?.run()
- }
- builder.setNegativeButton(R.string.cancel_label, null)
- builder.create().show()
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/EditUrlSettingsDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/EditUrlSettingsDialog.kt
deleted file mode 100644
index c23526f9..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/EditUrlSettingsDialog.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.app.Activity
-import android.content.DialogInterface
-import android.os.CountDownTimer
-import android.view.LayoutInflater
-import androidx.appcompat.app.AlertDialog
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.core.util.download.FeedUpdateManager.runOnce
-import ac.mdiq.podcini.databinding.EditTextDialogBinding
-import ac.mdiq.podcini.model.feed.Feed
-import androidx.media3.common.util.UnstableApi
-import java.lang.ref.WeakReference
-import java.util.*
-import java.util.concurrent.ExecutionException
-
- @UnstableApi
- abstract class EditUrlSettingsDialog(activity: Activity, private val feed: Feed) {
- private val activityRef = WeakReference(activity)
-
- fun show() {
- val activity = activityRef.get() ?: return
-
- val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity))
-
- binding.urlEditText.setText(feed.download_url)
-
- MaterialAlertDialogBuilder(activity)
- .setView(binding.root)
- .setTitle(R.string.edit_url_menu)
- .setPositiveButton(android.R.string.ok) { d: DialogInterface?, input: Int -> showConfirmAlertDialog(binding.urlEditText.text.toString()) }
- .setNegativeButton(R.string.cancel_label, null)
- .show()
- }
-
- @UnstableApi private fun onConfirmed(original: String, updated: String) {
- try {
- DBWriter.updateFeedDownloadURL(original, updated).get()
- feed.download_url = updated
- runOnce(activityRef.get()!!, feed)
- } catch (e: ExecutionException) {
- throw RuntimeException(e)
- } catch (e: InterruptedException) {
- throw RuntimeException(e)
- }
- }
-
- @UnstableApi private fun showConfirmAlertDialog(url: String) {
- val activity = activityRef.get()
-
- val alertDialog = MaterialAlertDialogBuilder(activity!!)
- .setTitle(R.string.edit_url_menu)
- .setMessage(R.string.edit_url_confirmation_msg)
- .setPositiveButton(android.R.string.ok) { d: DialogInterface?, input: Int ->
- onConfirmed(feed.download_url?:"", url)
- setUrl(url)
- }
- .setNegativeButton(R.string.cancel_label, null)
- .show()
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
-
- object : CountDownTimer(15000, 1000) {
- override fun onTick(millisUntilFinished: Long) {
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).text = String.format(Locale.getDefault(), "%s (%d)",
- activity.getString(android.R.string.ok), millisUntilFinished / 1000 + 1)
- }
-
- override fun onFinish() {
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
- alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(android.R.string.ok)
- }
- }.start()
- }
-
- protected abstract fun setUrl(url: String?)
-
- companion object {
- const val TAG: String = "EditUrlSettingsDialog"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/EpisodeFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/EpisodeFilterDialog.kt
deleted file mode 100644
index 8e652944..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/EpisodeFilterDialog.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.content.DialogInterface
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.CompoundButton
-import androidx.recyclerview.widget.GridLayoutManager
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.adapter.SimpleChipAdapter
-import ac.mdiq.podcini.databinding.EpisodeFilterDialogBinding
-import ac.mdiq.podcini.model.feed.FeedFilter
-import ac.mdiq.podcini.view.ItemOffsetDecoration
-
-/**
- * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion
- */
-abstract class EpisodeFilterDialog(context: Context?, filter: FeedFilter) : MaterialAlertDialogBuilder(
- context!!) {
- private val viewBinding = EpisodeFilterDialogBinding.inflate(LayoutInflater.from(context))
- private val termList: MutableList
-
- init {
- setTitle(R.string.episode_filters_label)
- setView(viewBinding.root)
-
- viewBinding.durationCheckBox.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
- viewBinding.episodeFilterDurationText.isEnabled = isChecked
- }
- if (filter.hasMinimalDurationFilter()) {
- viewBinding.durationCheckBox.isChecked = true
- // Store minimal duration in seconds, show in minutes
- viewBinding.episodeFilterDurationText
- .setText((filter.minimalDurationFilter / 60).toString())
- } else {
- viewBinding.episodeFilterDurationText.isEnabled = false
- }
-
- if (filter.excludeOnly()) {
- termList = filter.getExcludeFilter().toMutableList()
- viewBinding.excludeRadio.isChecked = true
- } else {
- termList = filter.getIncludeFilter().toMutableList()
- viewBinding.includeRadio.isChecked = true
- }
- setupWordsList()
-
- setNegativeButton(R.string.cancel_label, null)
- setPositiveButton(R.string.confirm_label) { dialog: DialogInterface, which: Int ->
- this.onConfirmClick(dialog,
- which)
- }
- }
-
- private fun setupWordsList() {
- viewBinding.termsRecycler.layoutManager = GridLayoutManager(context, 2)
- viewBinding.termsRecycler.addItemDecoration(ItemOffsetDecoration(context, 4))
- val adapter: SimpleChipAdapter = object : SimpleChipAdapter(context) {
- override fun getChips(): List {
- return termList
- }
-
- override fun onRemoveClicked(position: Int) {
- termList.removeAt(position)
- notifyDataSetChanged()
- }
- }
- viewBinding.termsRecycler.adapter = adapter
- viewBinding.termsTextInput.setEndIconOnClickListener { v: View? ->
- val newWord = viewBinding.termsTextInput.editText!!.text.toString().replace("\"", "").trim { it <= ' ' }
- if (TextUtils.isEmpty(newWord) || termList.contains(newWord)) {
- return@setEndIconOnClickListener
- }
- termList.add(newWord)
- viewBinding.termsTextInput.editText!!.setText("")
- adapter.notifyDataSetChanged()
- }
- }
-
- protected abstract fun onConfirmed(filter: FeedFilter)
-
- private fun onConfirmClick(dialog: DialogInterface, which: Int) {
- var minimalDuration = -1
- if (viewBinding.durationCheckBox.isChecked) {
- try {
- // Store minimal duration in seconds
- minimalDuration = viewBinding.episodeFilterDurationText.text.toString().toInt() * 60
- } catch (e: NumberFormatException) {
- // Do not change anything on error
- }
- }
- var excludeFilter = ""
- var includeFilter = ""
- if (viewBinding.includeRadio.isChecked) {
- includeFilter = toFilterString(termList)
- } else {
- excludeFilter = toFilterString(termList)
- }
- onConfirmed(FeedFilter(includeFilter, excludeFilter, minimalDuration))
- }
-
- private fun toFilterString(words: List?): String {
- val result = StringBuilder()
- for (word in words!!) {
- result.append("\"").append(word).append("\" ")
- }
- return result.toString()
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/FeedItemFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/FeedItemFilterDialog.kt
deleted file mode 100644
index 93b90a5f..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/FeedItemFilterDialog.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.os.Bundle
-import ac.mdiq.podcini.core.storage.DBWriter
-import ac.mdiq.podcini.model.feed.Feed
-
-class FeedItemFilterDialog : ItemFilterDialog() {
- override fun onFilterChanged(newFilterValues: Set) {
- val feedId = requireArguments().getLong(ARGUMENT_FEED_ID)
- DBWriter.setFeedItemsFilter(feedId, newFilterValues)
- }
-
- companion object {
- private const val ARGUMENT_FEED_ID = "feedId"
-
- fun newInstance(feed: Feed): FeedItemFilterDialog {
- val dialog = FeedItemFilterDialog()
- val arguments = Bundle()
- arguments.putSerializable(ARGUMENT_FILTER, feed.itemFilter)
- arguments.putLong(ARGUMENT_FEED_ID, feed.id)
- dialog.arguments = arguments
- return dialog
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/FeedPreferenceSkipDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/FeedPreferenceSkipDialog.kt
deleted file mode 100644
index 9fa9f1ee..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/FeedPreferenceSkipDialog.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.content.DialogInterface
-import android.view.View
-import android.widget.EditText
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-
-/**
- * Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
- */
-abstract class FeedPreferenceSkipDialog(context: Context?, skipIntroInitialValue: Int,
- skipEndInitialValue: Int
-) : MaterialAlertDialogBuilder(context!!) {
- init {
- setTitle(R.string.pref_feed_skip)
- val rootView = View.inflate(context, R.layout.feed_pref_skip_dialog, null)
- setView(rootView)
-
- val etxtSkipIntro = rootView.findViewById(R.id.etxtSkipIntro)
- val etxtSkipEnd = rootView.findViewById(R.id.etxtSkipEnd)
-
- etxtSkipIntro.setText(skipIntroInitialValue.toString())
- etxtSkipEnd.setText(skipEndInitialValue.toString())
-
- setNegativeButton(R.string.cancel_label, null)
- setPositiveButton(R.string.confirm_label) { dialog: DialogInterface?, which: Int ->
- var skipIntro = try {
- etxtSkipIntro.text.toString().toInt()
- } catch (e: NumberFormatException) {
- 0
- }
- var skipEnding = try {
- etxtSkipEnd.text.toString().toInt()
- } catch (e: NumberFormatException) {
- 0
- }
- onConfirmed(skipIntro, skipEnding)
- }
- }
-
- protected abstract fun onConfirmed(skipIntro: Int, skipEndig: Int)
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/FeedSortDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/FeedSortDialog.kt
deleted file mode 100644
index b4fe5c02..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/FeedSortDialog.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.content.Context
-import android.content.DialogInterface
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.event.UnreadItemsUpdateEvent
-import ac.mdiq.podcini.storage.preferences.UserPreferences.feedOrder
-import ac.mdiq.podcini.storage.preferences.UserPreferences.setFeedOrder
-import org.greenrobot.eventbus.EventBus
-
-object FeedSortDialog {
- fun showDialog(context: Context) {
- val dialog = MaterialAlertDialogBuilder(context)
- dialog.setTitle(context.getString(R.string.pref_nav_drawer_feed_order_title))
- dialog.setNegativeButton(android.R.string.cancel) { d: DialogInterface, listener: Int -> d.dismiss() }
-
- val selected = feedOrder
- val entryValues =
- listOf(*context.resources.getStringArray(R.array.nav_drawer_feed_order_values))
- val selectedIndex = entryValues.indexOf("" + selected)
-
- val items = context.resources.getStringArray(R.array.nav_drawer_feed_order_options)
- dialog.setSingleChoiceItems(items, selectedIndex) { d: DialogInterface, which: Int ->
- if (selectedIndex != which) {
- setFeedOrder(entryValues[which])
- //Update subscriptions
- EventBus.getDefault().post(UnreadItemsUpdateEvent())
- }
- d.dismiss()
- }
- dialog.show()
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/dialog/ItemFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/dialog/ItemFilterDialog.kt
deleted file mode 100644
index 83693d12..00000000
--- a/app/src/main/java/ac/mdiq/podcini/dialog/ItemFilterDialog.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package ac.mdiq.podcini.dialog
-
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.FrameLayout
-import android.widget.LinearLayout
-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.core.feed.FeedItemFilterGroup
-import ac.mdiq.podcini.databinding.FilterDialogBinding
-import ac.mdiq.podcini.databinding.FilterDialogRowBinding
-import ac.mdiq.podcini.model.feed.FeedItemFilter
-
-abstract class ItemFilterDialog : BottomSheetDialogFragment() {
- private var rows: LinearLayout? = null
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val layout = inflater.inflate(R.layout.filter_dialog, null, false)
- val binding = FilterDialogBinding.bind(layout)
- rows = binding.filterRows
- val filter = requireArguments().getSerializable(ARGUMENT_FILTER) as FeedItemFilter?
-
- //add filter rows
- for (item in FeedItemFilterGroup.entries) {
- val rowBinding = FilterDialogRowBinding.inflate(inflater)
- rowBinding.root.addOnButtonCheckedListener { group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean ->
- onFilterChanged(newFilterValues)
- }
- rowBinding.filterButton1.setText(item.values[0].displayName)
- rowBinding.filterButton1.tag = item.values[0].filterId
- rowBinding.filterButton2.setText(item.values[1].displayName)
- rowBinding.filterButton2.tag = item.values[1].filterId
- rowBinding.filterButton1.maxLines = 3
- rowBinding.filterButton1.isSingleLine = false
- rowBinding.filterButton2.maxLines = 3
- rowBinding.filterButton2.isSingleLine = false
- rows!!.addView(rowBinding.root, rows!!.childCount - 1)
- }
-
- binding.confirmFiltermenu.setOnClickListener { view1: View? -> dismiss() }
- binding.resetFiltermenu.setOnClickListener { view1: View? ->
- onFilterChanged(emptySet())
- for (i in 0 until rows!!.childCount) {
- if (rows!!.getChildAt(i) is MaterialButtonToggleGroup) {
- (rows!!.getChildAt(i) as MaterialButtonToggleGroup).clearChecked()
- }
- }
- }
-
- for (filterId in filter!!.values) {
- if (!TextUtils.isEmpty(filterId)) {
- val button = layout.findViewWithTag