summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CONTRIBUTORS1
-rw-r--r--app/build.gradle48
-rw-r--r--app/proguard.cfg5
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java15
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java60
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java2
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java108
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java119
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java453
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java9
-rw-r--r--app/src/main/AndroidManifest.xml4
-rw-r--r--app/src/main/assets/.gitignore2
-rw-r--r--app/src/main/assets/LICENSE_ANDROID_ICONIFY.txt18
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java110
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java42
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java333
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java112
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java39
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java20
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java40
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java (renamed from app/src/main/java/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java)26
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java89
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java102
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java50
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java161
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java18
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java27
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java539
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java11
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java155
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java9
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java443
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java20
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java82
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java22
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java6
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java58
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java40
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java42
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java93
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java54
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java43
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java47
-rw-r--r--app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java45
-rw-r--r--app/src/main/res/layout/all_episodes_fragment.xml52
-rw-r--r--app/src/main/res/layout/audioplayer_activity.xml24
-rw-r--r--app/src/main/res/layout/downloadlog_item.xml120
-rw-r--r--app/src/main/res/layout/feedinfo.xml162
-rw-r--r--app/src/main/res/layout/feeditemlist_header.xml30
-rw-r--r--app/src/main/res/layout/feeditemlist_item.xml56
-rw-r--r--app/src/main/res/layout/nav_feedlistitem.xml42
-rw-r--r--app/src/main/res/layout/nav_list.xml64
-rw-r--r--app/src/main/res/layout/nav_listitem.xml12
-rw-r--r--app/src/main/res/layout/nav_section_item.xml2
-rw-r--r--app/src/main/res/layout/new_episodes_listitem.xml46
-rw-r--r--app/src/main/res/layout/queue_listitem.xml6
-rw-r--r--app/src/main/res/menu/allepisodes_context.xml56
-rw-r--r--app/src/main/res/menu/feeditem.xml77
-rw-r--r--app/src/main/res/menu/feeditem_options.xml (renamed from app/src/main/res/menu/feeditem_dialog.xml)19
-rw-r--r--app/src/main/res/menu/feeditemlist_context.xml56
-rw-r--r--app/src/main/res/menu/feedlist.xml15
-rw-r--r--app/src/main/res/menu/gpodder_podcasts.xml13
-rw-r--r--app/src/main/res/menu/new_episodes.xml14
-rw-r--r--app/src/main/res/menu/queue.xml15
-rw-r--r--app/src/main/res/menu/queue_context.xml44
-rw-r--r--app/src/main/res/xml/preferences.xml18
-rw-r--r--app/src/main/templates/about.html (renamed from app/src/main/assets/about.html)11
-rw-r--r--build.gradle4
-rw-r--r--core/build.gradle8
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/ApplicationTest.java13
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java15
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java)20
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java61
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java123
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java33
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java82
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java69
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java82
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java574
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java25
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java146
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java77
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java11
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java27
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java102
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java267
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java71
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java107
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java188
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java60
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/IntList.java240
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java252
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java50
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.pngbin1601 -> 2359 bytes
-rwxr-xr-xcore/src/main/res/drawable-hdpi/ic_feed_white_24dp.pngbin1367 -> 1663 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.pngbin0 -> 135 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_filter_white_24dp.pngbin0 -> 131 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.pngbin0 -> 366 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.pngbin0 -> 358 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.pngbin0 -> 362 bytes
-rw-r--r--core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.pngbin0 -> 356 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.pngbin1018 -> 1566 bytes
-rwxr-xr-xcore/src/main/res/drawable-mdpi/ic_feed_white_24dp.pngbin875 -> 1156 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.pngbin0 -> 111 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_filter_white_24dp.pngbin0 -> 111 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.pngbin0 -> 242 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.pngbin0 -> 237 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.pngbin0 -> 242 bytes
-rw-r--r--core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.pngbin0 -> 238 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.pngbin2223 -> 3200 bytes
-rwxr-xr-xcore/src/main/res/drawable-xhdpi/ic_feed_white_24dp.pngbin1933 -> 2314 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.pngbin0 -> 141 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.pngbin0 -> 141 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.pngbin0 -> 430 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.pngbin0 -> 421 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.pngbin0 -> 427 bytes
-rw-r--r--core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.pngbin0 -> 420 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.pngbin3265 -> 4754 bytes
-rwxr-xr-xcore/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.pngbin2884 -> 3406 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.pngbin0 -> 189 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.pngbin0 -> 188 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.pngbin0 -> 630 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.pngbin0 -> 621 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.pngbin0 -> 627 bytes
-rw-r--r--core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.pngbin0 -> 621 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.pngbin0 -> 235 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.pngbin0 -> 234 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.pngbin0 -> 854 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.pngbin0 -> 839 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.pngbin0 -> 850 bytes
-rw-r--r--core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.pngbin0 -> 838 bytes
-rw-r--r--core/src/main/res/values-az/strings.xml2
-rw-r--r--core/src/main/res/values-ca/strings.xml2
-rw-r--r--core/src/main/res/values-cs-rCZ/strings.xml2
-rw-r--r--core/src/main/res/values-da/strings.xml2
-rw-r--r--core/src/main/res/values-de/strings.xml2
-rw-r--r--core/src/main/res/values-es-rES/strings.xml2
-rw-r--r--core/src/main/res/values-es/strings.xml3
-rw-r--r--core/src/main/res/values-fr/strings.xml2
-rw-r--r--core/src/main/res/values-hi-rIN/strings.xml2
-rw-r--r--core/src/main/res/values-it-rIT/strings.xml7
-rw-r--r--core/src/main/res/values-iw-rIL/strings.xml16
-rw-r--r--core/src/main/res/values-ja/strings.xml2
-rw-r--r--core/src/main/res/values-ko/strings.xml18
-rw-r--r--core/src/main/res/values-nl/strings.xml2
-rw-r--r--core/src/main/res/values-pl-rPL/strings.xml2
-rw-r--r--core/src/main/res/values-pt-rBR/strings.xml2
-rw-r--r--core/src/main/res/values-pt/strings.xml2
-rw-r--r--core/src/main/res/values-ro-rRO/strings.xml2
-rw-r--r--core/src/main/res/values-ru/strings.xml17
-rw-r--r--core/src/main/res/values-sv-rSE/strings.xml2
-rw-r--r--core/src/main/res/values-tr/strings.xml19
-rw-r--r--core/src/main/res/values-uk-rUA/strings.xml16
-rw-r--r--core/src/main/res/values-zh-rCN/strings.xml2
-rw-r--r--core/src/main/res/values/arrays.xml35
-rw-r--r--core/src/main/res/values/attrs.xml3
-rw-r--r--core/src/main/res/values/dimens.xml4
-rw-r--r--core/src/main/res/values/strings.xml45
-rw-r--r--core/src/main/res/values/styles.xml12
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin51017 -> 52266 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rw-r--r--gradlew.bat180
-rw-r--r--library/drag-sort-listview/src/main/java/com/mobeta/android/dslv/DragSortController.java6
182 files changed, 5629 insertions, 2381 deletions
diff --git a/.gitignore b/.gitignore
index 482ba1839..5a3070e8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@ gen-external-apklibs
out
#transifex downloads
changelog
+description
# other
*.odg#
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 9e6042bf4..65d29f75a 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -1,5 +1,6 @@
Code contributions:
+danieloeh
patheticpat
toggles
ligi
diff --git a/app/build.gradle b/app/build.gradle
index f9c768d3e..40451e8ea 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,27 +1,46 @@
+import org.apache.tools.ant.filters.ReplaceTokens
+
apply plugin: 'com.android.application'
repositories {
mavenCentral()
}
+
dependencies {
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
+ compile 'com.android.support:gridlayout-v7:21.0.3'
compile 'org.apache.commons:commons-lang3:3.3.2'
compile('org.shredzone.flattr4j:flattr4j-core:2.12') {
exclude group: 'org.json', module: 'json'
}
compile 'commons-io:commons-io:2.4'
- compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
compile 'org.jsoup:jsoup:1.7.3'
- compile 'com.squareup.picasso:picasso:2.4.0'
- compile 'com.squareup.okhttp:okhttp:2.2.0'
- compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
+ compile 'com.squareup.picasso:picasso:2.5.2'
+ compile 'com.squareup.okhttp:okhttp:2.3.0'
+ compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
compile 'com.squareup.okio:okio:1.2.0'
compile 'de.greenrobot:eventbus:2.4.0'
+ compile 'com.joanzapata.android:android-iconify:1.0.9'
+
compile project(':core')
compile project(':library:drag-sort-listview')
}
+def getMyVersionName() {
+ def parsedManifestXml = (new XmlSlurper())
+ .parse('app/src/main/AndroidManifest.xml')
+ .declareNamespace(android:"http://schemas.android.com/apk/res/android")
+ return parsedManifestXml.'@android:versionName'
+}
+
+def getMyVersionCode() {
+ def parsedManifestXml = (new XmlSlurper())
+ .parse('app/src/main/AndroidManifest.xml')
+ .declareNamespace(android:"http://schemas.android.com/apk/res/android")
+ return parsedManifestXml.'@android:versionCode'.toInteger()
+}
+
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
@@ -29,6 +48,8 @@ android {
defaultConfig {
minSdkVersion 10
targetSdkVersion 21
+ versionCode getMyVersionCode()
+ versionName "${getMyVersionName()}"
testApplicationId "de.test.antennapod"
testInstrumentationRunner "de.test.antennapod.AntennaPodTestRunner"
}
@@ -87,4 +108,21 @@ android {
lintOptions {
abortOnError false
}
-} \ No newline at end of file
+}
+
+// about.html is templatized so that we can automatically insert
+// our version string in to it at build time.
+task filterAbout {
+ inputs.files files(['src/main/templates/about.html',
+ 'src/main/AndroidManifest.xml'])
+ outputs.file 'src/main/assets/about.html'
+} << {
+ copy {
+ from 'src/main/templates/about.html'
+ into 'src/main/assets'
+ filter(ReplaceTokens, tokens: [versionname: android.defaultConfig.versionName,
+ versioncode: android.defaultConfig.versionCode.toString()])
+ }
+}
+
+preBuild.dependsOn filterAbout \ No newline at end of file
diff --git a/app/proguard.cfg b/app/proguard.cfg
index c0f1796ea..536f54eca 100644
--- a/app/proguard.cfg
+++ b/app/proguard.cfg
@@ -1,5 +1,7 @@
+-dontobfuscate
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
+-optimizations !code/allocation/variable
-dontpreverify
-repackageclasses ''
@@ -84,3 +86,6 @@
-keepclassmembers class ** {
public void onEvent*(**);
}
+
+# android-iconify
+-keep class com.joanzapata.** { *; }
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java
index dfb5fd381..95d2ce58a 100644
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java
@@ -45,7 +45,7 @@ public class DBReaderTest extends InstrumentationTestCase {
private void expiredFeedListTestHelper(long lastUpdate, long expirationTime, boolean shouldReturn) {
final Context context = getInstrumentation().getTargetContext();
Feed feed = new Feed(0, new Date(lastUpdate), "feed", "link", "descr", null,
- null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null);
+ null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null, false);
feed.setItems(new ArrayList<FeedItem>());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@@ -301,7 +301,7 @@ public class DBReaderTest extends InstrumentationTestCase {
}
}
- public void testGetUnreadItemIds() {
+ public void testGetNewItemIds() {
final Context context = getInstrumentation().getTargetContext();
final int numItems = 10;
@@ -310,10 +310,11 @@ public class DBReaderTest extends InstrumentationTestCase {
for (int i = 0; i < unread.size(); i++) {
unreadIds[i] = unread.get(i).getId();
}
- long[] unreadSaved = DBReader.getUnreadItemIds(context);
+ LongList unreadSaved = DBReader.getNewItemIds(context);
assertNotNull(unreadSaved);
- assertTrue(unread.size() == unreadSaved.length);
- for (long savedId : unreadSaved) {
+ assertTrue(unread.size() == unreadSaved.size());
+ for(int i=0; i < unreadSaved.size(); i++) {
+ long savedId = unreadSaved.get(i);
boolean found = false;
for (long id : unreadIds) {
if (id == savedId) {
@@ -375,7 +376,7 @@ public class DBReaderTest extends InstrumentationTestCase {
List<Feed> feeds = DBTestUtils.saveFeedlist(context, NUM_FEEDS, NUM_ITEMS, true);
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
- assertEquals(0, navDrawerData.numUnreadItems);
+ assertEquals(0, navDrawerData.numNewItems);
assertEquals(0, navDrawerData.queueSize);
}
@@ -404,7 +405,7 @@ public class DBReaderTest extends InstrumentationTestCase {
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
- assertEquals(NUM_UNREAD, navDrawerData.numUnreadItems);
+ assertEquals(NUM_UNREAD, navDrawerData.numNewItems);
assertEquals(NUM_QUEUE, navDrawerData.queueSize);
}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
index e28a7918f..16f50cf28 100644
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
@@ -3,7 +3,16 @@ package de.test.antennapod.storage;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import android.test.FlakyTest;
import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -13,28 +22,23 @@ import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import static de.test.antennapod.storage.DBTestUtils.*;
+import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
/**
* Test class for DBTasks
*/
public class DBTasksTest extends InstrumentationTestCase {
- private static final String TEST_FOLDER = "testDBTasks";
+
+ private static final String TAG = "DBTasksTest";
private static final int EPISODE_CACHE_SIZE = 5;
+ private Context context;
+
private File destFolder;
@Override
protected void tearDown() throws Exception {
super.tearDown();
- final Context context = getInstrumentation().getTargetContext();
assertTrue(PodDBAdapter.deleteDatabase(context));
for (File f : destFolder.listFiles()) {
@@ -47,23 +51,24 @@ public class DBTasksTest extends InstrumentationTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
- destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ context = getInstrumentation().getTargetContext();
+ destFolder = context.getExternalCacheDir();
assertNotNull(destFolder);
assertTrue(destFolder.exists());
assertTrue(destFolder.canWrite());
- final Context context = getInstrumentation().getTargetContext();
context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
// make sure database is created
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.close();
- SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext().getApplicationContext()).edit();
+ SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
prefEdit.commit();
}
+ @FlakyTest(tolerance = 3)
public void testPerformAutoCleanupShouldDelete() throws IOException {
final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
@@ -81,7 +86,7 @@ public class DBTasksTest extends InstrumentationTestCase {
items.add(item);
}
- PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
@@ -91,7 +96,7 @@ public class DBTasksTest extends InstrumentationTestCase {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
}
- DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ DBTasks.performAutoCleanup(context);
for (int i = 0; i < files.size(); i++) {
if (i < EPISODE_CACHE_SIZE) {
assertTrue(files.get(i).exists());
@@ -101,6 +106,7 @@ public class DBTasksTest extends InstrumentationTestCase {
}
}
+ @FlakyTest(tolerance = 3)
public void testPerformAutoCleanupShouldNotDeleteBecauseUnread() throws IOException {
final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
@@ -119,7 +125,7 @@ public class DBTasksTest extends InstrumentationTestCase {
items.add(item);
}
- PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
@@ -129,12 +135,13 @@ public class DBTasksTest extends InstrumentationTestCase {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
}
- DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ DBTasks.performAutoCleanup(context);
for (File file : files) {
assertTrue(file.exists());
}
}
+ @FlakyTest(tolerance = 3)
public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
@@ -153,7 +160,7 @@ public class DBTasksTest extends InstrumentationTestCase {
items.add(item);
}
- PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setCompleteFeed(feed);
adapter.setQueue(items);
@@ -164,7 +171,7 @@ public class DBTasksTest extends InstrumentationTestCase {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
}
- DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ DBTasks.performAutoCleanup(context);
for (File file : files) {
assertTrue(file.exists());
}
@@ -175,13 +182,13 @@ public class DBTasksTest extends InstrumentationTestCase {
* call to DBWriter.deleteFeedMediaOfItem instead of the ID of the FeedMedia. This would cause the wrong item to be deleted.
* @throws IOException
*/
+ @FlakyTest(tolerance = 3)
public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException {
- final Context context = getInstrumentation().getTargetContext();
// add feed with no enclosures so that item ID != media ID
saveFeedlist(context, 1, 10, false);
// add candidate for performAutoCleanup
- List<Feed> feeds = saveFeedlist(getInstrumentation().getTargetContext(), 1, 1, true);
+ List<Feed> feeds = saveFeedlist(context, 1, 1, true);
FeedMedia m = feeds.get(0).getItems().get(0).getMedia();
m.setDownloaded(true);
m.setFile_url("file");
@@ -193,8 +200,8 @@ public class DBTasksTest extends InstrumentationTestCase {
testPerformAutoCleanupShouldNotDeleteBecauseInQueue();
}
+ @FlakyTest(tolerance = 3)
public void testUpdateFeedNewFeed() {
- final Context context = getInstrumentation().getTargetContext();
final int NUM_ITEMS = 10;
Feed feed = new Feed("url", new Date(), "title");
@@ -214,7 +221,6 @@ public class DBTasksTest extends InstrumentationTestCase {
/** Two feeds with the same title, but different download URLs should be treated as different feeds. */
public void testUpdateFeedSameTitle() {
- final Context context = getInstrumentation().getTargetContext();
Feed feed1 = new Feed("url1", new Date(), "title");
Feed feed2 = new Feed("url2", new Date(), "title");
@@ -229,7 +235,6 @@ public class DBTasksTest extends InstrumentationTestCase {
}
public void testUpdateFeedUpdatedFeed() {
- final Context context = getInstrumentation().getTargetContext();
final int NUM_ITEMS_OLD = 10;
final int NUM_ITEMS_NEW = 10;
@@ -269,6 +274,7 @@ public class DBTasksTest extends InstrumentationTestCase {
updatedFeedTest(feedFromDB, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW);
}
+ @FlakyTest(tolerance = 3)
private void updatedFeedTest(final Feed newFeed, long feedID, List<Long> itemIDs, final int NUM_ITEMS_OLD, final int NUM_ITEMS_NEW) {
assertTrue(newFeed.getId() == feedID);
assertTrue(newFeed.getItems().size() == NUM_ITEMS_NEW + NUM_ITEMS_OLD);
@@ -292,11 +298,11 @@ public class DBTasksTest extends InstrumentationTestCase {
}
}
+ @FlakyTest(tolerance = 3)
private void expiredFeedListTestHelper(long lastUpdate, long expirationTime, boolean shouldReturn) {
- final Context context = getInstrumentation().getTargetContext();
UserPreferences.setUpdateInterval(context, expirationTime);
Feed feed = new Feed(0, new Date(lastUpdate), "feed", "link", "descr", null,
- null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null);
+ null, null, null, "feed", null, null, "url", false, new FlattrStatus(), false, null, null, false);
feed.setItems(new ArrayList<FeedItem>());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@@ -314,11 +320,13 @@ public class DBTasksTest extends InstrumentationTestCase {
}
}
+ @FlakyTest(tolerance = 3)
public void testGetExpiredFeedsTestShouldReturn() {
final long expirationTime = 1000 * 60 * 60;
expiredFeedListTestHelper(System.currentTimeMillis() - expirationTime - 1, expirationTime, true);
}
+ @FlakyTest(tolerance = 3)
public void testGetExpiredFeedsTestShouldNotReturn() {
final long expirationTime = 1000 * 60 * 60;
expiredFeedListTestHelper(System.currentTimeMillis() - expirationTime / 2, expirationTime, false);
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java
index 17c926cc2..4f33cd798 100644
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java
@@ -47,7 +47,7 @@ public class DBTestUtils {
adapter.open();
for (int i = 0; i < numFeeds; i++) {
Feed f = new Feed(0, new Date(), "feed " + i, "link" + i, "descr", null, null,
- null, null, "id" + i, null, null, "url" + i, false, new FlattrStatus(), false, null);
+ null, null, "id" + i, null, null, "url" + i, false, new FlattrStatus(), false, null, null, false);
f.setItems(new ArrayList<FeedItem>());
for (int j = 0; j < numItems; j++) {
FeedItem item = new FeedItem(0, "item " + j, "id" + j, "link" + j, new Date(),
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
index b092264cd..0326174e3 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
@@ -3,15 +3,26 @@ package de.test.antennapod.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.FlakyTest;
import android.widget.ListView;
import com.robotium.solo.Solo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.fragment.AllEpisodesFragment;
+import de.danoeh.antennapod.fragment.DownloadsFragment;
+import de.danoeh.antennapod.fragment.NewEpisodesFragment;
+import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.preferences.PreferenceController;
/**
@@ -22,6 +33,8 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
private Solo solo;
private UITestUtils uiTestUtils;
+ private SharedPreferences prefs;
+
public MainActivityTest() {
super(MainActivity.class);
}
@@ -38,7 +51,7 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
adapter.close();
// override first launch preference
- SharedPreferences prefs = getInstrumentation().getTargetContext().getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE);
+ prefs = getInstrumentation().getTargetContext().getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit();
}
@@ -46,7 +59,12 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
protected void tearDown() throws Exception {
uiTestUtils.tearDown();
solo.finishOpenedActivities();
+
PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext());
+
+ // reset preferences
+ prefs.edit().clear().commit();
+
super.tearDown();
}
@@ -68,20 +86,29 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
solo.waitForText(solo.getString(R.string.subscribed_label));
}
+ @FlakyTest(tolerance = 3)
public void testClickNavDrawer() throws Exception {
uiTestUtils.addLocalFeedData(false);
- // all episodes
+ UserPreferences.setHiddenDrawerItems(getInstrumentation().getTargetContext(), new ArrayList<String>());
+
+ // queue
+ openNavDrawer();
+ solo.clickOnText(solo.getString(R.string.queue_label));
+ solo.waitForView(android.R.id.list);
+ assertEquals(solo.getString(R.string.queue_label), getActionbarTitle());
+
+ // new episodes
openNavDrawer();
solo.clickOnText(solo.getString(R.string.new_episodes_label));
solo.waitForView(android.R.id.list);
assertEquals(solo.getString(R.string.new_episodes_label), getActionbarTitle());
- // queue
+ // all episodes
openNavDrawer();
- solo.clickOnText(solo.getString(R.string.queue_label));
+ solo.clickOnText(solo.getString(R.string.all_episodes_label));
solo.waitForView(android.R.id.list);
- assertEquals(solo.getString(R.string.queue_label), getActionbarTitle());
+ assertEquals(solo.getString(R.string.all_episodes_label), getActionbarTitle());
// downloads
openNavDrawer();
@@ -117,9 +144,78 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
return ((MainActivity)solo.getCurrentActivity()).getMainActivtyActionBar().getTitle().toString();
}
+ @FlakyTest(tolerance = 3)
public void testGoToPreferences() {
openNavDrawer();
- solo.clickOnMenuItem(solo.getString(R.string.settings_label));
+ solo.clickOnText(solo.getString(R.string.settings_label));
solo.waitForActivity(PreferenceController.getPreferenceActivity());
}
+
+ public void testDrawerPreferencesHideSomeElements() {
+ UserPreferences.setHiddenDrawerItems(getInstrumentation().getTargetContext(), new ArrayList<String>());
+ openNavDrawer();
+ solo.clickLongOnText(solo.getString(R.string.queue_label));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(solo.getString(R.string.all_episodes_label));
+ solo.clickOnText(solo.getString(R.string.playback_history_label));
+ solo.clickOnText(solo.getString(R.string.confirm_label));
+ solo.waitForDialogToClose();
+ List<String> hidden = UserPreferences.getHiddenDrawerItems();
+ assertEquals(2, hidden.size());
+ assertTrue(hidden.contains(AllEpisodesFragment.TAG));
+ assertTrue(hidden.contains(PlaybackHistoryFragment.TAG));
+ }
+
+ public void testDrawerPreferencesUnhideSomeElements() {
+ List<String> hidden = Arrays.asList(NewEpisodesFragment.TAG, DownloadsFragment.TAG);
+ UserPreferences.setHiddenDrawerItems(getInstrumentation().getTargetContext(), hidden);
+ openNavDrawer();
+ solo.clickLongOnText(solo.getString(R.string.queue_label));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(solo.getString(R.string.downloads_label));
+ solo.clickOnText(solo.getString(R.string.queue_label));
+ solo.clickOnText(solo.getString(R.string.confirm_label));
+ solo.waitForDialogToClose();
+ hidden = UserPreferences.getHiddenDrawerItems();
+ assertEquals(2, hidden.size());
+ assertTrue(hidden.contains(QueueFragment.TAG));
+ assertTrue(hidden.contains(NewEpisodesFragment.TAG));
+ }
+
+ public void testDrawerPreferencesHideAllElements() {
+ UserPreferences.setHiddenDrawerItems(getInstrumentation().getTargetContext(), new ArrayList<String>());
+ String[] titles = getInstrumentation().getTargetContext().getResources().getStringArray(R.array.nav_drawer_titles);
+
+ openNavDrawer();
+ solo.clickLongOnText(solo.getString(R.string.queue_label));
+ solo.waitForDialogToOpen();
+ for(String title : titles) {
+ solo.clickOnText(title);
+ }
+ solo.clickOnText(solo.getString(R.string.confirm_label));
+ solo.waitForDialogToClose();
+ List<String> hidden = UserPreferences.getHiddenDrawerItems();
+ assertEquals(6, hidden.size());
+ for(String tag : MainActivity.NAV_DRAWER_TAGS) {
+ assertTrue(hidden.contains(tag));
+ }
+ }
+
+ public void testDrawerPreferencesHideCurrentElement() {
+ UserPreferences.setHiddenDrawerItems(getInstrumentation().getTargetContext(), new ArrayList<String>());
+
+ openNavDrawer();
+ String downloads = solo.getString(R.string.downloads_label);
+ solo.clickOnText(downloads);
+ solo.waitForView(android.R.id.list);
+ openNavDrawer();
+ solo.clickLongOnText(downloads);
+ solo.waitForDialogToOpen();
+ solo.clickOnText(downloads);
+ solo.clickOnText(solo.getString(R.string.confirm_label));
+ solo.waitForDialogToClose();
+ List<String> hidden = UserPreferences.getHiddenDrawerItems();
+ assertEquals(1, hidden.size());
+ assertTrue(hidden.contains(DownloadsFragment.TAG));
+ }
}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java
index 346ef6c18..775bc0ecd 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java
@@ -1,17 +1,18 @@
package de.test.antennapod.ui;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.test.ActivityInstrumentationTestCase2;
-import android.widget.TextView;
+import com.robotium.solo.Condition;
import com.robotium.solo.Solo;
+import com.robotium.solo.Timeout;
import java.util.List;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.AudioplayerActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -28,6 +29,8 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
private Solo solo;
private UITestUtils uiTestUtils;
+ private Context context;
+
public PlaybackTest() {
super(MainActivity.class);
}
@@ -36,28 +39,33 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
public void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
- uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext());
+ context = getInstrumentation().getContext();
+
+ uiTestUtils = new UITestUtils(context);
uiTestUtils.setup();
+
// create database
- PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.close();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
- prefs.edit().putBoolean(UserPreferences.PREF_UNPAUSE_ON_HEADSET_RECONNECT, false).commit();
- prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false).commit();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.edit()
+ .putBoolean(UserPreferences.PREF_UNPAUSE_ON_HEADSET_RECONNECT, false)
+ .putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false)
+ .putString(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS, "")
+ .commit();
}
@Override
public void tearDown() throws Exception {
uiTestUtils.tearDown();
solo.finishOpenedActivities();
- PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext());
+ PodDBAdapter.deleteDatabase(context);
// shut down playback service
skipEpisode();
- getInstrumentation().getTargetContext().sendBroadcast(
- new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
super.tearDown();
}
@@ -67,70 +75,97 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
}
private void setContinuousPlaybackPreference(boolean value) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit();
}
private void skipEpisode() {
Intent skipIntent = new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
- getInstrumentation().getTargetContext().sendBroadcast(skipIntent);
+ context.sendBroadcast(skipIntent);
}
private void startLocalPlayback() {
- assertTrue(solo.waitForActivity(MainActivity.class));
openNavDrawer();
- solo.clickOnText(solo.getString(R.string.new_episodes_label));
- solo.waitForView(android.R.id.list);
+
+ solo.clickOnText(solo.getString(R.string.all_episodes_label));
+ final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(context, 10);
+ assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
+
solo.clickOnView(solo.getView(R.id.butSecondaryAction));
- assertTrue(solo.waitForActivity(AudioplayerActivity.class));
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return episodes.get(0).getMedia().isCurrentlyPlaying();
+ }
+ }, Timeout.getLargeTimeout());
}
private void startLocalPlaybackFromQueue() {
- assertTrue(solo.waitForActivity(MainActivity.class));
- openNavDrawer();
- solo.clickOnText(solo.getString(R.string.queue_label));
assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
+ final List<FeedItem> queue = DBReader.getQueue(context);
solo.clickOnImageButton(1);
- assertTrue(solo.waitForActivity(AudioplayerActivity.class));
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return queue.get(0).getMedia().isCurrentlyPlaying();
+ }
+ }, Timeout.getLargeTimeout());
}
public void testStartLocal() throws Exception {
uiTestUtils.addLocalFeedData(true);
- DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
+ DBWriter.clearQueue(context).get();
startLocalPlayback();
-
- solo.clickOnView(solo.getView(R.id.butPlay));
}
public void testContinousPlaybackOffSingleEpisode() throws Exception {
setContinuousPlaybackPreference(false);
uiTestUtils.addLocalFeedData(true);
- DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
+ DBWriter.clearQueue(context).get();
startLocalPlayback();
- assertTrue(solo.waitForActivity(MainActivity.class));
}
public void testContinousPlaybackOffMultipleEpisodes() throws Exception {
setContinuousPlaybackPreference(false);
uiTestUtils.addLocalFeedData(true);
- List<FeedItem> queue = DBReader.getQueue(getInstrumentation().getTargetContext());
- FeedItem second = queue.get(0);
+ List<FeedItem> queue = DBReader.getQueue(context);
+ final FeedItem first = queue.get(0);
+ final FeedItem second = queue.get(1);
startLocalPlaybackFromQueue();
- assertTrue(solo.waitForText(second.getTitle()));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return first.getMedia().isCurrentlyPlaying() == false;
+ }
+ }, 10000);
+ Thread.sleep(1000);
+ assertTrue(second.getMedia().isCurrentlyPlaying() == false);
}
public void testContinuousPlaybackOnMultipleEpisodes() throws Exception {
setContinuousPlaybackPreference(true);
uiTestUtils.addLocalFeedData(true);
- List<FeedItem> queue = DBReader.getQueue(getInstrumentation().getTargetContext());
- FeedItem second = queue.get(1);
+ List<FeedItem> queue = DBReader.getQueue(context);
+ final FeedItem first = queue.get(0);
+ final FeedItem second = queue.get(1);
startLocalPlaybackFromQueue();
- assertTrue(solo.waitForText(second.getTitle()));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return first.getMedia().isCurrentlyPlaying() == false;
+ }
+ }, 10000);
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return second.getMedia().isCurrentlyPlaying() == true;
+ }
+ }, 10000);
}
/**
@@ -139,14 +174,24 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
private void replayEpisodeCheck(boolean followQueue) throws Exception {
setContinuousPlaybackPreference(followQueue);
uiTestUtils.addLocalFeedData(true);
- DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
- String title = ((TextView) solo.getView(R.id.txtvTitle)).getText().toString();
+ DBWriter.clearQueue(context).get();
+ final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(context, 10);
+
startLocalPlayback();
- assertTrue(solo.waitForText(title));
- assertTrue(solo.waitForActivity(MainActivity.class));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return false == episodes.get(0).getMedia().isCurrentlyPlaying();
+ }
+ }, Timeout.getLargeTimeout());
+
startLocalPlayback();
- assertTrue(solo.waitForText(title));
- assertTrue(solo.waitForActivity(MainActivity.class));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return false == episodes.get(0).getMedia().isCurrentlyPlaying();
+ }
+ }, Timeout.getLargeTimeout());
}
public void testReplayEpisodeContinuousPlaybackOn() throws Exception {
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
new file mode 100644
index 000000000..eb1cb9c71
--- /dev/null
+++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
@@ -0,0 +1,453 @@
+package de.test.antennapod.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.FlakyTest;
+
+import com.robotium.solo.Condition;
+import com.robotium.solo.Solo;
+import com.robotium.solo.Timeout;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.PreferenceActivity;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+public class PreferencesTest extends ActivityInstrumentationTestCase2<PreferenceActivity> {
+
+ private static final String TAG = "PreferencesTest";
+
+ private Solo solo;
+ private Context context;
+ private Resources res;
+
+ public PreferencesTest() {
+ super(PreferenceActivity.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ solo = new Solo(getInstrumentation(), getActivity());
+ Timeout.setSmallTimeout(500);
+ Timeout.setLargeTimeout(1000);
+ context = getInstrumentation().getTargetContext();
+ res = getActivity().getResources();
+ UserPreferences.createInstance(context);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ solo.finishOpenedActivities();
+ super.tearDown();
+ }
+
+ public void testSwitchTheme() {
+ final int theme = UserPreferences.getTheme();
+ int otherTheme;
+ if(theme == de.danoeh.antennapod.core.R.style.Theme_AntennaPod_Light) {
+ otherTheme = R.string.pref_theme_title_dark;
+ } else {
+ otherTheme = R.string.pref_theme_title_light;
+ }
+ solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(solo.getString(otherTheme));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getTheme() != theme;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testSwitchThemeBack() {
+ final int theme = UserPreferences.getTheme();
+ int otherTheme;
+ if(theme == de.danoeh.antennapod.core.R.style.Theme_AntennaPod_Light) {
+ otherTheme = R.string.pref_theme_title_dark;
+ } else {
+ otherTheme = R.string.pref_theme_title_light;
+ }
+ solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
+ solo.waitForDialogToOpen(1000);
+ solo.clickOnText(solo.getString(otherTheme));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getTheme() != theme;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testExpandNotification() {
+ final int priority = UserPreferences.getNotifyPriority();
+ solo.clickOnText(solo.getString(R.string.pref_expandNotify_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return priority != UserPreferences.getNotifyPriority();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_expandNotify_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return priority == UserPreferences.getNotifyPriority();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testEnablePersistentPlaybackControls() {
+ final boolean persistNotify = UserPreferences.isPersistNotify();
+ solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return persistNotify != UserPreferences.isPersistNotify();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return persistNotify == UserPreferences.isPersistNotify();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testEnqueueAtFront() {
+ final boolean enqueueAtFront = UserPreferences.enqueueAtFront();
+ solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return enqueueAtFront != UserPreferences.enqueueAtFront();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return enqueueAtFront == UserPreferences.enqueueAtFront();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testHeadPhonesDisconnect() {
+ final boolean pauseOnHeadsetDisconnect = UserPreferences.isPauseOnHeadsetDisconnect();
+ solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return pauseOnHeadsetDisconnect != UserPreferences.isPauseOnHeadsetDisconnect();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return pauseOnHeadsetDisconnect == UserPreferences.isPauseOnHeadsetDisconnect();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testHeadPhonesReconnect() {
+ if(UserPreferences.isPauseOnHeadsetDisconnect() == false) {
+ solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.isPauseOnHeadsetDisconnect();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+ final boolean unpauseOnHeadsetReconnect = UserPreferences.isUnpauseOnHeadsetReconnect();
+ solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return unpauseOnHeadsetReconnect != UserPreferences.isUnpauseOnHeadsetReconnect();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return unpauseOnHeadsetReconnect == UserPreferences.isUnpauseOnHeadsetReconnect();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testContinuousPlayback() {
+ final boolean continuousPlayback = UserPreferences.isFollowQueue();
+ solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return continuousPlayback != UserPreferences.isFollowQueue();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return continuousPlayback == UserPreferences.isFollowQueue();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testAutoDelete() {
+ final boolean autoDelete = UserPreferences.isAutoDelete();
+ solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return autoDelete != UserPreferences.isAutoDelete();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
+ solo.waitForCondition(new Condition() {
+ @Override public boolean isSatisfied() {
+ return autoDelete == UserPreferences.isAutoDelete();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testPlaybackSpeeds() {
+ solo.clickOnText(solo.getString(R.string.pref_playback_speed_title));
+ solo.waitForDialogToOpen(1000);
+ assertTrue(solo.searchText(solo.getString(R.string.no_playback_plugin_title)));
+ solo.clickOnText(solo.getString(R.string.close_label));
+ solo.waitForDialogToClose(1000);
+ }
+
+ public void testPauseForInterruptions() {
+ final boolean pauseForFocusLoss = UserPreferences.shouldPauseForFocusLoss();
+ solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return pauseForFocusLoss != UserPreferences.shouldPauseForFocusLoss();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testDisableUpdateInterval() {
+ solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervall_title));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(solo.getString(R.string.pref_update_interval_hours_manual));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getUpdateInterval() == 0;
+ }
+ }, 1000);
+ }
+
+ public void testSetUpdateInterval() {
+ solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervall_title));
+ solo.waitForDialogToOpen();
+ String search = "12 " + solo.getString(R.string.pref_update_interval_hours_plural);
+ solo.clickOnText(search);
+ solo.waitForDialogToClose();
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getUpdateInterval() == 12;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testMobileUpdates() {
+ final boolean mobileUpdates = UserPreferences.isAllowMobileUpdate();
+ solo.clickOnText(solo.getString(R.string.pref_mobileUpdate_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return mobileUpdates != UserPreferences.isAllowMobileUpdate();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_mobileUpdate_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return mobileUpdates == UserPreferences.isAllowMobileUpdate();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testSetSequentialDownload() {
+ solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
+ solo.waitForDialogToOpen();
+ solo.clearEditText(0);
+ solo.enterText(0, "1");
+ solo.clickOnText(solo.getString(android.R.string.ok));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getParallelDownloads() == 1;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testSetParallelDownloads() {
+ solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
+ solo.waitForDialogToOpen();
+ solo.clearEditText(0);
+ solo.enterText(0, "10");
+ solo.clickOnText(solo.getString(android.R.string.ok));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getParallelDownloads() == 10;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testSetParallelDownloadsInvalidInput() {
+ solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
+ solo.waitForDialogToOpen();
+ solo.clearEditText(0);
+ solo.enterText(0, "0");
+ assertEquals("1", solo.getEditText(0).getText().toString());
+ solo.clearEditText(0);
+ solo.enterText(0, "100");
+ assertEquals("50", solo.getEditText(0).getText().toString());
+ }
+
+ public void testSetEpisodeCache() {
+ String[] entries = res.getStringArray(R.array.episode_cache_size_entries);
+ String[] values = res.getStringArray(R.array.episode_cache_size_values);
+ String entry = entries[entries.length/2];
+ final int value = Integer.valueOf(values[values.length/2]);
+ solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(entry);
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getEpisodeCacheSize() == value;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testSetEpisodeCacheMin() {
+ String[] entries = res.getStringArray(R.array.episode_cache_size_entries);
+ String[] values = res.getStringArray(R.array.episode_cache_size_values);
+ String minEntry = entries[0];
+ final int minValue = Integer.valueOf(values[0]);
+ solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
+ solo.waitForDialogToOpen(1000);
+ solo.scrollUp();
+ solo.clickOnText(minEntry);
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getEpisodeCacheSize() == minValue;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+
+ public void testSetEpisodeCacheMax() {
+ String[] entries = res.getStringArray(R.array.episode_cache_size_entries);
+ String[] values = res.getStringArray(R.array.episode_cache_size_values);
+ String maxEntry = entries[entries.length-1];
+ final int maxValue = Integer.valueOf(values[values.length-1]);
+ solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
+ solo.waitForDialogToOpen();
+ solo.clickOnText(maxEntry);
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.getEpisodeCacheSize() == maxValue;
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ public void testAutomaticDownload() {
+ final boolean automaticDownload = UserPreferences.isEnableAutodownload();
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
+ solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return automaticDownload != UserPreferences.isEnableAutodownload();
+ }
+ }, Timeout.getLargeTimeout());
+ if(UserPreferences.isEnableAutodownload() == false) {
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
+ }
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return UserPreferences.isEnableAutodownload() == true;
+ }
+ }, Timeout.getLargeTimeout());
+ final boolean enableAutodownloadOnBattery = UserPreferences.isEnableAutodownloadOnBattery();
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return enableAutodownloadOnBattery != UserPreferences.isEnableAutodownloadOnBattery();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery();
+ }
+ }, Timeout.getLargeTimeout());
+ final boolean enableWifiFilter = UserPreferences.isEnableAutodownloadWifiFilter();
+ solo.clickOnText(solo.getString(R.string.pref_autodl_wifi_filter_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return enableWifiFilter != UserPreferences.isEnableAutodownloadWifiFilter();
+ }
+ }, Timeout.getLargeTimeout());
+ solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
+ solo.waitForCondition(new Condition() {
+ @Override
+ public boolean isSatisfied() {
+ return enableWifiFilter == UserPreferences.isEnableAutodownloadWifiFilter();
+ }
+ }, Timeout.getLargeTimeout());
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testAbout() throws IOException {
+ int numViews = 0, numLinks = 0;
+ InputStream input = getActivity().getResources().getAssets().open("about.html");
+ List<String> lines = IOUtils.readLines(input);
+ input.close();
+ for(String line : lines) {
+ if(line.contains("(View)")) {
+ numViews++;
+ } else if(line.contains("(Link)")) {
+ numLinks++;
+ }
+ }
+ for(int i=0; i < numViews; i++) {
+ solo.clickOnText(solo.getString(R.string.about_pref));
+ solo.clickOnText("(View)", i);
+ solo.goBack();
+ }
+ for(int i=0; i < numLinks; i++) {
+ solo.clickOnText(solo.getString(R.string.about_pref));
+ solo.clickOnText("(Link)", i);
+ solo.goBack();
+ }
+ }
+
+}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
index 85923d40f..613826932 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java
@@ -141,11 +141,12 @@ public class UITestUtils {
// create items
List<FeedItem> items = new ArrayList<FeedItem>();
for (int j = 0; j < NUM_ITEMS_PER_FEED; j++) {
- FeedItem item = new FeedItem(0, "item" + j, "item" + j, "http://example.com/feed" + i + "/item/" + j, new Date(), true, feed);
+ FeedItem item = new FeedItem(j, "Feed " + (i+1) + ": Item " + (j+1), "item" + j,
+ "http://example.com/feed" + i + "/item/" + j, new Date(), false, feed);
items.add(item);
File mediaFile = newMediaFile("feed-" + i + "-episode-" + j + ".mp3");
- item.setMedia(new FeedMedia(0, item, 0, 0, mediaFile.length(), "audio/mp3", null, hostFile(mediaFile), false, null, 0));
+ item.setMedia(new FeedMedia(j, item, 0, 0, mediaFile.length(), "audio/mp3", null, hostFile(mediaFile), false, null, 0));
}
feed.setItems(items);
@@ -169,7 +170,9 @@ public class UITestUtils {
* @param downloadEpisodes true if episodes should also be marked as downloaded.
*/
public void addLocalFeedData(boolean downloadEpisodes) throws Exception {
- if (localFeedDataAdded) throw new IllegalStateException("addLocalFeedData was called twice on the same instance");
+ if (localFeedDataAdded) {
+ throw new IllegalStateException("addLocalFeedData was called twice on the same instance");
+ }
if (!feedDataHosted) {
addHostedFeedData();
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 23136cd37..97f8bbdad 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
- android:versionCode="48"
- android:versionName="1.1">
+ android:versionCode="51"
+ android:versionName="1.2">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
diff --git a/app/src/main/assets/.gitignore b/app/src/main/assets/.gitignore
new file mode 100644
index 000000000..328840cfc
--- /dev/null
+++ b/app/src/main/assets/.gitignore
@@ -0,0 +1,2 @@
+# this file is generated automatically
+about.html
diff --git a/app/src/main/assets/LICENSE_ANDROID_ICONIFY.txt b/app/src/main/assets/LICENSE_ANDROID_ICONIFY.txt
new file mode 100644
index 000000000..85a6c0cf2
--- /dev/null
+++ b/app/src/main/assets/LICENSE_ANDROID_ICONIFY.txt
@@ -0,0 +1,18 @@
+Copyright 2013 Joan Zapata
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+It uses FontAwesome font, licensed under OFL 1.1, which is compatible
+with this library's license.
+
+ http://scripts.sil.org/cms/scripts/render_download.php?format=file&media_id=OFL_plaintext&filename=OFL.txt
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java
index f056b107b..b59f6ba35 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java
@@ -13,7 +13,6 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
-import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
@@ -29,7 +28,6 @@ import com.squareup.picasso.Picasso;
import org.apache.commons.lang3.StringUtils;
-import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.adapter.NavListAdapter;
@@ -47,7 +45,6 @@ import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
-import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.danoeh.antennapod.preferences.PreferenceController;
@@ -96,30 +93,25 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
if (coverFragment != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing cover fragment");
+ Log.d(TAG, "Removing cover fragment");
fT.remove(coverFragment);
}
if (descriptionFragment != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing description fragment");
+ Log.d(TAG, "Removing description fragment");
fT.remove(descriptionFragment);
}
if (chapterFragment != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing chapter fragment");
+ Log.d(TAG, "Removing chapter fragment");
fT.remove(chapterFragment);
}
if (currentlyShownFragment != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing currently shown fragment");
+ Log.d(TAG, "Removing currently shown fragment");
fT.remove(currentlyShownFragment);
}
for (int i = 0; i < detachedFragments.length; i++) {
Fragment f = detachedFragments[i];
if (f != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Removing detached fragment");
+ Log.d(TAG, "Removing detached fragment");
fT.remove(f);
}
}
@@ -152,8 +144,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
}
private void savePreferences() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Saving preferences");
+ Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (currentlyShownPosition >= 0 && controller != null
@@ -180,9 +171,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState); would cause crash
- if (BuildConfig.DEBUG)
- Log.d(TAG, "onSaveInstanceState");
-
+ Log.d(TAG, "onSaveInstanceState");
}
@Override
@@ -205,8 +194,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
* @return true if restoreFromPrefernces changed the activity's state
*/
private boolean restoreFromPreferences() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Restoring instance state");
+ Log.d(TAG, "Restoring instance state");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
-1);
@@ -220,15 +208,10 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
switchToFragment(savedPosition);
return true;
} else if (controller == null || controller.getMedia() == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Couldn't restore from preferences: controller or media was null");
+ Log.d(TAG, "Couldn't restore from preferences: controller or media was null");
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
- + savedPosition + ", id: " + playableId
- );
+ Log.d(TAG, "Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
+ + savedPosition + ", id: " + playableId);
}
return false;
@@ -239,9 +222,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
super.onResume();
if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
Intent intent = getIntent();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received VIEW intent: "
- + intent.getData().getPath());
+ Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.AUDIO);
Intent launchIntent = new Intent(this, PlaybackService.class);
@@ -269,8 +250,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
@Override
protected void onAwaitingVideoSurface() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
+ Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
startActivity(new Intent(this, VideoplayerActivity.class));
}
@@ -295,8 +275,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
* @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
*/
private void switchToFragment(int pos) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Switching contentView to position " + pos);
+ Log.d(TAG, "Switching contentView to position " + pos);
if (currentlyShownPosition != pos && controller != null) {
Playable media = controller.getMedia();
if (media != null) {
@@ -354,9 +333,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
lastShownPosition = currentlyShownPosition;
currentlyShownPosition = pos;
if (detachedFragments[pos] != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Reattaching fragment at position "
- + pos);
+ Log.d(TAG, "Reattaching fragment at position " + pos);
ft.attach(detachedFragments[pos]);
} else {
ft.add(R.id.contentView, currentlyShownFragment);
@@ -434,25 +411,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
butShowCover = (ImageButton) findViewById(R.id.butCover);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
- drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
- CharSequence currentTitle = getSupportActionBar().getTitle();
-
- @Override
- public void onDrawerOpened(View drawerView) {
- super.onDrawerOpened(drawerView);
- currentTitle = getSupportActionBar().getTitle();
- getSupportActionBar().setTitle(R.string.app_name);
- supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onDrawerClosed(View drawerView) {
- super.onDrawerClosed(drawerView);
- getSupportActionBar().setTitle(currentTitle);
- supportInvalidateOptionsMenu();
- }
- };
-
+ drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close);
drawerToggle.setDrawerIndicatorEnabled(false);
drawerLayout.setDrawerListener(drawerToggle);
@@ -463,10 +422,9 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int viewType = parent.getAdapter().getItemViewType(position);
if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) {
- int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET;
Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType);
- intent.putExtra(MainActivity.EXTRA_NAV_INDEX, relPos);
+ intent.putExtra(MainActivity.EXTRA_NAV_INDEX, position);
startActivity(intent);
}
drawerLayout.closeDrawer(navDrawer);
@@ -630,9 +588,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "ReloadNotification received, switching to Videoplayer now");
+ Log.d(TAG, "ReloadNotification received, switching to Videoplayer now");
finish();
startActivity(new Intent(this, VideoplayerActivity.class));
@@ -659,24 +615,6 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (!MenuItemUtils.isActivityDrawerOpen(this)) {
- return super.onCreateOptionsMenu(menu);
- } else {
- return false;
- }
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- if (!MenuItemUtils.isActivityDrawerOpen(this)) {
- return super.onPrepareOptionsMenu(menu);
- } else {
- return false;
- }
- }
-
public interface AudioplayerContentFragment {
public void onDataSetChanged(Playable media);
}
@@ -729,8 +667,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received contentUpdate Intent.");
+ Log.d(TAG, "Received contentUpdate Intent.");
loadData();
}
}
@@ -766,8 +703,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
}
@Override
- public int getNumberOfUnreadItems() {
- return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
+ public int getNumberOfNewItems() {
+ return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
+ }
+
+ @Override
+ public int getNumberOfUnreadFeedItems(long feedId) {
+ return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
}
};
}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java
index 93c71a868..24b684752 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java
@@ -1,5 +1,7 @@
package de.danoeh.antennapod.activity;
+import android.content.ClipData;
+import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
@@ -9,15 +11,17 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Toast;
+import com.joanzapata.android.iconify.Iconify;
import com.squareup.picasso.Picasso;
-import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.Feed;
@@ -44,10 +48,32 @@ public class FeedInfoActivity extends ActionBarActivity {
private TextView txtvDescription;
private TextView txtvLanguage;
private TextView txtvAuthor;
+ private TextView txtvUrl;
private EditText etxtUsername;
private EditText etxtPassword;
private CheckBox cbxAutoDownload;
+ private final View.OnClickListener copyUrlToClipboard = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(feed != null && feed.getDownload_url() != null) {
+ String url = feed.getDownload_url();
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ ClipData clipData = ClipData.newPlainText(url, url);
+ android.content.ClipboardManager cm = (android.content.ClipboardManager) FeedInfoActivity.this
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(clipData);
+ } else {
+ android.text.ClipboardManager cm = (android.text.ClipboardManager) FeedInfoActivity.this
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(url);
+ }
+ Toast t = Toast.makeText(FeedInfoActivity.this, R.string.copied_url_msg, Toast.LENGTH_SHORT);
+ t.show();
+ }
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
@@ -61,10 +87,13 @@ public class FeedInfoActivity extends ActionBarActivity {
txtvDescription = (TextView) findViewById(R.id.txtvDescription);
txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
+ txtvUrl = (TextView) findViewById(R.id.txtvUrl);
cbxAutoDownload = (CheckBox) findViewById(R.id.cbxAutoDownload);
etxtUsername = (EditText) findViewById(R.id.etxtUsername);
etxtPassword = (EditText) findViewById(R.id.etxtPassword);
+ txtvUrl.setOnClickListener(copyUrlToClipboard);
+
AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
@Override
@@ -76,10 +105,9 @@ public class FeedInfoActivity extends ActionBarActivity {
protected void onPostExecute(Feed result) {
if (result != null) {
feed = result;
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Language is " + feed.getLanguage());
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Author is " + feed.getAuthor());
+ Log.d(TAG, "Language is " + feed.getLanguage());
+ Log.d(TAG, "Author is " + feed.getAuthor());
+ Log.d(TAG, "URL is " + feed.getDownload_url());
imgvCover.post(new Runnable() {
@Override
@@ -92,7 +120,7 @@ public class FeedInfoActivity extends ActionBarActivity {
});
txtvTitle.setText(feed.getTitle());
- txtvDescription.setText(feed.getDescription());
+ txtvDescription.setText(feed.getDescription().trim());
if (feed.getAuthor() != null) {
txtvAuthor.setText(feed.getAuthor());
}
@@ -100,6 +128,8 @@ public class FeedInfoActivity extends ActionBarActivity {
txtvLanguage.setText(LangUtils
.getLanguageString(feed.getLanguage()));
}
+ txtvUrl.setText(feed.getDownload_url() + " {fa-paperclip}");
+ Iconify.addIcons(txtvUrl);
cbxAutoDownload.setEnabled(UserPreferences.isEnableAutodownload());
cbxAutoDownload.setChecked(feed.getPreferences().getAutoDownload());
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
index a28e4114b..d62612c76 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
@@ -1,8 +1,11 @@
package de.danoeh.antennapod.activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.database.DataSetObserver;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -21,6 +24,7 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import java.util.List;
@@ -34,6 +38,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.fragment.AddFeedFragment;
+import de.danoeh.antennapod.fragment.AllEpisodesFragment;
import de.danoeh.antennapod.fragment.DownloadsFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
@@ -48,7 +53,9 @@ import de.greenrobot.event.EventBus;
* The activity that is shown when the user launches the app.
*/
public class MainActivity extends ActionBarActivity implements NavDrawerActivity {
+
private static final String TAG = "MainActivity";
+
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED
| EventDistributor.FEED_LIST_UPDATE
@@ -56,21 +63,24 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
public static final String PREF_NAME = "MainActivityPrefs";
public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
+ public static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag";
- public static final String EXTRA_NAV_INDEX = "nav_index";
public static final String EXTRA_NAV_TYPE = "nav_type";
+ public static final String EXTRA_NAV_INDEX = "nav_index";
+ public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
public static final String SAVE_BACKSTACK_COUNT = "backstackCount";
- public static final String SAVE_SELECTED_NAV_INDEX = "selectedNavIndex";
public static final String SAVE_TITLE = "title";
-
- public static final int POS_QUEUE = 0,
- POS_NEW = 1,
- POS_DOWNLOADS = 2,
- POS_HISTORY = 3,
- POS_ADD = 4;
+ public static final String[] NAV_DRAWER_TAGS = {
+ QueueFragment.TAG,
+ NewEpisodesFragment.TAG,
+ AllEpisodesFragment.TAG,
+ DownloadsFragment.TAG,
+ PlaybackHistoryFragment.TAG,
+ AddFeedFragment.TAG
+ };
private Toolbar toolbar;
private ExternalPlayerFragment externalPlayerFragment;
@@ -85,7 +95,6 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
private CharSequence drawerTitle;
private CharSequence currentTitle;
-
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getNoTitleTheme());
@@ -103,30 +112,12 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navList = (ListView) findViewById(R.id.nav_list);
navDrawer = findViewById(R.id.nav_layout);
- Log.i(TAG, "");
- drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
- @Override
- public void onDrawerOpened(View drawerView) {
- super.onDrawerOpened(drawerView);
- currentTitle = getSupportActionBar().getTitle();
- getSupportActionBar().setTitle(drawerTitle);
- supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onDrawerClosed(View drawerView) {
- super.onDrawerClosed(drawerView);
- getSupportActionBar().setTitle(currentTitle);
- supportInvalidateOptionsMenu();
-
- }
- };
+ drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close);
if (savedInstanceState != null) {
int backstackCount = savedInstanceState.getInt(SAVE_BACKSTACK_COUNT, 0);
drawerToggle.setDrawerIndicatorEnabled(backstackCount == 0);
}
-
drawerLayout.setDrawerListener(drawerToggle);
final FragmentManager fm = getSupportFragmentManager();
@@ -138,25 +129,20 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
});
- FragmentTransaction transaction = fm.beginTransaction();
-
- Fragment mainFragment = fm.findFragmentByTag("main");
- if (mainFragment != null) {
- transaction.replace(R.id.main_view, mainFragment);
- } else {
- loadFragment(NavListAdapter.VIEW_TYPE_NAV, POS_QUEUE, null);
- }
-
- externalPlayerFragment = new ExternalPlayerFragment();
- transaction.replace(R.id.playerFragment, externalPlayerFragment);
- transaction.commit();
-
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
navAdapter = new NavListAdapter(itemAccess, this);
navList.setAdapter(navAdapter);
navList.setOnItemClickListener(navListClickListener);
+ navList.setOnItemLongClickListener(newListLongClickListener);
+
+ navAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ selectedNavListIndex = getSelectedNavListIndex();
+ }
+ });
findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() {
@Override
@@ -166,9 +152,42 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
});
+ FragmentTransaction transaction = fm.beginTransaction();
+
+ Fragment mainFragment = fm.findFragmentByTag("main");
+ if (mainFragment != null) {
+ transaction.replace(R.id.main_view, mainFragment);
+ } else {
+ String lastFragment = getLastNavFragment();
+ if(ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) {
+ loadFragment(lastFragment, null);
+ }
+ // else: lastFragment contains feed id - drawer data is not loaded yet,
+ // so loading is postponed until then
+ }
+ externalPlayerFragment = new ExternalPlayerFragment();
+ transaction.replace(R.id.playerFragment, externalPlayerFragment);
+ transaction.commit();
+
checkFirstLaunch();
}
+ private void saveLastNavFragment(String tag) {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ SharedPreferences.Editor edit = prefs.edit();
+ if(tag != null) {
+ edit.putString(PREF_LAST_FRAGMENT_TAG, tag);
+ } else {
+ edit.remove(PREF_LAST_FRAGMENT_TAG);
+ }
+ edit.commit();
+ }
+
+ private String getLastNavFragment() {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ return prefs.getString(PREF_LAST_FRAGMENT_TAG, QueueFragment.TAG);
+ }
+
private void checkFirstLaunch() {
SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
@@ -185,6 +204,40 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
}
+ public void showDrawerPreferencesDialog() {
+ final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems();
+ String[] navLabels = new String[NAV_DRAWER_TAGS.length];
+ final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length];
+ for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) {
+ String tag = NAV_DRAWER_TAGS[i];
+ navLabels[i] = navAdapter.getLabel(tag);
+ if (!hiddenDrawerItems.contains(tag)) {
+ checked[i] = true;
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+ builder.setTitle(R.string.drawer_preferences);
+ builder.setMultiChoiceItems(navLabels, checked, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ if (isChecked) {
+ hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
+ } else {
+ hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
+ }
+ }
+ });
+ builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ UserPreferences.setHiddenDrawerItems(MainActivity.this, hiddenDrawerItems);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.create().show();
+ }
+
public ActionBar getMainActivtyActionBar() {
return getSupportActionBar();
}
@@ -197,70 +250,91 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
return (navDrawerData != null) ? navDrawerData.feeds : null;
}
- private void loadFragment(int viewType, int relPos, Bundle args) {
- FragmentManager fragmentManager = getSupportFragmentManager();
- // clear back stack
- for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
- fragmentManager.popBackStack();
+ public void loadFragment(int index, Bundle args) {
+ if (index < navAdapter.getSubscriptionOffset()) {
+ String tag = navAdapter.getTags().get(index);
+ loadFragment(tag, args);
+ } else {
+ int pos = index - navAdapter.getSubscriptionOffset();
+ loadFeedFragmentByPosition(pos, args);
}
+ }
- FragmentTransaction fT = fragmentManager.beginTransaction();
+ public void loadFragment(final String tag, Bundle args) {
+ Log.d(TAG, "loadFragment(\"" + tag + "\", " + args + ")");
Fragment fragment = null;
- if (viewType == NavListAdapter.VIEW_TYPE_NAV) {
- switch (relPos) {
- case POS_NEW:
- fragment = new NewEpisodesFragment();
- break;
- case POS_QUEUE:
- fragment = new QueueFragment();
- break;
- case POS_DOWNLOADS:
- fragment = new DownloadsFragment();
- break;
- case POS_HISTORY:
- fragment = new PlaybackHistoryFragment();
- break;
- case POS_ADD:
- fragment = new AddFeedFragment();
- break;
-
- }
- currentTitle = getString(NavListAdapter.NAV_TITLES[relPos]);
- selectedNavListIndex = relPos;
-
- } else if (viewType == NavListAdapter.VIEW_TYPE_SUBSCRIPTION) {
- Feed feed = itemAccess.getItem(relPos);
- currentTitle = "";
- fragment = ItemlistFragment.newInstance(feed.getId());
- selectedNavListIndex = NavListAdapter.SUBSCRIPTION_OFFSET + relPos;
-
+ switch (tag) {
+ case QueueFragment.TAG:
+ fragment = new QueueFragment();
+ break;
+ case NewEpisodesFragment.TAG:
+ fragment = new NewEpisodesFragment();
+ break;
+ case AllEpisodesFragment.TAG:
+ fragment = new AllEpisodesFragment();
+ break;
+ case DownloadsFragment.TAG:
+ fragment = new DownloadsFragment();
+ break;
+ case PlaybackHistoryFragment.TAG:
+ fragment = new PlaybackHistoryFragment();
+ break;
+ case AddFeedFragment.TAG:
+ fragment = new AddFeedFragment();
+ break;
}
- if (fragment != null) {
- if (args != null) {
- fragment.setArguments(args);
- }
- fT.replace(R.id.main_view, fragment, "main");
- fragmentManager.popBackStack();
- }
- fT.commit();
+ currentTitle = navAdapter.getLabel(tag);
getSupportActionBar().setTitle(currentTitle);
- if (navAdapter != null) {
- navAdapter.notifyDataSetChanged();
+ saveLastNavFragment(tag);
+ if (args != null) {
+ fragment.setArguments(args);
}
+ loadFragment(fragment);
}
- public void loadNavFragment(int position, Bundle args) {
- loadFragment(NavListAdapter.VIEW_TYPE_NAV, position, args);
+ private void loadFeedFragmentByPosition(int relPos, Bundle args) {
+ if(relPos < 0) {
+ return;
+ }
+ Feed feed = itemAccess.getItem(relPos);
+ long feedId = feed.getId();
+ Fragment fragment = ItemlistFragment.newInstance(feedId);
+ if(args != null) {
+ fragment.setArguments(args);
+ }
+ saveLastNavFragment(String.valueOf(feed.getId()));
+ currentTitle = "";
+ getSupportActionBar().setTitle(currentTitle);
+ loadFragment(fragment);
}
- public void loadFeedFragment(long feedID) {
+ public void loadFeedFragmentById(long feedId) {
if (navDrawerData != null) {
- for (int i = 0; i < navDrawerData.feeds.size(); i++) {
- if (navDrawerData.feeds.get(i).getId() == feedID) {
- loadFragment(NavListAdapter.VIEW_TYPE_SUBSCRIPTION, i, null);
- break;
+ int relPos = -1;
+ List<Feed> feeds = navDrawerData.feeds;
+ for (int i = 0; relPos < 0 && i < feeds.size(); i++) {
+ if (feeds.get(i).getId() == feedId) {
+ relPos = i;
}
}
+ if(relPos >= 0) {
+ loadFeedFragmentByPosition(relPos, null);
+ }
+ }
+ }
+
+ private void loadFragment(Fragment fragment) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ // clear back stack
+ for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
+ fragmentManager.popBackStack();
+ }
+ FragmentTransaction t = fragmentManager.beginTransaction();
+ t.replace(R.id.main_view, fragment, "main");
+ fragmentManager.popBackStack();
+ t.commit();
+ if (navAdapter != null) {
+ navAdapter.notifyDataSetChanged();
}
}
@@ -281,20 +355,50 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
return toolbar;
}
+ private int getSelectedNavListIndex() {
+ String lastFragment = getLastNavFragment();
+ int tagIndex = navAdapter.getTags().indexOf(lastFragment);
+ if(tagIndex >= 0) {
+ return tagIndex;
+ } else if(ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) {
+ // the fragment was just hidden
+ return -1;
+ } else { // last fragment was not a list, but a feed
+ long feedId = Long.parseLong(lastFragment);
+ List<Feed> feeds = navDrawerData.feeds;
+ for (int i = 0; i < feeds.size(); i++) {
+ if (feeds.get(i).getId() == feedId) {
+ return i + navAdapter.getSubscriptionOffset();
+ }
+ }
+ return -1;
+ }
+ }
+
private AdapterView.OnItemClickListener navListClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int viewType = parent.getAdapter().getItemViewType(position);
if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER && position != selectedNavListIndex) {
- int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET;
- loadFragment(viewType, relPos, null);
- selectedNavListIndex = position;
- navAdapter.notifyDataSetChanged();
+ loadFragment(position, null);
}
drawerLayout.closeDrawer(navDrawer);
}
};
+ private AdapterView.OnItemLongClickListener newListLongClickListener = new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ if(position < navAdapter.getTags().size()) {
+ showDrawerPreferencesDialog();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -304,7 +408,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
if (!drawerLayout.isDrawerOpen(navDrawer)) {
getSupportActionBar().setTitle(currentTitle);
}
- selectedNavListIndex = savedInstanceState.getInt(SAVE_SELECTED_NAV_INDEX);
+ selectedNavListIndex = getSelectedNavListIndex();
}
}
@@ -318,9 +422,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVE_TITLE, getSupportActionBar().getTitle().toString());
- outState.putInt(SAVE_SELECTED_NAV_INDEX, selectedNavListIndex);
outState.putInt(SAVE_BACKSTACK_COUNT, getSupportFragmentManager().getBackStackEntryCount());
-
}
@Override
@@ -341,7 +443,8 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
StorageUtils.checkStorageAvailability(this);
Intent intent = getIntent();
- if (navDrawerData != null && intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
+ if (navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) &&
+ (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG))) {
handleNavIntent();
}
@@ -370,7 +473,6 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
}
-
private DBReader.NavDrawerData navDrawerData;
private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
private int selectedNavListIndex = 0;
@@ -405,10 +507,14 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
@Override
- public int getNumberOfUnreadItems() {
- return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
+ public int getNumberOfNewItems() {
+ return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
}
+ @Override
+ public int getNumberOfUnreadFeedItems(long feedId) {
+ return (navDrawerData != null) ? navDrawerData.numUnreadFeedItems.get(feedId) : 0;
+ }
};
@@ -428,6 +534,13 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
navDrawerData = result;
navAdapter.notifyDataSetChanged();
+ String lastFragment = getLastNavFragment();
+ if(!ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) {
+ long feedId = Long.valueOf(lastFragment);
+ loadFeedFragmentById(feedId);
+ saveLastNavFragment(null);
+ }
+
if (handleIntent) {
handleNavIntent();
}
@@ -459,12 +572,18 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
};
private void handleNavIntent() {
+ Log.d(TAG, "handleNavIntent()");
Intent intent = getIntent();
- if (intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
- int index = intent.getIntExtra(EXTRA_NAV_INDEX, 0);
- int type = intent.getIntExtra(EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV);
+ if (intent.hasExtra(EXTRA_NAV_TYPE) &&
+ intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)) {
+ int index = intent.getIntExtra(EXTRA_NAV_INDEX, -1);
+ String tag = intent.getStringExtra(EXTRA_FRAGMENT_TAG);
Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
- loadFragment(type, index, args);
+ if (index >= 0) {
+ loadFragment(index, args);
+ } else if (tag != null) {
+ loadFragment(tag, args);
+ }
}
setIntent(new Intent(MainActivity.this, MainActivity.class)); // to avoid handling the intent twice when the configuration changes
}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
index 8edd4185c..0cd388b9d 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -12,6 +12,8 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+
+import android.view.View;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -46,7 +48,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity
protected SeekBar sbPosition;
protected ImageButton butPlay;
protected ImageButton butRev;
+ protected TextView txtvRev;
protected ImageButton butFF;
+ protected TextView txtvFF;
private PlaybackController newPlaybackController() {
return new PlaybackController(this, false) {
@@ -222,7 +226,6 @@ public abstract class MediaplayerActivity extends ActionBarActivity
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
-
if (controller != null) {
controller.release();
}
@@ -373,6 +376,8 @@ public abstract class MediaplayerActivity extends ActionBarActivity
if (controller != null) {
int currentPosition = controller.getPosition();
int duration = controller.getDuration();
+ Log.d(TAG, "currentPosition " + Converter
+ .getDurationStringLong(currentPosition));
if (currentPosition != PlaybackService.INVALID_TIME
&& duration != PlaybackService.INVALID_TIME
&& controller.getMedia() != null) {
@@ -381,8 +386,7 @@ public abstract class MediaplayerActivity extends ActionBarActivity
txtvLength.setText(Converter.getDurationStringLong(duration));
updateProgressbarPosition(currentPosition, duration);
} else {
- Log.w(TAG,
- "Could not react to position observer update because of invalid time");
+ Log.w(TAG, "Could not react to position observer update because of invalid time");
}
}
}
@@ -426,7 +430,15 @@ public abstract class MediaplayerActivity extends ActionBarActivity
txtvLength = (TextView) findViewById(R.id.txtvLength);
butPlay = (ImageButton) findViewById(R.id.butPlay);
butRev = (ImageButton) findViewById(R.id.butRev);
+ txtvRev = (TextView) findViewById(R.id.txtvRev);
+ if(txtvRev != null) {
+ txtvRev.setText(String.valueOf(UserPreferences.getRewindSecs()));
+ }
butFF = (ImageButton) findViewById(R.id.butFF);
+ txtvFF = (TextView) findViewById(R.id.txtvFF);
+ if(txtvFF != null) {
+ txtvFF.setText(String.valueOf(UserPreferences.getFastFowardSecs()));
+ }
// SEEKBAR SETUP
@@ -437,10 +449,100 @@ public abstract class MediaplayerActivity extends ActionBarActivity
butPlay.setOnClickListener(controller.newOnPlayButtonClickListener());
if (butFF != null) {
- butFF.setOnClickListener(controller.newOnFFButtonClickListener());
+ butFF.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int curr = controller.getPosition();
+ controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000);
+ }
+ });
+ butFF.setOnLongClickListener(new View.OnLongClickListener() {
+
+ int choice;
+
+ @Override
+ public boolean onLongClick(View v) {
+ int checked = 0;
+ int rewindSecs = UserPreferences.getFastFowardSecs();
+ final int[] values = getResources().getIntArray(R.array.seek_delta_values);
+ final String[] choices = new String[values.length];
+ for(int i=0; i < values.length; i++) {
+ if (rewindSecs == values[i]) {
+ checked = i;
+ }
+ choices[i] = String.valueOf(values[i]) + " "
+ + getString(R.string.time_unit_seconds);
+ }
+ choice = values[checked];
+ AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this);
+ builder.setTitle(R.string.pref_fast_forward);
+ builder.setSingleChoiceItems(choices, checked,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ choice = values[which];
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ UserPreferences.setPrefFastForwardSecs(choice);
+ txtvFF.setText(String.valueOf(choice));
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+ });
}
if (butRev != null) {
- butRev.setOnClickListener(controller.newOnRevButtonClickListener());
+ butRev.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int curr = controller.getPosition();
+ controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000);
+ }
+ });
+ butRev.setOnLongClickListener(new View.OnLongClickListener() {
+
+ int choice;
+
+ @Override
+ public boolean onLongClick(View v) {
+ int checked = 0;
+ int rewindSecs = UserPreferences.getRewindSecs();
+ final int[] values = getResources().getIntArray(R.array.seek_delta_values);
+ final String[] choices = new String[values.length];
+ for(int i=0; i < values.length; i++) {
+ if (rewindSecs == values[i]) {
+ checked = i;
+ }
+ choices[i] = String.valueOf(values[i]) + " "
+ + getString(R.string.time_unit_seconds);
+ }
+ choice = values[checked];
+ AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this);
+ builder.setTitle(R.string.pref_rewind);
+ builder.setSingleChoiceItems(choices, checked,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ choice = values[which];
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ UserPreferences.setPrefRewindSecs(choice);
+ txtvRev.setText(String.valueOf(choice));
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+ });
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index 3b03ed2db..2b1b13ae6 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -26,7 +26,6 @@ import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
-import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@@ -91,12 +90,10 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
getSupportActionBar().setTitle(R.string.add_new_feed_label);
} else {
- throw new IllegalArgumentException(
- "Activity must be started with feedurl argument!");
+ throw new IllegalArgumentException("Activity must be started with feedurl argument!");
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Activity was started with url " + feedUrl);
+ Log.d(TAG, "Activity was started with url " + feedUrl);
setLoadingLayout();
if (savedInstanceState == null) {
startFeedDownload(feedUrl, null, null);
@@ -147,7 +144,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
@Override
public void run() {
- if (BuildConfig.DEBUG) Log.d(TAG, "Download was completed");
+ Log.d(TAG, "Download was completed");
DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
@@ -164,15 +161,13 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
OnlineFeedViewActivity.this);
if (errorMsg != null
&& status.getReasonDetailed() != null) {
- errorMsg += " ("
- + status.getReasonDetailed() + ")";
+ errorMsg += " (" + status.getReasonDetailed() + ")";
}
showErrorDialog(errorMsg);
}
}
} else {
- Log.wtf(TAG,
- "DownloadStatus returned by Downloader was null");
+ Log.wtf(TAG, "DownloadStatus returned by Downloader was null");
finish();
}
}
@@ -181,21 +176,18 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
private void startFeedDownload(String url, String username, String password) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting feed download");
+ Log.d(TAG, "Starting feed download");
url = URLChecker.prepareURL(url);
feed = new Feed(url, new Date(0));
if (username != null && password != null) {
feed.setPreferences(new FeedPreferences(0, false, username, password));
}
String fileUrl = new File(getExternalCacheDir(),
- FileNameGenerator.generateFileName(feed.getDownload_url()))
- .toString();
+ FileNameGenerator.generateFileName(feed.getDownload_url())).toString();
feed.setFile_url(fileUrl);
final DownloadRequest request = new DownloadRequest(feed.getFile_url(),
feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, true, null);
- downloader = new HttpDownloader(
- request);
+ downloader = new HttpDownloader(request);
new Thread() {
@Override
public void run() {
@@ -233,8 +225,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
"feed must be non-null and downloaded when parseFeed is called");
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Parsing feed");
+ Log.d(TAG, "Parsing feed");
Thread thread = new Thread() {
@@ -258,7 +249,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (UnsupportedFeedtypeException e) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Unsupported feed type detected");
+ Log.d(TAG, "Unsupported feed type detected");
if (StringUtils.equalsIgnoreCase("html", e.getRootElement())) {
if (showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url())) {
return;
@@ -269,8 +260,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
} finally {
boolean rc = new File(feed.getFile_url()).delete();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleted feed source file. Result: " + rc);
+ Log.d(TAG, "Deleted feed source file. Result: " + rc);
}
if (successful) {
@@ -386,7 +376,12 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
String selectedUrl = urls.get(which);
dialog.dismiss();
resetIntent(selectedUrl, titles.get(which));
- startFeedDownload(selectedUrl, null, null);
+ FeedPreferences prefs = feed.getPreferences();
+ if(prefs != null) {
+ startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword());
+ } else {
+ startFeedDownload(selectedUrl, null, null);
+ }
}
};
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
index d7b069b19..511115b3c 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
@@ -11,7 +11,20 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.*;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.ViewFlipper;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
@@ -22,11 +35,6 @@ import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
/**
* Guides the user through the authentication process
* Step 1: Request username and password from user
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java
index 8d3e73429..8e347a819 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonUtils.java
@@ -10,7 +10,9 @@ import org.apache.commons.lang3.Validate;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.LongList;
/**
* Utility methods for the action button that is displayed on the right hand side
@@ -26,9 +28,21 @@ public class ActionButtonUtils {
Validate.notNull(context);
this.context = context;
- drawables = context.obtainStyledAttributes(new int[]{
- R.attr.av_play, R.attr.navigation_cancel, R.attr.av_download, R.attr.av_pause, R.attr.navigation_accept});
- labels = new int[]{R.string.play_label, R.string.cancel_download_label, R.string.download_label, R.string.mark_read_label};
+ drawables = context.obtainStyledAttributes(new int[] {
+ R.attr.av_play,
+ R.attr.navigation_cancel,
+ R.attr.av_download,
+ R.attr.av_pause,
+ R.attr.navigation_accept,
+ R.attr.content_new
+ });
+ labels = new int[] {
+ R.string.play_label,
+ R.string.cancel_download_label,
+ R.string.download_label,
+ R.string.mark_read_label,
+ R.string.add_to_queue_label
+ };
}
/**
@@ -50,18 +64,26 @@ public class ActionButtonUtils {
butSecondary.setContentDescription(context.getString(labels[1]));
} else {
// item is not downloaded and not being downloaded
- butSecondary.setVisibility(View.VISIBLE);
- butSecondary.setImageDrawable(drawables.getDrawable(2));
- butSecondary.setContentDescription(context.getString(labels[2]));
+ LongList queueIds = DBReader.getQueueIDList(context);
+ if(DefaultActionButtonCallback.userAllowedMobileDownloads() ||
+ !DefaultActionButtonCallback.userChoseAddToQueue() || queueIds.contains(item.getId())) {
+ butSecondary.setVisibility(View.VISIBLE);
+ butSecondary.setImageDrawable(drawables.getDrawable(2));
+ butSecondary.setContentDescription(context.getString(labels[2]));
+ } else {
+ // mobile download not allowed yet, item is not in queue and user chose add to queue
+ butSecondary.setVisibility(View.VISIBLE);
+ butSecondary.setImageDrawable(drawables.getDrawable(5));
+ butSecondary.setContentDescription(context.getString(labels[4]));
+ }
}
} else {
- // item is not being downloaded
+ // item is downloaded
butSecondary.setVisibility(View.VISIBLE);
if (media.isCurrentlyPlaying()) {
butSecondary.setImageDrawable(drawables.getDrawable(3));
} else {
- butSecondary
- .setImageDrawable(drawables.getDrawable(0));
+ butSecondary.setImageDrawable(drawables.getDrawable(0));
}
butSecondary.setContentDescription(context.getString(labels[0]));
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java
index 2d481a7ef..ea0c96be9 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
-import android.graphics.Color;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -12,11 +11,9 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.nineoldandroids.view.ViewHelper;
import com.squareup.picasso.Picasso;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DownloadRequester;
@@ -25,19 +22,22 @@ import de.danoeh.antennapod.core.util.Converter;
/**
* List adapter for the list of new episodes
*/
-public class NewEpisodesListAdapter extends BaseAdapter {
+public class AllEpisodesListAdapter extends BaseAdapter {
private final Context context;
private final ItemAccess itemAccess;
private final ActionButtonCallback actionButtonCallback;
private final ActionButtonUtils actionButtonUtils;
+ private final boolean showOnlyNewEpisodes;
- public NewEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) {
+ public AllEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback,
+ boolean showOnlyNewEpisodes) {
super();
this.context = context;
this.itemAccess = itemAccess;
this.actionButtonUtils = new ActionButtonUtils(context);
this.actionButtonCallback = actionButtonCallback;
+ this.showOnlyNewEpisodes = showOnlyNewEpisodes;
}
@Override
@@ -91,7 +91,7 @@ public class NewEpisodesListAdapter extends BaseAdapter {
holder.title.setText(item.getTitle());
holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL));
- if (item.isRead()) {
+ if (showOnlyNewEpisodes || item.isRead() || false == itemAccess.isNew(item)) {
holder.statusUnread.setVisibility(View.INVISIBLE);
} else {
holder.statusUnread.setVisibility(View.VISIBLE);
@@ -125,7 +125,11 @@ public class NewEpisodesListAdapter extends BaseAdapter {
holder.downloadProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
}
}
+ } else {
+ holder.downloadProgress.setVisibility(View.GONE);
+ holder.txtvDuration.setVisibility(View.GONE);
}
+
if (itemAccess.isInQueue(item)) {
holder.queueStatus.setVisibility(View.VISIBLE);
} else {
@@ -142,13 +146,6 @@ public class NewEpisodesListAdapter extends BaseAdapter {
.fit()
.into(holder.imageView);
- if (item.isRead()) {
- // grey it out
- ViewHelper.setAlpha(convertView, .2f);
- } else {
- ViewHelper.setAlpha(convertView, 1.0f);
- }
-
return convertView;
}
@@ -181,5 +178,8 @@ public class NewEpisodesListAdapter extends BaseAdapter {
int getItemDownloadProgressPercent(FeedItem item);
boolean isInQueue(FeedItem item);
+
+ boolean isNew(FeedItem item);
+
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java
index d3843934a..3d233817b 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java
@@ -1,6 +1,8 @@
package de.danoeh.antennapod.adapter;
+import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.widget.Toast;
@@ -10,45 +12,77 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
+
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.LongList;
+import de.danoeh.antennapod.core.util.NetworkUtils;
/**
* Default implementation of an ActionButtonCallback
*/
public class DefaultActionButtonCallback implements ActionButtonCallback {
+
private static final String TAG = "DefaultActionButtonCallback";
private final Context context;
+ private static final int TEN_MINUTES_IN_MILLIS = 60 * 1000 * 10;
+
+ // remember timestamp when user allowed downloading via mobile connection
+ private static long allowMobileDownloadsTimestamp;
+ private static long onlyAddToQueueTimeStamp;
+
public DefaultActionButtonCallback(Context context) {
Validate.notNull(context);
this.context = context;
}
+ public static boolean userAllowedMobileDownloads() {
+ return System.currentTimeMillis() - allowMobileDownloadsTimestamp < TEN_MINUTES_IN_MILLIS;
+ }
+
+ public static boolean userChoseAddToQueue() {
+ return System.currentTimeMillis() - onlyAddToQueueTimeStamp < TEN_MINUTES_IN_MILLIS;
+ }
+
@Override
public void onActionButtonPressed(final FeedItem item) {
-
if (item.hasMedia()) {
final FeedMedia media = item.getMedia();
boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media);
if (!isDownloading && !media.isDownloaded()) {
- try {
- DBTasks.downloadFeedItems(context, item);
- Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show();
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage());
+ LongList queueIds = DBReader.getQueueIDList(context);
+ if (NetworkUtils.isDownloadAllowed(context) || userAllowedMobileDownloads()) {
+ try {
+ DBTasks.downloadFeedItems(context, item);
+ Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show();
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage());
+ }
+ } else if(userChoseAddToQueue() && !queueIds.contains(item.getId())) {
+ DBWriter.addQueueItem(context, item.getId());
+ Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show();
+ } else {
+ confirmMobileDownload(context, item);
}
} else if (isDownloading) {
DownloadRequester.getInstance().cancelDownload(context, media);
- Toast.makeText(context, R.string.download_cancelled_msg, Toast.LENGTH_SHORT).show();
+ if(UserPreferences.isEnableAutodownload()) {
+ DBWriter.setFeedItemAutoDownload(context, media.getItem(), false);
+ Toast.makeText(context, R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(context, R.string.download_canceled_msg, Toast.LENGTH_LONG).show();
+ }
} else { // media is downloaded
if (item.hasMedia() && item.getMedia().isCurrentlyPlaying()) {
context.sendBroadcast(new Intent(PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE));
@@ -79,4 +113,43 @@ public class DefaultActionButtonCallback implements ActionButtonCallback {
}
}
}
+
+ private void confirmMobileDownload(final Context context, final FeedItem item) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder
+ .setTitle(R.string.confirm_mobile_download_dialog_title)
+ .setMessage(context.getText(R.string.confirm_mobile_download_dialog_message))
+ .setPositiveButton(R.string.confirm_mobile_download_dialog_enable_temporarily,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ allowMobileDownloadsTimestamp = System.currentTimeMillis();
+ try {
+ DBTasks.downloadFeedItems(context, item);
+ Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show();
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage());
+ }
+ }
+ });
+ LongList queueIds = DBReader.getQueueIDList(context);
+ if(!queueIds.contains(item.getId())) {
+ builder.setNeutralButton(R.string.confirm_mobile_download_dialog_only_add_to_queue,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ onlyAddToQueueTimeStamp = System.currentTimeMillis();
+ DBWriter.addQueueItem(context, item.getId());
+ Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show();
+ }
+ })
+ .setMessage(context.getText(R.string.confirm_mobile_download_dialog_message_not_in_queue));
+ } else {
+ builder.setMessage(context.getText(R.string.confirm_mobile_download_dialog_message));
+ }
+ builder.setNegativeButton(R.string.cancel_label, null)
+ .create()
+ .show();
+ }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
index f982e86ce..f29cfdf2f 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
@@ -2,20 +2,34 @@ package de.danoeh.antennapod.adapter;
import android.content.Context;
import android.text.format.DateUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import android.widget.Button;
import android.widget.TextView;
+import android.widget.Toast;
+
+import com.joanzapata.android.iconify.Iconify;
+
+import java.util.Date;
+
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
/** Displays a list of DownloadStatus entries. */
public class DownloadLogAdapter extends BaseAdapter {
+ private final String TAG = "DownloadLogAdapter";
+
private Context context;
private ItemAccess itemAccess;
@@ -35,11 +49,11 @@ public class DownloadLogAdapter extends BaseAdapter {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.downloadlog_item, parent, false);
+ holder.icon = (TextView) convertView.findViewById(R.id.txtvIcon);
+ holder.retry = (Button) convertView.findViewById(R.id.btnRetry);
+ holder.date = (TextView) convertView.findViewById(R.id.txtvDate);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.type = (TextView) convertView.findViewById(R.id.txtvType);
- holder.date = (TextView) convertView.findViewById(R.id.txtvDate);
- holder.successful = (TextView) convertView
- .findViewById(R.id.txtvStatus);
holder.reason = (TextView) convertView
.findViewById(R.id.txtvReason);
convertView.setTag(holder);
@@ -62,33 +76,99 @@ public class DownloadLogAdapter extends BaseAdapter {
status.getCompletionDate().getTime(),
System.currentTimeMillis(), 0, 0));
if (status.isSuccessful()) {
- holder.successful.setTextColor(convertView.getResources().getColor(
+ holder.icon.setTextColor(convertView.getResources().getColor(
R.color.download_success_green));
- holder.successful.setText(R.string.download_successful);
+ holder.icon.setText("{fa-check-circle}");
+ Iconify.addIcons(holder.icon);
+ holder.retry.setVisibility(View.GONE);
holder.reason.setVisibility(View.GONE);
} else {
- holder.successful.setTextColor(convertView.getResources().getColor(
+ holder.icon.setTextColor(convertView.getResources().getColor(
R.color.download_failed_red));
- holder.successful.setText(R.string.download_failed);
+ holder.icon.setText("{fa-times-circle}");
+ Iconify.addIcons(holder.icon);
String reasonText = status.getReason().getErrorString(context);
if (status.getReasonDetailed() != null) {
reasonText += ": " + status.getReasonDetailed();
}
holder.reason.setText(reasonText);
holder.reason.setVisibility(View.VISIBLE);
+ if(status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE &&
+ !newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) {
+ holder.retry.setVisibility(View.VISIBLE);
+ holder.retry.setText("{fa-repeat}");
+ Iconify.addIcons(holder.retry);
+ holder.retry.setOnClickListener(clickListener);
+ ButtonHolder btnHolder;
+ if(holder.retry.getTag() != null) {
+ btnHolder = (ButtonHolder) holder.retry.getTag();
+ } else {
+ btnHolder = new ButtonHolder();
+ }
+ btnHolder.typeId = status.getFeedfileType();
+ btnHolder.id = status.getFeedfileId();
+ holder.retry.setTag(btnHolder);
+ } else {
+ holder.retry.setVisibility(View.GONE);
+ holder.retry.setOnClickListener(null);
+ holder.retry.setTag(null);
+ }
}
return convertView;
}
+ private final View.OnClickListener clickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ButtonHolder holder = (ButtonHolder) v.getTag();
+ if(holder.typeId == Feed.FEEDFILETYPE_FEED) {
+ Feed feed = DBReader.getFeed(context, holder.id);
+ feed.setLastUpdate(new Date(0)); // force refresh
+ try {
+ DBTasks.refreshFeed(context, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ } else if(holder.typeId == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ FeedMedia media = DBReader.getFeedMedia(context, holder.id);
+ try {
+ DBTasks.downloadFeedItems(context, media.getItem());
+ Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show();
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage());
+ }
+ } else {
+ Log.wtf(TAG, "Unexpected type id: " + holder.typeId);
+ }
+ v.setVisibility(View.GONE);
+ }
+ };
+
+ private boolean newerWasSuccessful(int position, int feedTypeId, long id) {
+ for (int i = 0; i < position; i++) {
+ DownloadStatus status = getItem(i);
+ if (status.getFeedfileType() == feedTypeId && status.getFeedfileId() == id &&
+ status.isSuccessful()) return true;
+ }
+ return false;
+ }
+
static class Holder {
+ TextView icon;
+ Button retry;
TextView title;
TextView type;
TextView date;
- TextView successful;
TextView reason;
}
+ static class ButtonHolder {
+ int typeId;
+ long id;
+ }
+
@Override
public int getCount() {
return itemAccess.getCount();
@@ -104,9 +184,9 @@ public class DownloadLogAdapter extends BaseAdapter {
return position;
}
- public static interface ItemAccess {
- public int getCount();
- public DownloadStatus getItem(int position);
+ public interface ItemAccess {
+ int getCount();
+ DownloadStatus getItem(int position);
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java
index d56bfc587..b39e23d42 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java
@@ -7,9 +7,16 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.*;
+import android.widget.Adapter;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.nineoldandroids.view.ViewHelper;
+
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
@@ -105,22 +112,15 @@ public class FeedItemlistAdapter extends BaseAdapter {
}
holder.title.setText(buffer.toString());
- FeedItem.State state = item.getState();
- switch (state) {
- case PLAYING:
- holder.statusUnread.setVisibility(View.INVISIBLE);
- holder.episodeProgress.setVisibility(View.VISIBLE);
- break;
- case IN_PROGRESS:
- holder.statusUnread.setVisibility(View.INVISIBLE);
- holder.episodeProgress.setVisibility(View.VISIBLE);
- break;
- case NEW:
- holder.statusUnread.setVisibility(View.VISIBLE);
- break;
- default:
- holder.statusUnread.setVisibility(View.INVISIBLE);
- break;
+ if(false == item.isRead() && itemAccess.isNew(item)) {
+ holder.statusUnread.setVisibility(View.VISIBLE);
+ } else {
+ holder.statusUnread.setVisibility(View.INVISIBLE);
+ }
+ if(item.isRead()) {
+ ViewHelper.setAlpha(convertView, 0.5f);
+ } else {
+ ViewHelper.setAlpha(convertView, 1.0f);
}
holder.published.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL));
@@ -146,10 +146,10 @@ public class FeedItemlistAdapter extends BaseAdapter {
item.getMedia())) {
holder.episodeProgress.setVisibility(View.VISIBLE);
holder.episodeProgress.setProgress(((ItemAccess) itemAccess).getItemDownloadProgressPercent(item));
- holder.published.setVisibility(View.GONE);
} else {
- holder.episodeProgress.setVisibility(View.GONE);
- holder.published.setVisibility(View.VISIBLE);
+ if(media.getPosition() == 0) {
+ holder.episodeProgress.setVisibility(View.GONE);
+ }
}
TypedArray typeDrawables = context.obtainStyledAttributes(
@@ -211,14 +211,18 @@ public class FeedItemlistAdapter extends BaseAdapter {
notifyDataSetChanged();
}
- public static interface ItemAccess {
- public boolean isInQueue(FeedItem item);
+ public interface ItemAccess {
+
+ boolean isInQueue(FeedItem item);
int getItemDownloadProgressPercent(FeedItem item);
int getCount();
FeedItem getItem(int position);
+
+ boolean isNew(FeedItem item);
+
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java
index 05783e3ee..13982f57d 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java
@@ -1,35 +1,51 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import android.widget.IconTextView;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.fragment.AddFeedFragment;
+import de.danoeh.antennapod.fragment.AllEpisodesFragment;
+import de.danoeh.antennapod.fragment.DownloadsFragment;
+import de.danoeh.antennapod.fragment.NewEpisodesFragment;
+import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
/**
* BaseAdapter for the navigation drawer
*/
-public class NavListAdapter extends BaseAdapter {
+public class NavListAdapter extends BaseAdapter
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final int VIEW_TYPE_COUNT = 3;
public static final int VIEW_TYPE_NAV = 0;
public static final int VIEW_TYPE_SECTION_DIVIDER = 1;
public static final int VIEW_TYPE_SUBSCRIPTION = 2;
- public static final int[] NAV_TITLES = {R.string.queue_label, R.string.new_episodes_label, R.string.downloads_label, R.string.playback_history_label, R.string.add_feed_label};
-
- private final Drawable[] drawables;
-
- public static final int SUBSCRIPTION_OFFSET = 1 + NAV_TITLES.length;
+ private static List<String> tags;
+ private static String[] titles;
private ItemAccess itemAccess;
private Context context;
@@ -38,23 +54,79 @@ public class NavListAdapter extends BaseAdapter {
this.itemAccess = itemAccess;
this.context = context;
- TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.stat_playlist, R.attr.ic_new,
- R.attr.av_download, R.attr.ic_history, R.attr.content_new});
- drawables = new Drawable[]{ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2),
- ta.getDrawable(3), ta.getDrawable(4)};
+ titles = context.getResources().getStringArray(R.array.nav_drawer_titles);
+ loadItems();
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) {
+ loadItems();
+ }
+ }
+
+ private void loadItems() {
+ List<String> newTags = new ArrayList<String>(Arrays.asList(MainActivity.NAV_DRAWER_TAGS));
+ List<String> hiddenFragments = UserPreferences.getHiddenDrawerItems();
+ for(String hidden : hiddenFragments) {
+ newTags.remove(hidden);
+ }
+ tags = newTags;
+ notifyDataSetChanged();
+ }
+
+ public String getLabel(String tag) {
+ int index = ArrayUtils.indexOf(MainActivity.NAV_DRAWER_TAGS, tag);
+ return titles[index];
+ }
+
+ private Drawable getDrawable(String tag) {
+ int icon;
+ switch (tag) {
+ case QueueFragment.TAG:
+ icon = R.attr.stat_playlist;
+ break;
+ case NewEpisodesFragment.TAG:
+ icon = R.attr.ic_new;
+ break;
+ case AllEpisodesFragment.TAG:
+ icon = R.attr.feed;
+ break;
+ case DownloadsFragment.TAG:
+ icon = R.attr.av_download;
+ break;
+ case PlaybackHistoryFragment.TAG:
+ icon = R.attr.ic_history;
+ break;
+ case AddFeedFragment.TAG:
+ icon = R.attr.content_new;
+ break;
+ default:
+ return null;
+ }
+ TypedArray ta = context.obtainStyledAttributes(new int[] { icon } );
+ Drawable result = ta.getDrawable(0);
ta.recycle();
+ return result;
+ }
+
+ public List<String> getTags() {
+ return Collections.unmodifiableList(tags);
}
+
@Override
public int getCount() {
- return NAV_TITLES.length + 1 + itemAccess.getCount();
+ return getSubscriptionOffset() + itemAccess.getCount();
}
@Override
public Object getItem(int position) {
int viewType = getItemViewType(position);
if (viewType == VIEW_TYPE_NAV) {
- return context.getString(NAV_TITLES[position]);
+ return getLabel(tags.get(position));
} else if (viewType == VIEW_TYPE_SECTION_DIVIDER) {
return "";
} else {
@@ -69,9 +141,9 @@ public class NavListAdapter extends BaseAdapter {
@Override
public int getItemViewType(int position) {
- if (0 <= position && position < NAV_TITLES.length) {
+ if (0 <= position && position < tags.size()) {
return VIEW_TYPE_NAV;
- } else if (position < NAV_TITLES.length + 1) {
+ } else if (position < getSubscriptionOffset()) {
return VIEW_TYPE_SECTION_DIVIDER;
} else {
return VIEW_TYPE_SUBSCRIPTION;
@@ -83,6 +155,11 @@ public class NavListAdapter extends BaseAdapter {
return VIEW_TYPE_COUNT;
}
+ public int getSubscriptionOffset() {
+ return tags.size() > 0 ? tags.size() + 1 : 0;
+ }
+
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int viewType = getItemViewType(position);
@@ -92,7 +169,7 @@ public class NavListAdapter extends BaseAdapter {
} else if (viewType == VIEW_TYPE_SECTION_DIVIDER) {
v = getSectionDividerView(convertView, parent);
} else {
- v = getFeedView(position - SUBSCRIPTION_OFFSET, convertView, parent);
+ v = getFeedView(position - getSubscriptionOffset(), convertView, parent);
}
if (v != null && viewType != VIEW_TYPE_SECTION_DIVIDER) {
TextView txtvTitle = (TextView) v.findViewById(R.id.txtvTitle);
@@ -114,9 +191,9 @@ public class NavListAdapter extends BaseAdapter {
convertView = inflater.inflate(R.layout.nav_listitem, parent, false);
+ holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.count = (TextView) convertView.findViewById(R.id.txtvCount);
- holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
convertView.setTag(holder);
} else {
holder = (NavHolder) convertView.getTag();
@@ -124,7 +201,7 @@ public class NavListAdapter extends BaseAdapter {
holder.title.setText(title);
- if (NAV_TITLES[position] == R.string.queue_label) {
+ if (tags.get(position).equals(QueueFragment.TAG)) {
int queueSize = itemAccess.getQueueSize();
if (queueSize > 0) {
holder.count.setVisibility(View.VISIBLE);
@@ -132,8 +209,8 @@ public class NavListAdapter extends BaseAdapter {
} else {
holder.count.setVisibility(View.GONE);
}
- } else if (NAV_TITLES[position] == R.string.new_episodes_label) {
- int unreadItems = itemAccess.getNumberOfUnreadItems();
+ } else if (tags.get(position).equals(NewEpisodesFragment.TAG)) {
+ int unreadItems = itemAccess.getNumberOfNewItems();
if (unreadItems > 0) {
holder.count.setVisibility(View.VISIBLE);
holder.count.setText(String.valueOf(unreadItems));
@@ -144,7 +221,7 @@ public class NavListAdapter extends BaseAdapter {
holder.count.setVisibility(View.GONE);
}
- holder.image.setImageDrawable(drawables[position]);
+ holder.image.setImageDrawable(getDrawable(tags.get(position)));
return convertView;
}
@@ -172,45 +249,59 @@ public class NavListAdapter extends BaseAdapter {
convertView = inflater.inflate(R.layout.nav_feedlistitem, parent, false);
- holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
+ holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
+ holder.failure = (IconTextView) convertView.findViewById(R.id.itxtvFailure);
+ holder.count = (TextView) convertView.findViewById(R.id.txtvCount);
convertView.setTag(holder);
} else {
holder = (FeedHolder) convertView.getTag();
}
- holder.title.setText(feed.getTitle());
-
Picasso.with(context)
.load(feed.getImageUri())
.fit()
.into(holder.image);
+ holder.title.setText(feed.getTitle());
+
+
+ if(feed.hasLastUpdateFailed()) {
+ holder.failure.setVisibility(View.VISIBLE);
+ } else {
+ holder.failure.setVisibility(View.GONE);
+ }
+ int feedUnreadItems = itemAccess.getNumberOfUnreadFeedItems(feed.getId());
+ if(feedUnreadItems > 0) {
+ holder.count.setVisibility(View.VISIBLE);
+ holder.count.setText(String.valueOf(feedUnreadItems));
+ holder.count.setTypeface(holder.title.getTypeface());
+ } else {
+ holder.count.setVisibility(View.INVISIBLE);
+ }
return convertView;
}
static class NavHolder {
+ ImageView image;
TextView title;
TextView count;
- ImageView image;
}
static class FeedHolder {
- TextView title;
ImageView image;
+ TextView title;
+ IconTextView failure;
+ TextView count;
}
-
public interface ItemAccess {
- public int getCount();
-
- public Feed getItem(int position);
-
- public int getSelectedItemIndex();
-
- public int getQueueSize();
-
- public int getNumberOfUnreadItems();
+ int getCount();
+ Feed getItem(int position);
+ int getSelectedItemIndex();
+ int getQueueSize();
+ int getNumberOfNewItems();
+ int getNumberOfUnreadFeedItems(long feedId);
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java
index a256dc129..bba5a00a9 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java
@@ -14,9 +14,9 @@ import android.widget.TextView;
import com.squareup.picasso.Picasso;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
@@ -31,6 +31,8 @@ public class QueueListAdapter extends BaseAdapter {
private final ActionButtonCallback actionButtonCallback;
private final ActionButtonUtils actionButtonUtils;
+ private boolean locked;
+
public QueueListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) {
super();
@@ -38,6 +40,12 @@ public class QueueListAdapter extends BaseAdapter {
this.itemAccess = itemAccess;
this.actionButtonUtils = new ActionButtonUtils(context);
this.actionButtonCallback = actionButtonCallback;
+ locked = UserPreferences.isQueueLocked();
+ }
+
+ public void setLocked(boolean locked) {
+ this.locked = locked;
+ notifyDataSetChanged();
}
@Override
@@ -67,6 +75,7 @@ public class QueueListAdapter extends BaseAdapter {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.queue_listitem,
parent, false);
+ holder.dragHandle = (ImageView) convertView.findViewById(R.id.drag_handle);
holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.pubDate = (TextView) convertView.findViewById(R.id.txtvPubDate);
@@ -83,6 +92,12 @@ public class QueueListAdapter extends BaseAdapter {
holder = (Holder) convertView.getTag();
}
+ if(locked) {
+ holder.dragHandle.setVisibility(View.GONE);
+ } else {
+ holder.dragHandle.setVisibility(View.VISIBLE);
+ }
+
holder.title.setText(item.getTitle());
FeedMedia media = item.getMedia();
@@ -143,6 +158,7 @@ public class QueueListAdapter extends BaseAdapter {
static class Holder {
+ ImageView dragHandle;
ImageView imageView;
TextView title;
TextView pubDate;
diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java
index 3a9e62435..998bfa6c9 100644
--- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java
+++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java
@@ -20,7 +20,7 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
public PendingIntent getNotificationContentIntent(Context context) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV);
- intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS);
+ intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, DownloadsFragment.TAG);
Bundle args = new Bundle();
args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_RUNNING);
intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args);
@@ -41,7 +41,7 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
public PendingIntent getReportNotificationContentIntent(Context context) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV);
- intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS);
+ intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, DownloadsFragment.TAG);
Bundle args = new Bundle();
args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG);
intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args);
diff --git a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java
index 10a3c1b32..943e05690 100644
--- a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java
+++ b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java
@@ -13,7 +13,7 @@ public class StorageCallbacksImpl implements StorageCallbacks {
@Override
public int getDatabaseVersion() {
- return 14;
+ return 15;
}
@Override
@@ -124,5 +124,30 @@ public class StorageCallbacksImpl implements StorageCallbacks {
PodDBAdapter.KEY_LINK,
PodDBAdapter.KEY_CHAPTER_TYPE));
}
+ if(oldVersion <= 14) {
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER");
+ db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = "
+ + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID
+ + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")");
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT");
+
+
+ db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ + " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
+
+ // create indexes
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+ }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
index e4ae1683b..bbe6fab46 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -20,7 +20,8 @@ import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment;
* Provides actions for adding new podcast subscriptions
*/
public class AddFeedFragment extends Fragment {
- private static final String TAG = "AddFeedFragment";
+
+ public static final String TAG = "AddFeedFragment";
/**
* Preset value for url text field.
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
new file mode 100644
index 000000000..ff5485251
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
@@ -0,0 +1,539 @@
+package de.danoeh.antennapod.fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.SearchView;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.mobeta.android.dslv.DragSortListView;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
+import de.danoeh.antennapod.adapter.AllEpisodesListAdapter;
+import de.danoeh.antennapod.core.asynctask.DownloadObserver;
+import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
+import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.service.download.DownloadService;
+import de.danoeh.antennapod.core.service.download.Downloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.LongList;
+import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
+import de.danoeh.antennapod.menuhandler.MenuItemUtils;
+
+/**
+ * Shows unread or recently published episodes
+ */
+public class AllEpisodesFragment extends Fragment {
+
+ public static final String TAG = "AllEpisodesFragment";
+
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
+ EventDistributor.DOWNLOAD_QUEUED |
+ EventDistributor.UNREAD_ITEMS_UPDATE |
+ EventDistributor.PLAYER_STATUS_UPDATE;
+
+ private static final int RECENT_EPISODES_LIMIT = 150;
+ private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment";
+ private static final String PREF_KEY_LIST_TOP = "list_top";
+ private static final String PREF_KEY_LIST_SELECTION = "list_selection";
+
+ private String prefName;
+ protected DragSortListView listView;
+ private AllEpisodesListAdapter listAdapter;
+ private TextView txtvEmpty;
+ private ProgressBar progLoading;
+ private ContextMenu contextMenu;
+
+ private List<FeedItem> episodes;
+ private LongList queuedItemsIds;
+ private LongList newItemsIds;
+ private List<Downloader> downloaderList;
+
+ private boolean itemsLoaded = false;
+ private boolean viewsCreated = false;
+ private final boolean showOnlyNewEpisodes;
+
+ private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>();
+
+ private DownloadObserver downloadObserver = null;
+
+ private boolean isUpdatingFeeds;
+
+ public AllEpisodesFragment() {
+ // by default we show all the episodes
+ this(false, DEFAULT_PREF_NAME);
+ }
+
+ // this is only going to be called by our sub-class.
+ // The Android docs say to avoid non-default constructors
+ // but I think this will be OK since it will only be invoked
+ // from a fragment via a default constructor
+ protected AllEpisodesFragment(boolean showOnlyNewEpisodes, String prefName) {
+ this.showOnlyNewEpisodes = showOnlyNewEpisodes;
+ this.prefName = prefName;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ startItemLoader();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventDistributor.getInstance().register(contentUpdate);
+ this.activity.set((MainActivity) getActivity());
+ if (downloadObserver != null) {
+ downloadObserver.setActivity(getActivity());
+ downloadObserver.onResume();
+ }
+ if (viewsCreated && itemsLoaded) {
+ onFragmentLoaded();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ saveScrollPosition();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ stopItemLoader();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ this.activity.set((MainActivity) getActivity());
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ resetViewState();
+ }
+
+ private void saveScrollPosition() {
+ SharedPreferences prefs = getActivity().getSharedPreferences(prefName, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ View v = listView.getChildAt(0);
+ int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
+ editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
+ editor.putInt(PREF_KEY_LIST_TOP, top);
+ editor.commit();
+ }
+
+ private void restoreScrollPosition() {
+ SharedPreferences prefs = getActivity().getSharedPreferences(prefName, Context.MODE_PRIVATE);
+ int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
+ int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
+ if (listSelection > 0 || top > 0) {
+ listView.setSelectionFromTop(listSelection, top);
+ // restore once, then forget
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(PREF_KEY_LIST_SELECTION, 0);
+ editor.putInt(PREF_KEY_LIST_TOP, 0);
+ editor.commit();
+ }
+ }
+
+ protected void resetViewState() {
+ listAdapter = null;
+ activity.set(null);
+ viewsCreated = false;
+ if (downloadObserver != null) {
+ downloadObserver.onPause();
+ }
+ }
+
+
+ private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() {
+ @Override
+ public boolean isRefreshing() {
+ return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
+ }
+ };
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ if (itemsLoaded) {
+ inflater.inflate(R.menu.new_episodes, menu);
+
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
+ sv.setQueryHint(getString(R.string.search_hint));
+ sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ sv.clearFocus();
+ ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s));
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
+ isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
+ }
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ if (itemsLoaded) {
+ MenuItem menuItem = menu.findItem(R.id.mark_all_read_item);
+ if (menuItem != null) {
+ menuItem.setVisible(episodes != null && !episodes.isEmpty());
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (!super.onOptionsItemSelected(item)) {
+ switch (item.getItemId()) {
+ case R.id.refresh_item:
+ List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
+ if (feeds != null) {
+ DBTasks.refreshAllFeeds(getActivity(), feeds);
+ }
+ return true;
+ case R.id.mark_all_read_item:
+ ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
+ R.string.mark_all_read_label,
+ R.string.mark_all_read_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ DBWriter.markAllItemsRead(getActivity());
+ Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
+ }
+ };
+ conDialog.createNewDialog().show();
+ return true;
+ default:
+ return false;
+ }
+ } else {
+ return true;
+ }
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return onCreateViewHelper(inflater, container, savedInstanceState,
+ R.layout.all_episodes_fragment, R.string.all_episodes_label);
+ }
+
+ protected View onCreateViewHelper(LayoutInflater inflater,
+ ViewGroup container,
+ Bundle savedInstanceState,
+ int fragmentResource,
+ int titleString) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ ((MainActivity) getActivity()).getSupportActionBar().setTitle(titleString);
+
+ View root = inflater.inflate(fragmentResource, container, false);
+
+ listView = (DragSortListView) root.findViewById(android.R.id.list);
+ txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
+ progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
+
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount());
+ if (item != null) {
+ ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
+ }
+
+ }
+ });
+
+ registerForContextMenu(listView);
+
+ if (!itemsLoaded) {
+ progLoading.setVisibility(View.VISIBLE);
+ txtvEmpty.setVisibility(View.GONE);
+ }
+
+ viewsCreated = true;
+
+ if (itemsLoaded && activity.get() != null) {
+ onFragmentLoaded();
+ }
+
+ return root;
+ }
+
+ private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ if(contextMenu == null) {
+ return;
+ }
+ MenuItem item = contextMenu.findItem(id);
+ if (item != null) {
+ item.setVisible(visible);
+ }
+ }
+ };
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ FeedItem item = itemAccess.getItem(adapterInfo.position);
+
+ MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.allepisodes_context, menu);
+
+ if (item != null) {
+ menu.setHeaderTitle(item.getTitle());
+ }
+
+ contextMenu = menu;
+ FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ FeedItem selectedItem = itemAccess.getItem(menuInfo.position);
+
+ if (selectedItem == null) {
+ Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection");
+ return super.onContextItemSelected(item);
+ }
+
+ try {
+ return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
+ return true;
+ }
+ }
+
+ private void onFragmentLoaded() {
+ if (listAdapter == null) {
+ listAdapter = new AllEpisodesListAdapter(activity.get(), itemAccess,
+ new DefaultActionButtonCallback(activity.get()), showOnlyNewEpisodes);
+ listView.setAdapter(listAdapter);
+ listView.setEmptyView(txtvEmpty);
+ downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback);
+ downloadObserver.onResume();
+ }
+ listAdapter.notifyDataSetChanged();
+ restoreScrollPosition();
+ getActivity().supportInvalidateOptionsMenu();
+ updateShowOnlyEpisodesListViewState();
+ }
+
+ private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
+ @Override
+ public void onContentChanged() {
+ if (listAdapter != null) {
+ listAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onDownloadDataAvailable(List<Downloader> downloaderList) {
+ AllEpisodesFragment.this.downloaderList = downloaderList;
+ if (listAdapter != null) {
+ listAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ private AllEpisodesListAdapter.ItemAccess itemAccess = new AllEpisodesListAdapter.ItemAccess() {
+
+ @Override
+ public int getCount() {
+ if (itemsLoaded) {
+ return episodes.size();
+ }
+ return 0;
+ }
+
+ @Override
+ public FeedItem getItem(int position) {
+ if (itemsLoaded) {
+ return episodes.get(position);
+ }
+ return null;
+ }
+
+ @Override
+ public int getItemDownloadProgressPercent(FeedItem item) {
+ if (downloaderList != null) {
+ for (Downloader downloader : downloaderList) {
+ if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
+ && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
+ return downloader.getDownloadRequest().getProgressPercent();
+ }
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean isInQueue(FeedItem item) {
+ if (itemsLoaded) {
+ return queuedItemsIds.contains(item.getId());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isNew(FeedItem item) {
+ if (itemsLoaded) {
+ // should actually never be called in NewEpisodesFragment, but better safe than sorry
+ return showOnlyNewEpisodes || newItemsIds.contains(item.getId());
+ } else {
+ return false;
+ }
+ }
+
+
+ };
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((arg & EVENTS) != 0) {
+ startItemLoader();
+ if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
+ getActivity().supportInvalidateOptionsMenu();
+ }
+ }
+ }
+ };
+
+ private void updateShowOnlyEpisodesListViewState() {
+ if (showOnlyNewEpisodes) {
+ listView.setEmptyView(null);
+ txtvEmpty.setVisibility(View.GONE);
+ } else {
+ listView.setEmptyView(txtvEmpty);
+ }
+ }
+
+ private ItemLoader itemLoader;
+
+ protected void startItemLoader() {
+ if (itemLoader != null) {
+ itemLoader.cancel(true);
+ }
+ itemLoader = new ItemLoader();
+ itemLoader.execute();
+ }
+
+ protected void stopItemLoader() {
+ if (itemLoader != null) {
+ itemLoader.cancel(true);
+ }
+ }
+
+ private class ItemLoader extends AsyncTask<Void, Void, Object[]> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (viewsCreated && !itemsLoaded) {
+ listView.setVisibility(View.GONE);
+ txtvEmpty.setVisibility(View.GONE);
+ progLoading.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected Object[] doInBackground(Void... params) {
+ Context context = activity.get();
+ if (context != null) {
+ if(showOnlyNewEpisodes) {
+ return new Object[] {
+ DBReader.getNewItemsList(context),
+ DBReader.getQueueIDList(context),
+ null // see ItemAccess.isNew
+ };
+ } else {
+ return new Object[]{
+ DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
+ DBReader.getQueueIDList(context),
+ DBReader.getNewItemIds(context)
+ };
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Object[] lists) {
+ super.onPostExecute(lists);
+ listView.setVisibility(View.VISIBLE);
+ progLoading.setVisibility(View.GONE);
+
+ if (lists != null) {
+ episodes = (List<FeedItem>) lists[0];
+ queuedItemsIds = (LongList) lists[1];
+ newItemsIds = (LongList) lists[2];
+ itemsLoaded = true;
+ if (viewsCreated && activity.get() != null) {
+ onFragmentLoaded();
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
index 0f6f7d53c..074a87ea0 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
@@ -20,8 +20,6 @@ import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
/**
* Shows the download log
@@ -119,7 +117,7 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
@@ -131,8 +129,11 @@ public class DownloadLogFragment extends ListFragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- menu.findItem(R.id.clear_history_item).setVisible(downloadLog != null && !downloadLog.isEmpty());
+ if (itemsLoaded) {
+ MenuItem menuItem = menu.findItem(R.id.clear_history_item);
+ if(menuItem != null) {
+ menuItem.setVisible(downloadLog != null && !downloadLog.isEmpty());
+ }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java
index 712db1421..1ce379cf8 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java
@@ -17,6 +17,8 @@ import de.danoeh.antennapod.R;
*/
public class DownloadsFragment extends Fragment {
+ public static final String TAG = "DownloadsFragment";
+
public static final String ARG_SELECTED_TAB = "selected_tab";
public static final int POS_RUNNING = 0;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
index 10409a421..51a1e2252 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
@@ -270,7 +270,7 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
return;
}
popupMenu.getMenu().clear();
- popupMenu.inflate(R.menu.feeditem_dialog);
+ popupMenu.inflate(R.menu.feeditem_options);
if (item.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue);
} else {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java
index a2cb8da18..a9cbe8291 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java
@@ -9,21 +9,27 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ListFragment;
-import android.support.v4.util.Pair;
+import android.support.v4.view.MenuItemCompat;
+
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
+import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
+import android.widget.IconTextView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
+import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.joanzapata.android.iconify.Iconify;
import com.squareup.picasso.Picasso;
import org.apache.commons.lang3.Validate;
@@ -42,7 +48,9 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedItemFilter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.service.download.DownloadService;
@@ -53,9 +61,9 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
+import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.greenrobot.event.EventBus;
/**
@@ -74,10 +82,13 @@ public class ItemlistFragment extends ListFragment {
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected FeedItemlistAdapter adapter;
+ private ContextMenu contextMenu;
private long feedID;
private Feed feed;
- private LongList queue;
+ private LongList queuedItemsIds;
+ private LongList newItemsIds;
+
private boolean itemsLoaded = false;
private boolean viewsCreated = false;
@@ -88,6 +99,10 @@ public class ItemlistFragment extends ListFragment {
private MoreContentListFooterUtil listFooter;
private boolean isUpdatingFeed;
+
+ private IconTextView txtvFailure;
+
+ private TextView txtvInformation;
/**
* Creates new ItemlistFragment which shows the Feeditems of a specific
@@ -180,11 +195,12 @@ public class ItemlistFragment extends ListFragment {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
FeedMenuHandler.onCreateOptionsMenu(inflater, menu);
- final SearchView sv = new SearchView(getActivity());
- MenuItemUtils.addSearchItem(menu, sv);
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
@@ -207,7 +223,7 @@ public class ItemlistFragment extends ListFragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
}
}
@@ -224,7 +240,7 @@ public class ItemlistFragment extends ListFragment {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
- ((MainActivity) getActivity()).loadNavFragment(MainActivity.POS_NEW, null);
+ ((MainActivity) getActivity()).loadFragment(NewEpisodesFragment.TAG, null);
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
@@ -255,10 +271,61 @@ public class ItemlistFragment extends ListFragment {
} else {
return true;
}
+ }
+
+ private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ if(contextMenu == null) {
+ return;
+ }
+ MenuItem item = contextMenu.findItem(id);
+ if (item != null) {
+ item.setVisible(visible);
+ }
+ }
+ };
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
+
+ // because of addHeaderView(), positions are increased by 1!
+ FeedItem item = itemAccess.getItem(adapterInfo.position-1);
+
+ MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.feeditemlist_context, menu);
+
+ if (item != null) {
+ menu.setHeaderTitle(item.getTitle());
+ }
+ contextMenu = menu;
+ FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds);
}
@Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+ // because of addHeaderView(), positions are increased by 1!
+ FeedItem selectedItem = itemAccess.getItem(menuInfo.position-1);
+
+ if (selectedItem == null) {
+ Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection");
+ return super.onContextItemSelected(item);
+ }
+
+ try {
+ return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
+ } catch (DownloadRequestException e) {
+ // context menu doesn't contain download functionality
+ return true;
+ }
+ }
+
+
+ @Override
public void setListAdapter(ListAdapter adapter) {
// This workaround prevents the ListFragment from setting a list adapter when its state is restored.
// This is only necessary on API 10 because addFooterView throws an internal exception in this case.
@@ -272,6 +339,8 @@ public class ItemlistFragment extends ListFragment {
super.onViewCreated(view, savedInstanceState);
((ActionBarActivity) getActivity()).getSupportActionBar().setTitle("");
+ registerForContextMenu(getListView());
+
viewsCreated = true;
if (itemsLoaded) {
onFragmentLoaded();
@@ -291,12 +360,19 @@ public class ItemlistFragment extends ListFragment {
startItemLoader();
}
+ public void onEvent(FeedEvent event) {
+ Log.d(TAG, "onEvent(" + event + ")");
+ if(event.feedId == feedID) {
+ startItemLoader();
+ }
+ }
+
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
- Log.d(TAG, "Received contentUpdate Intent.");
+ Log.d(TAG, "Received contentUpdate Intent. arg " + arg);
if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) {
updateProgressBarVisibility();
} else {
@@ -330,6 +406,7 @@ public class ItemlistFragment extends ListFragment {
downloadObserver = new DownloadObserver(getActivity(), new Handler(), downloadObserverCallback);
downloadObserver.onResume();
}
+ refreshHeaderView();
setListShown(true);
adapter.notifyDataSetChanged();
@@ -343,6 +420,32 @@ public class ItemlistFragment extends ListFragment {
}
+ private void refreshHeaderView() {
+ if(feed.hasLastUpdateFailed()) {
+ txtvFailure.setVisibility(View.VISIBLE);
+ } else {
+ txtvFailure.setVisibility(View.GONE);
+ }
+ if(feed.getItemFilter() != null) {
+ FeedItemFilter filter = feed.getItemFilter();
+ if(filter.getValues().length > 0) {
+ if(feed.hasLastUpdateFailed()) {
+ RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) txtvInformation.getLayoutParams();
+ p.addRule(RelativeLayout.BELOW, R.id.txtvFailure);
+ }
+ txtvInformation.setText("{fa-info-circle} " + this.getString(R.string.filtered_label));
+ Iconify.addIcons(txtvInformation);
+ txtvInformation.setVisibility(View.VISIBLE);
+ } else {
+ txtvInformation.setVisibility(View.GONE);
+ }
+ } else {
+
+ txtvInformation.setVisibility(View.GONE);
+ }
+ }
+
+
private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
@Override
public void onContentChanged() {
@@ -376,6 +479,8 @@ public class ItemlistFragment extends ListFragment {
ImageView imgvBackground = (ImageView) header.findViewById(R.id.imgvBackground);
ImageView imgvCover = (ImageView) header.findViewById(R.id.imgvCover);
ImageButton butShowInfo = (ImageButton) header.findViewById(R.id.butShowInfo);
+ txtvInformation = (TextView) header.findViewById(R.id.txtvInformation);
+ txtvFailure = (IconTextView) header.findViewById(R.id.txtvFailure);
txtvTitle.setText(feed.getTitle());
txtvAuthor.setText(feed.getAuthor());
@@ -406,6 +511,7 @@ public class ItemlistFragment extends ListFragment {
});
}
+
private void setupFooterView() {
if (getListView() == null || feed == null) {
Log.e(TAG, "Unable to setup listview: listView = null or feed = null");
@@ -438,17 +544,22 @@ public class ItemlistFragment extends ListFragment {
@Override
public FeedItem getItem(int position) {
- return (feed != null) ? feed.getItemAtIndex(true, position) : null;
+ return (feed != null) ? feed.getItemAtIndex(position) : null;
}
@Override
public int getCount() {
- return (feed != null) ? feed.getNumOfItems(true) : 0;
+ return (feed != null) ? feed.getNumOfItems() : 0;
}
@Override
public boolean isInQueue(FeedItem item) {
- return (queue != null) && queue.contains(item.getId());
+ return (queuedItemsIds != null) && queuedItemsIds.contains(item.getId());
+ }
+
+ @Override
+ public boolean isNew(FeedItem item) {
+ return (newItemsIds != null) && newItemsIds.contains(item.getId());
}
@Override
@@ -481,26 +592,32 @@ public class ItemlistFragment extends ListFragment {
}
}
- private class ItemLoader extends AsyncTask<Long, Void, Pair<Feed,LongList>> {
+ private class ItemLoader extends AsyncTask<Long, Void, Object[]> {
@Override
- protected Pair<Feed,LongList> doInBackground(Long... params) {
+ protected Object[] doInBackground(Long... params) {
long feedID = params[0];
Context context = getActivity();
if (context != null) {
Feed feed = DBReader.getFeed(context, feedID);
- LongList queue = DBReader.getQueueIDList(context);
- return Pair.create(feed, queue);
+ if(feed.getItemFilter() != null) {
+ FeedItemFilter filter = feed.getItemFilter();
+ feed.setItems(filter.filter(context, feed.getItems()));
+ }
+ LongList queuedItemsIds = DBReader.getQueueIDList(context);
+ LongList newItemsIds = DBReader.getNewItemIds(context);
+ return new Object[] { feed, queuedItemsIds, newItemsIds };
} else {
return null;
}
}
@Override
- protected void onPostExecute(Pair<Feed,LongList> res) {
+ protected void onPostExecute(Object[] res) {
super.onPostExecute(res);
if (res != null) {
- feed = res.first;
- queue = res.second;
+ feed = (Feed) res[0];
+ queuedItemsIds = (LongList) res[1];
+ newItemsIds = res[2] == null ? null : (LongList) res[2];
itemsLoaded = true;
if (viewsCreated) {
onFragmentLoaded();
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
index c14b0cc6e..16789d694 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.content.Intent;
+import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -28,6 +29,7 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.*;
@@ -124,6 +126,13 @@ public class ItunesSearchFragment extends Fragment {
}
});
+ SearchView.SearchAutoComplete textField = (SearchView.SearchAutoComplete) searchView.findViewById(de.danoeh.antennapod.R.id.search_src_text);
+ if(UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark) {
+ textField.setTextColor(Resources.getSystem().getColor(android.R.color.white));
+ } else {
+ textField.setTextColor(Resources.getSystem().getColor(android.R.color.black));
+ }
+
return view;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
index eda01a851..4bce3c7ba 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
@@ -1,295 +1,75 @@
package de.danoeh.antennapod.fragment;
-import android.app.Activity;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.app.Fragment;
-import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
-import de.danoeh.antennapod.adapter.NewEpisodesListAdapter;
-import de.danoeh.antennapod.core.asynctask.DownloadObserver;
-import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
-import de.danoeh.antennapod.core.feed.EventDistributor;
-import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.download.DownloadService;
-import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.DownloadRequester;
-import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
import de.danoeh.antennapod.core.util.gui.UndoBarController;
-import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.greenrobot.event.EventBus;
+
/**
- * Shows unread or recently published episodes
+ * Like 'EpisodesFragment' except that it only shows new episodes and
+ * supports swiping to mark as read.
*/
-public class NewEpisodesFragment extends Fragment {
- private static final String TAG = "NewEpisodesFragment";
- private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
- EventDistributor.DOWNLOAD_QUEUED |
- EventDistributor.UNREAD_ITEMS_UPDATE |
- EventDistributor.PLAYER_STATUS_UPDATE;
-
- private static final int RECENT_EPISODES_LIMIT = 150;
- private static final String PREF_NAME = "PrefNewEpisodesFragment";
- private static final String PREF_EPISODE_FILTER_BOOL = "newEpisodeFilterEnabled";
- private static final String PREF_KEY_LIST_TOP = "list_top";
- private static final String PREF_KEY_LIST_SELECTION = "list_selection";
-
- private DragSortListView listView;
- private NewEpisodesListAdapter listAdapter;
- private TextView txtvEmpty;
- private ProgressBar progLoading;
-
- private UndoBarController undoBarController;
- private List<FeedItem> unreadItems;
- private List<FeedItem> recentItems;
- private LongList queue;
- private List<Downloader> downloaderList;
+public class NewEpisodesFragment extends AllEpisodesFragment {
- private boolean itemsLoaded = false;
- private boolean viewsCreated = false;
- private boolean showOnlyNewEpisodes = false;
+ public static final String TAG = "NewEpisodesFragment";
- private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>();
-
- private DownloadObserver downloadObserver = null;
-
- private boolean isUpdatingFeeds;
+ private static final String PREF_NAME = "PrefNewEpisodesFragment";
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setRetainInstance(true);
- setHasOptionsMenu(true);
+ private UndoBarController undoBarController;
- updateShowOnlyEpisodes();
+ public NewEpisodesFragment() {
+ super(true, PREF_NAME);
}
- @Override
- public void onResume() {
- super.onResume();
+ public void onEvent(QueueEvent event) {
+ Log.d(TAG, "onEvent(" + event + ")");
startItemLoader();
}
@Override
public void onStart() {
super.onStart();
- EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
- this.activity.set((MainActivity) getActivity());
- if (downloadObserver != null) {
- downloadObserver.setActivity(getActivity());
- downloadObserver.onResume();
- }
- if (viewsCreated && itemsLoaded) {
- onFragmentLoaded();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- saveScrollPosition();
}
@Override
public void onStop() {
super.onStop();
- EventDistributor.getInstance().unregister(contentUpdate);
EventBus.getDefault().unregister(this);
- stopItemLoader();
- if(undoBarController.isShowing()) {
- undoBarController.close();
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- this.activity.set((MainActivity) getActivity());
}
@Override
- public void onDestroyView() {
- super.onDestroyView();
- resetViewState();
- }
-
- private void saveScrollPosition() {
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- View v = listView.getChildAt(0);
- int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
- editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
- editor.putInt(PREF_KEY_LIST_TOP, top);
- editor.commit();
- }
-
- private void restoreScrollPosition() {
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
- int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
- if(listSelection > 0 || top > 0) {
- listView.setSelectionFromTop(listSelection, top);
- // restore once, then forget
- SharedPreferences.Editor editor = prefs.edit();
- editor.putInt(PREF_KEY_LIST_SELECTION, 0);
- editor.putInt(PREF_KEY_LIST_TOP, 0);
- editor.commit();
- }
- }
-
- private void resetViewState() {
- listAdapter = null;
- activity.set(null);
- viewsCreated = false;
+ protected void resetViewState() {
+ super.resetViewState();
undoBarController = null;
- if (downloadObserver != null) {
- downloadObserver.onPause();
- }
- }
-
-
- private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() {
- @Override
- public boolean isRefreshing() {
- return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
- }
- };
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- inflater.inflate(R.menu.new_episodes, menu);
-
- final SearchView sv = new SearchView(getActivity());
- MenuItemUtils.addSearchItem(menu, sv);
- sv.setQueryHint(getString(R.string.search_hint));
- sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String s) {
- sv.clearFocus();
- ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s));
- return true;
- }
-
- @Override
- public boolean onQueryTextChange(String s) {
- return false;
- }
- });
- isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
- }
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- menu.findItem(R.id.mark_all_read_item).setVisible(unreadItems != null && !unreadItems.isEmpty());
- menu.findItem(R.id.episode_filter_item).setChecked(showOnlyNewEpisodes);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (!super.onOptionsItemSelected(item)) {
- switch (item.getItemId()) {
- case R.id.refresh_item:
- List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
- if (feeds != null) {
- DBTasks.refreshAllFeeds(getActivity(), feeds);
- }
- return true;
- case R.id.mark_all_read_item:
- ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
- R.string.mark_all_read_label,
- R.string.mark_all_read_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- DBWriter.markAllItemsRead(getActivity());
- Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
- }
- };
- conDialog.createNewDialog().show();
- return true;
- case R.id.episode_filter_item:
- boolean newVal = !item.isChecked();
- setShowOnlyNewEpisodes(newVal);
- item.setChecked(newVal);
- return true;
- default:
- return false;
- }
- } else {
- return true;
- }
-
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- super.onCreateView(inflater, container, savedInstanceState);
- ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.new_episodes_label);
-
- View root = inflater.inflate(R.layout.new_episodes_fragment, container, false);
-
- listView = (DragSortListView) root.findViewById(android.R.id.list);
- txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
- progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
-
- listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount());
- if (item != null) {
- ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
- }
-
- }
- });
+ View root = super.onCreateViewHelper(inflater, container, savedInstanceState,
+ R.layout.new_episodes_fragment, R.string.new_episodes_label);
listView.setRemoveListener(new DragSortListView.RemoveListener() {
@Override
public void remove(int which) {
- Log.d(TAG, "remove("+which+")");
+ Log.d(TAG, "remove(" + which + ")");
stopItemLoader();
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
DBWriter.markItemRead(getActivity(), item.getId(), true);
@@ -323,198 +103,7 @@ public class NewEpisodesFragment extends Fragment {
}
}
});
-
- final int secondColor = (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) ? R.color.swipe_refresh_secondary_color_dark : R.color.swipe_refresh_secondary_color_light;
-
- if (!itemsLoaded) {
- progLoading.setVisibility(View.VISIBLE);
- txtvEmpty.setVisibility(View.GONE);
- }
-
- viewsCreated = true;
-
- if (itemsLoaded && activity.get() != null) {
- onFragmentLoaded();
- }
-
return root;
}
- private void onFragmentLoaded() {
- if (listAdapter == null) {
- listAdapter = new NewEpisodesListAdapter(activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get()));
- listView.setAdapter(listAdapter);
- listView.setEmptyView(txtvEmpty);
- downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback);
- downloadObserver.onResume();
- }
- listAdapter.notifyDataSetChanged();
- restoreScrollPosition();
- getActivity().supportInvalidateOptionsMenu();
- updateShowOnlyEpisodesListViewState();
- }
-
- private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
- @Override
- public void onContentChanged() {
- if (listAdapter != null) {
- listAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void onDownloadDataAvailable(List<Downloader> downloaderList) {
- NewEpisodesFragment.this.downloaderList = downloaderList;
- if (listAdapter != null) {
- listAdapter.notifyDataSetChanged();
- }
- }
- };
-
- private NewEpisodesListAdapter.ItemAccess itemAccess = new NewEpisodesListAdapter.ItemAccess() {
-
- @Override
- public int getCount() {
- if (itemsLoaded) {
- return (showOnlyNewEpisodes) ? unreadItems.size() : recentItems.size();
- }
- return 0;
- }
-
- @Override
- public FeedItem getItem(int position) {
- if (itemsLoaded) {
- return (showOnlyNewEpisodes) ? unreadItems.get(position) : recentItems.get(position);
- }
- return null;
- }
-
- @Override
- public int getItemDownloadProgressPercent(FeedItem item) {
- if (downloaderList != null) {
- for (Downloader downloader : downloaderList) {
- if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
- && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
- return downloader.getDownloadRequest().getProgressPercent();
- }
- }
- }
- return 0;
- }
-
- @Override
- public boolean isInQueue(FeedItem item) {
- if (itemsLoaded) {
- return queue.contains(item.getId());
- } else {
- return false;
- }
- }
-
-
- };
-
- public void onEvent(QueueEvent event) {
- Log.d(TAG, "onEvent(" + event + ")");
- startItemLoader();
- }
-
- private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((arg & EVENTS) != 0) {
- startItemLoader();
- if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
- getActivity().supportInvalidateOptionsMenu();
- }
- }
- }
- };
-
- private void updateShowOnlyEpisodes() {
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- showOnlyNewEpisodes = prefs.getBoolean(PREF_EPISODE_FILTER_BOOL, true);
- }
-
- private void setShowOnlyNewEpisodes(boolean newVal) {
- showOnlyNewEpisodes = newVal;
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(PREF_EPISODE_FILTER_BOOL, showOnlyNewEpisodes);
- editor.commit();
- if (itemsLoaded && viewsCreated) {
- listAdapter.notifyDataSetChanged();
- activity.get().supportInvalidateOptionsMenu();
- updateShowOnlyEpisodesListViewState();
- }
- }
-
- private void updateShowOnlyEpisodesListViewState() {
- if (showOnlyNewEpisodes) {
- listView.setEmptyView(null);
- txtvEmpty.setVisibility(View.GONE);
- } else {
- listView.setEmptyView(txtvEmpty);
- }
- }
-
- private ItemLoader itemLoader;
-
- private void startItemLoader() {
- if (itemLoader != null) {
- itemLoader.cancel(true);
- }
- itemLoader = new ItemLoader();
- itemLoader.execute();
- }
-
- private void stopItemLoader() {
- if (itemLoader != null) {
- itemLoader.cancel(true);
- }
- }
-
- private class ItemLoader extends AsyncTask<Void, Void, Object[]> {
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (viewsCreated && !itemsLoaded) {
- listView.setVisibility(View.GONE);
- txtvEmpty.setVisibility(View.GONE);
- progLoading.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- protected Object[] doInBackground(Void... params) {
- Context context = activity.get();
- if (context != null) {
- return new Object[] {
- DBReader.getUnreadItemsList(context),
- DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT),
- DBReader.getQueueIDList(context)
- };
- } else {
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(Object[] lists) {
- super.onPostExecute(lists);
- listView.setVisibility(View.VISIBLE);
- progLoading.setVisibility(View.GONE);
-
- if (lists != null) {
- unreadItems = (List<FeedItem>) lists[0];
- recentItems = (List<FeedItem>) lists[1];
- queue = (LongList) lists[2];
- itemsLoaded = true;
- if (viewsCreated && activity.get() != null) {
- onFragmentLoaded();
- }
- }
- }
- }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
index 647fe550d..9099829d8 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
@@ -32,12 +32,12 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.LongList;
-import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.greenrobot.event.EventBus;
public class PlaybackHistoryFragment extends ListFragment {
- private static final String TAG = "PlaybackHistoryFragment";
+
+ public static final String TAG = "PlaybackHistoryFragment";
+
private static final int EVENTS = EventDistributor.PLAYBACK_HISTORY_UPDATE |
EventDistributor.PLAYER_STATUS_UPDATE;
@@ -139,7 +139,7 @@ public class PlaybackHistoryFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
@@ -151,8 +151,11 @@ public class PlaybackHistoryFragment extends ListFragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- menu.findItem(R.id.clear_history_item).setVisible(playbackHistory != null && !playbackHistory.isEmpty());
+ if (itemsLoaded) {
+ MenuItem menuItem = menu.findItem(R.id.clear_history_item);
+ if (menuItem != null) {
+ menuItem.setVisible(playbackHistory != null && !playbackHistory.isEmpty());
+ }
}
}
@@ -223,6 +226,11 @@ public class PlaybackHistoryFragment extends ListFragment {
}
@Override
+ public boolean isNew(FeedItem item) {
+ return false;
+ }
+
+ @Override
public int getItemDownloadProgressPercent(FeedItem item) {
if (downloaderList != null) {
for (Downloader downloader : downloaderList) {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
index a3721d7b3..d82c7b8f7 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java
@@ -8,6 +8,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
+import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.ContextMenu;
@@ -20,6 +21,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
@@ -43,19 +45,23 @@ import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.QueueSorter;
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
import de.danoeh.antennapod.core.util.gui.UndoBarController;
+import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.greenrobot.event.EventBus;
/**
* Shows all items in the queue
*/
public class QueueFragment extends Fragment {
- private static final String TAG = "QueueFragment";
+
+ public static final String TAG = "QueueFragment";
+
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
EventDistributor.DOWNLOAD_QUEUED |
EventDistributor.PLAYER_STATUS_UPDATE;
@@ -65,6 +71,8 @@ public class QueueFragment extends Fragment {
private TextView txtvEmpty;
private ProgressBar progLoading;
+ private ContextMenu contextMenu;
+
private UndoBarController<FeedItemUndoToken> undoBarController;
private List<FeedItem> queue;
@@ -200,11 +208,12 @@ public class QueueFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
inflater.inflate(R.menu.queue, menu);
- final SearchView sv = new SearchView(getActivity());
- MenuItemUtils.addSearchItem(menu, sv);
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
sv.setQueryHint(getString(R.string.search_hint));
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
@@ -219,6 +228,9 @@ public class QueueFragment extends Fragment {
return false;
}
});
+
+ MenuItemUtils.refreshLockItem(getActivity(), menu);
+
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
}
}
@@ -227,6 +239,17 @@ public class QueueFragment extends Fragment {
public boolean onOptionsItemSelected(MenuItem item) {
if (!super.onOptionsItemSelected(item)) {
switch (item.getItemId()) {
+ case R.id.queue_lock:
+ boolean locked = !UserPreferences.isQueueLocked();
+ if(locked) {
+ listView.setDragEnabled(false);
+ } else {
+ listView.setDragEnabled(true);
+ }
+ UserPreferences.setQueueLocked(locked);
+ getActivity().supportInvalidateOptionsMenu();
+ listAdapter.setLocked(locked);
+ return true;
case R.id.refresh_item:
List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
if (feeds != null) {
@@ -275,6 +298,19 @@ public class QueueFragment extends Fragment {
}
+ private final FeedItemMenuHandler.MenuInterface contextMenuInterface = new FeedItemMenuHandler.MenuInterface() {
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ if(contextMenu == null) {
+ return;
+ }
+ MenuItem item = contextMenu.findItem(id);
+ if (item != null) {
+ item.setVisible(visible);
+ }
+ }
+ };
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
@@ -288,8 +324,12 @@ public class QueueFragment extends Fragment {
menu.setHeaderTitle(item.getTitle());
}
- menu.findItem(R.id.move_to_top_item).setEnabled(!queue.isEmpty() && queue.get(0) != item);
- menu.findItem(R.id.move_to_bottom_item).setEnabled(!queue.isEmpty() && queue.get(queue.size() - 1) != item);
+ contextMenu = menu;
+ LongList queueIds = new LongList(queue.size());
+ for(FeedItem queueItem : queue) {
+ queueIds.add(queueItem.getId());
+ }
+ FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queueIds);
}
@Override
@@ -302,21 +342,16 @@ public class QueueFragment extends Fragment {
return super.onContextItemSelected(item);
}
- switch (item.getItemId()) {
- case R.id.move_to_top_item:
- DBWriter.moveQueueItemToTop(getActivity(), selectedItem.getId(), true);
- return true;
- case R.id.move_to_bottom_item:
- DBWriter.moveQueueItemToBottom(getActivity(), selectedItem.getId(), true);
- return true;
- case R.id.remove_from_queue_item:
- DBWriter.removeQueueItem(getActivity(), selectedItem, false);
- return true;
- default:
- return super.onContextItemSelected(item);
+ try {
+ return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
+ return true;
}
}
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
@@ -328,6 +363,12 @@ public class QueueFragment extends Fragment {
progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
listView.setEmptyView(txtvEmpty);
+ if(UserPreferences.isQueueLocked()) {
+ listView.setDragEnabled(false);
+ } else {
+ listView.setDragEnabled(true);
+ }
+
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -361,7 +402,6 @@ public class QueueFragment extends Fragment {
Log.d(TAG, "remove(" + which + ")");
stopItemLoader();
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
- DBWriter.markItemRead(getActivity(), item.getId(), true);
DBWriter.removeQueueItem(getActivity(), item, true);
}
});
@@ -376,7 +416,6 @@ public class QueueFragment extends Fragment {
if (token != null) {
long itemId = token.getFeedItemId();
int position = token.getPosition();
- DBWriter.markItemRead(context, itemId, false);
DBWriter.addQueueItemAt(context, itemId, position, false);
}
}
@@ -395,7 +434,6 @@ public class QueueFragment extends Fragment {
});
-
registerForContextMenu(listView);
if (!itemsLoaded) {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
index f578d4338..b1b61f74b 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
@@ -5,15 +5,21 @@ import android.os.Handler;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
+import android.widget.Toast;
+
+import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadlistAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.Downloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequester;
-import java.util.List;
-
/**
* Displays all running downloads and provides actions to cancel them
*/
@@ -73,7 +79,17 @@ public class RunningDownloadsFragment extends ListFragment {
@Override
public void onSecondaryActionClick(Downloader downloader) {
- DownloadRequester.getInstance().cancelDownload(getActivity(), downloader.getDownloadRequest().getSource());
+ DownloadRequest downloadRequest = downloader.getDownloadRequest();
+ DownloadRequester.getInstance().cancelDownload(getActivity(), downloadRequest.getSource());
+
+ if(downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA &&
+ UserPreferences.isEnableAutodownload()) {
+ FeedMedia media = DBReader.getFeedMedia(getActivity(), downloadRequest.getFeedfileId());
+ DBWriter.setFeedItemAutoDownload(getActivity(), media.getItem(), false);
+ Toast.makeText(getActivity(), R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(getActivity(), R.string.download_canceled_msg, Toast.LENGTH_SHORT).show();
+ }
}
};
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
index 3d6722370..fc6225409 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java
@@ -25,8 +25,6 @@ import de.danoeh.antennapod.core.feed.FeedComponent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.SearchResult;
import de.danoeh.antennapod.core.storage.FeedSearcher;
-import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
/**
* Performs a search operation on all feeds or one specific feed and displays the search result.
@@ -122,7 +120,7 @@ public class SearchFragment extends ListFragment {
SearchResult result = (SearchResult) l.getAdapter().getItem(position);
FeedComponent comp = result.getComponent();
if (comp.getClass() == Feed.class) {
- ((MainActivity) getActivity()).loadFeedFragment(comp.getId());
+ ((MainActivity) getActivity()).loadFeedFragmentById(comp.getId());
} else {
if (comp.getClass() == FeedItem.class) {
FeedItem item = (FeedItem) comp;
@@ -134,7 +132,7 @@ public class SearchFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
+ if (itemsLoaded) {
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
final SearchView sv = new SearchView(getActivity());
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java
index 45b2403c8..55d4b940f 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java
@@ -31,8 +31,8 @@ public class GpodnetMainFragment extends Fragment {
private static final int NUM_PAGES = 2;
- private static final int POS_TAGS = 0;
- private static final int POS_TOPLIST = 1;
+ private static final int POS_TOPLIST = 0;
+ private static final int POS_TAGS = 1;
private static final int POS_SUGGESTIONS = 2;
Resources resources;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
index 15a0b55b1..6139a4901 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
@@ -5,11 +5,23 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.SearchView;
import android.util.Log;
-import android.view.*;
-import android.widget.*;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.util.List;
-import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
@@ -19,14 +31,12 @@ import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
-
-import java.util.List;
/**
* Displays a list of GPodnetPodcast-Objects in a GridView
*/
public abstract class PodcastListFragment extends Fragment {
+
private static final String TAG = "PodcastListFragment";
private GridView gridView;
@@ -43,24 +53,24 @@ public abstract class PodcastListFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (!MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- final android.support.v7.widget.SearchView sv = new android.support.v7.widget.SearchView(getActivity());
- MenuItemUtils.addSearchItem(menu, sv);
- sv.setQueryHint(getString(R.string.gpodnet_search_hint));
- sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String s) {
- sv.clearFocus();
- ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s));
- return true;
- }
+ inflater.inflate(R.menu.gpodder_podcasts, menu);
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
+ sv.setQueryHint(getString(R.string.gpodnet_search_hint));
+ sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ sv.clearFocus();
+ ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s));
+ return true;
+ }
- @Override
- public boolean onQueryTextChange(String s) {
- return false;
- }
- });
- }
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
}
@Override
@@ -90,7 +100,7 @@ public abstract class PodcastListFragment extends Fragment {
}
protected void onPodcastSelected(GpodnetPodcast selection) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Selected podcast: " + selection.toString());
+ Log.d(TAG, "Selected podcast: " + selection.toString());
Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, selection.getUrl());
intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label));
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java
index 635842196..613e06805 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java
@@ -1,9 +1,11 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import org.apache.commons.lang3.Validate;
@@ -14,7 +16,6 @@ import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
/**
* Performs a search on the gpodder.net directory and displays the results.
@@ -45,25 +46,26 @@ public class SearchListFragment extends PodcastListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- final SearchView sv = new SearchView(getActivity());
- if (!MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- MenuItemUtils.addSearchItem(menu, sv);
- sv.setQueryHint(getString(R.string.gpodnet_search_hint));
- sv.setQuery(query, false);
- sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String s) {
- sv.clearFocus();
- changeQuery(s);
- return true;
- }
+ super.onCreateOptionsMenu(menu, inflater);
+ // parent already inflated menu
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
+ sv.setQueryHint(getString(R.string.gpodnet_search_hint));
+ sv.setQuery(query, false);
+ sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ sv.clearFocus();
+ changeQuery(s);
+ return true;
+ }
- @Override
- public boolean onQueryTextChange(String s) {
- return false;
- }
- });
- }
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
}
@Override
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
index cc87407b4..5bd567a2f 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java
@@ -5,9 +5,11 @@ import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
+import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.TextView;
@@ -21,9 +23,9 @@ import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
public class TagListFragment extends ListFragment {
+
private static final String TAG = "TagListFragment";
private static final int COUNT = 50;
@@ -36,27 +38,27 @@ public class TagListFragment extends ListFragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (!MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
- final SearchView sv = new SearchView(getActivity());
- MenuItemUtils.addSearchItem(menu, sv);
- sv.setQueryHint(getString(R.string.gpodnet_search_hint));
- sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String s) {
- Activity activity = getActivity();
- if (activity != null) {
- sv.clearFocus();
- ((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s));
- }
- return true;
+ inflater.inflate(R.menu.gpodder_podcasts, menu);
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
+ sv.setQueryHint(getString(R.string.gpodnet_search_hint));
+ sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ Activity activity = getActivity();
+ if (activity != null) {
+ sv.clearFocus();
+ ((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s));
}
+ return true;
+ }
- @Override
- public boolean onQueryTextChange(String s) {
- return false;
- }
- });
- }
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
}
@Override
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
index ebb0a9e58..fe1a09149 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java
@@ -3,14 +3,15 @@ package de.danoeh.antennapod.menuhandler;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.util.Log;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -23,10 +24,10 @@ import de.danoeh.antennapod.core.util.ShareUtils;
* Handles interactions with the FeedItemMenu.
*/
public class FeedItemMenuHandler {
+
private static final String TAG = "FeedItemMenuHandler";
private FeedItemMenuHandler() {
-
}
/**
@@ -55,18 +56,12 @@ public class FeedItemMenuHandler {
* @param queueAccess Used for testing if the queue contains the selected item
* @return Returns true if selectedItem is not null.
*/
- public static boolean onPrepareMenu(MenuInterface mi,
- FeedItem selectedItem, boolean showExtendedMenu, LongList queueAccess) {
+ public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem,
+ boolean showExtendedMenu, LongList queueAccess) {
if (selectedItem == null) {
return false;
}
- DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
- boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
- boolean downloading = hasMedia
- && requester.isDownloadingFile(selectedItem.getMedia());
- boolean notLoadedAndNotLoading = hasMedia && (!downloaded)
- && (!downloading);
boolean isPlaying = hasMedia
&& selectedItem.getState() == FeedItem.State.PLAYING;
@@ -75,21 +70,14 @@ public class FeedItemMenuHandler {
if (!isPlaying) {
mi.setItemVisibility(R.id.skip_episode_item, false);
}
- if (!downloaded || isPlaying) {
- mi.setItemVisibility(R.id.play_item, false);
- mi.setItemVisibility(R.id.remove_item, false);
- }
- if (!notLoadedAndNotLoading) {
- mi.setItemVisibility(R.id.download_item, false);
- }
- if (!(notLoadedAndNotLoading | downloading) | isPlaying) {
- mi.setItemVisibility(R.id.stream_item, false);
- }
- if (!downloading) {
- mi.setItemVisibility(R.id.cancel_download_item, false);
- }
boolean isInQueue = queueAccess.contains(selectedItem.getId());
+ if(queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) {
+ mi.setItemVisibility(R.id.move_to_top_item, false);
+ }
+ if(queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId()) {
+ mi.setItemVisibility(R.id.move_to_bottom_item, false);
+ }
if (!isInQueue || isPlaying) {
mi.setItemVisibility(R.id.remove_from_queue_item, false);
}
@@ -100,12 +88,24 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.share_link_item, false);
}
- if (!BuildConfig.DEBUG
- || !(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) {
+ if (!(state == FeedItem.State.UNREAD || state == FeedItem.State.IN_PROGRESS)) {
+ mi.setItemVisibility(R.id.mark_read_item, false);
+ }
+ if (!(state == FeedItem.State.IN_PROGRESS || state == FeedItem.State.READ)) {
mi.setItemVisibility(R.id.mark_unread_item, false);
}
- if (!(state == FeedItem.State.NEW || state == FeedItem.State.IN_PROGRESS)) {
- mi.setItemVisibility(R.id.mark_read_item, false);
+
+ if(selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) {
+ mi.setItemVisibility(R.id.reset_position, false);
+ }
+
+ if(false == UserPreferences.isEnableAutodownload()) {
+ mi.setItemVisibility(R.id.activate_auto_download, false);
+ mi.setItemVisibility(R.id.deactivate_auto_download, false);
+ } else if(selectedItem.getAutoDownload()) {
+ mi.setItemVisibility(R.id.activate_auto_download, false);
+ } else {
+ mi.setItemVisibility(R.id.deactivate_auto_download, false);
}
if (!showExtendedMenu || selectedItem.getLink() == null) {
@@ -142,24 +142,14 @@ public class FeedItemMenuHandler {
DownloadRequester requester = DownloadRequester.getInstance();
switch (menuItemId) {
case R.id.skip_episode_item:
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
- break;
- case R.id.download_item:
- DBTasks.downloadFeedItems(context, selectedItem);
- break;
- case R.id.play_item:
- DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
- false);
+ context.sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.remove_item:
DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
break;
- case R.id.cancel_download_item:
- requester.cancelDownload(context, selectedItem.getMedia());
- break;
case R.id.mark_read_item:
- DBWriter.markItemRead(context, selectedItem, true, true);
+ selectedItem.setRead(true);
+ DBWriter.markItemRead(context, selectedItem, true, false);
if(GpodnetPreferences.loggedIn()) {
FeedMedia media = selectedItem.getMedia();
GpodnetEpisodeAction actionPlay = new GpodnetEpisodeAction.Builder(selectedItem, Action.PLAY)
@@ -173,7 +163,8 @@ public class FeedItemMenuHandler {
}
break;
case R.id.mark_unread_item:
- DBWriter.markItemRead(context, selectedItem, false, true);
+ selectedItem.setRead(false);
+ DBWriter.markItemRead(context, selectedItem, false, false);
if(GpodnetPreferences.loggedIn()) {
GpodnetEpisodeAction actionNew = new GpodnetEpisodeAction.Builder(selectedItem, Action.NEW)
.currentDeviceId()
@@ -182,15 +173,28 @@ public class FeedItemMenuHandler {
GpodnetPreferences.enqueueEpisodeAction(actionNew);
}
break;
+ case R.id.move_to_top_item:
+ DBWriter.moveQueueItemToTop(context, selectedItem.getId(), true);
+ return true;
+ case R.id.move_to_bottom_item:
+ DBWriter.moveQueueItemToBottom(context, selectedItem.getId(), true);
case R.id.add_to_queue_item:
DBWriter.addQueueItem(context, selectedItem.getId());
break;
case R.id.remove_from_queue_item:
DBWriter.removeQueueItem(context, selectedItem, true);
break;
- case R.id.stream_item:
- DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
- true);
+ case R.id.reset_position:
+ selectedItem.getMedia().setPosition(0);
+ DBWriter.markItemRead(context, selectedItem, false, true);
+ break;
+ case R.id.activate_auto_download:
+ selectedItem.setAutoDownload(true);
+ DBWriter.setFeedItemAutoDownload(context, selectedItem, true);
+ break;
+ case R.id.deactivate_auto_download:
+ selectedItem.setAutoDownload(false);
+ DBWriter.setFeedItemAutoDownload(context, selectedItem, false);
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedItem.getLink());
@@ -203,6 +207,7 @@ public class FeedItemMenuHandler {
ShareUtils.shareFeedItemLink(context, selectedItem);
break;
default:
+ Log.d(TAG, "Unknown menuItemId: " + menuItemId);
return false;
}
// Refresh menu state
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
index efb4adb01..7bd8fedc9 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.menuhandler;
+import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -9,8 +10,11 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.storage.DBTasks;
@@ -34,10 +38,8 @@ public class FeedMenuHandler {
return true;
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Preparing options menu");
- menu.findItem(R.id.mark_all_read_item).setVisible(
- selectedFeed.hasNewItems(true));
+ Log.d(TAG, "Preparing options menu");
+ menu.findItem(R.id.mark_all_read_item).setVisible(selectedFeed.hasNewItems());
if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable())
menu.findItem(R.id.support_item).setVisible(true);
else
@@ -62,6 +64,9 @@ public class FeedMenuHandler {
case R.id.refresh_complete_item:
DBTasks.refreshCompleteFeed(context, selectedFeed);
break;
+ case R.id.hide_items:
+ showHideDialog(context, selectedFeed);
+ break;
case R.id.mark_all_read_item:
ConfirmationDialog conDialog = new ConfirmationDialog(context,
R.string.mark_all_read_label,
@@ -94,4 +99,43 @@ public class FeedMenuHandler {
}
return true;
}
+
+ private static void showHideDialog(final Context context, final Feed feed) {
+
+ final String[] items = context.getResources().getStringArray(R.array.episode_hide_options);
+ final String[] values = context.getResources().getStringArray(R.array.episode_hide_values);
+ final boolean[] checkedItems = new boolean[items.length];
+
+ final List<String> hidden = new ArrayList<String>(Arrays.asList(feed.getItemFilter().getValues()));
+ for(int i=0; i < values.length; i++) {
+ String value = values[i];
+ if(hidden.contains(value)) {
+ checkedItems[i] = true;
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.hide_episodes_title);
+ builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ if (isChecked) {
+ hidden.add(values[which]);
+ } else {
+ hidden.remove(values[which]);
+ }
+ }
+ });
+ builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ feed.setHiddenItemProperties(hidden.toArray(new String[hidden.size()]));
+ DBWriter.setFeedItemsFilter(context, feed.getId(), hidden);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.create().show();
+
+ }
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
index fc942ce20..cfc540fd6 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
@@ -1,31 +1,44 @@
package de.danoeh.antennapod.menuhandler;
-import android.support.v4.view.MenuItemCompat;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Build;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
+import android.widget.EditText;
-import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
/**
* Utilities for menu items
*/
public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuItemUtils {
- public static MenuItem addSearchItem(Menu menu, SearchView searchView) {
- MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
- MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
- MenuItemCompat.setActionView(item, searchView);
- return item;
+ public static void adjustTextColor(Context context, SearchView sv) {
+ if(Build.VERSION.SDK_INT < 14) {
+ EditText searchEditText = (EditText) sv.findViewById(R.id.search_src_text);
+ if(UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark) {
+ searchEditText.setTextColor(Color.WHITE);
+ } else {
+ searchEditText.setTextColor(Color.BLACK);
+ }
+ }
}
- /**
- * Checks if the navigation drawer of the DrawerActivity is opened. This can be useful for Fragments
- * that hide their menu if the navigation drawer is open.
- *
- * @return True if the drawer is open, false otherwise (also if the parameter is null)
- */
- public static boolean isActivityDrawerOpen(NavDrawerActivity activity) {
- return activity != null && activity.isDrawerOpen();
+ public static void refreshLockItem(Context context, Menu menu) {
+ final MenuItem queueLock = menu.findItem(de.danoeh.antennapod.R.id.queue_lock);
+ int[] lockIcons = new int[] { de.danoeh.antennapod.R.attr.ic_lock_open, de.danoeh.antennapod.R.attr.ic_lock_closed };
+ TypedArray ta = context.obtainStyledAttributes(lockIcons);
+ if (UserPreferences.isQueueLocked()) {
+ queueLock.setTitle(de.danoeh.antennapod.R.string.unlock_queue);
+ queueLock.setIcon(ta.getDrawable(1));
+ } else {
+ queueLock.setTitle(de.danoeh.antennapod.R.string.lock_queue);
+ queueLock.setIcon(ta.getDrawable(0));
+ }
}
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
index 32683c65c..f387b7524 100644
--- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.preferences;
import android.app.Activity;
+import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -190,6 +191,15 @@ public class PreferenceController {
}
}
);
+ ui.findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)
+ .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ showDrawerPreferencesDialog();
+ return true;
+ }
+ });
+
ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL)
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@@ -561,6 +571,43 @@ public class PreferenceController {
}
}
+ private void showDrawerPreferencesDialog() {
+ final Context context = ui.getActivity();
+ final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems();
+ final String[] navTitles = context.getResources().getStringArray(R.array.nav_drawer_titles);
+ final String[] NAV_DRAWER_TAGS = MainActivity.NAV_DRAWER_TAGS;
+ boolean[] checked = new boolean[MainActivity.NAV_DRAWER_TAGS.length];
+ for(int i=0; i < NAV_DRAWER_TAGS.length; i++) {
+ String tag = NAV_DRAWER_TAGS[i];
+ if(!hiddenDrawerItems.contains(tag)) {
+ checked[i] = true;
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.drawer_preferences);
+ builder.setMultiChoiceItems(navTitles, checked, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ if (isChecked) {
+ hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
+ } else {
+ hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
+ }
+ }
+ });
+ builder.setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ UserPreferences.setHiddenDrawerItems(context, hiddenDrawerItems);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.create().show();
+ }
+
+
+
public static interface PreferenceUI {
diff --git a/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java
index 1d9e8e412..1fe9e2cf9 100644
--- a/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java
+++ b/app/src/main/java/de/danoeh/antennapod/service/PlayerWidgetService.java
@@ -52,19 +52,20 @@ public class PlayerWidgetService extends Service {
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service is about to be destroyed");
-
- Playable playable = playbackService.getPlayable();
- if(playable != null && playable instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) playable;
- if(media.hasAlmostEnded()) {
- Log.d(TAG, "smart mark as read");
- FeedItem item = media.getItem();
- DBWriter.markItemRead(this, item, true, false);
- DBWriter.removeQueueItem(this, item, false);
- DBWriter.addItemToPlaybackHistory(this, media);
- if (UserPreferences.isAutoDelete()) {
- Log.d(TAG, "Delete " + media.toString());
- DBWriter.deleteFeedMediaOfItem(this, media.getId());
+ if (playbackService != null) {
+ Playable playable = playbackService.getPlayable();
+ if (playable != null && playable instanceof FeedMedia) {
+ FeedMedia media = (FeedMedia) playable;
+ if (media.hasAlmostEnded()) {
+ Log.d(TAG, "smart mark as read");
+ FeedItem item = media.getItem();
+ DBWriter.markItemRead(this, item, true, false);
+ DBWriter.removeQueueItem(this, item, false);
+ DBWriter.addItemToPlaybackHistory(this, media);
+ if (UserPreferences.isAutoDelete()) {
+ Log.d(TAG, "Delete " + media.toString());
+ DBWriter.deleteFeedMediaOfItem(this, media.getId());
+ }
}
}
}
@@ -116,11 +117,12 @@ public class PlayerWidgetService extends Service {
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
+ String progressString = getProgressString(media);
+ if (progressString != null) {
+ views.setTextViewText(R.id.txtvProgress, progressString);
+ }
+
if (status == PlayerStatus.PLAYING) {
- String progressString = getProgressString(playbackService);
- if (progressString != null) {
- views.setTextViewText(R.id.txtvProgress, progressString);
- }
views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
@@ -156,11 +158,10 @@ public class PlayerWidgetService extends Service {
return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
}
- private String getProgressString(PlaybackService ps) {
- int position = ps.getCurrentPosition();
- int duration = ps.getDuration();
- if (position != PlaybackService.INVALID_TIME
- && duration != PlaybackService.INVALID_TIME) {
+ private String getProgressString(Playable media) {
+ int position = media.getPosition();
+ int duration = media.getDuration();
+ if (position > 0 && duration > 0) {
return Converter.getDurationStringLong(position) + " / "
+ Converter.getDurationStringLong(duration);
} else {
diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml
new file mode 100644
index 000000000..19db02f1d
--- /dev/null
+++ b/app/src/main/res/layout/all_episodes_fragment.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:dslv="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.mobeta.android.dslv.DragSortListView
+ android:id="@android:id/list"
+ android:scrollbarStyle="outsideOverlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/list_vertical_padding"
+ android:paddingBottom="@dimen/list_vertical_padding"
+ android:clipToPadding="false"
+ dslv:collapsed_height="2dp"
+ dslv:drag_enabled="false"
+ dslv:drag_scroll_start="0.33"
+ dslv:float_alpha="0.6"
+ dslv:max_drag_scroll_speed="0.5"
+ dslv:remove_enabled="true"
+ dslv:remove_mode="flingRemove"
+ dslv:slide_shuffle_speed="0.3"
+ dslv:sort_enabled="false"
+ dslv:track_drag_sort="false"
+ dslv:float_background_color="?attr/dragview_float_background"
+ dslv:use_default_controller="true"
+ tools:background="@android:color/holo_green_dark"/>
+
+ <TextView
+ android:id="@id/android:empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:text="@string/no_items_label"/>
+
+ <ProgressBar
+ android:id="@+id/progLoading"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminateOnly="true"
+ android:visibility="gone"
+ tools:visibility="visible"
+ tools:layout_width="match_parent"
+ tools:layout_height="64dp"
+ tools:background="@android:color/holo_red_light"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/audioplayer_activity.xml b/app/src/main/res/layout/audioplayer_activity.xml
index 644d8fe90..827e06e00 100644
--- a/app/src/main/res/layout/audioplayer_activity.xml
+++ b/app/src/main/res/layout/audioplayer_activity.xml
@@ -95,6 +95,18 @@
tools:src="@drawable/ic_fast_rewind_white_36dp"
tools:background="@android:color/holo_blue_dark" />
+ <TextView
+ android:id="@+id/txtvRev"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:layout_alignTop="@id/butRev"
+ android:layout_alignLeft="@id/butRev"
+ android:layout_alignRight="@id/butRev"
+ android:gravity="center"
+ android:text="30"
+ android:textSize="8dp"
+ android:clickable="false"/>
+
<ImageButton
android:id="@+id/butFF"
android:layout_width="@dimen/audioplayer_playercontrols_length"
@@ -106,6 +118,18 @@
tools:src="@drawable/ic_fast_forward_white_36dp"
tools:background="@android:color/holo_blue_dark" />
+ <TextView
+ android:id="@+id/txtvFF"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:layout_alignTop="@id/butFF"
+ android:layout_alignLeft="@id/butFF"
+ android:layout_alignRight="@id/butFF"
+ android:gravity="center"
+ android:text="30"
+ android:textSize="8dp"
+ android:clickable="false"/>
+
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="@dimen/audioplayer_playercontrols_length"
diff --git a/app/src/main/res/layout/downloadlog_item.xml b/app/src/main/res/layout/downloadlog_item.xml
index df1501222..c6a34a517 100644
--- a/app/src/main/res/layout/downloadlog_item.xml
+++ b/app/src/main/res/layout/downloadlog_item.xml
@@ -1,78 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
+ android:paddingTop="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="8dp"
tools:background="@android:color/darker_gray">
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
- android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
- android:layout_marginTop="@dimen/listitem_threeline_verticalpadding">
-
- <TextView
- android:id="@+id/txtvType"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding"
- tools:text="Media file"
- tools:background="@android:color/holo_green_dark" />
-
- <TextView
- android:id="@+id/txtvTitle"
- style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_toLeftOf="@id/txtvType"
- tools:text="Download item title"
- tools:background="@android:color/holo_blue_light" />
- </RelativeLayout>
+ <TextView
+ android:id="@+id/txtvIcon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:textSize="48sp"
+ tools:text="[Icon]"
+ android:gravity="center" />
- <RelativeLayout
- android:layout_width="match_parent"
+ <Button
+ android:id="@+id/btnRetry"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
- android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
- android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding">
+ android:layout_below="@id/txtvIcon"
+ android:layout_alignLeft="@id/txtvIcon"
+ android:layout_alignRight="@id/txtvIcon"
+ android:layout_marginTop="8dp"
+ tools:text="↻" />
- <TextView
- android:id="@+id/txtvDate"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_marginRight="8dp"
- tools:text="January 23"
- tools:background="@android:color/holo_green_dark" />
+ <TextView
+ android:id="@+id/txtvType"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_marginLeft="8dp"
+ android:layout_marginBottom="8dp"
+ tools:text="Media file"
+ tools:background="@android:color/holo_green_dark" />
- <TextView
- android:id="@+id/txtvStatus"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- tools:text="successful"
- tools:background="@android:color/holo_green_dark" />
+ <TextView
+ android:id="@+id/txtvTitle"
+ style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@id/txtvIcon"
+ android:layout_toLeftOf="@id/txtvType"
+ android:layout_marginLeft="8dp"
+ android:layout_marginBottom="8dp"
+ android:minLines="1"
+ android:maxLines="2"
+ tools:text="Download item title"
+ tools:background="@android:color/holo_blue_light" />
- </RelativeLayout>
+ <TextView
+ android:id="@+id/txtvDate"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/txtvIcon"
+ android:layout_below="@id/txtvTitle"
+ android:layout_marginLeft="8dp"
+ android:layout_marginBottom="8dp"
+ tools:text="January 23"
+ tools:background="@android:color/holo_green_dark" />
<TextView
android:id="@+id/txtvReason"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
- android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
- android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
+ android:layout_below="@id/txtvDate"
+ android:layout_toRightOf="@id/txtvIcon"
+ android:layout_marginLeft="8dp"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"
tools:text="@string/design_time_downloaded_log_failure_reason"
tools:background="@android:color/holo_green_dark" />
-</LinearLayout> \ No newline at end of file
+</RelativeLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml
index a52104afa..db897865c 100644
--- a/app/src/main/res/layout/feedinfo.xml
+++ b/app/src/main/res/layout/feedinfo.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -11,7 +12,10 @@
android:focusableInTouchMode="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:layout_marginTop="8dp">
<ImageView
android:id="@+id/imgvCover"
@@ -20,26 +24,26 @@
android:layout_height="70dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
- android:layout_margin="4dp"
tools:src="@drawable/ic_stat_antenna_default"
- tools:background="@android:color/holo_green_dark" />
+ tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
- android:layout_margin="4dp"
+ android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/imgvCover"
style="@style/AntennaPod.TextView.Heading"
tools:text="Feed title"
- tools:background="@android:color/holo_green_dark" />
+ tools:background="@android:color/holo_green_dark"/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/imgvCover"
+ android:layout_marginTop="8dp"
android:background="@color/bright_blue"/>
</RelativeLayout>
@@ -47,99 +51,120 @@
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1">
+ android:layout_weight="1"
+ android:scrollbarStyle="outsideInset"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <RelativeLayout
+ <android.support.v7.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="16dp">
-
- <View
- android:id="@+id/center_divider"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_centerHorizontal="true"/>
+ android:layout_marginTop="8dp"
+ app:columnCount="2"
+ app:rowCount="3">
<TextView
android:id="@+id/lblAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
- android:layout_margin="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_row="0"
+ app:layout_column="0"
+ android:lines="1"
android:text="@string/author_label"
android:textColor="?android:attr/textColorPrimary"
- tools:background="@android:color/holo_red_light" />
+ tools:background="@android:color/holo_red_light"/>
<TextView
android:id="@+id/txtvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_margin="8dp"
- android:layout_toRightOf="@id/center_divider"
+ app:layout_row="0"
+ app:layout_column="1"
tools:text="Daniel Oeh"
- tools:background="@android:color/holo_green_dark" />
+ tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/lblLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_below="@id/txtvAuthor"
- android:layout_margin="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_row="1"
+ app:layout_column="0"
+ android:lines="1"
android:text="@string/language_label"
android:textColor="?android:attr/textColorPrimary"
- tools:background="@android:color/holo_red_light" />
+ tools:background="@android:color/holo_red_light"/>
<TextView
android:id="@+id/txtvLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_below="@id/txtvAuthor"
- android:layout_margin="8dp"
- android:layout_toRightOf="@id/center_divider"
+ app:layout_row="1"
+ app:layout_column="1"
tools:text="English"
- tools:background="@android:color/holo_green_dark" />
- </RelativeLayout>
+ tools:background="@android:color/holo_green_dark"/>
+
+ <TextView
+ android:id="@+id/lblUrl"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_row="2"
+ app:layout_column="0"
+ android:lines="1"
+ android:text="@string/url_label"
+ android:textColor="?android:attr/textColorPrimary"
+ tools:background="@android:color/holo_red_light"/>
+
+ <TextView
+ android:id="@+id/txtvUrl"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_row="2"
+ app:layout_column="1"
+ tools:text="http://www.example.com/feed"
+ tools:background="@android:color/holo_green_dark"/>
+
+ </android.support.v7.widget.GridLayout>
<TextView
android:id="@+id/txtvSettings"
style="@style/AntennaPod.TextView.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
android:text="@string/podcast_settings_label"
- android:layout_marginLeft="8dp"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="24dp"/>
+ android:layout_marginTop="8dp"/>
<CheckBox
android:id="@+id/cbxAutoDownload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
+ android:layout_marginTop="8dp"
android:text="@string/auto_download_label"
android:enabled="false"
android:textColor="?android:attr/textColorPrimary"
- tools:background="@android:color/holo_red_light" />
+ tools:background="@android:color/holo_red_light"
+ android:checked="false"/>
<TextView
android:id="@+id/txtvAuthentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
+ android:layout_marginTop="8dp"
android:text="@string/authentication_label"
android:textSize="@dimen/text_size_medium"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_marginLeft="8dp"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="24dp"/>
+ android:textColor="?android:attr/textColorPrimary"/>
<TextView
android:id="@+id/txtvAuthenticationDescr"
@@ -148,71 +173,74 @@
android:textColor="?android:attr/textColorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="8dp"/>
+ android:layout_marginTop="8dp"/>
- <LinearLayout
- android:layout_width="match_parent"
+ <android.support.v7.widget.GridLayout
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="8dp">
+ android:layout_marginTop="8dp"
+ app:columnCount="2"
+ app:rowCount="3"
+ android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/txtvUsername"
- android:layout_width="0dp"
- android:layout_weight="1"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_row="0"
+ app:layout_column="0"
android:text="@string/username_label"
android:textColor="?android:attr/textColorPrimary"/>
<EditText
android:id="@+id/etxtUsername"
- android:layout_width="0dp"
- android:layout_weight="2"
+ android:layout_width="140sp"
android:layout_height="wrap_content"
+ app:layout_row="0"
+ app:layout_column="1"
android:hint="@string/username_label"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="8dp">
<TextView
android:id="@+id/txtvPassword"
- android:layout_width="0dp"
- android:layout_weight="1"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_row="1"
+ app:layout_column="0"
android:text="@string/password_label"
android:textColor="?android:attr/textColorPrimary"/>
<EditText
android:id="@+id/etxtPassword"
- android:layout_width="0dp"
- android:layout_weight="2"
+ android:layout_width="140sp"
android:layout_height="wrap_content"
+ app:layout_row="1"
+ app:layout_column="1"
android:hint="@string/password_label"
android:inputType="textPassword"/>
- </LinearLayout>
+
+ </android.support.v7.widget.GridLayout>
<TextView
style="@style/AntennaPod.TextView.Heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginLeft="8dp"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="24dp"
+ android:layout_marginTop="8dp"
android:text="@string/description_label"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
+ android:layout_marginTop="8dp"
android:text="@string/design_time_lorem_ipsum"
- tools:background="@android:color/holo_green_dark" />
+ tools:background="@android:color/holo_green_dark"/>
</LinearLayout>
+
</ScrollView>
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml
index 4619580e3..667f777af 100644
--- a/app/src/main/res/layout/feeditemlist_header.xml
+++ b/app/src/main/res/layout/feeditemlist_header.xml
@@ -3,7 +3,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="@dimen/feeditemlist_header_height"
+ android:layout_height="wrap_content"
tools:context="de.danoeh.antennapod.activity.MainActivity"
tools:background="@android:color/darker_gray">
@@ -11,7 +11,7 @@
android:id="@+id/imgvBackground"
style="@style/BigBlurryBackground"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="@dimen/feeditemlist_header_height" />
<ImageView
android:id="@+id/imgvCover"
@@ -78,5 +78,31 @@
tools:text="Podcast author"
tools:background="@android:color/holo_green_dark" />
+ <IconTextView
+ android:id="@+id/txtvFailure"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/imgvBackground"
+ android:paddingTop="2dp"
+ android:paddingBottom="2dp"
+ android:background="@color/download_failed_red"
+ android:gravity="center"
+ android:textColor="@color/white"
+ android:visibility="gone"
+ android:text="@string/refresh_failed_msg"
+ tools:text="(!) Last refresh failed"
+ />
+
+ <TextView
+ android:id="@+id/txtvInformation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/imgvBackground"
+ android:paddingTop="2dp"
+ android:paddingBottom="2dp"
+ android:gravity="center"
+ android:visibility="gone"
+ tools:text="(i) Information"
+ />
</RelativeLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/feeditemlist_item.xml b/app/src/main/res/layout/feeditemlist_item.xml
index 03595990e..6b7c45978 100644
--- a/app/src/main/res/layout/feeditemlist_item.xml
+++ b/app/src/main/res/layout/feeditemlist_item.xml
@@ -22,8 +22,10 @@
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
- android:layout_margin="16dp"
- tools:text="Status unread"
+ android:layout_marginTop="16dp"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ tools:text="NEW"
tools:background="@android:color/white" />
<TextView
@@ -36,9 +38,18 @@
android:layout_marginBottom="8dp"
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
android:layout_toLeftOf="@id/statusUnread"
- tools:text="Feed item name"
+ tools:text="Episode title"
tools:background="@android:color/holo_green_dark" />
+ <TextView
+ android:id="@+id/txtvLenSize"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/txtvItemname"
+ tools:text="00:42:23"
+ tools:background="@android:color/holo_green_dark" />
<ImageView
android:id="@+id/imgvInPlaylist"
@@ -46,7 +57,7 @@
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_below="@id/txtvItemname"
- android:layout_marginRight="4dp"
+ android:layout_marginRight="8dp"
android:contentDescription="@string/in_queue_label"
android:src="?attr/stat_playlist"
android:visibility="visible"
@@ -58,20 +69,21 @@
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_below="@id/txtvItemname"
- android:layout_marginRight="4dp"
- android:layout_toLeftOf="@+id/imgvInPlaylist"
+ android:layout_marginRight="8dp"
+ android:layout_toLeftOf="@id/imgvInPlaylist"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_hearing_white_18dp"
tools:background="@android:color/holo_red_light" />
<TextView
- android:id="@+id/txtvLenSize"
+ android:id="@+id/txtvPublished"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
android:layout_below="@id/txtvItemname"
- tools:text="00:42:23"
+ android:layout_marginRight="8dp"
+ android:layout_toLeftOf="@id/imgvType"
+ tools:text="Jan 23"
tools:background="@android:color/holo_green_dark" />
<ProgressBar
@@ -79,24 +91,20 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_below="@id/txtvItemname"
- android:layout_marginLeft="4dp"
- android:layout_marginRight="4dp"
- android:layout_toLeftOf="@id/imgvType"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_toLeftOf="@id/txtvPublished"
android:layout_toRightOf="@id/txtvLenSize"
- tools:background="@android:color/holo_blue_light" />
+ android:layout_alignTop="@id/txtvPublished"
+ android:layout_alignBottom="@id/txtvPublished"
+ tools:background="@android:color/holo_blue_light"
+ android:max="100"
+ android:progress="42"
+ android:indeterminate="false"
+ />
+
- <TextView
- android:id="@+id/txtvPublished"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/txtvItemname"
- android:layout_marginRight="4dp"
- android:layout_toLeftOf="@id/imgvType"
- tools:text="Jan 23"
- tools:background="@android:color/holo_green_dark" />
</RelativeLayout>
<include layout="@layout/vertical_list_divider"/>
diff --git a/app/src/main/res/layout/nav_feedlistitem.xml b/app/src/main/res/layout/nav_feedlistitem.xml
index e942d1b08..238beff88 100644
--- a/app/src/main/res/layout/nav_feedlistitem.xml
+++ b/app/src/main/res/layout/nav_feedlistitem.xml
@@ -7,7 +7,6 @@
android:layout_height="@dimen/listitem_iconwithtext_height"
tools:background="@android:color/darker_gray">
-
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
@@ -18,13 +17,12 @@
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="fitXY"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/listitem_icon_leftpadding"
tools:src="@drawable/ic_stat_antenna_default"
tools:background="@android:color/holo_green_dark"/>
-
<TextView
android:id="@+id/txtvTitle"
android:lines="1"
@@ -36,11 +34,37 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_iconwithtext_textleftpadding"
- android:layout_marginTop="@dimen/listitem_iconwithtext_textverticalpadding"
- android:layout_marginBottom="@dimen/listitem_iconwithtext_textverticalpadding"
android:layout_marginRight="@dimen/listitem_icon_rightpadding"
android:layout_toRightOf="@id/imgvCover"
tools:text="Navigation feed item title"
- tools:background="@android:color/holo_green_dark"
- />
-</RelativeLayout> \ No newline at end of file
+ tools:background="@android:color/holo_green_dark"/>
+
+ <TextView
+ android:id="@+id/txtvCount"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:lines="1"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textSize="@dimen/text_size_navdrawer"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="@dimen/listitem_icon_rightpadding"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ tools:text="23"
+ tools:background="@android:color/holo_green_dark"/>
+
+ <IconTextView
+ android:id="@+id/itxtvFailure"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toLeftOf="@id/txtvCount"
+ android:lines="1"
+ android:text="{fa-exclamation-circle}"
+ android:textColor="@color/download_failed_red"
+ android:textSize="@dimen/text_size_navdrawer"
+ android:layout_marginLeft="8dp"
+ android:layout_centerVertical="true"
+ tools:text="!"
+ tools:background="@android:color/holo_green_dark"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/nav_list.xml b/app/src/main/res/layout/nav_list.xml
index 8c46e456d..9fcf9d9fc 100644
--- a/app/src/main/res/layout/nav_list.xml
+++ b/app/src/main/res/layout/nav_list.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_layout"
android:layout_width="@dimen/drawer_width"
@@ -9,32 +10,11 @@
android:background="?attr/nav_drawer_background"
android:orientation="vertical">
- <ListView
- android:id="@+id/nav_list"
- android:layout_width="@dimen/drawer_width"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:choiceMode="singleChoice"
- android:clipToPadding="false"
- android:divider="@android:color/transparent"
- android:dividerHeight="0dp"
- android:paddingBottom="@dimen/list_vertical_padding"
- android:paddingTop="@dimen/list_vertical_padding"
- android:scrollbarStyle="outsideOverlay"
- tools:listitem="@layout/nav_listitem"
- tools:background="@android:color/holo_purple" />
-
- <View
- android:layout_width="@dimen/drawer_width"
- android:layout_height="1dp"
- android:layout_centerVertical="true"
- android:background="?android:attr/listDivider"
- tools:background="@android:color/holo_red_dark" />
-
<LinearLayout
android:id="@+id/nav_settings"
android:layout_width="@dimen/drawer_width"
android:layout_height="@dimen/listitem_iconwithtext_height"
+ android:layout_alignParentBottom="true"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/settings_label"
android:orientation="horizontal">
@@ -45,9 +25,9 @@
android:layout_height="@dimen/thumbnail_length_navlist"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
- android:layout_marginBottom="8dp"
+ android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/listitem_icon_leftpadding"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="4dp"
android:adjustViewBounds="true"
android:contentDescription="@string/cover_label"
android:cropToPadding="true"
@@ -58,10 +38,10 @@
tools:background="@android:color/holo_orange_dark" />
<TextView
- android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_margin="16dp"
+ android:layout_marginLeft="16dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/settings_label"
@@ -71,4 +51,30 @@
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+ <View
+ android:id="@+id/divider"
+ android:layout_width="@dimen/drawer_width"
+ android:layout_height="1dp"
+ android:layout_centerVertical="true"
+ android:layout_above="@id/nav_settings"
+ android:background="?android:attr/listDivider"
+ tools:background="@android:color/holo_red_dark" />
+
+ <ListView
+ android:id="@+id/nav_list"
+ android:layout_width="@dimen/drawer_width"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_above="@id/divider"
+ android:layout_alignParentTop="true"
+ android:choiceMode="singleChoice"
+ android:clipToPadding="false"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="0dp"
+ android:paddingBottom="@dimen/list_vertical_padding"
+ android:paddingTop="@dimen/list_vertical_padding"
+ android:scrollbarStyle="outsideOverlay"
+ tools:listitem="@layout/nav_listitem"
+ tools:background="@android:color/holo_purple" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/nav_listitem.xml b/app/src/main/res/layout/nav_listitem.xml
index bb5865ee6..d62672c34 100644
--- a/app/src/main/res/layout/nav_listitem.xml
+++ b/app/src/main/res/layout/nav_listitem.xml
@@ -16,11 +16,11 @@
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:cropToPadding="true"
- android:scaleType="centerCrop"
- android:padding="8dp"
+ android:scaleType="centerInside"
+ android:padding="4dp"
android:layout_marginLeft="@dimen/listitem_icon_leftpadding"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
tools:src="@drawable/ic_new_releases_white_24dp"
tools:background="@android:color/holo_green_dark"/>
@@ -36,8 +36,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_iconwithtext_textleftpadding"
- android:layout_marginTop="@dimen/listitem_iconwithtext_textverticalpadding"
- android:layout_marginBottom="@dimen/listitem_iconwithtext_textverticalpadding"
android:layout_marginRight="48dp"
android:layout_toRightOf="@id/imgvCover"
tools:text="Navigation item title"
@@ -52,8 +50,6 @@
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_navdrawer"
android:layout_marginLeft="12dp"
- android:layout_marginTop="14dp"
- android:layout_marginBottom="14dp"
android:layout_marginRight="@dimen/listitem_icon_rightpadding"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
diff --git a/app/src/main/res/layout/nav_section_item.xml b/app/src/main/res/layout/nav_section_item.xml
index 77b2ff253..fa1db865d 100644
--- a/app/src/main/res/layout/nav_section_item.xml
+++ b/app/src/main/res/layout/nav_section_item.xml
@@ -3,7 +3,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="24dp"
+ android:layout_height="16dp"
android:background="@android:color/transparent"
android:orientation="vertical">
diff --git a/app/src/main/res/layout/new_episodes_listitem.xml b/app/src/main/res/layout/new_episodes_listitem.xml
index 43ada14b0..b738cf836 100644
--- a/app/src/main/res/layout/new_episodes_listitem.xml
+++ b/app/src/main/res/layout/new_episodes_listitem.xml
@@ -37,7 +37,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
- android:layout_alignParentTop="true" />
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="8dp"/>
<TextView
android:id="@+id/txtvTitle"
@@ -60,38 +61,26 @@
android:layout_marginTop="16dp"
tools:background="@android:color/holo_red_light" >
+ <TextView
+ android:id="@+id/txtvDuration"
+ style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ tools:text="00:42:23"
+ tools:background="@android:color/holo_blue_dark" />
+
<ImageView
- android:id="@id/imgvInPlaylist"
+ android:id="@+id/imgvInPlaylist"
android:layout_width="@dimen/enc_icons_size"
android:layout_height="@dimen/enc_icons_size"
android:layout_alignParentRight="true"
android:layout_marginLeft="8dp"
- android:layout_marginRight="4dp"
android:contentDescription="@string/in_queue_label"
android:src="?attr/stat_playlist"
tools:src="@drawable/ic_list_grey600_24dp"
tools:background="@android:color/black" />
- <ProgressBar
- android:id="@+id/pbar_download_progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_marginRight="8dp"
- android:layout_toLeftOf="@id/imgvInPlaylist"
- android:max="100" />
-
- <TextView
- android:id="@+id/txtvDuration"
- style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@id/imgvInPlaylist"
- tools:text="00:42:23"
- tools:background="@android:color/holo_blue_dark" />
-
<TextView
android:id="@+id/txtvPublished"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
@@ -103,6 +92,17 @@
tools:text="Jan 23"
tools:background="@android:color/holo_green_dark" />
+ <ProgressBar
+ android:id="@+id/pbar_download_progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ android:layout_toLeftOf="@id/txtvPublished"
+ android:layout_toRightOf="@id/txtvDuration"
+ android:max="100" />
+
</RelativeLayout>
</RelativeLayout>
diff --git a/app/src/main/res/layout/queue_listitem.xml b/app/src/main/res/layout/queue_listitem.xml
index bc5b951a2..39e9c72a5 100644
--- a/app/src/main/res/layout/queue_listitem.xml
+++ b/app/src/main/res/layout/queue_listitem.xml
@@ -1,17 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/listitem_threeline_height"
android:orientation="horizontal"
+ android:paddingLeft="16dp"
tools:background="@android:color/darker_gray" >
<ImageView
android:id="@+id/drag_handle"
android:layout_width="100dp"
android:layout_height="match_parent"
- android:layout_marginLeft="8dp"
+ android:layout_marginLeft="-8dp"
android:layout_marginRight="-64dp"
android:contentDescription="@string/drag_handle_content_description"
android:scaleType="fitXY"
diff --git a/app/src/main/res/menu/allepisodes_context.xml b/app/src/main/res/menu/allepisodes_context.xml
new file mode 100644
index 000000000..f89ad5065
--- /dev/null
+++ b/app/src/main/res/menu/allepisodes_context.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@id/skip_episode_item"
+ android:menuCategory="container"
+ android:title="@string/skip_episode_label" />
+
+ <item
+ android:id="@+id/mark_read_item"
+ android:menuCategory="container"
+ android:title="@string/mark_read_label" />
+ <item
+ android:id="@+id/mark_unread_item"
+ android:menuCategory="container"
+ android:title="@string/mark_unread_label" />
+
+ <item
+ android:id="@+id/add_to_queue_item"
+ android:menuCategory="container"
+ android:title="@string/add_to_queue_label" />
+ <item
+ android:id="@+id/remove_from_queue_item"
+ android:menuCategory="container"
+ android:title="@string/remove_from_queue_label" />
+
+ <item
+ android:id="@+id/reset_position"
+ android:menuCategory="container"
+ android:title="@string/reset_position" />
+
+ <item
+ android:id="@+id/activate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/activate_auto_download" />
+ <item
+ android:id="@+id/deactivate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/deactivate_auto_download" />
+
+ <item
+ android:id="@+id/share_link_item"
+ android:menuCategory="container"
+ android:title="@string/share_link_label" />
+ <item
+ android:id="@+id/visit_website_item"
+ android:menuCategory="container"
+ android:title="@string/visit_website_label" />
+
+ <item
+ android:id="@+id/support_item"
+ android:menuCategory="container"
+ android:title="@string/support_label" />
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/feeditem.xml b/app/src/main/res/menu/feeditem.xml
deleted file mode 100644
index 8227f8b14..000000000
--- a/app/src/main/res/menu/feeditem.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:custom="http://schemas.android.com/apk/res-auto">
-
- <item
- android:id="@+id/download_item"
- android:icon="?attr/av_download"
- custom:showAsAction="collapseActionView"
- android:title="@string/download_label">
- </item>
- <item
- android:id="@+id/stream_item"
- android:icon="?attr/action_stream"
- custom:showAsAction="collapseActionView"
- android:title="@string/stream_label">
- </item>
- <item
- android:id="@+id/play_item"
- android:icon="?attr/av_play"
- custom:showAsAction="collapseActionView"
- android:title="@string/play_label">
- </item>
- <item
- android:id="@+id/remove_item"
- android:icon="?attr/content_discard"
- custom:showAsAction="collapseActionView"
- android:title="@string/remove_label">
- </item>
- <item
- android:id="@id/skip_episode_item"
- android:title="@string/skip_episode_label"
- custom:showAsAction="collapseActionView">
- </item>
- <item
- android:id="@+id/cancel_download_item"
- android:icon="?attr/navigation_cancel"
- custom:showAsAction="ifRoom|collapseActionView"
- android:title="@string/cancel_download_label">
- </item>
- <item
- android:id="@+id/mark_read_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/mark_read_label">
- </item>
- <item
- android:id="@+id/mark_unread_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/mark_unread_label">
- </item>
- <item
- android:id="@+id/add_to_queue_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/add_to_queue_label">
- </item>
- <item
- android:id="@+id/remove_from_queue_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/remove_from_queue_label">
- </item>
- <item
- android:id="@+id/share_link_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/share_link_label">
- </item>
- <item
- android:id="@+id/visit_website_item"
- android:icon="?attr/location_web_site"
- custom:showAsAction="collapseActionView"
- android:title="@string/visit_website_label">
- </item>
- <item
- android:id="@+id/support_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/support_label">
- </item>
-
-</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/feeditem_dialog.xml b/app/src/main/res/menu/feeditem_options.xml
index f33b7502a..f8e9b9c75 100644
--- a/app/src/main/res/menu/feeditem_dialog.xml
+++ b/app/src/main/res/menu/feeditem_options.xml
@@ -18,6 +18,7 @@
custom:showAsAction="collapseActionView"
android:title="@string/mark_unread_label">
</item>
+
<item
android:id="@+id/add_to_queue_item"
custom:showAsAction="collapseActionView"
@@ -28,6 +29,24 @@
custom:showAsAction="collapseActionView"
android:title="@string/remove_from_queue_label">
</item>
+
+ <item
+ android:id="@+id/reset_position"
+ custom:showAsAction="collapseActionView"
+ android:title="@string/reset_position">
+ </item>
+
+ <item
+ android:id="@+id/activate_auto_download"
+ custom:showAsAction="collapseActionView"
+ android:title="@string/activate_auto_download">
+ </item>
+ <item
+ android:id="@+id/deactivate_auto_download"
+ custom:showAsAction="collapseActionView"
+ android:title="@string/deactivate_auto_download">
+ </item>
+
<item
android:id="@+id/share_link_item"
custom:showAsAction="collapseActionView"
diff --git a/app/src/main/res/menu/feeditemlist_context.xml b/app/src/main/res/menu/feeditemlist_context.xml
new file mode 100644
index 000000000..f89ad5065
--- /dev/null
+++ b/app/src/main/res/menu/feeditemlist_context.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@id/skip_episode_item"
+ android:menuCategory="container"
+ android:title="@string/skip_episode_label" />
+
+ <item
+ android:id="@+id/mark_read_item"
+ android:menuCategory="container"
+ android:title="@string/mark_read_label" />
+ <item
+ android:id="@+id/mark_unread_item"
+ android:menuCategory="container"
+ android:title="@string/mark_unread_label" />
+
+ <item
+ android:id="@+id/add_to_queue_item"
+ android:menuCategory="container"
+ android:title="@string/add_to_queue_label" />
+ <item
+ android:id="@+id/remove_from_queue_item"
+ android:menuCategory="container"
+ android:title="@string/remove_from_queue_label" />
+
+ <item
+ android:id="@+id/reset_position"
+ android:menuCategory="container"
+ android:title="@string/reset_position" />
+
+ <item
+ android:id="@+id/activate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/activate_auto_download" />
+ <item
+ android:id="@+id/deactivate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/deactivate_auto_download" />
+
+ <item
+ android:id="@+id/share_link_item"
+ android:menuCategory="container"
+ android:title="@string/share_link_label" />
+ <item
+ android:id="@+id/visit_website_item"
+ android:menuCategory="container"
+ android:title="@string/visit_website_label" />
+
+ <item
+ android:id="@+id/support_item"
+ android:menuCategory="container"
+ android:title="@string/support_label" />
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feedlist.xml
index b6512e828..e0da72667 100644
--- a/app/src/main/res/menu/feedlist.xml
+++ b/app/src/main/res/menu/feedlist.xml
@@ -3,6 +3,13 @@
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
+ android:id="@+id/hide_items"
+ android:icon="?attr/ic_filter"
+ android:menuCategory="container"
+ android:title="@string/hide_episodes_title"
+ custom:showAsAction="always">
+ </item>
+ <item
android:id="@+id/refresh_item"
android:icon="?attr/navigation_refresh"
android:menuCategory="container"
@@ -15,6 +22,14 @@
android:title="@string/load_complete_feed"
custom:showAsAction="collapseActionView">
</item>
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="?attr/action_search"
+ custom:showAsAction="always"
+ custom:actionViewClass="android.support.v7.widget.SearchView"
+ android:title="@string/search_label"/>
+
<item
android:id="@+id/mark_all_read_item"
android:menuCategory="container"
diff --git a/app/src/main/res/menu/gpodder_podcasts.xml b/app/src/main/res/menu/gpodder_podcasts.xml
new file mode 100644
index 000000000..88fa36a4a
--- /dev/null
+++ b/app/src/main/res/menu/gpodder_podcasts.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:custom="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/action_search"
+ android:icon="?attr/action_search"
+ custom:showAsAction="collapseActionView|ifRoom"
+ custom:actionViewClass="android.support.v7.widget.SearchView"
+ android:title="@string/search_label"/>
+
+</menu>
diff --git a/app/src/main/res/menu/new_episodes.xml b/app/src/main/res/menu/new_episodes.xml
index 72661a17e..1e1d7ab78 100644
--- a/app/src/main/res/menu/new_episodes.xml
+++ b/app/src/main/res/menu/new_episodes.xml
@@ -4,6 +4,13 @@
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
+ android:id="@+id/action_search"
+ android:icon="?attr/action_search"
+ custom:showAsAction="always"
+ custom:actionViewClass="android.support.v7.widget.SearchView"
+ android:title="@string/search_label"/>
+
+ <item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
@@ -17,11 +24,4 @@
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>
- <item
- android:id="@+id/episode_filter_item"
- android:title="@string/episode_filter_label"
- android:menuCategory="container"
- android:checkable="true"
- custom:showAsAction="collapseActionView"/>
-
</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/queue.xml b/app/src/main/res/menu/queue.xml
index c7dd4d371..01a11b10e 100644
--- a/app/src/main/res/menu/queue.xml
+++ b/app/src/main/res/menu/queue.xml
@@ -4,6 +4,12 @@
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
+ android:id="@+id/queue_lock"
+ android:title=""
+ android:menuCategory="container"
+ custom:showAsAction="always" />
+
+ <item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
@@ -11,8 +17,15 @@
android:icon="?attr/navigation_refresh"/>
<item
+ android:id="@+id/action_search"
+ android:icon="?attr/action_search"
+ custom:showAsAction="collapseActionView|ifRoom"
+ custom:actionViewClass="android.support.v7.widget.SearchView"
+ android:title="@string/search_label"/>
+
+ <item
android:id="@+id/clear_queue"
- android:title="Clear Queue"
+ android:title="@string/clear_queue_label"
android:menuCategory="container"
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>
diff --git a/app/src/main/res/menu/queue_context.xml b/app/src/main/res/menu/queue_context.xml
index 327600038..6ab2daabf 100644
--- a/app/src/main/res/menu/queue_context.xml
+++ b/app/src/main/res/menu/queue_context.xml
@@ -8,13 +8,53 @@
android:title="@string/move_to_top_label" />
<item
+ android:id="@+id/move_to_bottom_item"
+ android:menuCategory="container"
+ android:title="@string/move_to_bottom_label" />
+
+ <item
+ android:id="@+id/mark_read_item"
+ android:menuCategory="container"
+ android:title="@string/mark_read_label" />
+
+ <item
+ android:id="@+id/mark_unread_item"
+ android:menuCategory="container"
+ android:title="@string/mark_unread_label" />
+
+ <item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
<item
- android:id="@+id/move_to_bottom_item"
+ android:id="@+id/reset_position"
android:menuCategory="container"
- android:title="@string/move_to_bottom_label" />
+ android:title="@string/reset_position" />
+
+ <item
+ android:id="@+id/activate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/activate_auto_download" />
+
+ <item
+ android:id="@+id/deactivate_auto_download"
+ android:menuCategory="container"
+ android:title="@string/deactivate_auto_download" />
+
+ <item
+ android:id="@+id/share_link_item"
+ android:menuCategory="container"
+ android:title="@string/share_link_label" />
+ <item
+ android:id="@+id/visit_website_item"
+ android:menuCategory="container"
+ android:title="@string/visit_website_label" />
+
+ <item
+ android:id="@+id/support_item"
+ android:menuCategory="container"
+ android:title="@string/support_label" />
+
</menu> \ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index eb6a3fe1f..e848915cf 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -9,6 +9,10 @@
android:key="prefTheme"
android:summary="@string/pref_set_theme_sum"
android:defaultValue="0"/>
+ <Preference
+ android:key="prefHiddenDrawerItems"
+ android:summary="@string/pref_nav_drawer_items_sum"
+ android:title="@string/pref_nav_drawer_items_title" />
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
@@ -77,14 +81,12 @@
android:key="prefPauseForFocusLoss"
android:summary="@string/pref_pausePlaybackForFocusLoss_sum"
android:title="@string/pref_pausePlaybackForFocusLoss_title" />
-
- <ListPreference
- android:defaultValue="30"
- android:entries="@array/seek_delta_values"
- android:entryValues="@array/seek_delta_values"
- android:key="prefSeekDeltaSecs"
- android:summary="@string/pref_seek_delta_sum"
- android:title="@string/pref_seek_delta_title" />
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:enabled="true"
+ android:key="prefResumeAfterCall"
+ android:summary="@string/pref_resumeAfterCall_sum"
+ android:title="@string/pref_resumeAfterCall_title"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/network_pref">
diff --git a/app/src/main/assets/about.html b/app/src/main/templates/about.html
index f1a1fdf44..3313fa12f 100644
--- a/app/src/main/assets/about.html
+++ b/app/src/main/templates/about.html
@@ -41,9 +41,11 @@
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
- <p>AntennaPod, Version 1.1</p>
+ <p>AntennaPod, Version @versionname@, Build @versioncode@</p>
- <p>Copyright © 2014 Daniel Oeh</p>
+ <p>Created by Daniel Oeh</p>
+
+ <p>Copyright © 2015 AntennaPod Contributors <a href="https://github.com/AntennaPod/AntennaPod/blob/master/CONTRIBUTORS">(View)</a></p>
<p>Licensed under the MIT License <a href="LICENSE.html">(View)</a></p>
</div>
@@ -77,8 +79,11 @@ licensed under the Apache 2.0 license <a href="LICENSE_OKIO.txt">(View)</a>
<h2>Material Design Icons <a href="https://github.com/google/material-design-icons">(Link)</a></h2>
by Google, licensed under an Attribution-ShareAlike 4.0 International license <a href="LICENSE_MATERIAL_DESIGN_ICONS.txt">(View)</a>
-<h2>EventBus <a href="https://github.com/greenrobot/EventBus">(Link>)</a></h2>
+<h2>EventBus <a href="https://github.com/greenrobot/EventBus">(Link)</a></h2>
by greenrobot, licensed under the Apache 2.0 license <a href="LICENSE_EVENTBUS.txt">(View)</a>
+<h2>Iconify <a href="https://github.com/JoanZapata/android-iconify">(Link)</a></h2>
+by Joan Zapata, licensed under the Apache 2.0 license <a href="LICENSE_ANDROID_ICONIFY.txt">(View)</a>
+
</body>
</html>
diff --git a/build.gradle b/build.gradle
index 9f1a2d6ab..363f02283 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0'
+ classpath 'com.android.tools.build:gradle:1.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -19,5 +19,5 @@ allprojects {
}
task wrapper(type: Wrapper) {
- gradleVersion = '2.2.1'
+ gradleVersion = '2.4'
} \ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
index c327f194c..ae2c11070 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -9,6 +9,8 @@ android {
targetSdkVersion 21
versionCode 1
versionName "1.0"
+ testApplicationId "de.danoeh.antennapod.core.tests"
+ testInstrumentationRunner "de.danoeh.antennapod.core.tests.AntennaPodTestRunner"
}
buildTypes {
release {
@@ -40,9 +42,9 @@ dependencies {
compile 'commons-io:commons-io:2.4'
compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
compile 'org.jsoup:jsoup:1.7.3'
- compile 'com.squareup.picasso:picasso:2.4.0'
- compile 'com.squareup.okhttp:okhttp:2.2.0'
- compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
+ compile 'com.squareup.picasso:picasso:2.5.2'
+ compile 'com.squareup.okhttp:okhttp:2.3.0'
+ compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
compile 'com.squareup.okio:okio:1.2.0'
compile 'com.nineoldandroids:library:2.4.0'
compile 'de.greenrobot:eventbus:2.4.0'
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/ApplicationTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/ApplicationTest.java
deleted file mode 100644
index 894bcfa63..000000000
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/ApplicationTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.danoeh.antennapod.core;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
- */
-public class ApplicationTest extends ApplicationTestCase<Application> {
- public ApplicationTest() {
- super(Application.class);
- }
-} \ No newline at end of file
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java
new file mode 100644
index 000000000..fbb5459d4
--- /dev/null
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/AntennaPodTestRunner.java
@@ -0,0 +1,15 @@
+package de.danoeh.antennapod.core.tests;
+
+import android.test.InstrumentationTestRunner;
+import android.test.suitebuilder.TestSuiteBuilder;
+import junit.framework.TestSuite;
+
+public class AntennaPodTestRunner extends InstrumentationTestRunner {
+
+ @Override
+ public TestSuite getAllTests() {
+ return new TestSuiteBuilder(AntennaPodTestRunner.class)
+ .includeAllPackagesUnderHere()
+ .build();
+ }
+} \ No newline at end of file
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
index cca753895..2a2d6414a 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/util/DateUtilsTest.java
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.core.util;
+package de.danoeh.antennapod.core.tests.util;
import android.test.AndroidTestCase;
@@ -7,6 +7,8 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
+import de.danoeh.antennapod.core.util.DateUtils;
+
public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithMicroseconds() throws Exception {
@@ -56,4 +58,20 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(900, actual.getTime()%1000);
}
+ public void testParseDateWithTimezoneName() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 6, 31, 4);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis());
+ Date actual = DateUtils.parse("Sat, 28 Mar 2015 01:31:04 EST");
+ assertEquals(expected, actual);
+ }
+
+ public void testParseDateWithTimeZoneOffset() throws Exception {
+ GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 12, 16, 12);
+ exp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Date expected = new Date(exp.getTimeInMillis());
+ Date actual = DateUtils.parse("Sat, 28 March 2015 08:16:12 -0400");
+ assertEquals(expected, actual);
+ }
+
}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java
new file mode 100644
index 000000000..50c2a9c3c
--- /dev/null
+++ b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/LongLongMapTest.java
@@ -0,0 +1,61 @@
+package de.danoeh.antennapod.core.tests.util;
+
+import android.test.AndroidTestCase;
+
+import de.danoeh.antennapod.core.util.LongIntMap;
+
+public class LongLongMapTest extends AndroidTestCase {
+
+ public void testEmptyMap() {
+ LongIntMap map = new LongIntMap();
+ assertEquals(0, map.size());
+ assertEquals("LongLongMap{}", map.toString());
+ assertEquals(0, map.get(42));
+ assertEquals(-1, map.get(42, -1));
+ assertEquals(false, map.delete(42));
+ assertEquals(-1, map.indexOfKey(42));
+ assertEquals(-1, map.indexOfValue(42));
+ assertEquals(1, map.hashCode());
+ }
+
+ public void testSingleElement() {
+ LongIntMap map = new LongIntMap();
+ map.put(17, 42);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=42}", map.toString());
+ assertEquals(42, map.get(17));
+ assertEquals(42, map.get(17, -1));
+ assertEquals(0, map.indexOfKey(17));
+ assertEquals(0, map.indexOfValue(42));
+ assertEquals(true, map.delete(17));
+ }
+
+ public void testAddAndDelete() {
+ LongIntMap map = new LongIntMap();
+ for(int i=0; i < 100; i++) {
+ map.put(i * 17, i * 42);
+ }
+ assertEquals(100, map.size());
+ assertEquals(0, map.get(0));
+ assertEquals(42, map.get(17));
+ assertEquals(42, map.get(17, -1));
+ assertEquals(1, map.indexOfKey(17));
+ assertEquals(1, map.indexOfValue(42));
+ for(int i=0; i < 100; i++) {
+ assertEquals(true, map.delete(i * 17));
+ }
+ }
+
+ public void testOverwrite() {
+ LongIntMap map = new LongIntMap();
+ map.put(17, 42);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=42}", map.toString());
+ assertEquals(42, map.get(17));
+ map.put(17, 23);
+ assertEquals(1, map.size());
+ assertEquals("LongLongMap{17=23}", map.toString());
+ assertEquals(23, map.get(17));
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
index b6ece6dc8..4f2d5b204 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java
@@ -6,8 +6,12 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.Log;
+import com.squareup.okhttp.Interceptor;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Response;
import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.OkHttpDownloader;
@@ -22,13 +26,18 @@ import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+
/**
* Provides access to Picasso instances.
*/
public class PicassoProvider {
+
private static final String TAG = "PicassoProvider";
private static final boolean DEBUG = false;
@@ -56,10 +65,12 @@ public class PicassoProvider {
if (picassoSetup) {
return;
}
+ OkHttpClient client = new OkHttpClient();
+ client.interceptors().add(new BasicAuthenticationInterceptor(appContext));
Picasso picasso = new Picasso.Builder(appContext)
.indicatorsEnabled(DEBUG)
.loggingEnabled(DEBUG)
- .downloader(new OkHttpDownloader(appContext))
+ .downloader(new OkHttpDownloader(client))
.addRequestHandler(new MediaRequestHandler(appContext))
.executor(getExecutorService())
.memoryCache(getMemoryCache(appContext))
@@ -75,6 +86,48 @@ public class PicassoProvider {
picassoSetup = true;
}
+ private static class BasicAuthenticationInterceptor implements Interceptor {
+
+ private final Context context;
+
+ public BasicAuthenticationInterceptor(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ com.squareup.okhttp.Request request = chain.request();
+ String url = request.urlString();
+ String authentication = DBReader.getImageAuthentication(context, url);
+
+ if(TextUtils.isEmpty(authentication)) {
+ Log.d(TAG, "no credentials for '" + url + "'");
+ return chain.proceed(request);
+ }
+
+ // add authentication
+ String[] auth = authentication.split(":");
+ String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1");
+ com.squareup.okhttp.Request newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with ISO-8859-1 encoding");
+ Response response = chain.proceed(newRequest);
+ if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8");
+ newRequest = request
+ .newBuilder()
+ .addHeader("Authorization", credentials)
+ .build();
+ Log.d(TAG, "Basic authentication with UTF-8 encoding");
+ return chain.proceed(newRequest);
+ } else {
+ return response;
+ }
+ }
+ }
+
private static class MediaRequestHandler extends RequestHandler {
final Context context;
@@ -90,7 +143,7 @@ public class PicassoProvider {
}
@Override
- public Result load(Request data) throws IOException {
+ public Result load(Request data, int networkPolicy) throws IOException {
Bitmap bitmap = null;
MediaMetadataRetriever mmr = null;
try {
@@ -109,13 +162,7 @@ public class PicassoProvider {
}
if (bitmap == null) {
- // check for fallback Uri
- String fallbackParam = data.uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK);
-
- if (fallbackParam != null) {
- Uri fallback = Uri.parse(fallbackParam);
- bitmap = decodeStreamFromFile(data, fallback);
- }
+ Log.wtf(TAG, "THIS SHOULD NEVER EVER HAPPEN!!");
}
return new Result(bitmap, Picasso.LoadedFrom.DISK);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index c0f71ed55..29ba721fe 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed;
import android.content.Context;
import android.net.Uri;
+import android.support.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
@@ -10,9 +11,7 @@ import java.util.Date;
import java.util.List;
import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.util.EpisodeFilter;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.danoeh.antennapod.core.util.flattr.FlattrThing;
@@ -81,12 +80,20 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
private String nextPageLink;
+ private boolean lastUpdateFailed;
+
+ /**
+ * Contains property strings. If such a property applies to a feed item, it is not shown in the feed list
+ */
+ private FeedItemFilter itemfilter;
+
/**
* This constructor is used for restoring a feed from the database.
*/
public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
- String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink) {
+ String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
+ String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.title = title;
@@ -106,8 +113,13 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
-
- items = new ArrayList<FeedItem>();
+ this.items = new ArrayList<FeedItem>();
+ if(filter != null) {
+ this.itemfilter = new FeedItemFilter(filter);
+ } else {
+ this.itemfilter = new FeedItemFilter(new String[0]);
+ }
+ this.lastUpdateFailed = lastUpdateFailed;
}
/**
@@ -117,7 +129,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image,
- fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null);
+ fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
}
/**
@@ -125,7 +137,6 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
*/
public Feed() {
super();
- items = new ArrayList<FeedItem>();
lastUpdate = new Date();
this.flattrStatus = new FlattrStatus();
}
@@ -159,53 +170,15 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
preferences = new FeedPreferences(0, true, username, password);
}
- /**
- * Returns the number of FeedItems where 'read' is false. If the 'display
- * only episodes' - preference is set to true, this method will only count
- * items with episodes.
- */
- public int getNumOfNewItems() {
- int count = 0;
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!UserPreferences.isDisplayOnlyEpisodes()
- || item.getMedia() != null) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Returns the number of FeedItems where the media started to play but
- * wasn't finished yet.
- */
- public int getNumOfStartedItems() {
- int count = 0;
-
- for (FeedItem item : items) {
- FeedItem.State state = item.getState();
- if (state == FeedItem.State.IN_PROGRESS
- || state == FeedItem.State.PLAYING) {
- count++;
- }
- }
- return count;
- }
/**
* Returns true if at least one item in the itemlist is unread.
*
- * @param enableEpisodeFilter true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
*/
- public boolean hasNewItems(boolean enableEpisodeFilter) {
+ public boolean hasNewItems() {
for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!(enableEpisodeFilter && UserPreferences
- .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
+ if (item.getState() == FeedItem.State.UNREAD) {
+ if (item.getMedia() != null) {
return true;
}
}
@@ -216,30 +189,17 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
/**
* Returns the number of FeedItems.
*
- * @param enableEpisodeFilter true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
*/
- public int getNumOfItems(boolean enableEpisodeFilter) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.countItemsWithEpisodes(items);
- } else {
- return items.size();
- }
+ public int getNumOfItems() {
+ return items.size();
}
/**
* Returns the item at the specified index.
*
- * @param enableEpisodeFilter true if this method should ignore items without episdodes if
- * the episodes filter has been enabled by the user.
*/
- public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.accessEpisodeByIndex(items, position);
- } else {
- return items.get(position);
- }
+ public FeedItem getItemAtIndex(int position) {
+ return items.get(position);
}
/**
@@ -350,6 +310,19 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
return false;
}
+ public FeedItem getMostRecentItem() {
+ // we could sort, but we don't need to, a simple search is fine...
+ Date mostRecentDate = new Date(0);
+ FeedItem mostRecentItem = null;
+ for (FeedItem item : items) {
+ if (item.getPubDate().after(mostRecentDate)) {
+ mostRecentDate = item.getPubDate();
+ mostRecentItem = item;
+ }
+ }
+ return mostRecentItem;
+ }
+
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEED;
@@ -503,4 +476,24 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource
public void setNextPageLink(String nextPageLink) {
this.nextPageLink = nextPageLink;
}
+
+ @Nullable
+ public FeedItemFilter getItemFilter() {
+ return itemfilter;
+ }
+
+ public void setHiddenItemProperties(String[] properties) {
+ if (properties != null) {
+ this.itemfilter = new FeedItemFilter(properties);
+ }
+ }
+
+ public boolean hasLastUpdateFailed() {
+ return this.lastUpdateFailed;
+ }
+
+ public void setLastUpdateFailed(boolean lastUpdateFailed) {
+ this.lastUpdateFailed = lastUpdateFailed;
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
new file mode 100644
index 000000000..d04d236e4
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java
@@ -0,0 +1,28 @@
+package de.danoeh.antennapod.core.feed;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public class FeedEvent {
+
+ public enum Action {
+ FILTER_CHANGED
+ }
+
+ public final Action action;
+ public final long feedId;
+
+ public FeedEvent(Action action, long feedId) {
+ this.action = action;
+ this.feedId = feedId;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("action", action)
+ .append("feedId", feedId)
+ .toString();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
index 4fd7a184c..11348953e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java
@@ -63,6 +63,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
private List<Chapter> chapters;
private FeedImage image;
+ private boolean autoDownload = true;
+
public FeedItem() {
this.read = true;
this.flattrStatus = new FlattrStatus();
@@ -74,7 +76,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, boolean read,
- String itemIdentifier) {
+ String itemIdentifier, boolean autoDownload) {
this.id = id;
this.title = title;
this.link = link;
@@ -86,6 +88,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.image = image;
this.read = read;
this.itemIdentifier = itemIdentifier;
+ this.autoDownload = autoDownload;
}
/**
@@ -236,7 +239,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public boolean isRead() {
- return read || isInProgress();
+ return read;
}
public void setRead(boolean read) {
@@ -315,10 +318,10 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
@Override
public Uri getImageUri() {
- if (hasItemImageDownloaded()) {
- return image.getImageUri();
- } else if (hasMedia()) {
+ if(media != null && media.hasEmbeddedPicture()) {
return media.getImageUri();
+ } else if (hasItemImageDownloaded()) {
+ return image.getImageUri();
} else if (feed != null) {
return feed.getImageUri();
} else {
@@ -327,7 +330,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
}
public enum State {
- NEW, IN_PROGRESS, READ, PLAYING
+ UNREAD, IN_PROGRESS, READ, PLAYING
}
public State getState() {
@@ -339,7 +342,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
return State.IN_PROGRESS;
}
}
- return (isRead() ? State.READ : State.NEW);
+ return (isRead() ? State.READ : State.UNREAD);
}
public long getFeedId() {
@@ -388,6 +391,22 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
return hasChapters;
}
+ public void setAutoDownload(boolean autoDownload) {
+ this.autoDownload = autoDownload;
+ }
+
+ public boolean getAutoDownload() {
+ return this.autoDownload;
+ }
+
+ public boolean isAutoDownloadable() {
+ return this.hasMedia() &&
+ false == this.getMedia().isPlaying() &&
+ false == this.getMedia().isDownloaded() &&
+ false == this.isRead() &&
+ this.getAutoDownload();
+ }
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
new file mode 100644
index 000000000..4ad084b39
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
@@ -0,0 +1,82 @@
+package de.danoeh.antennapod.core.feed;
+
+import android.content.Context;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.core.storage.DBReader;
+
+public class FeedItemFilter {
+
+ private final String[] properties;
+
+ private boolean hideUnplayed = false;
+ private boolean hidePaused = false;
+ private boolean hidePlayed = false;
+ private boolean hideQueued = false;
+ private boolean hideNotQueued = false;
+ private boolean hideDownloaded = false;
+ private boolean hideNotDownloaded = false;
+
+ public FeedItemFilter(String properties) {
+ this(StringUtils.split(properties, ','));
+ }
+
+ public FeedItemFilter(String[] properties) {
+ this.properties = properties;
+ for(String property : properties) {
+ // see R.arrays.feed_filter_values
+ switch(property) {
+ case "unplayed":
+ hideUnplayed = true;
+ break;
+ case "paused":
+ hidePaused = true;
+ break;
+ case "played":
+ hidePlayed = true;
+ break;
+ case "queued":
+ hideQueued = true;
+ break;
+ case "not_queued":
+ hideNotQueued = true;
+ break;
+ case "downloaded":
+ hideDownloaded = true;
+ break;
+ case "not_downloaded":
+ hideNotDownloaded = true;
+ break;
+ }
+ }
+ }
+
+ public List<FeedItem> filter(Context context, List<FeedItem> items) {
+ if(properties.length == 0) {
+ return items;
+ }
+ List<FeedItem> result = new ArrayList<FeedItem>();
+ for(FeedItem item : items) {
+ if(hideUnplayed && false == item.isRead()) continue;
+ if(hidePaused && item.getState() == FeedItem.State.IN_PROGRESS) continue;
+ if(hidePlayed && item.isRead()) continue;
+ boolean isQueued = DBReader.getQueueIDList(context).contains(item.getId());
+ if(hideQueued && isQueued) continue;
+ if(hideNotQueued && false == isQueued) continue;
+ boolean isDownloaded = item.getMedia() != null && item.getMedia().isDownloaded();
+ if(hideDownloaded && isDownloaded) continue;
+ if(hideNotDownloaded && false == isDownloaded) continue;
+ result.add(item);
+ }
+ return result;
+ }
+
+ public String[] getValues() {
+ return properties.clone();
+ }
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 93f826894..f875eb812 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,6 +35,7 @@ public class FeedMedia extends FeedFile implements Playable {
private String mime_type;
private volatile FeedItem item;
private Date playbackCompletionDate;
+ private boolean hasEmbeddedPicture;
/* Used for loading item when restoring from parcel. */
private long itemID;
@@ -50,6 +52,7 @@ public class FeedMedia extends FeedFile implements Playable {
long size, String mime_type, String file_url, String download_url,
boolean downloaded, Date playbackCompletionDate, int played_duration) {
super(file_url, download_url, downloaded);
+ checkEmbeddedPicture();
this.id = id;
this.item = item;
this.duration = duration;
@@ -61,12 +64,6 @@ public class FeedMedia extends FeedFile implements Playable {
? null : (Date) playbackCompletionDate.clone();
}
- public FeedMedia(long id, FeedItem item) {
- super();
- this.id = id;
- this.item = item;
- }
-
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
@@ -227,18 +224,15 @@ public class FeedMedia extends FeedFile implements Playable {
return (this.position > 0);
}
- public FeedImage getImage() {
- if (item != null) {
- return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage();
- }
- return null;
- }
-
@Override
public int describeContents() {
return 0;
}
+ public boolean hasEmbeddedPicture() {
+ return this.hasEmbeddedPicture;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
@@ -415,28 +409,45 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public Uri getImageUri() {
- final Uri feedImgUri = getFeedImageUri();
-
- if (localFileAvailable()) {
+ if (hasEmbeddedPicture) {
Uri.Builder builder = new Uri.Builder();
- builder.scheme(SCHEME_MEDIA)
- .encodedPath(getLocalMediaUrl());
- if (feedImgUri != null) {
- builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString());
- }
+ builder.scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl());
return builder.build();
- } else if (item.hasItemImageDownloaded()) {
- return item.getImage().getImageUri();
} else {
- return feedImgUri;
+ return item.getImageUri();
}
}
- private Uri getFeedImageUri() {
- if (item != null && item.getFeed() != null) {
- return item.getFeed().getImageUri();
- } else {
- return null;
+ @Override
+ public void setDownloaded(boolean downloaded) {
+ super.setDownloaded(downloaded);
+ checkEmbeddedPicture();
+ }
+
+ @Override
+ public void setFile_url(String file_url) {
+ super.setFile_url(file_url);
+ checkEmbeddedPicture();
+ }
+
+ private void checkEmbeddedPicture() {
+ if (!localFileAvailable()) {
+ hasEmbeddedPicture = false;
+ return;
+ }
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ try {
+ mmr.setDataSource(getLocalMediaUrl());
+ byte[] image = mmr.getEmbeddedPicture();
+ if(image != null) {
+ hasEmbeddedPicture = true;
+ }
+ else {
+ hasEmbeddedPicture = false;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ hasEmbeddedPicture = false;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
index 9f1eec754..c8497f509 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java
@@ -8,7 +8,7 @@ import java.util.List;
public class QueueEvent {
public enum Action {
- ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED
+ ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED
}
public final Action action;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
index db242c3bc..1a40120e2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java
@@ -75,84 +75,9 @@ public class GpodnetService {
public GpodnetService() {
httpClient = AntennapodHttpClient.getHttpClient();
- if (Build.VERSION.SDK_INT <= 10) {
- Log.d(TAG, "Use custom SSL factory");
- SSLSocketFactory factory = getCustomSslSocketFactory();
- httpClient.setSslSocketFactory(factory);
- }
BASE_HOST = GpodnetPreferences.getHostname();
}
- private synchronized static SSLSocketFactory getCustomSslSocketFactory() {
- try {
- TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- defaultTrustManagerFactory.init((KeyStore) null); // use system keystore
- final X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0];
- TrustManager[] customTrustManagers = new TrustManager[]{new X509TrustManager() {
- @Override
- public java.security.cert.X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- @Override
- public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
- }
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType)
- throws CertificateException {
- // chain may out of order - construct data structures to walk from server certificate to root certificate
- Map<Principal, X509Certificate> certificates = new HashMap<Principal, X509Certificate>(chain.length - 1);
- X509Certificate subject = null;
- for (X509Certificate cert : chain) {
- cert.checkValidity();
- if (cert.getSubjectDN().toString().startsWith("CN=" + DEFAULT_BASE_HOST)) {
- subject = cert;
- } else {
- certificates.put(cert.getSubjectDN(), cert);
- }
- }
- if (subject == null) {
- throw new CertificateException("Chain does not contain a certificate for " + DEFAULT_BASE_HOST);
- }
- // follow chain to root CA
- while (certificates.get(subject.getIssuerDN()) != null) {
- subject.checkValidity();
- X509Certificate issuer = certificates.get(subject.getIssuerDN());
- try {
- subject.verify(issuer.getPublicKey());
- } catch (Exception e) {
- Log.d(TAG, "failed: " + issuer.getSubjectDN() + " -> " + subject.getSubjectDN());
- throw new CertificateException("Could not verify certificate");
- }
- subject = issuer;
- }
- X500Principal rootAuthority = subject.getIssuerX500Principal();
- boolean accepted = false;
- for (X509Certificate cert :
- defaultTrustManager.getAcceptedIssuers()) {
- if (cert.getSubjectX500Principal().equals(rootAuthority)) {
- try {
- subject.verify(cert.getPublicKey());
- accepted = true;
- } catch (Exception e) {
- Log.d(TAG, "failed: " + cert.getSubjectDN() + " -> " + subject.getSubjectDN());
- throw new CertificateException("Could not verify root certificate");
- }
- }
- }
- if (accepted == false) {
- throw new CertificateException("Could not verify root certificate");
- }
- }
- }};
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, customTrustManagers, null);
- return sslContext.getSocketFactory();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
-
/**
* Returns the [count] most used tags.
*/
@@ -726,7 +651,12 @@ public class GpodnetService {
Validate.notNull(body);
ByteArrayOutputStream outputStream;
- int contentLength = (int) body.contentLength();
+ int contentLength = 0;
+ try {
+ contentLength = (int) body.contentLength();
+ } catch (IOException ignore) {
+ // ignore
+ }
if (contentLength > 0) {
outputStream = new ByteArrayOutputStream(contentLength);
} else {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
index 0c431d60b..bd6210d13 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java
@@ -258,7 +258,7 @@ public class GpodnetEpisodeAction {
private int total = -1;
public Builder(FeedItem item, Action action) {
- this(item.getFeed().getDownload_url(), item.getItemIdentifier(), action);
+ this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action);
}
public Builder(String podcast, String episode, Action action) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
index cfdd0c5d6..c3c6ce8c5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java
@@ -173,7 +173,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_REMOVED, removedFeeds);
}
feedListLock.unlock();
- GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
+ GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static void addRemovedFeed(String feed) {
@@ -186,7 +186,7 @@ public class GpodnetPreferences {
writePreference(PREF_SYNC_ADDED, addedFeeds);
}
feedListLock.unlock();
- GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
+ GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static Set<String> getAddedFeedsCopy() {
@@ -225,6 +225,7 @@ public class GpodnetPreferences {
ensurePreferencesLoaded();
queuedEpisodeActions.add(action);
writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions));
+ GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance());
}
public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
index 022c03ca7..594241573 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java
@@ -16,11 +16,12 @@ import org.json.JSONException;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
@@ -33,35 +34,52 @@ import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
*/
public class UserPreferences implements
SharedPreferences.OnSharedPreferenceChangeListener {
+
public static final String IMPORT_DIR = "import/";
+
private static final String TAG = "UserPreferences";
+ // User Infercasce
+ public static final String PREF_THEME = "prefTheme";
+ public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
+ public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
+ public static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
+
+ // Queue
+ public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
+
+ // Playback
public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
- public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly";
- public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
- public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
- public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
- public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes";
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
+ private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
+ public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
+ public static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
+
+ // Network
+ public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
+ public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
+ public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
+ public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
+ public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
+ public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
+ public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
+ public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
+
+ // Services
public static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
public static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold";
- public static final String PREF_THEME = "prefTheme";
+
+ // Other
public static final String PREF_DATA_FOLDER = "prefDataFolder";
- public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
- public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
- public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
- private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
- public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
- private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
- private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
- public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
- private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs";
- private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
- private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
- public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
+
+ // Mediaplayer
+ public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
+ private static final String PREF_REWIND_SECS = "prefRewindSecs";
+ public static final String PREF_QUEUE_LOCKED = "prefQueueLocked";
// TODO: Make this value configurable
private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f;
@@ -71,32 +89,45 @@ public class UserPreferences implements
private static UserPreferences instance;
private final Context context;
- // Preferences
+ // User Interface
+ private int theme;
+ private List<String> hiddenDrawerItems;
+ private int notifyPriority;
+ private boolean persistNotify;
+
+ // Queue
+ private boolean enqueueAtFront;
+
+ // Playback
private boolean pauseOnHeadsetDisconnect;
private boolean unpauseOnHeadsetReconnect;
private boolean followQueue;
- private boolean downloadMediaOnWifiOnly;
- private long updateInterval;
- private boolean allowMobileUpdate;
- private boolean displayOnlyEpisodes;
private boolean autoDelete;
private int smartMarkAsPlayedSecs;
- private boolean autoFlattr;
- private float autoFlattrPlayedDurationThreshold;
- private int theme;
+ private String[] playbackSpeedArray;
+ private boolean pauseForFocusLoss;
+ private boolean resumeAfterCall;
+
+ // Network
+ private long updateInterval;
+ private boolean allowMobileUpdate;
+ private int parallelDownloads;
+ private int episodeCacheSize;
private boolean enableAutodownload;
- private boolean enableAutodownloadWifiFilter;
private boolean enableAutodownloadOnBattery;
+ private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
- private int parallelDownloads;
- private int episodeCacheSize;
+
+ // Services
+ private boolean autoFlattr;
+ private float autoFlattrPlayedDurationThreshold;
+
+ // Settings somewhere in the GUI
private String playbackSpeed;
- private String[] playbackSpeedArray;
- private boolean pauseForFocusLoss;
- private int seekDeltaSecs;
- private boolean isFreshInstall;
- private int notifyPriority;
- private boolean persistNotify;
+ private int fastForwardSecs;
+ private int rewindSecs;
+ private boolean queueLocked;
+
private UserPreferences(Context context) {
this.context = context;
@@ -109,8 +140,7 @@ public class UserPreferences implements
* @throws IllegalArgumentException if context is null
*/
public static void createInstance(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Creating new instance of UserPreferences");
+ Log.d(TAG, "Creating new instance of UserPreferences");
Validate.notNull(context);
instance = new UserPreferences(context);
@@ -123,48 +153,54 @@ public class UserPreferences implements
}
private void loadPreferences() {
- SharedPreferences sp = PreferenceManager
- .getDefaultSharedPreferences(context);
- EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger(
- R.integer.episode_cache_size_unlimited);
- pauseOnHeadsetDisconnect = sp.getBoolean(
- PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- unpauseOnHeadsetReconnect = sp.getBoolean(
- PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // User Interface
+ theme = readThemeValue(sp.getString(PREF_THEME, "0"));
+ if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
+ notifyPriority = NotificationCompat.PRIORITY_MAX;
+ } else {
+ notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
+ }
+ hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
+ persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+
+ // Queue
+ enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
+
+ // Playback
+ pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
+ unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
- downloadMediaOnWifiOnly = sp.getBoolean(
- PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true);
- updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL,
- "0"));
- allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
- displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false);
autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
- autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
- theme = readThemeValue(sp.getString(PREF_THEME, "0"));
- enableAutodownloadWifiFilter = sp.getBoolean(
- PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
+ pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
+
+ // Network
+ updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
+ allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
- PREF_EPISODE_CACHE_SIZE, "20"));
+ EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger(
+ R.integer.episode_cache_size_unlimited);
+ episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
+ enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
+ autodownloadSelectedNetworks = StringUtils.split(
+ sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+
+ // Services
+ autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
+ autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
+ PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
+
+ // MediaPlayer
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
- PREF_PLAYBACK_SPEED_ARRAY, null));
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
- seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30"));
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- }
- else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
+ rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
+ queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
}
private int readThemeValue(String valueFromPrefs) {
@@ -214,8 +250,7 @@ public class UserPreferences implements
selectedSpeeds[i] = jsonArray.getString(i);
}
} catch (JSONException e) {
- Log.e(TAG,
- "Got JSON error when trying to get speeds from JSONArray");
+ Log.e(TAG, "Got JSON error when trying to get speeds from JSONArray");
e.printStackTrace();
}
}
@@ -224,45 +259,78 @@ public class UserPreferences implements
private static void instanceAvailable() {
if (instance == null) {
- throw new IllegalStateException(
- "UserPreferences was used before being set up");
+ throw new IllegalStateException("UserPreferences was used before being set up");
}
}
- public static boolean isPauseOnHeadsetDisconnect() {
+ /**
+ * Returns theme as R.style value
+ *
+ * @return R.style.Theme_AntennaPod_Light or R.style.Theme_AntennaPod_Dark
+ */
+ public static int getTheme() {
instanceAvailable();
- return instance.pauseOnHeadsetDisconnect;
+ return instance.theme;
}
- public static boolean isUnpauseOnHeadsetReconnect() {
+ public static int getNoTitleTheme() {
+ int theme = getTheme();
+ if (theme == R.style.Theme_AntennaPod_Dark) {
+ return R.style.Theme_AntennaPod_Dark_NoTitle;
+ } else {
+ return R.style.Theme_AntennaPod_Light_NoTitle;
+ }
+ }
+
+ public static List<String> getHiddenDrawerItems() {
instanceAvailable();
- return instance.unpauseOnHeadsetReconnect;
+ return new ArrayList<String>(instance.hiddenDrawerItems);
}
- public static boolean isFollowQueue() {
+ /**
+ * Returns notification priority.
+ *
+ * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT
+ */
+ public static int getNotifyPriority() {
instanceAvailable();
- return instance.followQueue;
+ return instance.notifyPriority;
+ }
+
+ /**
+ * Returns true if notifications are persistent
+ *
+ * @return {@code true} if notifications are persistent, {@code false} otherwise
+ */
+ public static boolean isPersistNotify() {
+ instanceAvailable();
+ return instance.persistNotify;
}
- public static boolean isDownloadMediaOnWifiOnly() {
+ /**
+ * Returns {@code true} if new queue elements are added to the front
+ *
+ * @return {@code true} if new queue elements are added to the front; {@code false} otherwise
+ */
+ public static boolean enqueueAtFront() {
instanceAvailable();
- return instance.downloadMediaOnWifiOnly;
+ return instance.enqueueAtFront;
}
- public static long getUpdateInterval() {
+ public static boolean isPauseOnHeadsetDisconnect() {
instanceAvailable();
- return instance.updateInterval;
+ return instance.pauseOnHeadsetDisconnect;
}
- public static boolean isAllowMobileUpdate() {
+ public static boolean isUnpauseOnHeadsetReconnect() {
instanceAvailable();
- return instance.allowMobileUpdate;
+ return instance.unpauseOnHeadsetReconnect;
}
- public static boolean isDisplayOnlyEpisodes() {
+
+ public static boolean isFollowQueue() {
instanceAvailable();
- //return instance.displayOnlyEpisodes;
- return false;
+ return instance.followQueue;
}
public static boolean isAutoDelete() {
@@ -271,7 +339,7 @@ public class UserPreferences implements
}
public static int getSmartMarkAsPlayedSecs() {
- instanceAvailable();;
+ instanceAvailable();
return instance.smartMarkAsPlayedSecs;
}
@@ -280,48 +348,29 @@ public class UserPreferences implements
return instance.autoFlattr;
}
- public static int getNotifyPriority() {
- instanceAvailable();
- return instance.notifyPriority;
- }
-
- public static boolean isPersistNotify() {
+ public static String getPlaybackSpeed() {
instanceAvailable();
- return instance.persistNotify;
+ return instance.playbackSpeed;
}
-
- /**
- * Returns the time after which an episode should be auto-flattr'd in percent of the episode's
- * duration.
- */
- public static float getAutoFlattrPlayedDurationThreshold() {
+ public static String[] getPlaybackSpeedArray() {
instanceAvailable();
- return instance.autoFlattrPlayedDurationThreshold;
+ return instance.playbackSpeedArray;
}
- public static int getTheme() {
+ public static boolean shouldPauseForFocusLoss() {
instanceAvailable();
- return instance.theme;
- }
-
- public static int getNoTitleTheme() {
- int theme = getTheme();
- if (theme == R.style.Theme_AntennaPod_Dark) {
- return R.style.Theme_AntennaPod_Dark_NoTitle;
- } else {
- return R.style.Theme_AntennaPod_Light_NoTitle;
- }
+ return instance.pauseForFocusLoss;
}
- public static boolean isEnableAutodownloadWifiFilter() {
+ public static long getUpdateInterval() {
instanceAvailable();
- return instance.enableAutodownloadWifiFilter;
+ return instance.updateInterval;
}
- public static String[] getAutodownloadSelectedNetworks() {
+ public static boolean isAllowMobileUpdate() {
instanceAvailable();
- return instance.autodownloadSelectedNetworks;
+ return instance.allowMobileUpdate;
}
public static int getParallelDownloads() {
@@ -333,21 +382,6 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
- public static String getPlaybackSpeed() {
- instanceAvailable();
- return instance.playbackSpeed;
- }
-
- public static String[] getPlaybackSpeedArray() {
- instanceAvailable();
- return instance.playbackSpeedArray;
- }
-
- public static int getSeekDeltaMs() {
- instanceAvailable();
- return 1000 * instance.seekDeltaSecs;
- }
-
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@@ -368,88 +402,163 @@ public class UserPreferences implements
return instance.enableAutodownloadOnBattery;
}
- public static boolean shouldPauseForFocusLoss() {
+ public static boolean isEnableAutodownloadWifiFilter() {
instanceAvailable();
- return instance.pauseForFocusLoss;
+ return instance.enableAutodownloadWifiFilter;
+ }
+
+ public static int getFastFowardSecs() {
+ instanceAvailable();
+ return instance.fastForwardSecs;
+ }
+
+ public static int getRewindSecs() {
+ instanceAvailable();
+ return instance.rewindSecs;
+ }
+
+
+ /**
+ * Returns the time after which an episode should be auto-flattr'd in percent of the episode's
+ * duration.
+ */
+ public static float getAutoFlattrPlayedDurationThreshold() {
+ instanceAvailable();
+ return instance.autoFlattrPlayedDurationThreshold;
+ }
+
+ public static String[] getAutodownloadSelectedNetworks() {
+ instanceAvailable();
+ return instance.autodownloadSelectedNetworks;
}
- public static boolean isFreshInstall() {
+ public static boolean shouldResumeAfterCall() {
instanceAvailable();
- return instance.isFreshInstall;
+ return instance.resumeAfterCall;
+ }
+
+ public static boolean isQueueLocked() {
+ instanceAvailable();
+ return instance.queueLocked;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
Log.d(TAG, "Registered change of user preferences. Key: " + key);
-
- if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) {
- downloadMediaOnWifiOnly = sp.getBoolean(
- PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true);
-
- } else if (key.equals(PREF_MOBILE_UPDATE)) {
- allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
-
- } else if (key.equals(PREF_FOLLOW_QUEUE)) {
- followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
-
- } else if (key.equals(PREF_UPDATE_INTERVAL)) {
- updateInterval = readUpdateInterval(sp.getString(
- PREF_UPDATE_INTERVAL, "0"));
- ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval);
- } else if (key.equals(PREF_AUTO_DELETE)) {
- autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
- } else if (key.equals(PREF_SMART_MARK_AS_PLAYED_SECS)) {
- smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
- } else if (key.equals(PREF_AUTO_FLATTR)) {
- autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
- } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) {
- displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES,
- false);
- } else if (key.equals(PREF_THEME)) {
- theme = readThemeValue(sp.getString(PREF_THEME, ""));
- } else if (key.equals(PREF_ENABLE_AUTODL_WIFI_FILTER)) {
- enableAutodownloadWifiFilter = sp.getBoolean(
- PREF_ENABLE_AUTODL_WIFI_FILTER, false);
- } else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) {
- autodownloadSelectedNetworks = StringUtils.split(
- sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
- } else if(key.equals(PREF_PARALLEL_DOWNLOADS)) {
- parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
- } else if (key.equals(PREF_EPISODE_CACHE_SIZE)) {
- episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
- PREF_EPISODE_CACHE_SIZE, "20"));
- } else if (key.equals(PREF_ENABLE_AUTODL)) {
- enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
- } else if (key.equals(PREF_ENABLE_AUTODL_ON_BATTERY)) {
- enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
- } else if (key.equals(PREF_PLAYBACK_SPEED)) {
- playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
- } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
- playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
- PREF_PLAYBACK_SPEED_ARRAY, null));
- } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
- pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
- } else if (key.equals(PREF_SEEK_DELTA_SECS)) {
- seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30"));
- } else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) {
- pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
- } else if (key.equals(PREF_UNPAUSE_ON_HEADSET_RECONNECT)) {
- unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
- } else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) {
- autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
- PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
- } else if (key.equals(PREF_EXPANDED_NOTIFICATION)) {
- if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
- notifyPriority = NotificationCompat.PRIORITY_MAX;
- }
- else {
- notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
- }
- } else if (key.equals(PREF_PERSISTENT_NOTIFICATION)) {
- persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ switch(key) {
+ // User Interface
+ case PREF_THEME:
+ theme = readThemeValue(sp.getString(PREF_THEME, ""));
+ break;
+ case PREF_HIDDEN_DRAWER_ITEMS:
+ hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ','));
+ break;
+ case PREF_EXPANDED_NOTIFICATION:
+ if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
+ notifyPriority = NotificationCompat.PRIORITY_MAX;
+ } else {
+ notifyPriority = NotificationCompat.PRIORITY_DEFAULT;
+ }
+ break;
+ case PREF_PERSISTENT_NOTIFICATION:
+ persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false);
+ break;
+ // Queue
+ case PREF_QUEUE_ADD_TO_FRONT:
+ enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
+ break;
+ // Playback
+ case PREF_PAUSE_ON_HEADSET_DISCONNECT:
+ pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
+ break;
+ case PREF_UNPAUSE_ON_HEADSET_RECONNECT:
+ unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
+ break;
+ case PREF_FOLLOW_QUEUE:
+ followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
+ break;
+ case PREF_AUTO_DELETE:
+ autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false);
+ break;
+ case PREF_SMART_MARK_AS_PLAYED_SECS:
+ smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
+ break;
+ case PREF_PLAYBACK_SPEED_ARRAY:
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
+ break;
+ case PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS:
+ pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
+ break;
+ case PREF_RESUME_AFTER_CALL:
+ resumeAfterCall = sp.getBoolean(PREF_RESUME_AFTER_CALL, true);
+ break;
+ // Network
+ case PREF_UPDATE_INTERVAL:
+ updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0"));
+ ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval);
+ break;
+ case PREF_MOBILE_UPDATE:
+ allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false);
+ break;
+ case PREF_PARALLEL_DOWNLOADS:
+ parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
+ break;
+ case PREF_EPISODE_CACHE_SIZE:
+ episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20"));
+ break;
+ case PREF_ENABLE_AUTODL:
+ enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ break;
+ case PREF_ENABLE_AUTODL_ON_BATTERY:
+ enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
+ break;
+ case PREF_ENABLE_AUTODL_WIFI_FILTER:
+ enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
+ break;
+ case PREF_AUTODL_SELECTED_NETWORKS:
+ autodownloadSelectedNetworks = StringUtils.split(
+ sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
+ break;
+ // Services
+ case PREF_AUTO_FLATTR:
+ autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false);
+ break;
+ case PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD:
+ autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
+ PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);
+ break;
+ // Mediaplayer
+ case PREF_PLAYBACK_SPEED:
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ break;
+ case PREF_FAST_FORWARD_SECS:
+ fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30);
+ break;
+ case PREF_REWIND_SECS:
+ rewindSecs = sp.getInt(PREF_REWIND_SECS, 30);
+ break;
+ case PREF_QUEUE_LOCKED:
+ queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false);
+ break;
+ default:
+ Log.w(TAG, "Unhandled key: " + key);
}
}
+ public static void setPrefFastForwardSecs(int secs) {
+ Log.d(TAG, "setPrefFastForwardSecs(" + secs +")");
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
+ editor.putInt(PREF_FAST_FORWARD_SECS, secs);
+ editor.commit();
+ }
+
+ public static void setPrefRewindSecs(int secs) {
+ Log.d(TAG, "setPrefRewindSecs(" + secs +")");
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit();
+ editor.putInt(PREF_REWIND_SECS, secs);
+ editor.commit();
+ }
+
public static void setPlaybackSpeed(String speed) {
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED, speed).apply();
@@ -509,6 +618,26 @@ public class UserPreferences implements
instance.autoFlattrPlayedDurationThreshold = autoFlattrThreshold;
}
+ public static void setHiddenDrawerItems(Context context, List<String> items) {
+ instanceAvailable();
+ instance.hiddenDrawerItems = items;
+ String str = StringUtils.join(items, ',');
+ PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext())
+ .edit()
+ .putString(PREF_HIDDEN_DRAWER_ITEMS, str)
+ .commit();
+ }
+
+ public static void setQueueLocked(boolean locked) {
+ instanceAvailable();
+ instance.queueLocked = locked;
+ PreferenceManager.getDefaultSharedPreferences(instance.context)
+ .edit()
+ .putBoolean(PREF_QUEUE_LOCKED, locked)
+ .commit();
+ }
+
+
/**
* Return the folder where the app stores all of its data. This method will
* return the standard data folder if none has been set by the user.
@@ -524,8 +653,7 @@ public class UserPreferences implements
.getDefaultSharedPreferences(context.getApplicationContext());
String strDir = prefs.getString(PREF_DATA_FOLDER, null);
if (strDir == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Using default data folder");
+ Log.d(TAG, "Using default data folder");
return context.getExternalFilesDir(type);
} else {
File dataDir = new File(strDir);
@@ -556,21 +684,18 @@ public class UserPreferences implements
if (!typeDir.exists()) {
if (dataDir.canWrite()) {
if (!typeDir.mkdir()) {
- Log.e(TAG, "Could not create data folder named "
- + type);
+ Log.e(TAG, "Could not create data folder named " + type);
return null;
}
}
}
return typeDir;
}
-
}
}
public static void setDataFolder(String dir) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Result from DirectoryChooser: " + dir);
+ Log.d(TAG, "Result from DirectoryChooser: " + dir);
instanceAvailable();
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(instance.context);
@@ -593,8 +718,7 @@ public class UserPreferences implements
Log.e(TAG, "Could not create .nomedia file");
e.printStackTrace();
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, ".nomedia file created");
+ Log.d(TAG, ".nomedia file created");
}
}
@@ -607,16 +731,13 @@ public class UserPreferences implements
IMPORT_DIR);
if (importDir != null) {
if (importDir.exists()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Import directory already exists");
+ Log.d(TAG, "Import directory already exists");
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Creating import directory");
+ Log.d(TAG, "Creating import directory");
importDir.mkdir();
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Could not access external storage.");
+ Log.d(TAG, "Could not access external storage.");
}
}
@@ -625,8 +746,7 @@ public class UserPreferences implements
*/
public static void restartUpdateAlarm(long triggerAtMillis, long intervalMillis) {
instanceAvailable();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Restarting update alarm.");
+ Log.d(TAG, "Restarting update alarm.");
AlarmManager alarmManager = (AlarmManager) instance.context
.getSystemService(Context.ALARM_SERVICE);
PendingIntent updateIntent = PendingIntent.getBroadcast(
@@ -635,11 +755,9 @@ public class UserPreferences implements
if (intervalMillis != 0) {
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis,
updateIntent);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Changed alarm to new interval");
+ Log.d(TAG, "Changed alarm to new interval");
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Automatic update was deactivated");
+ Log.d(TAG, "Automatic update was deactivated");
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
index 95dc4fb07..d37f97a5f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java
@@ -3,41 +3,26 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.util.NetworkUtils;
/**
* Refreshes all feeds when it receives an intent
*/
public class FeedUpdateReceiver extends BroadcastReceiver {
+
private static final String TAG = "FeedUpdateReceiver";
@Override
public void onReceive(Context context, Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received intent");
- boolean mobileUpdate = UserPreferences.isAllowMobileUpdate();
- if (mobileUpdate || connectedToWifi(context)) {
+ Log.d(TAG, "Received intent");
+ if (NetworkUtils.isDownloadAllowed(context)) {
DBTasks.refreshExpiredFeeds(context);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Blocking automatic update: no wifi available / no mobile updates allowed");
+ Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
}
}
- private boolean connectedToWifi(Context context) {
- ConnectivityManager connManager = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo mWifi = connManager
- .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
-
- return mWifi.isConnected();
- }
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
index e39197387..3f2222f42 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
@@ -12,7 +12,6 @@ import android.util.Log;
import android.util.Pair;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -22,6 +21,7 @@ import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException;
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
@@ -50,17 +50,38 @@ public class GpodnetSyncService extends Service {
public static final String ARG_ACTION = "action";
public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
+ public static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions";
+ public static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS";
private GpodnetService service;
+ private boolean syncSubscriptions = false;
+ private boolean syncActions = false;
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
- if (action != null && action.equals(ACTION_SYNC)) {
- Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
- syncWaiterThread.restart();
+ if (action != null) {
+ switch(action) {
+ case ACTION_SYNC:
+ syncSubscriptions = true;
+ syncActions = true;
+ break;
+ case ACTION_SYNC_SUBSCRIPTIONS:
+ syncSubscriptions = true;
+ break;
+ case ACTION_SYNC_ACTIONS:
+ syncActions = true;
+ break;
+ default:
+ Log.e(TAG, "Received invalid intent: action argument is invalid");
+ }
+ if(syncSubscriptions || syncActions) {
+ Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
+ syncWaiterThread.restart();
+ }
} else {
- Log.e(TAG, "Received invalid intent: action argument is null or invalid");
+ Log.e(TAG, "Received invalid intent: action argument is null");
}
return START_FLAG_REDELIVERY;
}
@@ -91,8 +112,14 @@ public class GpodnetSyncService extends Service {
stopSelf();
return;
}
- syncSubscriptionChanges();
- syncEpisodeActions();
+ if(syncSubscriptions) {
+ syncSubscriptionChanges();
+ syncSubscriptions = false;
+ }
+ if(syncActions) {
+ syncEpisodeActions();
+ syncActions = false;
+ }
stopSelf();
}
@@ -100,37 +127,36 @@ public class GpodnetSyncService extends Service {
final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp();
try {
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
+ Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy();
+ Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy();
GpodnetService service = tryLogin();
// first sync: download all subscriptions...
GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(),
GpodnetPreferences.getDeviceID(), timestamp);
- long lastUpdate = subscriptionChanges.getTimestamp();
+ long newTimeStamp = subscriptionChanges.getTimestamp();
Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
- processSubscriptionChanges(localSubscriptions, subscriptionChanges);
-
- Collection<String> added;
- Collection<String> removed;
- if (timestamp == 0) {
- added = localSubscriptions;
- GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy());
- removed = Collections.emptyList();
- } else {
- added = GpodnetPreferences.getAddedFeedsCopy();
- removed = GpodnetPreferences.getRemovedFeedsCopy();
+ processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges);
+
+ if(timestamp == 0) {
+ // this is this apps first sync with gpodder:
+ // only submit changes gpodder has not just sent us
+ localAdded = localSubscriptions;
+ localAdded.removeAll(subscriptionChanges.getAdded());
+ localRemoved.removeAll(subscriptionChanges.getRemoved());
}
- if(added.size() > 0 || removed.size() > 0) {
+ if(localAdded.size() > 0 || localRemoved.size() > 0) {
Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
- added, removed));
+ localAdded, localRemoved));
GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(),
- GpodnetPreferences.getDeviceID(), added, removed);
- lastUpdate = uploadResponse.timestamp;
+ GpodnetPreferences.getDeviceID(), localAdded, localRemoved);
+ newTimeStamp = uploadResponse.timestamp;
Log.d(TAG, "Upload changes response: " + uploadResponse);
- GpodnetPreferences.removeAddedFeeds(added);
- GpodnetPreferences.removeRemovedFeeds(removed);
+ GpodnetPreferences.removeAddedFeeds(localAdded);
+ GpodnetPreferences.removeRemovedFeeds(localRemoved);
}
- GpodnetPreferences.setLastSubscriptionSyncTimestamp(lastUpdate);
+ GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp);
clearErrorNotifications();
} catch (GpodnetServiceException e) {
e.printStackTrace();
@@ -140,6 +166,27 @@ public class GpodnetSyncService extends Service {
}
}
+ private synchronized void processSubscriptionChanges(List<String> localSubscriptions,
+ Collection<String> localAdded,
+ Collection<String> localRemoved,
+ GpodnetSubscriptionChange changes) throws DownloadRequestException {
+ // local changes are always superior to remote changes!
+ // add subscription if (1) not already subscribed and (2) not just unsubscribed
+ for (String downloadUrl : changes.getAdded()) {
+ if (false == localSubscriptions.contains(downloadUrl) &&
+ false == localRemoved.contains(downloadUrl)) {
+ Feed feed = new Feed(downloadUrl, new Date(0));
+ DownloadRequester.getInstance().downloadFeed(this, feed);
+ }
+ }
+ // remove subscription if not just subscribed (again)
+ for (String downloadUrl : changes.getRemoved()) {
+ if(false == localAdded.contains(downloadUrl)) {
+ DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
+ }
+ }
+ }
+
private synchronized void syncEpisodeActions() {
final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp();
Log.d(TAG, "last episode actions sync timestamp: " + timestamp);
@@ -173,25 +220,13 @@ public class GpodnetSyncService extends Service {
}
}
- private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
- for (String downloadUrl : changes.getAdded()) {
- if (!localSubscriptions.contains(downloadUrl)) {
- Feed feed = new Feed(downloadUrl, new Date());
- DownloadRequester.getInstance().downloadFeed(this, feed);
- }
- }
- for (String downloadUrl : changes.getRemoved()) {
- DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
- }
- }
- private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions, List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException {
+ private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions,
+ List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException {
if(remoteActions.size() == 0) {
return;
}
Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
- Map<Pair<String, String>, GpodnetEpisodeAction> remoteMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
- // make sure more recent local actions are not overwritten by older remote actions
for(GpodnetEpisodeAction action : localActions) {
Pair key = new Pair(action.getPodcast(), action.getEpisode());
GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key);
@@ -201,6 +236,9 @@ public class GpodnetSyncService extends Service {
localMostRecentPlayAction.put(key, action);
}
}
+
+ // make sure more recent local actions are not overwritten by older remote actions
+ Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>();
for (GpodnetEpisodeAction action : remoteActions) {
switch (action.getAction()) {
case NEW:
@@ -218,11 +256,11 @@ public class GpodnetSyncService extends Service {
GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key);
if(localMostRecent == null ||
localMostRecent.getTimestamp().before(action.getTimestamp())) {
- GpodnetEpisodeAction mostRecent = remoteMostRecentPlayAction.get(key);
+ GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
if (mostRecent == null) {
- remoteMostRecentPlayAction.put(key, action);
+ mostRecentPlayAction.put(key, action);
} else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
- remoteMostRecentPlayAction.put(key, action);
+ mostRecentPlayAction.put(key, action);
}
}
break;
@@ -231,10 +269,12 @@ public class GpodnetSyncService extends Service {
break;
}
}
- for (GpodnetEpisodeAction action : remoteMostRecentPlayAction.values()) {
+ for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) {
FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode());
if (playItem != null) {
- playItem.getMedia().setPosition(action.getPosition() * 1000);
+ FeedMedia media = playItem.getMedia();
+ media.setPosition(action.getPosition() * 1000);
+ DBWriter.setFeedMedia(this, media);
if(playItem.getMedia().hasAlmostEnded()) {
DBWriter.markItemRead(this, playItem, true, true);
DBWriter.addItemToPlaybackHistory(this, playItem.getMedia());
@@ -342,4 +382,20 @@ public class GpodnetSyncService extends Service {
context.startService(intent);
}
}
+
+ public static void sendSyncSubscriptionsIntent(Context context) {
+ if (GpodnetPreferences.loggedIn()) {
+ Intent intent = new Intent(context, GpodnetSyncService.class);
+ intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
+ context.startService(intent);
+ }
+ }
+
+ public static void sendSyncActionsIntent(Context context) {
+ if (GpodnetPreferences.loggedIn()) {
+ Intent intent = new Intent(context, GpodnetSyncService.class);
+ intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
+ context.startService(intent);
+ }
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
index d5f17c099..e7b226eca 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java
@@ -520,9 +520,7 @@ public class DownloadService extends Service {
/**
* Creates a notification at the end of the service lifecycle to notify the
* user about the number of completed downloads. A report will only be
- * created if the number of successfully downloaded feeds is bigger than 1
- * or if there is at least one failed download which is not an image or if
- * there is at least one downloaded media file.
+ * created if there is at least one failed download excluding images
*/
private void updateReport() {
// check if report should be created
@@ -550,16 +548,16 @@ public class DownloadService extends Service {
.setTicker(
getString(R.string.download_report_title))
.setContentTitle(
- getString(R.string.download_report_title))
+ getString(R.string.download_report_content_title))
.setContentText(
String.format(
getString(R.string.download_report_content),
successfulDownloads, failedDownloads)
)
- .setSmallIcon(R.drawable.stat_notify_sync)
+ .setSmallIcon(R.drawable.stat_notify_sync_error)
.setLargeIcon(
BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync)
+ R.drawable.stat_notify_sync_error)
)
.setContentIntent(
ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this)
@@ -939,6 +937,13 @@ public class DownloadService extends Service {
if (successful) {
+ // we create a 'successful' download log if the feed's last refresh failed
+ List<DownloadStatus> log = DBReader.getFeedDownloadLog(DownloadService.this, feed);
+ if(log.size() > 0 && log.get(0).isSuccessful() == false) {
+ saveDownloadStatus(new DownloadStatus(feed,
+ feed.getHumanReadableIdentifier(), DownloadError.SUCCESS, successful,
+ reasonDetailed));
+ }
return Pair.create(request, result);
} else {
numberOfDownloads.decrementAndGet();
@@ -1055,9 +1060,11 @@ public class DownloadService extends Service {
@Override
public void run() {
- if (request.isDeleteOnFailure()) {
+ if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ DBWriter.setFeedLastUpdateFailed(DownloadService.this, request.getFeedfileId(), true);
+ } else if (request.isDeleteOnFailure()) {
Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
- } else {
+ } else {
File dest = new File(request.getDestination());
if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
Log.d(TAG, "File has been partially downloaded. Writing file url");
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
index 7abb6df5e..ac0fe8036 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.service.download;
import android.util.Log;
-import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
@@ -18,19 +17,20 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Date;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URIUtil;
+import okio.ByteString;
public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader";
@@ -81,11 +81,12 @@ public class HttpDownloader extends Downloader {
if (userInfo != null) {
String[] parts = userInfo.split(":");
if (parts.length == 2) {
- String credentials = Credentials.basic(parts[0], parts[1]);
+ String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
} else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
- String credentials = Credentials.basic(request.getUsername(), request.getPassword());
+ String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
+ "ISO-8859-1");
httpReq.header("Authorization", credentials);
}
@@ -99,13 +100,29 @@ public class HttpDownloader extends Downloader {
Response response = httpClient.newCall(httpReq.build()).execute();
responseBody = response.body();
-
String contentEncodingHeader = response.header("Content-Encoding");
-
- final boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
-
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Response code is " + response.code());
+ boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+
+ Log.d(TAG, "Response code is " + response.code());
+
+ if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoding");
+ if (userInfo != null) {
+ String[] parts = userInfo.split(":");
+ if (parts.length == 2) {
+ String credentials = encodeCredentials(parts[0], parts[1], "UTF-8");
+ httpReq.header("Authorization", credentials);
+ }
+ } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) {
+ String credentials = encodeCredentials(request.getUsername(), request.getPassword(),
+ "UTF-8");
+ httpReq.header("Authorization", credentials);
+ }
+ response = httpClient.newCall(httpReq.build()).execute();
+ responseBody = response.body();
+ contentEncodingHeader = response.header("Content-Encoding");
+ isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip");
+ }
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled");
@@ -151,22 +168,18 @@ public class HttpDownloader extends Downloader {
out = new RandomAccessFile(destination, "rw");
}
-
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
request.setStatusMsg(R.string.download_running);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Getting size of download");
+ Log.d(TAG, "Getting size of download");
request.setSize(responseBody.contentLength() + request.getSoFar());
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Size is " + request.getSize());
+ Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Free space is " + freeSpace);
+ Log.d(TAG, "Free space is " + freeSpace);
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
&& request.getSize() > freeSpace) {
@@ -174,8 +187,7 @@ public class HttpDownloader extends Downloader {
return;
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting download");
+ Log.d(TAG, "Starting download");
while (!cancelled
&& (count = connection.read(buffer)) != -1) {
out.write(buffer, 0, count);
@@ -226,15 +238,12 @@ public class HttpDownloader extends Downloader {
}
private void onSuccess() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Download was successful");
+ Log.d(TAG, "Download was successful");
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "Download failed");
- }
+ Log.d(TAG, "Download failed");
result.setFailed(reason, reasonDetailed);
if (request.isDeleteOnFailure()) {
cleanup();
@@ -242,8 +251,7 @@ public class HttpDownloader extends Downloader {
}
private void onCancelled() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Download was cancelled");
+ Log.d(TAG, "Download was cancelled");
result.setCancelled();
cleanup();
}
@@ -256,14 +264,23 @@ public class HttpDownloader extends Downloader {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ rc);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "cleanup() didn't delete file: does not exist.");
+ Log.d(TAG, "cleanup() didn't delete file: does not exist.");
}
}
}
+ public static String encodeCredentials(String username, String password, String charset) {
+ try {
+ String credentials = username + ":" + password;
+ byte[] bytes = credentials.getBytes(charset);
+ String encoded = ByteString.of(bytes).base64();
+ return "Basic " + encoded;
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ }
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index 43c345fec..3f6769ee4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -254,6 +254,8 @@ public class PlaybackService extends Service {
unregisterReceiver(bluetoothStateUpdated);
unregisterReceiver(audioBecomingNoisy);
unregisterReceiver(skipCurrentEpisodeReceiver);
+ unregisterReceiver(pausePlayCurrentEpisodeReceiver);
+ unregisterReceiver(pauseResumeCurrentEpisodeReceiver);
mediaPlayer.shutdown();
taskManager.shutdown();
}
@@ -344,11 +346,11 @@ public class PlaybackService extends Service {
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- mediaPlayer.seekDelta(UserPreferences.getSeekDeltaMs());
+ mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000);
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
- mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs());
+ mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000);
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
if (status == PlayerStatus.PLAYING) {
@@ -481,9 +483,8 @@ public class PlaybackService extends Service {
}
Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED);
- statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
+ // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
sendBroadcast(statusUpdate);
- sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED));
updateWidget();
refreshRemoteControlClientState(newInfo);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
@@ -626,7 +627,6 @@ public class PlaybackService extends Service {
prepareImmediately = startWhenPrepared = true;
} else {
Log.d(TAG, "No more episodes available to play");
-
prepareImmediately = startWhenPrepared = false;
stopForeground(true);
stopWidgetUpdater();
@@ -933,7 +933,6 @@ public class PlaybackService extends Service {
// Auto flattr
if (isAutoFlattrable(media) &&
(media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
-
Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration())
+ " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration));
DBTasks.flattrItemIfLoggedIn(this, item);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index f0acc3531..243ee78e4 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -40,7 +40,7 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer;
* Manages the MediaPlayer object of the PlaybackService.
*/
public class PlaybackServiceMediaPlayer {
- public static final String TAG = "PlaybackServiceMediaPlayer";
+ public static final String TAG = "PlaybackSvcMediaPlayer";
/**
* Return value of some PSMP methods if the method call failed.
@@ -449,15 +449,15 @@ public class PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
- if (stream) {
- // statusBeforeSeeking = playerStatus;
- // setPlayerStatus(PlayerStatus.SEEKING, media);
+ if (!stream) {
+ statusBeforeSeeking = playerStatus;
+ setPlayerStatus(PlayerStatus.SEEKING, media);
}
mediaPlayer.seekTo(t);
} else if (playerStatus == PlayerStatus.INITIALIZED) {
media.setPosition(t);
- startWhenPrepared.set(true);
+ startWhenPrepared.set(false);
prepare();
}
playerLock.unlock();
@@ -534,20 +534,20 @@ public class PlaybackServiceMediaPlayer {
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
*/
public int getPosition() {
- if (!playerLock.tryLock()) {
- return INVALID_TIME;
- }
+ playerLock.lock();
int retVal = INVALID_TIME;
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
- || playerStatus == PlayerStatus.PREPARED) {
+ || playerStatus == PlayerStatus.PREPARED
+ || playerStatus == PlayerStatus.SEEKING) {
retVal = mediaPlayer.getCurrentPosition();
} else if (media != null && media.getPosition() > 0) {
retVal = media.getPosition();
}
playerLock.unlock();
+ Log.d(TAG, "getPosition() -> " + retVal);
return retVal;
}
@@ -735,6 +735,7 @@ public class PlaybackServiceMediaPlayer {
int state;
if (playerStatus != null) {
+ Log.d(TAG, "playerStatus: " + playerStatus.toString());
switch (playerStatus) {
case PLAYING:
state = PlaybackStateCompat.STATE_PLAYING;
@@ -798,10 +799,10 @@ public class PlaybackServiceMediaPlayer {
// If there is an incoming call, playback should be paused permanently
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final int callState = (tm != null) ? tm.getCallState() : 0;
- Log.d(TAG, "Call state: " + callState);
Log.i(TAG, "Call state:" + callState);
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS || callState != TelephonyManager.CALL_STATE_IDLE) {
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) {
Log.d(TAG, "Lost audio focus");
pause(true, false);
callback.shouldStop();
@@ -1095,13 +1096,13 @@ public class PlaybackServiceMediaPlayer {
@Override
public void onFastForward() {
super.onFastForward();
- seekDelta(UserPreferences.getSeekDeltaMs());
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
}
@Override
public void onRewind() {
super.onRewind();
- seekDelta(-UserPreferences.getSeekDeltaMs());
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index cde03adea..fc73c9446 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -19,8 +19,10 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.Playable;
+
import de.greenrobot.event.EventBus;
+
/**
* Manages the background tasks of PlaybackSerivce, i.e.
* the sleep timer, the position saver, the widget updater and
@@ -147,9 +149,9 @@ public class PlaybackServiceTaskManager {
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
- if (BuildConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
+ Log.d(TAG, "Started PositionSaver");
} else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
+ Log.d(TAG, "Call to startPositionSaver was ignored.");
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index de6c02de7..f647fd537 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -41,10 +41,10 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
Date r = rhs.getMedia().getPlaybackCompletionDate();
if (l == null) {
- l = new Date(0);
+ l = new Date();
}
if (r == null) {
- r = new Date(0);
+ r = new Date();
}
return l.compareTo(r);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
index c5f871f48..92de1eee7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
@@ -4,10 +4,9 @@ import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
@@ -53,75 +52,53 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
- final List<FeedItem> queue = DBReader.getQueue(context);
- final List<FeedItem> unreadItems = DBReader
- .getUnreadItemsList(context);
+ List<FeedItem> candidates;
+ if(mediaIds.length > 0) {
+ candidates = DBReader.getFeedItems(context, mediaIds);
+ } else {
+ final List<FeedItem> queue = DBReader.getQueue(context);
+ final List<FeedItem> unreadItems = DBReader.getUnreadItemsList(context);
+ candidates = new ArrayList<FeedItem>(queue.size() + unreadItems.size());
+ candidates.addAll(queue);
+ for(FeedItem unreadItem : unreadItems) {
+ if(candidates.contains(unreadItem) == false) {
+ candidates.add(unreadItem);
+ }
+ }
+ }
- int undownloadedEpisodes = DBTasks.getNumberOfUndownloadedEpisodes(queue,
- unreadItems);
- int downloadedEpisodes = DBReader
- .getNumberOfDownloadedEpisodes(context);
+ // filter items that are not auto downloadable
+ Iterator<FeedItem> it = candidates.iterator();
+ while(it.hasNext()) {
+ FeedItem item = it.next();
+ if(item.isAutoDownloadable() == false) {
+ it.remove();
+ }
+ }
+
+ int autoDownloadableEpisodes = candidates.size();
+ int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes(context);
int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
- APCleanupAlgorithm.getPerformAutoCleanupArgs(context, undownloadedEpisodes));
- int episodeSpaceLeft = undownloadedEpisodes;
+ APCleanupAlgorithm.getPerformAutoCleanupArgs(context, autoDownloadableEpisodes));
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
-
- if (!cacheIsUnlimited
- && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
- + undownloadedEpisodes) {
- episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- - (downloadedEpisodes - deletedEpisodes);
+ int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
+
+ int episodeSpaceLeft;
+ if (cacheIsUnlimited ||
+ episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) {
+ episodeSpaceLeft = autoDownloadableEpisodes;
+ } else {
+ episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes);
}
- Arrays.sort(mediaIds); // sort for binary search
- final boolean ignoreMediaIds = mediaIds.length == 0;
- List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
-
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (int i = 0; i < queue.size(); i++) { // ignore playing item
- FeedItem item = queue.get(i);
- long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
- if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
- && item.hasMedia()
- && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()
- && item.getFeed().getPreferences().getAutoDownload()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
+ FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft)
+ .toArray(new FeedItem[episodeSpaceLeft]);
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (FeedItem item : unreadItems) {
- long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
- if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
- && item.hasMedia()
- && !item.getMedia().isDownloaded()
- && item.getFeed().getPreferences().getAutoDownload()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Enqueueing " + itemsToDownload.size()
- + " items for download");
+ Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download");
try {
- DBTasks.downloadFeedItems(false, context,
- itemsToDownload.toArray(new FeedItem[itemsToDownload
- .size()])
- );
+ DBTasks.downloadFeedItems(false, context, itemsToDownload);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
@@ -130,4 +107,5 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
}
};
}
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index a7c98c7c6..cc20b3d37 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -4,8 +4,11 @@ import android.content.Context;
import android.database.Cursor;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
+
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -21,6 +24,7 @@ import de.danoeh.antennapod.core.feed.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
@@ -174,8 +178,7 @@ public final class DBReader {
*/
public static List<FeedItem> getFeedItemList(Context context,
final Feed feed) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+ Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
@@ -228,7 +231,9 @@ public final class DBReader {
itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0,
image,
(itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0),
- itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
+ itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER),
+ itemlistCursor.getInt(itemlistCursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD)) > 0
+ );
itemIds.add(String.valueOf(item.getId()));
@@ -312,7 +317,10 @@ public final class DBReader {
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0,
new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS)),
cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_IS_PAGED) > 0,
- cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK));
+ cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK),
+ cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE)),
+ cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED)) > 0
+ );
if (image != null) {
image.setOwner(feed);
@@ -327,6 +335,21 @@ public final class DBReader {
return feed;
}
+ private static DownloadStatus extractDownloadStatusFromCursorRow(final Cursor cursor) {
+ long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ long feedfileId = cursor.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
+ int feedfileType = cursor.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
+ boolean successful = cursor.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
+ int reason = cursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
+ String reasonDetailed = cursor.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
+ String title = cursor.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
+ Date completionDate = new Date(cursor.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
+
+ return new DownloadStatus(id, title, feedfileId,
+ feedfileType, successful, DownloadError.fromCode(reason), completionDate,
+ reasonDetailed);
+ }
+
private static FeedItem getMatchingItemForMedia(long itemId,
List<FeedItem> items) {
@@ -468,21 +491,45 @@ public final class DBReader {
}
/**
+ * Loads a list of FeedItems that are considered new.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems that are considered new.
+ */
+ public static List<FeedItem> getNewItemsList(Context context) {
+ Log.d(TAG, "getNewItemsList()");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getNewItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor);
+ itemlistCursor.close();
+
+ loadFeedDataOfFeedItemlist(context, items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ /**
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
*
* @param context A context that is used for opening a database connection.
* @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
* over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
*/
- public static long[] getUnreadItemIds(Context context) {
+ public static LongList getNewItemIds(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- Cursor cursor = adapter.getUnreadItemIdsCursor();
- long[] itemIds = new long[cursor.getCount()];
+ Cursor cursor = adapter.getNewItemIdsCursor();
+ LongList itemIds = new LongList(cursor.getCount());
int i = 0;
if (cursor.moveToFirst()) {
do {
- itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ itemIds.add(id);
i++;
} while (cursor.moveToNext());
}
@@ -565,27 +612,7 @@ public final class DBReader {
if (logCursor.moveToFirst()) {
do {
- long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
-
- long feedfileId = logCursor
- .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
- int feedfileType = logCursor
- .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
- boolean successful = logCursor
- .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
- int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
- String reasonDetailed = logCursor
- .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
- String title = logCursor
- .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
- Date completionDate = new Date(
- logCursor
- .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)
- );
- downloadLog.add(new DownloadStatus(id, title, feedfileId,
- feedfileType, successful, DownloadError.fromCode(reason), completionDate,
- reasonDetailed));
-
+ downloadLog.add(extractDownloadStatusFromCursorRow(logCursor));
} while (logCursor.moveToNext());
}
logCursor.close();
@@ -594,6 +621,60 @@ public final class DBReader {
}
/**
+ * Loads the download log for a particular feed from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feed Feed for which the download log is loaded
+ * @return A list with DownloadStatus objects that represent the feed's download log,
+ * newest events first.
+ */
+ public static List<DownloadStatus> getFeedDownloadLog(Context context, Feed feed) {
+ Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + feed.toString() + ")");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId());
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
+ * Loads the download log for a particular feed media from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media Feed media for which the download log is loaded
+ * @return A list with DownloadStatus objects that represent the feed media's download log,
+ * newest events first.
+ */
+ public static List<DownloadStatus> getFeedMediaDownloadLog(Context context, FeedMedia media) {
+ Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + media.toString() + ")");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getDownloadLog(FeedMedia.FEEDFILETYPE_FEEDMEDIA, media.getId());
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ downloadLog.add(extractDownloadStatusFromCursorRow(cursor));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
* Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
* {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)} if only metadata about
* the FeedItems is needed.
@@ -670,6 +751,31 @@ public final class DBReader {
}
}
return item;
+ }
+
+ static List<FeedItem> getFeedItems(final Context context, PodDBAdapter adapter, final long... itemIds) {
+
+ String[] ids = new String[itemIds.length];
+ for(int i = 0; i < itemIds.length; i++) {
+ long itemId = itemIds[i];
+ ids[i] = Long.toString(itemId);
+ }
+
+ List<FeedItem> result;
+
+ Cursor itemCursor = adapter.getFeedItemCursor(ids);
+ if (itemCursor.moveToFirst()) {
+ result = extractItemlistFromCursor(adapter, itemCursor);
+ loadFeedDataOfFeedItemlist(context, result);
+ for(FeedItem item : result) {
+ if (item.hasChapters()) {
+ loadChaptersOfFeedItem(adapter, item);
+ }
+ }
+ } else {
+ result = Collections.emptyList();
+ }
+ return result;
}
@@ -691,7 +797,6 @@ public final class DBReader {
FeedItem item = getFeedItem(context, itemId, adapter);
adapter.close();
return item;
-
}
static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) {
@@ -712,6 +817,55 @@ public final class DBReader {
}
/**
+ * Loads specific FeedItems from the database. This method canbe used for loading more
+ * than one FeedItem
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemIds The IDs of the FeedItems
+ * @return The FeedItems or an empty list if none of the FeedItems could be found. All FeedComponent-attributes
+ * as well as chapter marks of the FeedItems will also be loaded from the database.
+ */
+ public static List<FeedItem> getFeedItems(final Context context, final long... itemIds) {
+ Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ","));
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> items = getFeedItems(context, adapter, itemIds);
+ adapter.close();
+ return items;
+ }
+
+
+ /**
+ * Returns credentials based on image URL
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param imageUrl The URL of the image
+ * @return Credentials in format "<Username>:<Password>", empty String if no authorization given
+ */
+ public static String getImageAuthentication(final Context context, final String imageUrl) {
+ Log.d(TAG, "Loading credentials for image with URL " + imageUrl);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ String credentials = getImageAuthentication(context, imageUrl, adapter);
+ adapter.close();
+ return credentials;
+
+ }
+
+ static String getImageAuthentication(final Context context, final String imageUrl, PodDBAdapter adapter) {
+ String credentials = null;
+ Cursor cursor = adapter.getImageAuthenticationCursor(imageUrl);
+ if (cursor.moveToFirst()) {
+ String username = cursor.getString(0);
+ String password = cursor.getString(1);
+ return username + ":" + password;
+ }
+ return "";
+ }
+
+ /**
* Loads a specific FeedItem from the database.
*
* @param context A context that is used for opening a database connection.
@@ -828,10 +982,24 @@ public final class DBReader {
* @param context A context that is used for opening a database connection.
* @return The number of unread items.
*/
- public static int getNumberOfUnreadItems(final Context context) {
+ public static int getNumberOfNewItems(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final int result = adapter.getNumberOfNewItems();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Returns a map containing the number of unread items per feed
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of unread items per feed.
+ */
+ public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- final int result = adapter.getNumberOfUnreadItems();
+ final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds);
adapter.close();
return result;
}
@@ -960,9 +1128,31 @@ public final class DBReader {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> feeds = getFeedList(adapter);
+ long[] feedIds = new long[feeds.size()];
+ for(int i=0; i < feeds.size(); i++) {
+ feedIds[i] = feeds.get(i).getId();
+ }
+ final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds);
+ Collections.sort(feeds, new Comparator<Feed>() {
+ @Override
+ public int compare(Feed lhs, Feed rhs) {
+ long numUnreadLhs = numUnreadFeedItems.get(lhs.getId());
+ Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items");
+ long numUnreadRhs = numUnreadFeedItems.get(rhs.getId());
+ Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items");
+ if(numUnreadLhs > numUnreadRhs) {
+ // reverse natural order: podcast with most unplayed episodes first
+ return -1;
+ } else if(numUnreadLhs == numUnreadRhs) {
+ return lhs.getTitle().compareTo(rhs.getTitle());
+ } else {
+ return 1;
+ }
+ }
+ });
int queueSize = adapter.getQueueSize();
- int numUnreadItems = adapter.getNumberOfUnreadItems();
- NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
+ int numNewItems = adapter.getNumberOfNewItems();
+ NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numUnreadFeedItems);
adapter.close();
return result;
}
@@ -970,12 +1160,15 @@ public final class DBReader {
public static class NavDrawerData {
public List<Feed> feeds;
public int queueSize;
- public int numUnreadItems;
+ public int numNewItems;
+ public LongIntMap numUnreadFeedItems;
- public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) {
+ public NavDrawerData(List<Feed> feeds, int queueSize, int numNewItems,
+ LongIntMap numUnreadFeedItems) {
this.feeds = feeds;
this.queueSize = queueSize;
- this.numUnreadItems = numUnreadItems;
+ this.numNewItems = numNewItems;
+ this.numUnreadFeedItems = numUnreadFeedItems;
}
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index 9fa17bf72..e570ee709 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -20,7 +20,6 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher;
@@ -171,10 +170,10 @@ public final class DBTasks {
isRefreshing.set(false);
if (FlattrUtils.hasToken()) {
- if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things.");
+ Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
- if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status.");
+ Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
@@ -185,9 +184,7 @@ public final class DBTasks {
}
}.start();
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Ignoring request to refresh all feeds: Refresh lock is locked");
+ Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
}
}
@@ -223,8 +220,7 @@ public final class DBTasks {
* @param context Used for DB access.
*/
public static void refreshExpiredFeeds(final Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Refreshing expired feeds");
+ Log.d(TAG, "Refreshing expired feeds");
new Thread() {
public void run() {
@@ -306,6 +302,7 @@ public final class DBTasks {
*/
public static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
+ Log.d(TAG, "id " + feed.getId());
refreshFeed(context, feed, false);
}
@@ -428,25 +425,6 @@ public final class DBTasks {
}
}
- static int getNumberOfUndownloadedEpisodes(
- final List<FeedItem> queue, final List<FeedItem> unreadItems) {
- int counter = 0;
- for (FeedItem item : queue) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()
- && item.getFeed().getPreferences().getAutoDownload()) {
- counter++;
- }
- }
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && item.getFeed().getPreferences().getAutoDownload()) {
- counter++;
- }
- }
- return counter;
- }
-
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
@@ -479,14 +457,6 @@ public final class DBTasks {
}
/**
- * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
- */
- public static void enqueueAllNewItems(final Context context) {
- long[] unreadItems = DBReader.getUnreadItemIds(context);
- DBWriter.addQueueItem(context, unreadItems);
- }
-
- /**
* Returns the successor of a FeedItem in the queue.
*
* @param context Used for accessing the DB.
@@ -560,7 +530,7 @@ public final class DBTasks {
/**
* Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same
* identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
- * These FeedItems will be marked as unread.
+ * These FeedItems will be marked as unread with the exception of the most recent FeedItem.
* <p/>
* This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior.
* <p/>
@@ -586,17 +556,20 @@ public final class DBTasks {
final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter,
newFeed);
if (savedFeed == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Found no existing Feed with title "
- + newFeed.getTitle() + ". Adding as new one."
- );
+ Log.d(TAG, "Found no existing Feed with title "
+ + newFeed.getTitle() + ". Adding as new one.");
+
// Add a new Feed
+ // all new feeds will have the most recent item marked as unplayed
+ FeedItem mostRecent = newFeed.getMostRecentItem();
+ if (mostRecent != null) {
+ mostRecent.setRead(false);
+ }
+
newFeedsList.add(newFeed);
resultFeeds[feedIdx] = newFeed;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one.");
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
@@ -604,21 +577,17 @@ public final class DBTasks {
final boolean markNewItemsAsUnread;
if (newFeed.getPageNr() == savedFeed.getPageNr()) {
if (savedFeed.compareWithOther(newFeed)) {
- if (BuildConfig.DEBUG)
- Log.d(TAG,
- "Feed has updated attribute values. Updating old feed's attributes");
+ Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes");
savedFeed.updateFromOther(newFeed);
}
markNewItemsAsUnread = true;
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "New feed has a higher page number. Merging without marking as unread");
+ Log.d(TAG, "New feed has a higher page number. Merging without marking as unread");
markNewItemsAsUnread = false;
savedFeed.setNextPageLink(newFeed.getNextPageLink());
}
if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
+ Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences");
savedFeed.getPreferences().updateFromOther(newFeed.getPreferences());
}
// Look for new or updated Items
@@ -630,6 +599,7 @@ public final class DBTasks {
// item is new
final int i = idx;
item.setFeed(savedFeed);
+ item.setAutoDownload(savedFeed.getPreferences().getAutoDownload());
savedFeed.getItems().add(i, item);
if (markNewItemsAsUnread) {
item.setRead(false);
@@ -641,6 +611,7 @@ public final class DBTasks {
// update attributes
savedFeed.setLastUpdate(newFeed.getLastUpdate());
savedFeed.setType(newFeed.getType());
+ savedFeed.setLastUpdateFailed(false);
updatedFeedsList.add(savedFeed);
resultFeeds[feedIdx] = savedFeed;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index bd0cfee5b..fe5d0dfd3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -29,6 +29,7 @@ import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -353,30 +354,16 @@ public class DBWriter {
FeedItem item = null;
if (queue != null) {
- boolean queueModified = false;
- boolean unreadItemsModified = false;
-
if (!itemListContains(queue, itemId)) {
item = DBReader.getFeedItem(context, itemId);
if (item != null) {
queue.add(index, item);
- queueModified = true;
- if (!item.isRead()) {
- item.setRead(true);
- unreadItemsModified = true;
- }
+ adapter.setQueue(queue);
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
}
}
- if (queueModified) {
- adapter.setQueue(queue);
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index));
- }
- if (unreadItemsModified && item != null) {
- adapter.setSingleFeedItem(item);
- EventDistributor.getInstance()
- .sendUnreadItemsUpdateBroadcast();
- }
}
+
adapter.close();
if (performAutoDownload) {
DBTasks.autodownloadUndownloadedItems(context);
@@ -417,21 +404,15 @@ public class DBWriter {
if (item != null) {
// add item to either front ot back of queue
- boolean addToFront = PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(UserPreferences.PREF_QUEUE_ADD_TO_FRONT, false);
+ boolean addToFront = UserPreferences.enqueueAtFront();
if(addToFront){
queue.add(0, item);
- }else{
+ } else {
queue.add(item);
}
queueModified = true;
- if (!item.isRead()) {
- item.setRead(true);
- itemsToSave.add(item);
- unreadItemsModified = true;
- }
}
}
}
@@ -439,11 +420,6 @@ public class DBWriter {
adapter.setQueue(queue);
EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue));
}
- if (unreadItemsModified) {
- adapter.setFeedItemlist(itemsToSave);
- EventDistributor.getInstance()
- .sendUnreadItemsUpdateBroadcast();
- }
}
adapter.close();
DBTasks.autodownloadUndownloadedItems(context);
@@ -608,8 +584,7 @@ public class DBWriter {
adapter.setQueue(queue);
if (broadcastUpdate) {
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item));
- EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, to));
+ EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.MOVED, item, to));
}
}
@@ -937,6 +912,26 @@ public class DBWriter {
}
/**
+ * Saves if a feed's last update failed
+ *
+ * @param lastUpdateFailed true if last update failed
+ */
+ public static Future<?> setFeedLastUpdateFailed(final Context context,
+ final long feedId,
+ final boolean lastUpdateFailed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
* format an url for querying the database
* (postfix a / and apply percent-encoding)
*/
@@ -1040,4 +1035,52 @@ public class DBWriter {
}
});
}
+
+ /**
+ * Sets the 'auto_download'-attribute of specific FeedItem.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedItem FeedItem.
+ */
+ public static Future<?> setFeedItemAutoDownload(final Context context, final FeedItem feedItem,
+ final boolean autoDownload) {
+ Log.d(TAG, "FeedItem[id=" + feedItem.getId() + "] SET auto_download " + autoDownload);
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemAutoDownload(feedItem, autoDownload);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Set filter of the feed
+ *
+ * @param context Used for opening a database connection.
+ * @param feedId The feed's ID
+ * @param filterValues Values that represent properties to filter by
+ */
+ public static Future<?> setFeedItemsFilter(final Context context, final long feedId,
+ final List<String> filterValues) {
+ Log.d(TAG, "setFeedFilter");
+
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemFilter(feedId, filterValues);
+ adapter.close();
+ EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId));
+ }
+ });
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index f518a4f5f..4780098e0 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -11,6 +11,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import java.util.Arrays;
@@ -26,8 +27,11 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.util.LongIntMap;
import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
+;
+
// TODO Remove media column from feeditem table
/**
@@ -149,6 +153,8 @@ public class PodDBAdapter {
public static final String KEY_PASSWORD = "password";
public static final String KEY_IS_PAGED = "is_paged";
public static final String KEY_NEXT_PAGE_LINK = "next_page_link";
+ public static final String KEY_HIDE = "hide";
+ public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@@ -175,8 +181,9 @@ public class PodDBAdapter {
+ KEY_USERNAME + " TEXT,"
+ KEY_PASSWORD + " TEXT,"
+ KEY_IS_PAGED + " INTEGER DEFAULT 0,"
- + KEY_NEXT_PAGE_LINK + " TEXT)";
-
+ + KEY_NEXT_PAGE_LINK + " TEXT,"
+ + KEY_HIDE + " TEXT,"
+ + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)";
public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -186,7 +193,8 @@ public class PodDBAdapter {
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER,"
- + KEY_IMAGE + " INTEGER)";
+ + KEY_IMAGE + " INTEGER,"
+ + KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
@@ -200,7 +208,8 @@ public class PodDBAdapter {
+ " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ KEY_FEEDITEM + " INTEGER,"
- + KEY_PLAYED_DURATION + " INTEGER)";
+ + KEY_PLAYED_DURATION + " INTEGER,"
+ + KEY_AUTO_DOWNLOAD + " INTEGER)";
public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
@@ -218,6 +227,28 @@ public class PodDBAdapter {
+ " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
+ // SQL Statements for creating indexes
+ public static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_FEED + ")";
+
+ public static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_IMAGE + ")";
+
+ public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ + KEY_FEEDITEM + ")";
+
+ public static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ + KEY_FEEDITEM + ")";
+
+ public static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ + TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ + KEY_FEEDITEM + ")";
+
+
private SQLiteDatabase db;
private final Context context;
private PodDBHelper helper;
@@ -246,6 +277,8 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK,
TABLE_NAME_FEEDS + "." + KEY_USERNAME,
TABLE_NAME_FEEDS + "." + KEY_PASSWORD,
+ TABLE_NAME_FEEDS + "." + KEY_HIDE,
+ TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED,
};
// column indices for FEED_SEL_STD
@@ -270,7 +303,6 @@ public class PodDBAdapter {
public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 18;
public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 19;
-
/**
* Select all columns from the feeditems-table except description and
* content-encoded.
@@ -286,7 +318,9 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
- TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE};
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
+ };
/**
* Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries.
@@ -400,17 +434,20 @@ public class PodDBAdapter {
values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong());
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
+ if(feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) {
+ values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter().getValues(), ","));
+ } else {
+ values.put(KEY_HIDE, "");
+ }
+ values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed());
if (feed.getId() == 0) {
// Create new entry
- if (BuildConfig.DEBUG)
- Log.d(this.toString(), "Inserting new Feed into db");
+ Log.d(this.toString(), "Inserting new Feed into db");
feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
} else {
- if (BuildConfig.DEBUG)
- Log.d(this.toString(), "Updating existing Feed in db");
+ Log.d(this.toString(), "Updating existing Feed in db");
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())});
-
}
return feed.getId();
}
@@ -426,6 +463,13 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())});
}
+ public void setFeedItemFilter(long feedId, List<String> filterValues) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_HIDE, StringUtils.join(filterValues, ","));
+ Log.d(TAG, StringUtils.join(filterValues, ","));
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
+ }
+
/**
* Inserts or updates an image entry
*
@@ -696,6 +740,7 @@ public class PodDBAdapter {
values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters());
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
+ values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload());
if (item.hasItemImage()) {
if (item.getImage().getId() == 0) {
setImage(item.getImage());
@@ -766,6 +811,13 @@ public class PodDBAdapter {
}
}
+ public void setFeedLastUpdateFailed(long feedId, boolean failed) {
+ final String sql = "UPDATE " + TABLE_NAME_FEEDS
+ + " SET " + KEY_LAST_UPDATE_FAILED+ "=" + (failed ? "1" : "0")
+ + " WHERE " + KEY_ID + "="+ feedId;
+ db.execSQL(sql);
+ }
+
/**
* Inserts or updates a download status.
*/
@@ -787,6 +839,13 @@ public class PodDBAdapter {
return status.getId();
}
+ public void setFeedItemAutoDownload(FeedItem feedItem, boolean autoDownload) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_AUTO_DOWNLOAD, autoDownload);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
+ new String[] { String.valueOf(feedItem.getId()) } );
+ }
+
public long getDownloadLogSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG);
Cursor result = db.rawQuery(query, null);
@@ -977,6 +1036,14 @@ public class PodDBAdapter {
return c;
}
+ public final Cursor getDownloadLog(final int feedFileType, final long feedFileId) {
+ final String query = "SELECT * FROM " + TABLE_NAME_DOWNLOAD_LOG +
+ " WHERE " + KEY_FEEDFILE + "=" + feedFileId + " AND " + KEY_FEEDFILETYPE + "=" + feedFileType
+ + " ORDER BY " + KEY_ID + " DESC";
+ Cursor c = db.rawQuery(query, null);
+ return c;
+ }
+
public final Cursor getDownloadLogCursor(final int limit) {
Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit);
@@ -1021,11 +1088,43 @@ public class PodDBAdapter {
return c;
}
- public final Cursor getUnreadItemIdsCursor() {
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
- KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
- return c;
+ public final Cursor getNewItemIdsCursor() {
+ final String query = "SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
+ return db.rawQuery(query, null);
+ }
+ /**
+ * Returns a cursor which contains all feed items that are considered new.
+ * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection.
+ */
+ public final Cursor getNewItemsCursor() {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL" // not in queue
+ + " ORDER BY " + KEY_PUBDATE + " DESC";
+ Cursor c = db.rawQuery(query, null);
+ return c;
}
public final Cursor getRecentlyPublishedItemsCursor(int limit) {
@@ -1121,7 +1220,7 @@ public class PodDBAdapter {
}
public final Cursor getFeedItemCursor(final String id) {
- return getFeedItemCursor(new String[] { id });
+ return getFeedItemCursor(new String[]{id});
}
public final Cursor getFeedItemCursor(final String[] ids) {
@@ -1146,6 +1245,20 @@ public class PodDBAdapter {
return db.rawQuery(query, null);
}
+ public Cursor getImageAuthenticationCursor(final String imageUrl) {
+ final String query = "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM "
+ + TABLE_NAME_FEED_IMAGES + " INNER JOIN " + TABLE_NAME_FEEDS + " ON " +
+ TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE + " WHERE "
+ + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "' UNION SELECT "
+ + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES + " INNER JOIN "
+ + TABLE_NAME_FEED_ITEMS + " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE + " INNER JOIN " + TABLE_NAME_FEEDS + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID + " WHERE "
+ + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "='" + imageUrl + "'";
+ Log.d(TAG, "Query: " + query);
+ return db.rawQuery(query, null);
+ }
+
public int getQueueSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
Cursor c = db.rawQuery(query, null);
@@ -1157,9 +1270,20 @@ public class PodDBAdapter {
return result;
}
- public final int getNumberOfUnreadItems() {
- final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
- " WHERE " + KEY_READ + " = 0";
+ public final int getNumberOfNewItems() {
+ final String query = "SELECT COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ")"
+ +" FROM " + TABLE_NAME_FEED_ITEMS
+ + " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ + " LEFT JOIN " + TABLE_NAME_QUEUE + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ + " WHERE "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
@@ -1169,6 +1293,25 @@ public class PodDBAdapter {
return result;
}
+ public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) {
+ final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count "
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") "
+ + " AND " + KEY_READ + " = 0"
+ + " GROUP BY " + KEY_FEED;
+ Cursor c = db.rawQuery(query, null);
+ LongIntMap result = new LongIntMap(c.getCount());
+ if (c.moveToFirst()) {
+ do {
+ long feedId = c.getLong(0);
+ int count = c.getInt(1);
+ result.put(feedId, count);
+ } while(c.moveToNext());
+ }
+ c.close();
+ return result;
+ }
+
public final int getNumberOfDownloadedEpisodes() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
" WHERE " + KEY_DOWNLOADED + " > 0";
@@ -1330,6 +1473,13 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
+
+ db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
+ db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
+ db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
+
}
@Override
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
index 6622eab73..b6df2dc85 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java
@@ -1,5 +1,7 @@
package de.danoeh.antennapod.core.util;
+import android.util.Log;
+
import org.apache.commons.lang3.StringUtils;
import java.text.ParsePosition;
@@ -11,45 +13,14 @@ import java.util.Locale;
* Parses several date formats.
*/
public class DateUtils {
- private static final String TAG = "DateUtils";
-
- private static final String[] RFC822DATES = {"dd MMM yy HH:mm:ss Z",
- "dd MMM yy HH:mm Z"};
-
- /**
- * RFC 3339 date format for UTC dates.
- */
- public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'";
-
- /**
- * RFC 3339 date format for localtime dates with offset.
- */
- public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ";
+
+ private static final String TAG = "DateUtils";
- public static final String ISO8601_SHORT = "yyyy-MM-dd";
-
- private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
- }
-
- };
-
- private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
- }
-
- };
-
- public static Date parse(String date) {
- if(date == null) {
+ public static Date parse(final String input) {
+ if(input == null) {
throw new IllegalArgumentException("Date most not be null");
}
- date = date.replace('/', ' ');
- date = date.replace('-', ' ');
+ String date = input.replace('/', '-');
if(date.contains(".")) {
int start = date.indexOf('.');
int current = start+1;
@@ -75,15 +46,16 @@ public class DateUtils {
"dd MMM yy HH:mm:ss Z",
"dd MMM yy HH:mm Z",
"EEE, dd MMM yyyy HH:mm:ss Z",
+ "EEE, dd MMMM yyyy HH:mm:ss Z",
"EEEE, dd MMM yy HH:mm:ss Z",
"EEE MMM d HH:mm:ss yyyy",
- "yyyy MM dd'T'HH:mm:ss",
- "yyyy MM dd'T'HH:mm:ss.SSS",
- "yyyy MM dd'T'HH:mm:ss.SSS Z",
- "yyyy MM dd'T'HH:mm:ssZ",
- "yyyy MM dd'T'HH:mm:ss'Z'",
- "yyyy MM ddZ",
- "yyyy MM dd"
+ "yyyy-MM-dd'T'HH:mm:ss",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS Z",
+ "yyyy-MM-dd'T'HH:mm:ssZ",
+ "yyyy-MM-dd'T'HH:mm:ss'Z'",
+ "yyyy-MM-ddZ",
+ "yyyy-MM-dd"
};
SimpleDateFormat parser = new SimpleDateFormat("", Locale.US);
parser.setLenient(false);
@@ -96,6 +68,8 @@ public class DateUtils {
return result;
}
}
+
+ Log.d(TAG, "Could not parse date string \"" + input + "\"");
return null;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
index 4c23b161b..029e7fe84 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java
@@ -1,11 +1,12 @@
package de.danoeh.antennapod.core.util;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
import java.util.ArrayList;
import java.util.List;
+import de.danoeh.antennapod.core.feed.FeedItem;
+
public class EpisodeFilter {
+
private EpisodeFilter() {
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
new file mode 100644
index 000000000..673c81235
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java
@@ -0,0 +1,240 @@
+package de.danoeh.antennapod.core.util;
+
+import java.util.Arrays;
+
+/**
+ * Fast and memory efficient int list
+ */
+public final class IntList {
+
+ private int[] values;
+ protected int size;
+
+ /**
+ * Constructs an empty instance with a default initial capacity.
+ */
+ public IntList() {
+ this(4);
+ }
+
+ /**
+ * Constructs an empty instance.
+ *
+ * @param initialCapacity {@code >= 0;} initial capacity of the list
+ */
+ public IntList(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ values = new int[initialCapacity];
+ size = 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ int value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof IntList)) {
+ return false;
+ }
+ IntList otherList = (IntList) other;
+ if (size != otherList.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (values[i] != otherList.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer(size * 5 + 10);
+ sb.append("IntList{");
+ for (int i = 0; i < size; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(values[i]);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Gets the number of elements in this list.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Gets the indicated value.
+ *
+ * @param n {@code >= 0, < size();} which element
+ * @return the indicated element's value
+ */
+ public int get(int n) {
+ if (n >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[n];
+ }
+
+ /**
+ * Sets the value at the given index.
+ *
+ * @param index the index at which to put the specified object.
+ * @param value the object to add.
+ * @return the previous element at the index.
+ */
+ public int set(int index, int value) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ int result = values[index];
+ values[index] = value;
+ return result;
+ }
+
+ /**
+ * Adds an element to the end of the list. This will increase the
+ * list's capacity if necessary.
+ *
+ * @param value the value to add
+ */
+ public void add(int value) {
+ growIfNeeded();
+ values[size++] = value;
+ }
+
+ /**
+ * Inserts element into specified index, moving elements at and above
+ * that index up one. May not be used to insert at an index beyond the
+ * current size (that is, insertion as a last element is legal but
+ * no further).
+ *
+ * @param n {@code >= 0, <=size();} index of where to insert
+ * @param value value to insert
+ */
+ public void insert(int n, int value) {
+ if (n > size) {
+ throw new IndexOutOfBoundsException("n > size()");
+ } else if(n < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+
+ growIfNeeded();
+
+ System.arraycopy (values, n, values, n+1, size - n);
+ values[n] = value;
+ size++;
+ }
+
+ /**
+ * Removes value from this list.
+ *
+ * @param value value to remove
+ * return {@code true} if the value was removed, {@code false} otherwise
+ */
+ public boolean remove(int value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ size--;
+ System.arraycopy(values, i+1, values, i, size-i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes an element at a given index, shifting elements at greater
+ * indicies down one.
+ *
+ * @param index index of element to remove
+ */
+ public void removeIndex(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ size--;
+ System.arraycopy (values, index + 1, values, index, size - index);
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == values.length) {
+ // Resize.
+ int[] newArray = new int[size * 3 / 2 + 10];
+ System.arraycopy(values, 0, newArray, 0, size);
+ values = newArray;
+ }
+ }
+
+ /**
+ * Returns the index of the given value, or -1 if the value does not
+ * appear in the list.
+ *
+ * @param value value to find
+ * @return index of value or -1
+ */
+ public int indexOf(int value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all values from this list.
+ */
+ public void clear() {
+ values = new int[4];
+ size = 0;
+ }
+
+
+ /**
+ * Returns true if the given value is contained in the list
+ *
+ * @param value value to look for
+ * @return {@code true} if this list contains {@code value}, {@code false} otherwise
+ */
+ public boolean contains(int value) {
+ return indexOf(value) >= 0;
+ }
+
+ /**
+ * Returns an array with a copy of this list's values
+ *
+ * @return array with a copy of this list's values
+ */
+ public int[] toArray() {
+ return Arrays.copyOf(values, size);
+
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
new file mode 100644
index 000000000..33fd252eb
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java
@@ -0,0 +1,252 @@
+package de.danoeh.antennapod.core.util;
+
+
+/**
+ * Fast and memory efficient long to long map
+ */
+public class LongIntMap {
+
+ private long[] keys;
+ private int[] values;
+ private int size;
+
+ /**
+ * Creates a new LongLongMap containing no mappings.
+ */
+ public LongIntMap() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseLongArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public LongIntMap(int initialCapacity) {
+ if(initialCapacity < 0) {
+ throw new IllegalArgumentException("initial capacity must be 0 or higher");
+ }
+ keys = new long[initialCapacity];
+ values = new int[initialCapacity];
+ size = 0;
+ }
+
+ /**
+ * Increases size of array if needed
+ */
+ private void growIfNeeded() {
+ if (size == keys.length) {
+ // Resize.
+ long[] newKeysArray = new long[size * 3 / 2 + 10];
+ int[] newValuesArray = new int[size * 3 / 2 + 10];
+ System.arraycopy(keys, 0, newKeysArray, 0, size);
+ System.arraycopy(values, 0, newValuesArray, 0, size);
+ keys = newKeysArray;
+ values = newValuesArray;
+ }
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public int get(long key) {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public int get(long key, int valueIfKeyNotFound) {
+ int index = indexOfKey(key);
+ if(index >= 0) {
+ return values[index];
+ } else {
+ return valueIfKeyNotFound;
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public boolean delete(long key) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ removeAt(index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(keys, index + 1, keys, index, size - (index + 1));
+ System.arraycopy(values, index + 1, values, index, size - (index + 1));
+ size--;
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, int value) {
+ int index = indexOfKey(key);
+
+ if (index >= 0) {
+ values[index] = value;
+ } else {
+ growIfNeeded();
+ keys[size] = key;
+ values[size] = value;
+ size++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The keys corresponding to indices in ascending order are guaranteed to
+ * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+ * smallest key and <code>keyAt(size()-1)</code> will return the largest
+ * key.</p>
+ */
+ public long keyAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return keys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ *
+ * <p>The values corresponding to indices in ascending order are guaranteed
+ * to be associated with keys in ascending order, e.g.,
+ * <code>valueAt(0)</code> will return the value associated with the
+ * smallest key and <code>valueAt(size()-1)</code> will return the value
+ * associated with the largest key.</p>
+ */
+ public int valueAt(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException("n >= size()");
+ } else if(index < 0) {
+ throw new IndexOutOfBoundsException("n < 0");
+ }
+ return values[index];
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ for(int i=0; i < size; i++) {
+ if(keys[i] == key) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(long value) {
+ for (int i = 0; i < size; i++) {
+ if (values[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ keys = new long[10];
+ values = new int[10];
+ size = 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (! (other instanceof LongIntMap)) {
+ return false;
+ }
+ LongIntMap otherMap = (LongIntMap) other;
+ if (size != otherMap.size) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (keys[i] != otherMap.keys[i] ||
+ values[i] != otherMap.values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (int i = 0; i < size; i++) {
+ long value = values[i];
+ hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "LongLongMap{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size * 28);
+ buffer.append("LongLongMap{");
+ for (int i=0; i < size; i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ long key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ long value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
index f5d0cab0c..8934f3272 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java
@@ -32,7 +32,6 @@ public final class LongList {
@Override
public int hashCode() {
- Arrays.hashCode(values);
int hashCode = 1;
for (int i = 0; i < size; i++) {
long value = values[i];
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
index b321536a3..3a349e221 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
@@ -6,12 +6,13 @@ import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import java.util.Arrays;
import java.util.List;
+import de.danoeh.antennapod.core.BuildConfig;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
public class NetworkUtils {
private static final String TAG = "NetworkUtils";
@@ -66,4 +67,18 @@ public class NetworkUtils {
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnected();
}
+
+ public static boolean isDownloadAllowed(Context context) {
+ return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(context);
+ }
+
+ public static boolean connectedToWifi(Context context) {
+ ConnectivityManager connManager = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo mWifi = connManager
+ .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ return mWifi.isConnected();
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
index 90caad946..50792ae26 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java
@@ -26,7 +26,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.TimeZone;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.asynctask.FlattrTokenFetcher;
@@ -65,18 +64,15 @@ public class FlattrUtils {
private static AccessToken retrieveToken() {
if (cachedToken == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Retrieving access token");
+ Log.d(TAG, "Retrieving access token");
String token = PreferenceManager.getDefaultSharedPreferences(
ClientConfig.applicationCallbacks.getApplicationInstance())
.getString(PREF_ACCESS_TOKEN, null);
if (token != null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Found access token. Caching.");
+ Log.d(TAG, "Found access token. Caching.");
cachedToken = new AccessToken(token);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No access token found");
+ Log.d(TAG, "No access token found");
return null;
}
}
@@ -97,8 +93,7 @@ public class FlattrUtils {
}
public static void storeToken(AccessToken token) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Storing token");
+ Log.d(TAG, "Storing token");
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(ClientConfig.applicationCallbacks.getApplicationInstance()).edit();
if (token != null) {
@@ -111,8 +106,7 @@ public class FlattrUtils {
}
public static void deleteToken() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Deleting flattr token");
+ Log.d(TAG, "Deleting flattr token");
storeToken(null);
}
@@ -169,15 +163,11 @@ public class FlattrUtils {
}
}
- if (BuildConfig.DEBUG) {
- Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate);
-
- for (Flattr fl : myFlattrs) {
- Thing thing = fl.getThing();
- Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated());
- }
+ Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate);
+ for (Flattr fl : myFlattrs) {
+ Thing thing = fl.getThing();
+ Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated());
}
-
} else {
Log.e(TAG, "retrieveFlattrdThings was called with null access token");
}
@@ -191,8 +181,7 @@ public class FlattrUtils {
}
public static void revokeAccessToken(Context context) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Revoking access token");
+ Log.d(TAG, "Revoking access token");
deleteToken();
FlattrServiceCreator.deleteFlattrService();
showRevokeDialog(context);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
index 23d8cf7c7..26c712af3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java
@@ -88,7 +88,8 @@ public class UndoBarController<T> {
}
public void close() {
- mHideHandler.post(mHideRunnable);
+ hideUndoBar(true);
+ mUndoListener.onHide(mUndoToken);
}
public void hideUndoBar(boolean immediate) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index 17c752bb6..a0d12d3e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -32,13 +32,11 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
@@ -174,8 +172,7 @@ public abstract class PlaybackController {
* as the arguments of the launch intent.
*/
private void bindToService() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Trying to connect to service");
+ Log.d(TAG, "Trying to connect to service");
AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void... voids) {
@@ -211,8 +208,7 @@ public abstract class PlaybackController {
* played media or null if no last played media could be found.
*/
private Intent getPlayLastPlayedMediaIntent() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Trying to restore last played media");
+ Log.d(TAG, "Trying to restore last played media");
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(activity.getApplicationContext());
long currentlyPlayingMedia = PlaybackPreferences
@@ -240,8 +236,7 @@ public abstract class PlaybackController {
return serviceIntent;
}
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "No last played media found");
+ Log.d(TAG, "No last played media found");
return null;
}
@@ -253,8 +248,7 @@ public abstract class PlaybackController {
|| (positionObserverFuture != null && positionObserverFuture
.isDone()) || positionObserverFuture == null) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting up position observer");
+ Log.d(TAG, "Setting up position observer");
positionObserver = new MediaPositionObserver();
positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
positionObserver, MediaPositionObserver.WAITING_INTERVALL,
@@ -266,8 +260,7 @@ public abstract class PlaybackController {
private void cancelPositionObserver() {
if (positionObserverFuture != null) {
boolean result = positionObserverFuture.cancel(true);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "PositionObserver cancelled. Result: " + result);
+ Log.d(TAG, "PositionObserver cancelled. Result: " + result);
}
}
@@ -295,8 +288,7 @@ public abstract class PlaybackController {
protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Received statusUpdate Intent.");
+ Log.d(TAG, "Received statusUpdate Intent.");
if (isConnectedToPlaybackService()) {
PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo();
status = info.playerStatus;
@@ -353,8 +345,7 @@ public abstract class PlaybackController {
}
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Bad arguments. Won't handle intent");
+ Log.d(TAG, "Bad arguments. Won't handle intent");
}
} else {
bindToService();
@@ -425,6 +416,7 @@ public abstract class PlaybackController {
pauseResource = R.drawable.ic_av_pause_circle_outline_80dp;
}
+ Log.d(TAG, "status: " + status.toString());
switch (status) {
case ERROR:
@@ -470,6 +462,7 @@ public abstract class PlaybackController {
updatePlayButtonAppearance(playResource, playText);
break;
case SEEKING:
+ onPositionObserverUpdate();
postStatusMsg(R.string.player_seeking_msg);
break;
case INITIALIZED:
@@ -505,8 +498,7 @@ public abstract class PlaybackController {
* information has to be refreshed
*/
void queryService() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Querying service info");
+ Log.d(TAG, "Querying service info");
if (playbackService != null) {
status = playbackService.getStatus();
media = playbackService.getPlayable();
@@ -614,28 +606,6 @@ public abstract class PlaybackController {
};
}
- public OnClickListener newOnRevButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(-UserPreferences.getSeekDeltaMs());
- }
- }
- };
- }
-
- public OnClickListener newOnFFButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(UserPreferences.getSeekDeltaMs());
- }
- }
- };
- }
-
public boolean serviceAvailable() {
return playbackService != null;
}
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
index 46be3e14e..0c3bb0757 100755
--- a/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
index 3d57127f5..667300129 100755
--- a/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png
new file mode 100644
index 000000000..83c564377
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png
new file mode 100644
index 000000000..e3517a57d
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png
new file mode 100644
index 000000000..b6dba1002
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png
new file mode 100644
index 000000000..5c60ab08a
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png
new file mode 100644
index 000000000..a99e9f2b6
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 000000000..61c623ce2
--- /dev/null
+++ b/core/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
index 79f082610..d46b325d8 100755
--- a/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
index 15a4b16bf..ac94476c2 100755
--- a/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png
new file mode 100644
index 000000000..e1e55d72b
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png
new file mode 100644
index 000000000..7d72e7562
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png
new file mode 100644
index 000000000..f1627ce34
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png
new file mode 100644
index 000000000..8f18d11e6
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png
new file mode 100644
index 000000000..ada8d3be4
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 000000000..72d01c406
--- /dev/null
+++ b/core/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
index 5cb0262ee..b25d64863 100755
--- a/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
index 5f34b0492..3c3e74c1d 100755
--- a/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-xhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png
new file mode 100644
index 000000000..fdbb8eb4e
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png
new file mode 100644
index 000000000..7e14f7fbf
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png
new file mode 100644
index 000000000..ca35f6d0a
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png
new file mode 100644
index 000000000..01fb55ca1
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png
new file mode 100644
index 000000000..11d9a4b8b
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 000000000..01ca4b56c
--- /dev/null
+++ b/core/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
index 01ef2ee4d..aacf24d28 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
index 6dd465852..625dbaa1f 100755
--- a/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
+++ b/core/src/main/res/drawable-xxhdpi/ic_feed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png
new file mode 100644
index 000000000..43ec90ea5
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png
new file mode 100644
index 000000000..d3923efee
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png
new file mode 100644
index 000000000..311a7fa13
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png
new file mode 100644
index 000000000..39a163843
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png
new file mode 100644
index 000000000..c0552d564
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 000000000..46852d54f
--- /dev/null
+++ b/core/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png
new file mode 100644
index 000000000..5d14b5b25
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_filter_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png
new file mode 100644
index 000000000..e8b865e4a
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_filter_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png
new file mode 100644
index 000000000..e41d5b9ee
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png
new file mode 100644
index 000000000..2376b7334
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_closed_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png
new file mode 100644
index 000000000..c281784dd
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_grey600_24dp.png
Binary files differ
diff --git a/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
new file mode 100644
index 000000000..25ea3ab99
--- /dev/null
+++ b/core/src/main/res/drawable-xxxhdpi/ic_lock_open_white_24dp.png
Binary files differ
diff --git a/core/src/main/res/values-az/strings.xml b/core/src/main/res/values-az/strings.xml
index 9640412c1..b52ecf4a4 100644
--- a/core/src/main/res/values-az/strings.xml
+++ b/core/src/main/res/values-az/strings.xml
@@ -76,7 +76,7 @@
<string name="download_error_connection_error">Əlaqə xətasi</string>
<string name="download_error_unknown_host">Naməlum xost</string>
<string name="cancel_all_downloads_label">Yükləmələrin hamısını ləğv et</string>
- <string name="download_cancelled_msg">Yükləmə ləğv olundu</string>
+ <string name="download_canceled_msg">Yükləmə ləğv olundu</string>
<string name="download_report_title">Yükləmə başa çatdı</string>
<string name="download_error_malformed_url">Yanlış URL</string>
<string name="download_error_io_error">IO xətasi</string>
diff --git a/core/src/main/res/values-ca/strings.xml b/core/src/main/res/values-ca/strings.xml
index 0fe3ae415..be7a73e6d 100644
--- a/core/src/main/res/values-ca/strings.xml
+++ b/core/src/main/res/values-ca/strings.xml
@@ -108,7 +108,7 @@
<string name="download_error_unknown_host">Amfitrió desconegut</string>
<string name="download_error_unauthorized">Error d\'autenticació</string>
<string name="cancel_all_downloads_label">Cancel·la totes les baixades</string>
- <string name="download_cancelled_msg">S\'ha cancel·lat la baixada</string>
+ <string name="download_canceled_msg">S\'ha cancel·lat la baixada</string>
<string name="download_report_title">Baixades completades</string>
<string name="download_error_malformed_url">URL mal formatada</string>
<string name="download_error_io_error">Error d\'E/S</string>
diff --git a/core/src/main/res/values-cs-rCZ/strings.xml b/core/src/main/res/values-cs-rCZ/strings.xml
index a67a6d10b..661faefbe 100644
--- a/core/src/main/res/values-cs-rCZ/strings.xml
+++ b/core/src/main/res/values-cs-rCZ/strings.xml
@@ -108,7 +108,7 @@
<string name="download_error_unknown_host">Neznámý host</string>
<string name="download_error_unauthorized">Chyba přihlášení</string>
<string name="cancel_all_downloads_label">Zrušit všechna stahování</string>
- <string name="download_cancelled_msg">Stahování zrušeno</string>
+ <string name="download_canceled_msg">Stahování zrušeno</string>
<string name="download_report_title">Všechna stahování dokončena</string>
<string name="download_error_malformed_url">Chybné URL</string>
<string name="download_error_io_error">IO chyba</string>
diff --git a/core/src/main/res/values-da/strings.xml b/core/src/main/res/values-da/strings.xml
index d31c65614..ba7fafca6 100644
--- a/core/src/main/res/values-da/strings.xml
+++ b/core/src/main/res/values-da/strings.xml
@@ -108,7 +108,7 @@
<string name="download_error_unknown_host">Ukendt vært</string>
<string name="download_error_unauthorized">Godkendelses fejl</string>
<string name="cancel_all_downloads_label">Annuller alle downloads</string>
- <string name="download_cancelled_msg">Download afbrudt</string>
+ <string name="download_canceled_msg">Download afbrudt</string>
<string name="download_report_title">Downloads afsluttet</string>
<string name="download_error_malformed_url">Misdannet URL</string>
<string name="download_error_io_error">IO fejl</string>
diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml
index 1f1b519ab..f2d0a66ad 100644
--- a/core/src/main/res/values-de/strings.xml
+++ b/core/src/main/res/values-de/strings.xml
@@ -112,7 +112,7 @@
<string name="download_error_unknown_host">Unbekannter Host</string>
<string name="download_error_unauthorized">Authentifizierungsfehler</string>
<string name="cancel_all_downloads_label">Alle Downloads abbrechen</string>
- <string name="download_cancelled_msg">Download abgebrochen</string>
+ <string name="download_canceled_msg">Download abgebrochen</string>
<string name="download_report_title">Download abgeschlossen</string>
<string name="download_error_malformed_url">Fehler in URL</string>
<string name="download_error_io_error">E/A Error</string>
diff --git a/core/src/main/res/values-es-rES/strings.xml b/core/src/main/res/values-es-rES/strings.xml
index 48ff1570b..d05c34876 100644
--- a/core/src/main/res/values-es-rES/strings.xml
+++ b/core/src/main/res/values-es-rES/strings.xml
@@ -72,7 +72,7 @@
<string name="download_error_connection_error">Error de conexión</string>
<string name="download_error_unknown_host">Host desconocido</string>
<string name="cancel_all_downloads_label">Cancelar todas las descargas</string>
- <string name="download_cancelled_msg">Descarga cancelada</string>
+ <string name="download_canceled_msg">Descarga cancelada</string>
<string name="download_report_title">Descargas completadas</string>
<string name="download_error_malformed_url">URL malformada</string>
<string name="download_error_io_error">Error de E/S</string>
diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml
index 44421240b..eb1d1631b 100644
--- a/core/src/main/res/values-es/strings.xml
+++ b/core/src/main/res/values-es/strings.xml
@@ -88,6 +88,7 @@
<string name="remove_episode_lable">Quitar episodio</string>
<string name="mark_read_label">Marcar como leído</string>
<string name="mark_unread_label">Marcar como no leído</string>
+ <string name="marked_as_read_label">Marcado como leído</string>
<string name="add_to_queue_label">Añadir a la cola</string>
<string name="remove_from_queue_label">Quitar de la cola</string>
<string name="visit_website_label">Visitar el sitio web</string>
@@ -111,7 +112,7 @@
<string name="download_error_unknown_host">Host desconocido</string>
<string name="download_error_unauthorized">Error de autenticación</string>
<string name="cancel_all_downloads_label">Cancelar todas las descargas</string>
- <string name="download_cancelled_msg">Descarga cancelada</string>
+ <string name="download_canceled_msg">Descarga cancelada</string>
<string name="download_report_title">Descargas completadas</string>
<string name="download_error_malformed_url">URL con formato incorrecto</string>
<string name="download_error_io_error">Error de E/S</string>
diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml
index 617df3f8f..141b84bf7 100644
--- a/core/src/main/res/values-fr/strings.xml
+++ b/core/src/main/res/values-fr/strings.xml
@@ -112,7 +112,7 @@
<string name="download_error_unknown_host">Hôte inconnu</string>
<string name="download_error_unauthorized">Erreur d\'authentification</string>
<string name="cancel_all_downloads_label">Annuler tous les téléchargements</string>
- <string name="download_cancelled_msg">Téléchargement annulé</string>
+ <string name="download_canceled_msg">Téléchargement annulé</string>
<string name="download_report_title">Téléchargements terminés</string>
<string name="download_error_malformed_url">URL incorrecte</string>
<string name="download_error_io_error">Erreur d\'E/S</string>
diff --git a/core/src/main/res/values-hi-rIN/strings.xml b/core/src/main/res/values-hi-rIN/strings.xml
index 7a43ba15b..f32c7c02f 100644
--- a/core/src/main/res/values-hi-rIN/strings.xml
+++ b/core/src/main/res/values-hi-rIN/strings.xml
@@ -91,7 +91,7 @@
<string name="download_error_connection_error">कनेक्शन त्रुटि</string>
<string name="download_error_unknown_host">अज्ञात होस्ट</string>
<string name="cancel_all_downloads_label">सभी डाउनलोड रद्द करें</string>
- <string name="download_cancelled_msg">डाउनलोड रद्द</string>
+ <string name="download_canceled_msg">डाउनलोड रद्द</string>
<string name="download_report_title">डाउनलोड पूरा हो गया है</string>
<string name="download_error_malformed_url">गलत URL</string>
<string name="download_error_io_error">आईओ त्रुटि</string>
diff --git a/core/src/main/res/values-it-rIT/strings.xml b/core/src/main/res/values-it-rIT/strings.xml
index 52873d892..361657b6b 100644
--- a/core/src/main/res/values-it-rIT/strings.xml
+++ b/core/src/main/res/values-it-rIT/strings.xml
@@ -60,7 +60,7 @@
<string name="auto_download_label">Includi nei download automatici</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL del feed</string>
- <string name="etxtFeedurlHint">URL del feed o del sito web</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">Aggiungi un Podcast tramite URL</string>
<string name="podcastdirectories_label">Trova un podcast nella directory</string>
<string name="podcastdirectories_descr">Puoi cercare dei nuovi podcast in base al nome, alla categoria o alla popolarità nella directory di gpodder.net.</string>
@@ -85,6 +85,7 @@
<string name="remove_episode_lable">Rimuovi l\'episodio</string>
<string name="mark_read_label">Segna come letto</string>
<string name="mark_unread_label">Segna come non letto</string>
+ <string name="marked_as_read_label">Segnato come letto</string>
<string name="add_to_queue_label">Aggiungi alla coda</string>
<string name="remove_from_queue_label">Rimuovi dalla coda</string>
<string name="visit_website_label">Visita il sito</string>
@@ -108,7 +109,7 @@
<string name="download_error_unknown_host">Host sconosciuto</string>
<string name="download_error_unauthorized">Errore di autenticazione</string>
<string name="cancel_all_downloads_label">Annulla tutti i download</string>
- <string name="download_cancelled_msg">Download annullato</string>
+ <string name="download_canceled_msg">Download annullato</string>
<string name="download_report_title">Download completati</string>
<string name="download_error_malformed_url">URL malformato</string>
<string name="download_error_io_error">Errore IO</string>
@@ -225,6 +226,7 @@
<string name="pref_autodl_wifi_filter_sum">Abilita il download automatico solo per alcune reti Wi-Fi selezionate.</string>
<string name="pref_automatic_download_on_battery_title">Scarica quando la batteria non è in carica</string>
<string name="pref_automatic_download_on_battery_sum">Permetti il download automatico quando la batteria non è in carica</string>
+ <string name="pref_parallel_downloads_title">Download paralleli</string>
<string name="pref_episode_cache_title">Cache degli episodi</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
@@ -356,4 +358,5 @@
<string name="authentication_descr">Cambia il tuo nome utente e la tua password per questo podcast e i suoi episodi.</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Importazione di sottoscrizioni da applicazioni monouso in corso...</string>
+ <string name="search_itunes_label">Cerca su iTunes</string>
</resources>
diff --git a/core/src/main/res/values-iw-rIL/strings.xml b/core/src/main/res/values-iw-rIL/strings.xml
index dd129457d..9e9c0e6bc 100644
--- a/core/src/main/res/values-iw-rIL/strings.xml
+++ b/core/src/main/res/values-iw-rIL/strings.xml
@@ -58,6 +58,7 @@
<string name="close_label">סגור</string>
<string name="retry_label">נסה שוב</string>
<string name="auto_download_label">כלול בהורדות אוטומטיות</string>
+ <string name="parallel_downloads_suffix">\u0020הורדות במקביל</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">כתובת הזנה</string>
<string name="etxtFeedurlHint">כתובת של הזנה או אתר אינטרנט</string>
@@ -68,6 +69,8 @@
<!--Actions on feeds-->
<string name="mark_all_read_label">סמן הכל כנקרא</string>
<string name="mark_all_read_msg">סמן את כל הפרקים כנקרא</string>
+ <string name="mark_all_read_confirmation_msg">אנא אשר שאתה רוצה לסמן את כל פרקים כנקראים.</string>
+ <string name="mark_all_read_feed_confirmation_msg">אנא אשר שאתה רוצה לסמן את כל פרקים בהזנה זו כנקראים.</string>
<string name="show_info_label">הצג מידע</string>
<string name="remove_feed_label">הסר פודקאסט</string>
<string name="share_link_label">שתף קישור אתר</string>
@@ -85,6 +88,7 @@
<string name="remove_episode_lable">הסר פרק</string>
<string name="mark_read_label">סמן כנקרא</string>
<string name="mark_unread_label">סמן כלא נקרא</string>
+ <string name="marked_as_read_label">סומן כנקרא</string>
<string name="add_to_queue_label">הוסף לתור</string>
<string name="remove_from_queue_label">הסר מהתור</string>
<string name="visit_website_label">בקר באתר</string>
@@ -108,7 +112,7 @@
<string name="download_error_unknown_host">שרת לא ידוע</string>
<string name="download_error_unauthorized">שגיאת אימות</string>
<string name="cancel_all_downloads_label">בטל את כל ההורדות</string>
- <string name="download_cancelled_msg">הורדה בוטלה</string>
+ <string name="download_canceled_msg">הורדה בוטלה</string>
<string name="download_report_title">הורדות הושלמו</string>
<string name="download_error_malformed_url">כתובת אתר שגויה</string>
<string name="download_error_io_error">שגיאת קלט פלט</string>
@@ -150,6 +154,7 @@
<string name="duration">משך</string>
<string name="ascending">בסדר עולה</string>
<string name="descending">בסדר יורד</string>
+ <string name="clear_queue_confirmation_msg">אנא אשר שאתה רוצה לנקות את התור מכל הפרקים שבו</string>
<!--Flattr-->
<string name="flattr_auth_label">כניסה ל-Fattr</string>
<string name="flattr_auth_explanation">לחץ על הכפתור למטה כדי להתחיל את תהליך האימות. אתה תועבר למסך כניסת flattr בדפדפן שלך ותתבקש לתת לאנטנה-פוד רשות לתרום באמצעות flattr. לאחר שקבלת אישור, תוכל לחזור למסך זה באופן אוטומטי.</string>
@@ -226,6 +231,7 @@
<string name="pref_autodl_wifi_filter_sum">אפשר הורדה אוטומטית דרך רשתות אלחוטייות נבחרות.</string>
<string name="pref_automatic_download_on_battery_title">הורדה כשלא טוען</string>
<string name="pref_automatic_download_on_battery_sum">אפשר הורדה אוטומטית כשהסוללה אינה נטענת</string>
+ <string name="pref_parallel_downloads_title">הורדות במקביל</string>
<string name="pref_episode_cache_title">מטמון פרקים</string>
<string name="pref_theme_title_light">בהיר</string>
<string name="pref_theme_title_dark">כהה</string>
@@ -250,6 +256,8 @@
<string name="pref_persistNotify_title">פקדי הפעלה קבועים</string>
<string name="pref_persistNotify_sum">שמר בקרי הודעה ומסך נעילה בעת השהיית השמעה.</string>
<string name="pref_expand_notify_unsupport_toast">גרסאות אנדרויד לפני 4.1 לא תומכות בהודעות מורחבות.</string>
+ <string name="pref_queueAddToFront_sum">הוסף פרקים חדשים לראש התור.</string>
+ <string name="pref_queueAddToFront_title">הוסף לראש התור.</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">הפעל תרומות flattr אוטומטיות</string>
<string name="auto_flattr_after_percent">תרום באמצעות flattr כשנוגן %d אחוזים מהפרק</string>
@@ -264,6 +272,9 @@
<string name="found_in_title_label">נמצא בכותרת</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">קבצי OPML יאפשרו לכך לנייד פודקאסטים מלוכד פודקאסטים אחד למשנו.</string>
+ <string name="opml_import_explanation_1">בחר נתיב קובץ ספציפי במערכת הקבצים המקומית.</string>
+ <string name="opml_import_explanation_2">השתמש ביישומים חיצוניים כמו Dropbox, Google Drive או מנהל הקבצים האהוב עליך לפתוח קובץ OPML.</string>
+ <string name="opml_import_explanation_3">יישומים רבים כמו Google Mail, Dropbox, Google Drive ורוב מנהלי הקבצים יכולים <i>לפתוח</i> קבצי OPML <i>עם</i> אנטנה-פוד.</string>
<string name="start_import_label">התחל יבוא</string>
<string name="opml_import_label">יבוא OPML</string>
<string name="opml_directory_error">שגיאה!</string>
@@ -272,6 +283,8 @@
<string name="opml_import_error_dir_empty">ספריית היבוא ריקה.</string>
<string name="select_all_label">בחר הכל</string>
<string name="deselect_all_label">בטל בחירות</string>
+ <string name="choose_file_from_filesystem">ממערכת הקבצים המקומית</string>
+ <string name="choose_file_from_external_application">השתמש באפליקציה חיצונית</string>
<string name="opml_export_label">יצוא OPML</string>
<string name="exporting_label">מייצא...</string>
<string name="export_error_label">שגיאת יצוא</string>
@@ -357,4 +370,5 @@
<string name="authentication_descr">שנה את שם המשתמש והסיסמה שלך לפודקאסט ופרקים שלו.</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">מייבא רישום מאפליקציות יעודיות...</string>
+ <string name="search_itunes_label">חפש בiTunes</string>
</resources>
diff --git a/core/src/main/res/values-ja/strings.xml b/core/src/main/res/values-ja/strings.xml
index df73db23e..9021b2b42 100644
--- a/core/src/main/res/values-ja/strings.xml
+++ b/core/src/main/res/values-ja/strings.xml
@@ -112,7 +112,7 @@
<string name="download_error_unknown_host">ホスト不明</string>
<string name="download_error_unauthorized">認証エラー</string>
<string name="cancel_all_downloads_label">すべてのダウンロードをキャンセル</string>
- <string name="download_cancelled_msg">ダウンロードをキャンセルしました</string>
+ <string name="download_canceled_msg">ダウンロードをキャンセルしました</string>
<string name="download_report_title">ダウンロードが完了しました</string>
<string name="download_error_malformed_url">不正な形式のURL</string>
<string name="download_error_io_error">IOエラー</string>
diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml
index 8e8a264f4..7830be329 100644
--- a/core/src/main/res/values-ko/strings.xml
+++ b/core/src/main/res/values-ko/strings.xml
@@ -58,16 +58,19 @@
<string name="close_label">닫기</string>
<string name="retry_label">다시 시도</string>
<string name="auto_download_label">자동 다운로드에 포함</string>
+ <string name="parallel_downloads_suffix">\u0020동시 다운로드</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">피드 URL</string>
<string name="etxtFeedurlHint">피드의 URL 또는 홈페이지</string>
<string name="txtvfeedurl_label">URL로 팟캐스트를 추가</string>
<string name="podcastdirectories_label">디렉터리에서 팟캐스트 찾기</string>
- <string name="podcastdirectories_descr">gpodder.net 디렉터리에서 이름, 분류, 인기에 따라 새 팟캐스트를 검색할 수 있습니다</string>
+ <string name="podcastdirectories_descr">gpodder.net 디렉터리에서 이름, 분류, 인기에 따라 새 팟캐스트를 검색할 수 있고, iTunes 스토어에서 검색할 수도 있습니다.</string>
<string name="browse_gpoddernet_label">gpodder.net 둘러보기</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">모두 읽은 것으로 표시</string>
<string name="mark_all_read_msg">모든 에피소드 읽은 것으로 표시</string>
+ <string name="mark_all_read_confirmation_msg">에피소드 모두를 읽은 것으로 표시하는지 확인하십시오.</string>
+ <string name="mark_all_read_feed_confirmation_msg">이 피드의 에피소드 모두를 읽은 것으로 표시하는지 확인하십시오.</string>
<string name="show_info_label">정보 표시</string>
<string name="remove_feed_label">팟캐스트 제거</string>
<string name="share_link_label">홈페이지 링크 공유</string>
@@ -85,6 +88,7 @@
<string name="remove_episode_lable">에피소드 제거</string>
<string name="mark_read_label">읽은 것으로 표시</string>
<string name="mark_unread_label">읽지 않은 것으로 표시</string>
+ <string name="marked_as_read_label">읽은 것으로 표시</string>
<string name="add_to_queue_label">대기열에 추가</string>
<string name="remove_from_queue_label">대기열에서 제거</string>
<string name="visit_website_label">홈페이지 보기</string>
@@ -108,7 +112,7 @@
<string name="download_error_unknown_host">알 수 없는 호스트</string>
<string name="download_error_unauthorized">인증 오류</string>
<string name="cancel_all_downloads_label">모든 다운로드 취소</string>
- <string name="download_cancelled_msg">다운로드 취소됨</string>
+ <string name="download_canceled_msg">다운로드 취소됨</string>
<string name="download_report_title">다운로드 마침</string>
<string name="download_error_malformed_url">URL 형식 틀림</string>
<string name="download_error_io_error">입출력 오류</string>
@@ -150,6 +154,7 @@
<string name="duration">기간</string>
<string name="ascending">오름차순</string>
<string name="descending">내림차순</string>
+ <string name="clear_queue_confirmation_msg">내부의 모든 에피소드 대기열을 지울지 확인하십시오.</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr 로그인</string>
<string name="flattr_auth_explanation">인증 절차를 시작하려면 아래 버튼을 누르십시오. 브라우저의 Flattr 로그인 화면으로 이동하고, 안테나팟에 Flattr를 사용을 허락 여부를 물어봅니다. 허락을 하면 자동으로 이 화면으로 돌아옵니다.</string>
@@ -225,6 +230,7 @@
<string name="pref_autodl_wifi_filter_sum">선택한 Wi-Fi 네트워크에 대해서만 자동 다운로드를 허용합니다.</string>
<string name="pref_automatic_download_on_battery_title">충전하지 않을 때 다운로드</string>
<string name="pref_automatic_download_on_battery_sum">배터리 충전 중이 아닐 때 자동 다운로드 허용</string>
+ <string name="pref_parallel_downloads_title">동시 다운로드</string>
<string name="pref_episode_cache_title">에피소드 임시 저장</string>
<string name="pref_theme_title_light">밝게</string>
<string name="pref_theme_title_dark">어둡게</string>
@@ -249,6 +255,8 @@
<string name="pref_persistNotify_title">재생 조작 고정</string>
<string name="pref_persistNotify_sum">재생이 일시 중지했을 때에도 알림과 잠금 화면의 조작 기능 유지</string>
<string name="pref_expand_notify_unsupport_toast">안드로이드 4.1 전 버전에서는 알림 확장을 지원하지 않습니다.</string>
+ <string name="pref_queueAddToFront_sum">새 에피소드를 대기열 앞에 추가합니다.</string>
+ <string name="pref_queueAddToFront_title">대기열 앞에 추가</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">자동 flattr 사용</string>
<string name="auto_flattr_after_percent">%d 퍼센트를 재생하면 에피소드에 flattr합니다</string>
@@ -263,6 +271,9 @@
<string name="found_in_title_label">제목에서 발견</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML 파일을 이용하면 팟캐스트 목록을 한 팟캐스트 프로그램에서 다른 팟캐스트 프로그램으로 옮길 수 있습니다.</string>
+ <string name="opml_import_explanation_1">로컬 파일시스템의 특정 파일 경로를 선택하십시오.</string>
+ <string name="opml_import_explanation_2">OPML 파일을 여는데 Dropbox, Google Drive, 또는 파일 관리자 와 같은 외부 앱을 사용합니다.</string>
+ <string name="opml_import_explanation_3">Google Mail, Dropbox, Google Drive 및 대부분의 파일 관리자는 OPML 파일을 안테나팟<i>으로</i> <i>열 수</i> 있습니다.</string>
<string name="start_import_label">가져오기 시작</string>
<string name="opml_import_label">OPML 가져오기</string>
<string name="opml_directory_error">오류!</string>
@@ -271,6 +282,8 @@
<string name="opml_import_error_dir_empty">가져오기 디렉터리가 비어 있습니다.</string>
<string name="select_all_label">모두 선택</string>
<string name="deselect_all_label">모두 선택 해제</string>
+ <string name="choose_file_from_filesystem">로컬 파일시스템에서</string>
+ <string name="choose_file_from_external_application">외부 앱 사용</string>
<string name="opml_export_label">OPML 내보내기</string>
<string name="exporting_label">내보내는 중...</string>
<string name="export_error_label">내보내기 오류</string>
@@ -356,4 +369,5 @@
<string name="authentication_descr">이 팟캐스트와 에피소드에 대한 사용자 이름과 비밀번호를 바꿉니다.</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">단일 용도 앱에서 구독 정보를 가져옵니다...</string>
+ <string name="search_itunes_label">iTunes 검색</string>
</resources>
diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml
index 66ffaaf87..0f447d54a 100644
--- a/core/src/main/res/values-nl/strings.xml
+++ b/core/src/main/res/values-nl/strings.xml
@@ -86,7 +86,7 @@
<string name="download_error_unknown_host">Onbekende host</string>
<string name="download_error_unauthorized">Authenticatie fout</string>
<string name="cancel_all_downloads_label">Alle downloads annuleren</string>
- <string name="download_cancelled_msg">Download geannuleerd</string>
+ <string name="download_canceled_msg">Download geannuleerd</string>
<string name="download_report_title">Downloads afgerond</string>
<string name="download_error_malformed_url">Misvormde URL</string>
<string name="download_error_io_error">IO fout</string>
diff --git a/core/src/main/res/values-pl-rPL/strings.xml b/core/src/main/res/values-pl-rPL/strings.xml
index 6e5c2ce44..ba1a0bb91 100644
--- a/core/src/main/res/values-pl-rPL/strings.xml
+++ b/core/src/main/res/values-pl-rPL/strings.xml
@@ -108,7 +108,7 @@
<string name="download_error_unknown_host">Nieznany host</string>
<string name="download_error_unauthorized">Błąd autoryzacji</string>
<string name="cancel_all_downloads_label">Anuluj wszystkie pobierania</string>
- <string name="download_cancelled_msg">Pobieranie anulowane</string>
+ <string name="download_canceled_msg">Pobieranie anulowane</string>
<string name="download_report_title">Pobieranie ukończone</string>
<string name="download_error_malformed_url">Niepoprawny adres</string>
<string name="download_error_io_error">Błąd wejścia/wyjścia</string>
diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml
index aba186c1a..c3523acfb 100644
--- a/core/src/main/res/values-pt-rBR/strings.xml
+++ b/core/src/main/res/values-pt-rBR/strings.xml
@@ -85,7 +85,7 @@
<string name="download_error_connection_error">Erro de conexão</string>
<string name="download_error_unknown_host">Host desconhecido</string>
<string name="cancel_all_downloads_label">Cancelar todos os downloads</string>
- <string name="download_cancelled_msg">Download cancelado</string>
+ <string name="download_canceled_msg">Download cancelado</string>
<string name="download_report_title">Downloads finalizados</string>
<string name="download_error_malformed_url">URL inválida</string>
<string name="download_error_io_error">Erro de IO</string>
diff --git a/core/src/main/res/values-pt/strings.xml b/core/src/main/res/values-pt/strings.xml
index 9ef8474ee..d9e201e21 100644
--- a/core/src/main/res/values-pt/strings.xml
+++ b/core/src/main/res/values-pt/strings.xml
@@ -112,7 +112,7 @@
<string name="download_error_unknown_host">Servidor desconhecido</string>
<string name="download_error_unauthorized">Erro de autenticação</string>
<string name="cancel_all_downloads_label">Cancelar transferências</string>
- <string name="download_cancelled_msg">Transferência cancelada</string>
+ <string name="download_canceled_msg">Transferência cancelada</string>
<string name="download_report_title">Transferências terminadas</string>
<string name="download_error_malformed_url">URL inválido</string>
<string name="download_error_io_error">Erro I/O</string>
diff --git a/core/src/main/res/values-ro-rRO/strings.xml b/core/src/main/res/values-ro-rRO/strings.xml
index 7bfb99f9d..390f50767 100644
--- a/core/src/main/res/values-ro-rRO/strings.xml
+++ b/core/src/main/res/values-ro-rRO/strings.xml
@@ -80,7 +80,7 @@
<string name="download_error_connection_error">Eroare de conexiune</string>
<string name="download_error_unknown_host">Host necunoscut</string>
<string name="cancel_all_downloads_label">Anulează toate descărcările</string>
- <string name="download_cancelled_msg">Descărcare anulată</string>
+ <string name="download_canceled_msg">Descărcare anulată</string>
<string name="download_report_title">Descărcări terminate</string>
<string name="download_error_malformed_url">URL malformat</string>
<string name="download_error_io_error">Eroare IO</string>
diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml
index 8e209a5ad..187a92861 100644
--- a/core/src/main/res/values-ru/strings.xml
+++ b/core/src/main/res/values-ru/strings.xml
@@ -58,20 +58,26 @@
<string name="close_label">Закрыть</string>
<string name="retry_label">Повторить</string>
<string name="auto_download_label">Добавить в автозагрузки</string>
+ <string name="parallel_downloads_suffix">\u0020одновременных загрузок</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL канала</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">Добавить подкаст по URL</string>
<string name="podcastdirectories_label">Найти подкаст в каталоге</string>
+ <string name="podcastdirectories_descr">Вы можете искать новые подкасты по имени, категории или популярности в каталоге gpodder.net и в магазине iTunes.</string>
<string name="browse_gpoddernet_label">Просмотр gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Отметить как прослушанное</string>
<string name="mark_all_read_msg">Отметить все выпуски как прослушанные</string>
+ <string name="mark_all_read_confirmation_msg">Подтвердите, что хотите пометить все эпизоды как прослушанные.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Подтвердите, что хотите пометить все эпизоды в этом канале как прослушанные.</string>
<string name="show_info_label">Показать информацию</string>
<string name="remove_feed_label">Удалить подкаст</string>
<string name="share_link_label">Поделиться ссылкой на сайт</string>
<string name="share_source_label">Ссылка на канал</string>
<string name="feed_delete_confirmation_msg">Подтвердите удаление канала и всех выпусков, загруженных с этого канала.</string>
<string name="feed_remover_msg">Удаление канала</string>
+ <string name="load_complete_feed">Обновить весь канал</string>
<!--actions on feeditems-->
<string name="download_label">Загрузить</string>
<string name="play_label">Воспроизвести</string>
@@ -82,6 +88,7 @@
<string name="remove_episode_lable">Удалить</string>
<string name="mark_read_label">Отметить как прочитанное</string>
<string name="mark_unread_label">Отметить как непрочитанное</string>
+ <string name="marked_as_read_label">Помечено как прослушанное</string>
<string name="add_to_queue_label">Добавить в очередь</string>
<string name="remove_from_queue_label">Удалить из очереди</string>
<string name="visit_website_label">Посетить сайт</string>
@@ -105,7 +112,7 @@
<string name="download_error_unknown_host">Неизвестный узел</string>
<string name="download_error_unauthorized">Ошибка авторизации</string>
<string name="cancel_all_downloads_label">Отменить все загрузки</string>
- <string name="download_cancelled_msg">Загрузка отменена</string>
+ <string name="download_canceled_msg">Загрузка отменена</string>
<string name="download_report_title">Загрузки завершены</string>
<string name="download_error_malformed_url">Неправильный адрес</string>
<string name="download_error_io_error">Ошибка ввода-вывода</string>
@@ -146,6 +153,7 @@
<string name="duration">По продолжительности</string>
<string name="ascending">По возрастанию</string>
<string name="descending">По убыванию</string>
+ <string name="clear_queue_confirmation_msg">Подтвердите, что хотите очистить очередь от ВСЕХ эпизодов.</string>
<!--Flattr-->
<string name="flattr_auth_label">Авторизоваться в Flattr</string>
<string name="flattr_auth_explanation">Нажмите кнопку, чтобы начать процесс авторизации. Вы будете перенаправлены на сайт Flattr, где нужно будет разрешить AntennaPod использовать ваш аккаунт. После этого вы автоматически будете перенаправлены обратно.</string>
@@ -218,6 +226,7 @@
<string name="pref_autodl_wifi_filter_sum">Разрешать автоматическую загрузку только для выбранных сетей Wi-Fi.</string>
<string name="pref_automatic_download_on_battery_title">Загружать без зарядки</string>
<string name="pref_automatic_download_on_battery_sum">Разрешать автоматическую загрузку когда батарея не заряжается</string>
+ <string name="pref_parallel_downloads_title">Одновременные загрузки</string>
<string name="pref_episode_cache_title">Кэш выпусков</string>
<string name="pref_theme_title_light">Светлая</string>
<string name="pref_theme_title_dark">Тёмная</string>
@@ -243,6 +252,9 @@
<string name="pref_persistNotify_sum">Сохранять уведомление и кнопки воспроизведения на экране блокировки во время паузы.</string>
<string name="pref_expand_notify_unsupport_toast">Версии Android ниже 4.1 не поддерживают расширенные уведомления.</string>
<!--Auto-Flattr dialog-->
+ <string name="auto_flattr_after_percent">Поддерживать через Flattr эпизоды, прослушанные на %d процентов</string>
+ <string name="auto_flattr_ater_beginning">Поддерживать эпизод через Flattr в начале воспроизведения</string>
+ <string name="auto_flattr_ater_end">Поддерживать эпизод через Flattr в конце воспроизведения</string>
<!--Search-->
<string name="search_hint">Поиск каналов или выпусков</string>
<string name="found_in_shownotes_label">Найдено в описании выпуска</string>
@@ -252,6 +264,7 @@
<string name="found_in_title_label">Найдено в заголовке</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML файлы позволяют перемещать ваши подкасты из одного менеджера подкастов в другой.</string>
+ <string name="opml_import_explanation_1">Укажите путь к файлу на устройстве</string>
<string name="start_import_label">Начать импорт</string>
<string name="opml_import_label">Импорт OPML</string>
<string name="opml_directory_error">Ошибка</string>
@@ -282,6 +295,7 @@
<string name="gpodnetauth_login_title">Войти</string>
<string name="gpodnetauth_login_descr">Добро пожаловать в процесс авторизации на gpodder.net. Сначала введите вашу информацию для авторизации:</string>
<string name="gpodnetauth_login_butLabel">Войти</string>
+ <string name="gpodnetauth_login_register">Если у вас ещё нет аккаунта, вы можете создать его здесь:\nhttps://gpodder.net/register/</string>
<string name="username_label">Имя пользователя</string>
<string name="password_label">Пароль</string>
<string name="gpodnetauth_device_title">Выбор устройства</string>
@@ -342,4 +356,5 @@
<string name="authentication_label">Авторизация</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Импорт подписок из одноцелевых приложений…</string>
+ <string name="search_itunes_label">Поиск в iTunes</string>
</resources>
diff --git a/core/src/main/res/values-sv-rSE/strings.xml b/core/src/main/res/values-sv-rSE/strings.xml
index 4e468c1e1..e70ca5d3f 100644
--- a/core/src/main/res/values-sv-rSE/strings.xml
+++ b/core/src/main/res/values-sv-rSE/strings.xml
@@ -112,7 +112,7 @@
<string name="download_error_unknown_host">Okänd värd</string>
<string name="download_error_unauthorized">Autentiseringsproblem</string>
<string name="cancel_all_downloads_label">Avbryt alla nedladdningar</string>
- <string name="download_cancelled_msg">Nedladdning avbruten</string>
+ <string name="download_canceled_msg">Nedladdning avbruten</string>
<string name="download_report_title">Nedladdningar färdiga</string>
<string name="download_error_malformed_url">Felaktig webbadress</string>
<string name="download_error_io_error">IO fel</string>
diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml
index 52b92157e..e83c9b48e 100644
--- a/core/src/main/res/values-tr/strings.xml
+++ b/core/src/main/res/values-tr/strings.xml
@@ -58,14 +58,19 @@
<string name="close_label">Kapat</string>
<string name="retry_label">Yeniden dene</string>
<string name="auto_download_label">Otomatik indirmelere dahil et</string>
+ <string name="parallel_downloads_suffix">\u0020paralel indirmeler</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Besleme Adresi</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
<string name="txtvfeedurl_label">URL ile cep yayını ekle</string>
<string name="podcastdirectories_label">Dizinde cep yayını bul</string>
+ <string name="podcastdirectories_descr">gdpodder.net dizininde yeni cep yayınlarını isme, kategoriye veya popülerliğe göre arayabilirsiniz veya iTunes mağazasında arama yapabilirsiniz.</string>
<string name="browse_gpoddernet_label">gpodder.net\'e gözat</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Hepsini okundu olarak işaretle</string>
<string name="mark_all_read_msg">Tüm bölümler okundu olarak işaretlendi</string>
+ <string name="mark_all_read_confirmation_msg">Lütfen tüm bölümleri okundu olarak işaretlemek istediğinizi onaylayın.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Lütfen bu besleme içindeki tüm bölümleri okundu olarak işaretlemek istediğinizi onaylayın.</string>
<string name="show_info_label">Bilgiyi göster</string>
<string name="remove_feed_label">Cep yayını kaldır</string>
<string name="share_link_label">Web sayfası bağlantısı paylaş</string>
@@ -83,6 +88,7 @@
<string name="remove_episode_lable">Bölümü kaldır</string>
<string name="mark_read_label">Okundu olarak işaretle</string>
<string name="mark_unread_label">Okunmadı olarak işaretle</string>
+ <string name="marked_as_read_label">Okundu olarak işaretlendi</string>
<string name="add_to_queue_label">Kuyruğa Ekle</string>
<string name="remove_from_queue_label">Kuyruktan Kaldır</string>
<string name="visit_website_label">Siteyi Ziyaret Et</string>
@@ -106,7 +112,7 @@
<string name="download_error_unknown_host">Bilinmeyen sunucu</string>
<string name="download_error_unauthorized">Yetkilendirme hatası</string>
<string name="cancel_all_downloads_label">Bütün indirmeleri iptal et</string>
- <string name="download_cancelled_msg">İndirme iptal edildi</string>
+ <string name="download_canceled_msg">İndirme iptal edildi</string>
<string name="download_report_title">İndirme tamamlandı</string>
<string name="download_error_malformed_url">Bozuk URL</string>
<string name="download_error_io_error">G/Ç Hatası</string>
@@ -148,6 +154,7 @@
<string name="duration">Süre</string>
<string name="ascending">Artan</string>
<string name="descending">Azalan</string>
+ <string name="clear_queue_confirmation_msg">Lütfen içerisindeki BÜTÜN ölümlerle birlikte kuyruğu temizleme isteğinizi onaylayın.</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr giriş</string>
<string name="flattr_auth_explanation">Yetkilendirme işlemini başlatmak için aşağıdaki butona basın. Tarayıcınızda flattr giriş ekranına yönlendirileceksiniz ve AntennaPod\'un flattr ile etkileşime girebilmesi için izniniz istenecek. İzin verdikten sonra otomatik olarak bu ekrana döneceksiniz.</string>
@@ -223,6 +230,7 @@
<string name="pref_autodl_wifi_filter_sum">Seçilen kablosuz ağlar için otomatik indirmeye izin ver.</string>
<string name="pref_automatic_download_on_battery_title">Şarj olmuyorken indir</string>
<string name="pref_automatic_download_on_battery_sum">Pil şarj olmuyorken otomatik indirmeye izin ver</string>
+ <string name="pref_parallel_downloads_title">Paralel indirmeler</string>
<string name="pref_episode_cache_title">Bölüm ön belleği</string>
<string name="pref_theme_title_light">Aydınlık</string>
<string name="pref_theme_title_dark">Karanlık</string>
@@ -247,6 +255,8 @@
<string name="pref_persistNotify_title">Kalıcı oynatma kontrolleri</string>
<string name="pref_persistNotify_sum">Çalma duraklatıldığında bildirim ve ekran kilidi ayarlarını sakla.</string>
<string name="pref_expand_notify_unsupport_toast">Android 4.1 öncesi sürümler genişletilmiş bildirimleri desteklememektedir.</string>
+ <string name="pref_queueAddToFront_sum">Yeni bölümleri kuyruğun önüne ekle.</string>
+ <string name="pref_queueAddToFront_title">Kuyruğun önüne ekle.</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Otomatik Flattr\'lamayı etkinleştir</string>
<string name="auto_flattr_after_percent">Bölümün yüzde %d kısmı oynatıldığında Flattr\'la</string>
@@ -261,6 +271,9 @@
<string name="found_in_title_label">Başlıkta bulundu</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML dosyaları cep yayınlarını bir cihazdan diğerine aktarmanıza yarar.</string>
+ <string name="opml_import_explanation_1">Yerel dosya sisteminden belirli bir yol seçin.</string>
+ <string name="opml_import_explanation_2">OPML dosyasını açmak için harici uygulamalardan Dropbox, Google Drive veya kendi favori dosya yöneticinizi kullanın.</string>
+ <string name="opml_import_explanation_3">Google Mail, Dropbox, Google Drive gibi birçok uygulama ve çoğu dosya yöneticisi OPML dosyalarını AntennaPod <i>ile</i> <i>açabilir.</i></string>
<string name="start_import_label">İçe aktarmayı başlat</string>
<string name="opml_import_label">OPML içe aktar</string>
<string name="opml_directory_error">HATA!</string>
@@ -269,9 +282,12 @@
<string name="opml_import_error_dir_empty">İça aktarma dizini boş</string>
<string name="select_all_label">Hepsini seç</string>
<string name="deselect_all_label">Tüm seçimleri geri al</string>
+ <string name="choose_file_from_filesystem">Yerel dosya sisteminden</string>
+ <string name="choose_file_from_external_application">Harici uygulama kullan</string>
<string name="opml_export_label">OPML dışa aktar</string>
<string name="exporting_label">Dışa aktarılıyor...</string>
<string name="export_error_label">Dışa aktarma hatası</string>
+ <string name="opml_export_success_title">Opml dışa aktarma başarılı.</string>
<string name="opml_export_success_sum">.opml dosyasy yazıldı: \u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Zamanlayıcıyı ayarla</string>
@@ -353,4 +369,5 @@
<string name="authentication_descr">Bu cep yayını ve içerdiği bölümler için kullanıcı adı şifreyi değiştir.</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Üyelikler tek-amaçlı uygulamalardan içe aktarılıyor...</string>
+ <string name="search_itunes_label">iTunes\'da Arama</string>
</resources>
diff --git a/core/src/main/res/values-uk-rUA/strings.xml b/core/src/main/res/values-uk-rUA/strings.xml
index 9ceab72f6..20374232a 100644
--- a/core/src/main/res/values-uk-rUA/strings.xml
+++ b/core/src/main/res/values-uk-rUA/strings.xml
@@ -58,6 +58,7 @@
<string name="close_label">Закрити</string>
<string name="retry_label">Повторити знову</string>
<string name="auto_download_label">Включити до автозавантаження</string>
+ <string name="parallel_downloads_suffix">\u0020паралельні завантаження</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">Посилання на канал</string>
<string name="etxtFeedurlHint">URL канала або сайта</string>
@@ -68,6 +69,8 @@
<!--Actions on feeds-->
<string name="mark_all_read_label">Позначити всі як переглянуті</string>
<string name="mark_all_read_msg">Позначено всі епізоди як переглянуті</string>
+ <string name="mark_all_read_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди як прочитані.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте позначити всі епізоди цього канала як прочитані.</string>
<string name="show_info_label">Інформація</string>
<string name="remove_feed_label">Видалити подкаст</string>
<string name="share_link_label">Поділитися URL сайту</string>
@@ -85,6 +88,7 @@
<string name="remove_episode_lable">Видалити епізод</string>
<string name="mark_read_label">Позначити як переглянутий</string>
<string name="mark_unread_label">Позначити як не переглянутий</string>
+ <string name="marked_as_read_label">Позначено як прочитане</string>
<string name="add_to_queue_label">Додати до черги</string>
<string name="remove_from_queue_label">Видалити з черги</string>
<string name="visit_website_label">Відкрити сайт</string>
@@ -108,7 +112,7 @@
<string name="download_error_unknown_host">Невідомий host</string>
<string name="download_error_unauthorized">Помилка автентифікації</string>
<string name="cancel_all_downloads_label">Скасувати всі завантаження</string>
- <string name="download_cancelled_msg">Відмінено завантаження</string>
+ <string name="download_canceled_msg">Відмінено завантаження</string>
<string name="download_report_title">Завантажили</string>
<string name="download_error_malformed_url">Невірний URL</string>
<string name="download_error_io_error">Помилка IO</string>
@@ -150,6 +154,7 @@
<string name="duration">За тривалістю</string>
<string name="ascending">За зростанням</string>
<string name="descending">За спаданням</string>
+ <string name="clear_queue_confirmation_msg">Будь ласка, підтвердіть що ви бажаєте вилучити всі епізоди з черги.</string>
<!--Flattr-->
<string name="flattr_auth_label">Увійти до Flattr</string>
<string name="flattr_auth_explanation">Нажміть цю кнопку для початку авторізації. Буде відкрито flattr в браузері, буде запит на дозвіл доступу Antennapod до flattr. Після надання доступу ви повернетесь до цього екрану автоматично</string>
@@ -225,6 +230,7 @@
<string name="pref_autodl_wifi_filter_sum">Дозволити автоматичне завантаження тільки в цих Wi-Fi мережах</string>
<string name="pref_automatic_download_on_battery_title">Завантаження без зарядного пристрою</string>
<string name="pref_automatic_download_on_battery_sum">Дозволити завантаження коли зарядний пристрій не підключено</string>
+ <string name="pref_parallel_downloads_title">Паралельні завантаження</string>
<string name="pref_episode_cache_title">Кеш епізодів</string>
<string name="pref_theme_title_light">Світла</string>
<string name="pref_theme_title_dark">Темна</string>
@@ -249,6 +255,8 @@
<string name="pref_persistNotify_title">Завжди показувати елементи керування відтворенням</string>
<string name="pref_persistNotify_sum">Показувати повідомлення та елементи керування на lockscreen в режимі паузи.</string>
<string name="pref_expand_notify_unsupport_toast">Android до версії 4.1 не підтримує розширені повідомлення.</string>
+ <string name="pref_queueAddToFront_sum">Додавати нові епізоди до початку черги.</string>
+ <string name="pref_queueAddToFront_title">Додавати в початок черги.</string>
<!--Auto-Flattr dialog-->
<string name="auto_flattr_enable">Включити автоматичне заохочення авторів через сервіс flattr</string>
<string name="auto_flattr_after_percent">Заохотити автора через Flattr щойно %d відсотків епізода було відтворено</string>
@@ -263,6 +271,9 @@
<string name="found_in_title_label">Знайдено у назві</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">OPML файли дозволяют вам перенести подкасти з однієї программи до іншої</string>
+ <string name="opml_import_explanation_1">Виберіть локальну папку.</string>
+ <string name="opml_import_explanation_2">Вибрати OPML файл за допомогою таких додатків як Dropbox, Google Drive або файловий менеджер.</string>
+ <string name="opml_import_explanation_3">Багато додатків таких як Google Mail, Dropbox, Google Drive та більшість файлових менеджерів здатні <i>відкрити</i> OPML файли <i>для</i> AntennaPod.</string>
<string name="start_import_label">Почати імпорт</string>
<string name="opml_import_label">OPML імпорт</string>
<string name="opml_directory_error">Помилка!</string>
@@ -271,6 +282,8 @@
<string name="opml_import_error_dir_empty">Директорія імпорту пуста</string>
<string name="select_all_label">Обрати все</string>
<string name="deselect_all_label">Убрати виділення</string>
+ <string name="choose_file_from_filesystem">З локальної файлової системи</string>
+ <string name="choose_file_from_external_application">За допомогою додатка</string>
<string name="opml_export_label">OPML экспорт</string>
<string name="exporting_label">Експорт ...</string>
<string name="export_error_label">Помилка експорту</string>
@@ -356,4 +369,5 @@
<string name="authentication_descr">Змінити ваші логін та пароль для подкаста та епізодів</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">Імпорт подкастів з інших програм...</string>
+ <string name="search_itunes_label">Пошук в iTunes</string>
</resources>
diff --git a/core/src/main/res/values-zh-rCN/strings.xml b/core/src/main/res/values-zh-rCN/strings.xml
index d857ea194..0a2b9355e 100644
--- a/core/src/main/res/values-zh-rCN/strings.xml
+++ b/core/src/main/res/values-zh-rCN/strings.xml
@@ -103,7 +103,7 @@
<string name="download_error_unknown_host">未知主机</string>
<string name="download_error_unauthorized">认证错误</string>
<string name="cancel_all_downloads_label">取消所有下载</string>
- <string name="download_cancelled_msg">已取消下载</string>
+ <string name="download_canceled_msg">已取消下载</string>
<string name="download_report_title">下载完成</string>
<string name="download_error_malformed_url">畸形 URL</string>
<string name="download_error_io_error">IO 错误</string>
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index 4bb29ac85..4ecf2cf61 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -10,7 +10,7 @@
</string-array>
- <string-array name="seek_delta_values">
+ <integer-array name="seek_delta_values">
<item>5</item>
<item>10</item>
<item>15</item>
@@ -18,7 +18,7 @@
<item>30</item>
<item>45</item>
<item>60</item>
- </string-array>
+ </integer-array>
<string-array name="update_intervall_options">
<item>Manual</item>
@@ -118,6 +118,7 @@
<string-array name="autodl_select_networks_default_values">
<item>0</item>
</string-array>
+
<string-array name="theme_options">
<item>@string/pref_theme_title_light</item>
<item>@string/pref_theme_title_dark</item>
@@ -126,4 +127,34 @@
<item>0</item>
<item>1</item>
</string-array>
+
+ <string-array name="nav_drawer_titles">
+ <item>@string/queue_label</item>
+ <item>@string/new_episodes_label</item>
+ <item>@string/all_episodes_label</item>
+ <item>@string/downloads_label</item>
+ <item>@string/playback_history_label</item>
+ <item>@string/add_feed_label</item>
+ </string-array>
+
+ <string-array name="episode_hide_options">
+ <item>@string/hide_unplayed_episodes_label</item>
+ <item>@string/hide_paused_episodes_label</item>
+ <item>@string/hide_played_episodes_label</item>
+ <item>@string/hide_queued_episodes_label</item>
+ <item>@string/hide_not_queued_episodes_label</item>
+ <item>@string/hide_downloaded_episodes_label</item>
+ <item>@string/hide_not_downloaded_episodes_label</item>
+ </string-array>
+
+ <string-array name="episode_hide_values">
+ <item>unplayed</item>
+ <item>paused</item>
+ <item>played</item>
+ <item>queued</item>
+ <item>not_queued</item>
+ <item>downloaded</item>
+ <item>not_downloaded</item>
+ </string-array>
+
</resources>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index 368921f76..2bdda2378 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -37,6 +37,9 @@
<attr name="av_ff_big" format="reference"/>
<attr name="av_rew_big" format="reference"/>
<attr name="ic_settings" format="reference"/>
+ <attr name="ic_lock_open" format="reference"/>
+ <attr name="ic_lock_closed" format="reference"/>
+ <attr name="ic_filter" format="reference"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>
diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml
index 81a55142a..c46537b3e 100644
--- a/core/src/main/res/values/dimens.xml
+++ b/core/src/main/res/values/dimens.xml
@@ -16,10 +16,10 @@
<dimen name="thumbnail_length_downloaded_item">64dp</dimen>
<dimen name="thumbnail_length_onlinefeedview">100dp</dimen>
<dimen name="feeditemlist_header_height">132dp</dimen>
- <dimen name="thumbnail_length_navlist">42dp</dimen>
+ <dimen name="thumbnail_length_navlist">40dp</dimen>
<dimen name="listview_secondary_button_width">48dp</dimen>
<dimen name="drawer_width">280dp</dimen>
- <dimen name="listitem_iconwithtext_height">56dp</dimen>
+ <dimen name="listitem_iconwithtext_height">48dp</dimen>
<dimen name="listitem_iconwithtext_textleftpadding">14dp</dimen>
<dimen name="listitem_iconwithtext_textverticalpadding">16dp</dimen>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index bdb3ad606..c6308fa7b 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -32,6 +32,7 @@
<!-- Main activity -->
<string name="drawer_open">Open menu</string>
<string name="drawer_close">Close menu</string>
+ <string name="drawer_preferences">Drawer Preferences</string>
<!-- Webview actions -->
<string name="open_in_browser_label">Open in browser</string>
@@ -48,6 +49,7 @@
<string name="cancel_label">Cancel</string>
<string name="author_label">Author</string>
<string name="language_label">Language</string>
+ <string name="url_label">URL</string>
<string name="podcast_settings_label">Settings</string>
<string name="cover_label">Picture</string>
<string name="error_label">Error</string>
@@ -78,10 +80,10 @@
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
<!-- Actions on feeds -->
- <string name="mark_all_read_label">Mark all as read</string>
- <string name="mark_all_read_msg">Marked all episodes as read</string>
- <string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being read.</string>
- <string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this feed as being read.</string>
+ <string name="mark_all_read_label">Mark all as played</string>
+ <string name="mark_all_read_msg">Marked all episodes as played</string>
+ <string name="mark_all_read_confirmation_msg">Please confirm that you want to mark all episodes as being played.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Please confirm that you want to mark all episodes in this feed as being played.</string>
<string name="show_info_label">Show information</string>
<string name="remove_feed_label">Remove podcast</string>
<string name="share_link_label">Share website link</string>
@@ -89,6 +91,16 @@
<string name="feed_delete_confirmation_msg">Please confirm that you want to delete this feed and ALL episodes of this feed that you have downloaded.</string>
<string name="feed_remover_msg">Removing feed</string>
<string name="load_complete_feed">Refresh complete feed</string>
+ <string name="hide_episodes_title">Hide episodes</string>
+ <string name="hide_unplayed_episodes_label">Unplayed</string>
+ <string name="hide_paused_episodes_label">Paused</string>
+ <string name="hide_played_episodes_label">Played</string>
+ <string name="hide_queued_episodes_label">Queued</string>
+ <string name="hide_not_queued_episodes_label">Not queued</string>
+ <string name="hide_downloaded_episodes_label">Downloaded</string>
+ <string name="hide_not_downloaded_episodes_label">Not downloaded</string>
+ <string name="filtered_label">Filtered</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Last refresh failed</string>
<!-- actions on feeditems -->
<string name="download_label">Download</string>
@@ -102,12 +114,16 @@
<string name="mark_unread_label">Mark as unplayed</string>
<string name="marked_as_read_label">Marked as played</string>
<string name="add_to_queue_label">Add to Queue</string>
+ <string name="added_to_queue_label">Added to Queue</string>
<string name="remove_from_queue_label">Remove from Queue</string>
<string name="visit_website_label">Visit Website</string>
<string name="support_label">Flattr this</string>
<string name="enqueue_all_new">Enqueue all</string>
<string name="download_all">Download all</string>
<string name="skip_episode_label">Skip episode</string>
+ <string name="activate_auto_download">Activate auto download</string>
+ <string name="deactivate_auto_download">Deactivate auto download</string>
+ <string name="reset_position">Reset playback position</string>
<!-- Download messages and labels -->
<string name="download_successful">successful</string>
@@ -125,8 +141,10 @@
<string name="download_error_unknown_host">Unknown host</string>
<string name="download_error_unauthorized">Authentication error</string>
<string name="cancel_all_downloads_label">Cancel all downloads</string>
- <string name="download_cancelled_msg">Download cancelled</string>
- <string name="download_report_title">Downloads completed</string>
+ <string name="download_canceled_msg">Download canceled</string>
+ <string name="download_canceled_autodownload_enabled_msg">Download canceled\nDisabled <i>Auto Download</i> for this item</string>
+ <string name="download_report_title">Downloads completed with error(s)</string>
+ <string name="download_report_content_title">Download report</string>
<string name="download_error_malformed_url">Malformed URL</string>
<string name="download_error_io_error">IO Error</string>
<string name="download_error_request_error">Request error</string>
@@ -142,6 +160,11 @@
<string name="download_request_error_dialog_message_prefix">An error occurred when trying to download the file:\u0020</string>
<string name="authentication_notification_title">Authentication required</string>
<string name="authentication_notification_msg">The resource you requested requires a username and a password</string>
+ <string name="confirm_mobile_download_dialog_title">Confirm Mobile Download</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Downloading over mobile data connection is disabled in the settings.\n\nEnable temporarily or just add to queue?\n\n<small>Your choice will be remember for 10 minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Downloading over mobile data connection is disabled in the settings.\n\nEnable temporarily?\n\n<small>Your choice will be remember for 10 minutes.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Only add to Queue</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Enable temporarily</string>
<!-- Mediaplayer messages -->
<string name="player_error_msg">Error!</string>
@@ -158,6 +181,8 @@
<string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string>
<!-- Queue operations -->
+ <string name="lock_queue">Lock queue</string>
+ <string name="unlock_queue">Unlock queue</string>
<string name="clear_queue_label">Clear queue</string>
<string name="undo">Undo</string>
<string name="removed_from_queue">Item removed</string>
@@ -245,6 +270,8 @@
<string name="pref_auto_flattr_sum">Configure automatic flattring</string>
<string name="user_interface_label">User Interface</string>
<string name="pref_set_theme_title">Select theme</string>
+ <string name="pref_nav_drawer_items_title">Change navigation drawer</string>
+ <string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer.</string>
<string name="pref_set_theme_sum">Change the appearance of AntennaPod.</string>
<string name="pref_automatic_download_title">Automatic download</string>
<string name="pref_automatic_download_sum">Configure the automatic download of episodes.</string>
@@ -268,8 +295,8 @@
<string name="pref_gpodnet_setlogin_information_sum">Change the login information for your gpodder.net account.</string>
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
- <string name="pref_seek_delta_title">Seek time</string>
- <string name="pref_seek_delta_sum">Seek this many seconds when rewinding or fast-forwarding</string>
+ <string name="pref_fast_forward">Fast forward time</string>
+ <string name="pref_rewind">Rewind time</string>
<string name="pref_gpodnet_sethostname_title">Set hostname</string>
<string name="pref_gpodnet_sethostname_use_default_host">Use default host</string>
<string name="pref_expandNotify_title">Expand Notification</string>
@@ -372,6 +399,8 @@
<string name="set_to_default_folder">Choose default folder</string>
<string name="pref_pausePlaybackForFocusLoss_sum">Pause playback instead of lowering volume when another app wants to play sounds</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pause for interruptions</string>
+ <string name="pref_resumeAfterCall_sum">Resume playback after a phone call completes</string>
+ <string name="pref_resumeAfterCall_title">Resume after call</string>
<!-- Online feed view -->
<string name="subscribe_label">Subscribe</string>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index 4ac4a79fd..8619869c8 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -42,6 +42,9 @@
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
+ <item name="attr/ic_lock_open">@drawable/ic_lock_open_grey600_24dp</item>
+ <item name="attr/ic_lock_closed">@drawable/ic_lock_closed_grey600_24dp</item>
+ <item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
</style>
<style name="Theme.AntennaPod.Dark" parent="@style/Theme.AppCompat">
@@ -84,6 +87,9 @@
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
+ <item name="attr/ic_lock_open">@drawable/ic_lock_open_white_24dp</item>
+ <item name="attr/ic_lock_closed">@drawable/ic_lock_closed_white_24dp</item>
+ <item name="attr/ic_filter">@drawable/ic_filter_white_24dp</item>
</style>
<style name="Theme.AntennaPod.Light.NoTitle" parent="@style/Theme.AppCompat.Light.NoActionBar">
@@ -129,6 +135,9 @@
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
+ <item name="attr/ic_lock_open">@drawable/ic_lock_open_grey600_24dp</item>
+ <item name="attr/ic_lock_closed">@drawable/ic_lock_closed_grey600_24dp</item>
+ <item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
</style>
<style name="Theme.AntennaPod.Dark.NoTitle" parent="@style/Theme.AppCompat.NoActionBar">
@@ -173,6 +182,9 @@
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
+ <item name="attr/ic_lock_open">@drawable/ic_lock_open_white_24dp</item>
+ <item name="attr/ic_lock_closed">@drawable/ic_lock_closed_white_24dp</item>
+ <item name="attr/ic_filter">@drawable/ic_filter_white_24dp</item>
</style>
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 3d0dee6e8..b5166dad4 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5909f8dfb..fb3375000 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Nov 28 16:00:13 CET 2014
+#Tue May 19 11:59:21 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
diff --git a/gradlew.bat b/gradlew.bat
index 8a0b282aa..aec99730b 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,90 +1,90 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/library/drag-sort-listview/src/main/java/com/mobeta/android/dslv/DragSortController.java b/library/drag-sort-listview/src/main/java/com/mobeta/android/dslv/DragSortController.java
index 6acf6b42e..fef853d22 100644
--- a/library/drag-sort-listview/src/main/java/com/mobeta/android/dslv/DragSortController.java
+++ b/library/drag-sort-listview/src/main/java/com/mobeta/android/dslv/DragSortController.java
@@ -378,6 +378,12 @@ public class DragSortController extends SimpleFloatViewManager implements View.O
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ // it can happen where the motion events are null
+ if (e1 == null || e2 == null) {
+ // we can't really do anything
+ return false;
+ }
+
final int x1 = (int) e1.getX();
final int y1 = (int) e1.getY();
final int x2 = (int) e2.getX();