diff options
Diffstat (limited to 'app')
113 files changed, 3796 insertions, 1717 deletions
diff --git a/app/build.gradle b/app/build.gradle index 240fd6f9f..cd3fb7aa1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,6 +9,7 @@ repositories { } dependencies { + compile project(":core") compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:design:$supportVersion" @@ -19,7 +20,6 @@ dependencies { compile("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") { exclude group: "org.json", module: "json" } - compile "commons-io:commons-io:$commonsioVersion" compile "org.jsoup:jsoup:$jsoupVersion" compile "com.github.bumptech.glide:glide:$glideVersion" @@ -41,22 +41,22 @@ dependencies { exclude module: "support-v4" } - compile "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" + compile "com.github.shts:TriangleLabelView:$triangleLabelViewVersion" - compile project(":core") + compile "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" } def getMyVersionName() { def parsedManifestXml = (new XmlSlurper()) .parse("${projectDir}/src/main/AndroidManifest.xml") - .declareNamespace(android:"http://schemas.android.com/apk/res/android") + .declareNamespace(android: "http://schemas.android.com/apk/res/android") return parsedManifestXml."@android:versionName" } def getMyVersionCode() { def parsedManifestXml = (new XmlSlurper()) .parse("${projectDir}/src/main/AndroidManifest.xml") - .declareNamespace(android:"http://schemas.android.com/apk/res/android") + .declareNamespace(android: "http://schemas.android.com/apk/res/android") return parsedManifestXml."@android:versionCode".toInteger() } @@ -150,7 +150,7 @@ task filterAbout { from "src/main/templates/about.html" into "src/main/assets" filter(ReplaceTokens, tokens: [versionname: android.defaultConfig.versionName, - commit: "git rev-parse --short HEAD".execute().text]) + commit : "git rev-parse --short HEAD".execute().text]) } } diff --git a/app/proguard.cfg b/app/proguard.cfg index 19ae36f36..a25664490 100644 --- a/app/proguard.cfg +++ b/app/proguard.cfg @@ -64,7 +64,7 @@ -keep class android.support.v4.** { *; } -keep interface android.support.v4.** { *; } --keep class android.support.v7.** { *; } +-keep class !android.support.v7.internal.view.menu.**,android.support.v7.** {*;} -keep interface android.support.v7.** { *; } -dontwarn android.support.v4.** -dontwarn android.support.v7.** @@ -105,3 +105,6 @@ # for ViewPageIndicator problems (https://github.com/JakeWharton/ViewPagerIndicator/issues/366): -dontwarn com.viewpagerindicator.LinePageIndicator + +# for some reason ProGuard removes this file. Why? Unsure. +-keep class de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider { *; }
\ No newline at end of file diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java index 7862e986d..6f8dbcb1a 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java @@ -21,13 +21,14 @@ 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.playback.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.core.service.playback.LocalPSMP; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.playback.Playable; import de.test.antennapod.util.service.download.HTTPBin; /** - * Test class for PlaybackServiceMediaPlayer + * Test class for LocalPSMP */ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { private static final String TAG = "PlaybackServiceMediaPlayerTest"; @@ -53,7 +54,10 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { super.setUp(); assertionError = null; + final Context context = getInstrumentation().getTargetContext(); + // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -62,7 +66,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { httpServer = new HTTPBin(); httpServer.start(); - final Context context = getInstrumentation().getTargetContext(); File cacheDir = context.getExternalFilesDir("testFiles"); if (cacheDir == null) cacheDir = context.getExternalFilesDir("testFiles"); @@ -83,7 +86,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { assertEquals(0, httpServer.serveFile(dest)); } - private void checkPSMPInfo(PlaybackServiceMediaPlayer.PSMPInfo info) { + private void checkPSMPInfo(LocalPSMP.PSMPInfo info) { try { switch (info.playerStatus) { case PLAYING: @@ -109,7 +112,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { public void testInit() { final Context c = getInstrumentation().getTargetContext(); - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, defaultCallback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, defaultCallback); psmp.shutdown(); } @@ -137,7 +140,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(2); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -168,12 +171,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -183,12 +196,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, false, false); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -206,7 +219,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(2); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -237,12 +250,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -252,11 +275,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, true, false); @@ -275,7 +298,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(4); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -309,12 +332,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -324,11 +357,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, false, true); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -345,7 +378,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(5); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -382,12 +415,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -397,12 +440,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); psmp.playMediaObject(p, true, true, true); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -418,7 +461,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(2); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -449,12 +492,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -464,12 +517,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, false, false); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -486,7 +539,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(2); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -517,12 +570,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -532,11 +595,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, true, false); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -553,7 +616,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(4); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -587,12 +650,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -602,11 +675,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, false, true); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -622,7 +695,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(5); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { try { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) @@ -660,12 +733,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -675,11 +758,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); psmp.playMediaObject(p, false, true, true); boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -693,7 +776,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { private final PlaybackServiceMediaPlayer.PSMPCallback defaultCallback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); } @@ -708,12 +791,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { return false; } + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @Override public boolean onMediaPlayerError(Object inObj, int what, int extra) { @@ -721,7 +814,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; @@ -733,7 +826,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) { if (assertionError == null) @@ -776,12 +869,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -793,11 +896,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); if (initialState == PlayerStatus.PLAYING) { psmp.playMediaObject(p, stream, true, true); @@ -854,7 +957,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) { if (assertionError == null) @@ -881,12 +984,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -899,11 +1012,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) { boolean startWhenPrepared = (initialState != PlayerStatus.PREPARED); psmp.playMediaObject(writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL), false, startWhenPrepared, true); @@ -937,7 +1050,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(latchCount); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) { if (assertionError == null) @@ -963,12 +1076,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -980,11 +1103,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); if (initialState == PlayerStatus.INITIALIZED || initialState == PlayerStatus.PLAYING @@ -1032,7 +1155,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { final CountDownLatch countDownLatch = new CountDownLatch(latchCount); PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() { @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + public void statusChanged(LocalPSMP.PSMPInfo newInfo) { checkPSMPInfo(newInfo); if (newInfo.playerStatus == PlayerStatus.ERROR) { if (assertionError == null) @@ -1057,12 +1180,22 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override + public void setSpeedAbilityChanged() { + + } + + @Override + public void onMediaChanged(boolean reloadUI) { + + } + + @Override public void onBufferingUpdate(int percent) { } @Override - public boolean onMediaPlayerInfo(int code) { + public boolean onMediaPlayerInfo(int code, int resourceId) { return false; } @@ -1074,11 +1207,11 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase { } @Override - public boolean endPlayback(boolean playNextEpisode, boolean wasSkipped) { + public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { return false; } }; - PlaybackServiceMediaPlayer psmp = new PlaybackServiceMediaPlayer(c, callback); + PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL); boolean prepareImmediately = initialState != PlayerStatus.INITIALIZED; boolean startImmediately = initialState != PlayerStatus.PREPARED; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index 5c3d32960..6ab6e5c61 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -34,6 +34,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase { super.setUp(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java index 7300df395..5e5eb1e8b 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java @@ -70,6 +70,7 @@ public class DBCleanupTests extends InstrumentationTestCase { assertTrue(destFolder.canWrite()); // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java index 7205b42c4..7925941ec 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java @@ -19,8 +19,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.PodDBAdapter; -import static de.test.antennapod.storage.DBTestUtils.saveFeedlist; - /** * Tests that the APNullCleanupAlgorithm is working correctly. */ @@ -60,6 +58,7 @@ public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase { assertTrue(destFolder.canWrite()); // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); 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 0fc3b1892..9386e3bd6 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java @@ -34,6 +34,7 @@ public class DBReaderTest extends InstrumentationTestCase { super.setUp(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); 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 5b2393d45..785d32e93 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java @@ -5,12 +5,14 @@ import android.test.FlakyTest; import android.test.InstrumentationTestCase; import java.util.ArrayList; +import java.util.Arrays; 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; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; @@ -28,7 +30,6 @@ public class DBTasksTest extends InstrumentationTestCase { @Override protected void tearDown() throws Exception { super.tearDown(); - assertTrue(PodDBAdapter.deleteDatabase()); } @@ -38,6 +39,7 @@ public class DBTasksTest extends InstrumentationTestCase { context = getInstrumentation().getTargetContext(); // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -120,6 +122,32 @@ public class DBTasksTest extends InstrumentationTestCase { updatedFeedTest(feedFromDB, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW); } + public void testUpdateFeedMediaUrlResetState() { + final Feed feed = new Feed("url", null, "title"); + FeedItem item = new FeedItem(0, "item", "id", "link", new Date(), FeedItem.PLAYED, feed); + feed.setItems(Arrays.asList(item)); + + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + adapter.setCompleteFeed(feed); + adapter.close(); + + // ensure that objects have been saved in db, then reset + assertTrue(feed.getId() != 0); + assertTrue(item.getId() != 0); + + FeedMedia media = new FeedMedia(item, "url", 1024, "mime/type"); + item.setMedia(media); + feed.setItems(Arrays.asList(item)); + + final Feed newFeed = DBTasks.updateFeed(context, feed)[0]; + assertTrue(feed != newFeed); + + final Feed feedFromDB = DBReader.getFeed(newFeed.getId()); + final FeedItem feedItemFromDB = feedFromDB.getItems().get(0); + assertTrue("state: " + feedItemFromDB.getState(), feedItemFromDB.isNew()); + } + 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); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java index 0e1d19f7b..40083e507 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java @@ -53,6 +53,7 @@ public class DBWriterTest extends InstrumentationTestCase { super.setUp(); // create new database + PodDBAdapter.init(getInstrumentation().getTargetContext()); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -194,6 +195,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertEquals(0, c.getCount()); c.close(); } + adapter.close(); } public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException { @@ -250,6 +252,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException { @@ -287,6 +290,7 @@ public class DBWriterTest extends InstrumentationTestCase { c = adapter.getImageCursor(String.valueOf(image.getId())); assertTrue(c.getCount() == 0); c.close(); + adapter.close(); } public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException { @@ -339,6 +343,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } public void testDeleteFeedWithItemImages() throws InterruptedException, ExecutionException, TimeoutException, IOException { @@ -397,6 +402,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertEquals(0, c.getCount()); c.close(); } + adapter.close(); } public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException { @@ -527,6 +533,7 @@ public class DBWriterTest extends InstrumentationTestCase { assertTrue(c.getCount() == 0); c.close(); } + adapter.close(); } private FeedMedia playbackHistorySetup(Date playbackCompletionDate) { @@ -730,7 +737,6 @@ public class DBWriterTest extends InstrumentationTestCase { } assertTrue(idFound); } - queue.close(); adapter.close(); } 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 4e214cf81..9a0e11816 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java @@ -41,10 +41,12 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv @Override protected void setUp() throws Exception { super.setUp(); - uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext()); + Context context = getInstrumentation().getTargetContext(); + uiTestUtils = new UITestUtils(context); uiTestUtils.setup(); // create new database + PodDBAdapter.init(context); PodDBAdapter.deleteDatabase(); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); @@ -108,6 +110,12 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv solo.waitForView(android.R.id.list); assertEquals(solo.getString(R.string.episodes_label), getActionbarTitle()); + // Subscriptions + openNavDrawer(); + solo.clickOnText(solo.getString(R.string.subscriptions_label)); + solo.waitForView(R.id.subscriptions_grid); + assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle()); + // downloads openNavDrawer(); solo.clickOnText(solo.getString(R.string.downloads_label)); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index 54741502c..040f4150b 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -90,6 +90,30 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference assertTrue(solo.waitForCondition(() -> persistNotify == UserPreferences.isPersistNotify(), Timeout.getLargeTimeout())); } + public void testSetLockscreenButtons() { + String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options); + solo.clickOnText(solo.getString(R.string.pref_compact_notification_buttons_title)); + solo.waitForDialogToOpen(1000); + // First uncheck every checkbox + for (int i=0; i<buttons.length; i++) { + assertTrue(solo.searchText(buttons[i])); + if (solo.isTextChecked(buttons[i])) { + solo.clickOnText(buttons[i]); + } + } + // Now try to check all checkboxes + solo.clickOnText(buttons[0]); + solo.clickOnText(buttons[1]); + solo.clickOnText(buttons[2]); + // Make sure that the third checkbox is unchecked + assertTrue(!solo.isTextChecked(buttons[2])); + solo.clickOnText(solo.getString(R.string.confirm_label)); + solo.waitForDialogToClose(1000); + assertTrue(solo.waitForCondition(() -> UserPreferences.showRewindOnCompactNotification(), Timeout.getLargeTimeout())); + assertTrue(solo.waitForCondition(() -> UserPreferences.showFastForwardOnCompactNotification(), Timeout.getLargeTimeout())); + assertTrue(solo.waitForCondition(() -> !UserPreferences.showSkipOnCompactNotification(), Timeout.getLargeTimeout())); + } + public void testEnqueueAtFront() { final boolean enqueueAtFront = UserPreferences.enqueueAtFront(); solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title)); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af23c287c..c1b72602a 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="1050200" - android:versionName="1.5.2.0"> + android:versionCode="1060007" + android:versionName="1.6.0.7"> <!-- Version code schema: "1.2.3-SNAPSHOT" -> 1020300 @@ -41,6 +41,10 @@ android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/> + <meta-data + android:name="com.google.android.gms.version" + android:value="@integer/google_play_services_version" /> + <activity android:name=".activity.MainActivity" android:configChanges="keyboardHidden|orientation" @@ -67,6 +71,13 @@ <data android:mimeType="audio/*"/> </intent-filter> </activity> + <activity + android:name=".activity.CastplayerActivity" + android:launchMode="singleTop"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="de.danoeh.antennapod.activity.MainActivity"/> + </activity> <activity android:name=".activity.DownloadAuthenticationActivity" @@ -145,6 +156,13 @@ android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> </activity> <activity + android:name=".activity.StatisticsActivity" + android:label="@string/statistics_label"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> + </activity> + <activity android:name=".activity.OpmlImportFromPathActivity" android:configChanges="keyboardHidden|orientation" android:label="@string/opml_import_label"> @@ -179,7 +197,7 @@ <activity android:name=".activity.VideoplayerActivity" android:configChanges="keyboardHidden|orientation" - android:screenOrientation="landscape"> + android:screenOrientation="sensorLandscape"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="de.danoeh.antennapod.activity.MainActivity"/> @@ -201,10 +219,6 @@ android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> </activity> - <meta-data - android:name="android.support.PARENT_ACTIVITY" - android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> - <activity android:name=".activity.OnlineFeedViewActivity" android:configChanges="orientation" @@ -328,11 +342,6 @@ android:scheme="package"/> </intent-filter> </receiver> - <receiver android:name="de.danoeh.antennapod.core.service.playback.MediaButtonIntentReceiver"> - <intent-filter> - <action android:name="android.intent.action.MEDIA_BUTTON" /> - </intent-filter> - </receiver> <meta-data android:name="de.danoeh.antennapod.core.glide.ApGlideModule" diff --git a/app/src/main/assets/LICENSE_TRIANGLE_LABEL_VIEW.txt b/app/src/main/assets/LICENSE_TRIANGLE_LABEL_VIEW.txt new file mode 100644 index 000000000..de9a1f228 --- /dev/null +++ b/app/src/main/assets/LICENSE_TRIANGLE_LABEL_VIEW.txt @@ -0,0 +1,13 @@ +Copyright (C) 2016 Shota Saito + +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.
\ No newline at end of file diff --git a/app/src/main/assets/Roboto.ttf b/app/src/main/assets/Roboto.ttf Binary files differdeleted file mode 100644 index 0ba95c98c..000000000 --- a/app/src/main/assets/Roboto.ttf +++ /dev/null diff --git a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java index 829a49a15..f6a8db5fb 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -8,11 +8,8 @@ import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.fonts.FontAwesomeModule; import com.joanzapata.iconify.fonts.MaterialModule; +import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.PodDBAdapter; -import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.spa.SPAUtil; /** Main application class. */ @@ -56,11 +53,8 @@ public class PodcastApp extends Application { singleton = this; - PodDBAdapter.init(this); - UserPreferences.init(this); - UpdateManager.init(this); - PlaybackPreferences.init(this); - NetworkUtils.init(this); + ClientConfig.initialize(this); + EventDistributor.getInstance(); Iconify.with(new FontAwesomeModule()); Iconify.with(new MaterialModule()); diff --git a/app/src/main/java/de/danoeh/antennapod/UpdateManager.java b/app/src/main/java/de/danoeh/antennapod/UpdateManager.java deleted file mode 100644 index 0b3c43381..000000000 --- a/app/src/main/java/de/danoeh/antennapod/UpdateManager.java +++ /dev/null @@ -1,97 +0,0 @@ -package de.danoeh.antennapod; - - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.util.Log; - -import org.antennapod.audio.MediaPlayer; - -import java.io.File; -import java.util.List; - -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedImage; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; - -/* - * This class's job is do perform maintenance tasks whenever the app has been updated - */ -public class UpdateManager { - - public static final String TAG = UpdateManager.class.getSimpleName(); - - private static final String PREF_NAME = "app_version"; - private static final String KEY_VERSION_CODE = "version_code"; - - private static int currentVersionCode; - - private static Context context; - private static SharedPreferences prefs; - - public static void init(Context context) { - UpdateManager.context = context; - prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - PackageManager pm = context.getPackageManager(); - try { - PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0); - currentVersionCode = info.versionCode; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Failed to obtain package info for package name: " + context.getPackageName(), e); - currentVersionCode = 0; - return; - } - final int oldVersionCode = getStoredVersionCode(); - Log.d(TAG, "old: " + oldVersionCode + ", current: " + currentVersionCode); - if(oldVersionCode < currentVersionCode) { - onUpgrade(oldVersionCode, currentVersionCode); - setCurrentVersionCode(); - } - } - - public static int getStoredVersionCode() { - return prefs.getInt(KEY_VERSION_CODE, -1); - } - - public static void setCurrentVersionCode() { - prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply(); - } - - private static void onUpgrade(final int oldVersionCode, final int newVersionCode) { - if(oldVersionCode < 1030099) { - // delete the now obsolete image cache - // from now on, Glide will handle caching images - new Thread() { - public void run() { - List<Feed> feeds = DBReader.getFeedList(); - for (Feed podcast : feeds) { - List<FeedItem> episodes = DBReader.getFeedItemList(podcast); - for (FeedItem episode : episodes) { - FeedImage image = episode.getImage(); - if (image != null && image.isDownloaded() && image.getFile_url() != null) { - File imageFile = new File(image.getFile_url()); - if (imageFile.exists()) { - imageFile.delete(); - } - image.setFile_url(null); // calls setDownloaded(false) - DBWriter.setFeedImage(image); - } - } - } - } - }.start(); - } - if(oldVersionCode < 1050004) { - if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) { - UserPreferences.enableSonic(true); - } - } - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java index c835f8073..eef2fa4da 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java @@ -4,7 +4,7 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -30,7 +30,7 @@ import rx.schedulers.Schedulers; /** * Displays the 'about' screen */ -public class AboutActivity extends ActionBarActivity { +public class AboutActivity extends AppCompatActivity { private static final String TAG = AboutActivity.class.getSimpleName(); @@ -87,7 +87,7 @@ public class AboutActivity extends ActionBarActivity { res.recycle(); input = getAssets().open(filename); String webViewData = IOUtils.toString(input, Charset.defaultCharset()); - if(false == webViewData.startsWith("<!DOCTYPE html>")) { + if(!webViewData.startsWith("<!DOCTYPE html>")) { //webViewData = webViewData.replace("\n\n", "</p><p>"); webViewData = webViewData.replace("%", "%"); webViewData = 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 b4e8d4d71..ca214de9e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -1,156 +1,27 @@ 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.os.Build; -import android.support.annotation.Nullable; -import android.support.design.widget.AppBarLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; +import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.Log; -import android.util.TypedValue; -import android.view.ContextMenu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import com.viewpagerindicator.CirclePageIndicator; - -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.ChaptersListAdapter; -import de.danoeh.antennapod.adapter.NavListAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; -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.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.playback.ExternalMedia; -import de.danoeh.antennapod.core.util.playback.Playable; -import de.danoeh.antennapod.core.util.playback.PlaybackController; -import de.danoeh.antennapod.fragment.AddFeedFragment; -import de.danoeh.antennapod.fragment.ChaptersFragment; -import de.danoeh.antennapod.fragment.CoverFragment; -import de.danoeh.antennapod.fragment.DownloadsFragment; -import de.danoeh.antennapod.fragment.EpisodesFragment; -import de.danoeh.antennapod.fragment.ItemDescriptionFragment; -import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; -import de.danoeh.antennapod.fragment.QueueFragment; -import de.danoeh.antennapod.menuhandler.NavDrawerActivity; -import de.danoeh.antennapod.preferences.PreferenceController; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; /** * Activity for playing audio files. */ -public class AudioplayerActivity extends MediaplayerActivity implements NavDrawerActivity { - - private static final int POS_COVER = 0; - private static final int POS_DESCR = 1; - private static final int POS_CHAPTERS = 2; - private static final int NUM_CONTENT_FRAGMENTS = 3; - - final String TAG = "AudioplayerActivity"; - private static final String PREFS = "AudioPlayerActivityPreferences"; - private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition"; - - public static final String[] NAV_DRAWER_TAGS = { - QueueFragment.TAG, - EpisodesFragment.TAG, - DownloadsFragment.TAG, - PlaybackHistoryFragment.TAG, - AddFeedFragment.TAG - }; +public class AudioplayerActivity extends MediaplayerInfoActivity { + public static final String TAG = "AudioPlayerActivity"; private AtomicBoolean isSetup = new AtomicBoolean(false); - private DrawerLayout drawerLayout; - private NavListAdapter navAdapter; - private ListView navList; - private View navDrawer; - private ActionBarDrawerToggle drawerToggle; - private int mPosition = -1; - - private Playable media; - private ViewPager pager; - private AudioplayerPagerAdapter pagerAdapter; - - private Subscription subscription; - - @Override - protected void onStop() { - super.onStop(); - Log.d(TAG, "onStop()"); - if(subscription != null) { - subscription.unsubscribe(); - } - EventDistributor.getInstance().unregister(contentUpdate); - saveCurrentFragment(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - // don't risk creating memory leaks - navAdapter = null; - drawerToggle = null; - pager = null; - pagerAdapter = null; - } - - @Override - protected void chooseTheme() { - setTheme(UserPreferences.getNoTitleTheme()); - } - - private void saveCurrentFragment() { - if(pager == null) { - return; - } - Log.d(TAG, "Saving preferences"); - SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - prefs.edit() - .putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, pager.getCurrentItem()) - .commit(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if(drawerToggle != null) { - drawerToggle.onConfigurationChanged(newConfig); - } - } - - private void loadLastFragment() { - Log.d(TAG, "Restoring instance state"); - SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); - int lastPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1); - pager.setCurrentItem(lastPosition); - } - @Override protected void onResume() { super.onResume(); @@ -167,431 +38,116 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true); startService(launchIntent); - } - if(pagerAdapter != null && controller != null && controller.getMedia() != media) { - media = controller.getMedia(); - pagerAdapter.onMediaChanged(media); - } - - EventDistributor.getInstance().register(contentUpdate); - loadData(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - - @Override - protected void onAwaitingVideoSurface() { - Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player"); - startActivity(new Intent(this, VideoplayerActivity.class)); - } - - @Override - protected void postStatusMsg(int resId) { - if (resId == R.string.player_preparing_msg - || resId == R.string.player_seeking_msg - || resId == R.string.player_buffering_msg) { - // TODO Show progress bar here - } - } - - @Override - protected void clearStatusMsg() { - // TODO Hide progress bar here - } - - - @Override - protected void setupGUI() { - if(isSetup.getAndSet(true)) { - return; - } - super.setupGUI(); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(""); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - findViewById(R.id.shadow).setVisibility(View.GONE); - AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appBar); - float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); - appBarLayout.setElevation(px); - } - drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - navList = (ListView) findViewById(R.id.nav_list); - navDrawer = findViewById(R.id.nav_layout); - - drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); - drawerToggle.setDrawerIndicatorEnabled(false); - drawerLayout.setDrawerListener(drawerToggle); - - navAdapter = new NavListAdapter(itemAccess, this); - navList.setAdapter(navAdapter); - navList.setOnItemClickListener((parent, view, position, id) -> { - int viewType = parent.getAdapter().getItemViewType(position); - if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { - Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class); - intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType); - intent.putExtra(MainActivity.EXTRA_NAV_INDEX, position); + } else if (PlaybackService.isCasting()) { + Intent intent = PlaybackService.getPlayerActivityIntent(this); + if (!intent.getComponent().getClassName().equals(AudioplayerActivity.class.getName())) { + saveCurrentFragment(); + finish(); startActivity(intent); } - drawerLayout.closeDrawer(navDrawer); - }); - navList.setOnItemLongClickListener((parent, view, position, id) -> { - if (position < navAdapter.getTags().size()) { - showDrawerPreferencesDialog(); - return true; - } else { - mPosition = position; - return false; - } - }); - registerForContextMenu(navList); - drawerToggle.syncState(); - - findViewById(R.id.nav_settings).setOnClickListener(v -> { - drawerLayout.closeDrawer(navDrawer); - startActivity(new Intent(AudioplayerActivity.this, PreferenceController.getPreferenceActivity())); - }); - - pager = (ViewPager) findViewById(R.id.pager); - pagerAdapter = new AudioplayerPagerAdapter(getSupportFragmentManager()); - pager.setAdapter(pagerAdapter); - CirclePageIndicator pageIndicator = (CirclePageIndicator) findViewById(R.id.page_indicator); - pageIndicator.setViewPager(pager); - loadLastFragment(); - pager.onSaveInstanceState(); - } - - @Override - protected void onPositionObserverUpdate() { - super.onPositionObserverUpdate(); - notifyMediaPositionChanged(); - } - - @Override - protected boolean loadMediaInfo() { - if (!super.loadMediaInfo()) { - return false; - } - if(controller.getMedia() != media) { - media = controller.getMedia(); - pagerAdapter.onMediaChanged(media); - } - return true; - } - - public void notifyMediaPositionChanged() { - if(pagerAdapter == null) { - return; - } - ChaptersFragment chaptersFragment = pagerAdapter.getChaptersFragment(); - if(chaptersFragment != null) { - ChaptersListAdapter adapter = (ChaptersListAdapter) chaptersFragment.getListAdapter(); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } } } @Override protected void onReloadNotification(int notificationCode) { - if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) { - Log.d(TAG, "ReloadNotification received, switching to Videoplayer now"); + if (notificationCode == PlaybackService.EXTRA_CODE_CAST) { + Log.d(TAG, "ReloadNotification received, switching to Castplayer now"); + saveCurrentFragment(); finish(); - startActivity(new Intent(this, VideoplayerActivity.class)); + startActivity(new Intent(this, CastplayerActivity.class)); - } - } - - @Override - protected void onBufferStart() { - postStatusMsg(R.string.player_buffering_msg); - } - - @Override - protected void onBufferEnd() { - clearStatusMsg(); - } - - public PlaybackController getPlaybackController() { - return controller; - } - - @Override - public boolean isDrawerOpen() { - return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); - } - - @Override - protected int getContentViewResourceId() { - return R.layout.audioplayer_activity; - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) { - return true; } else { - return super.onOptionsItemSelected(item); + super.onReloadNotification(notificationCode); } } @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - if(v.getId() != R.id.nav_list) { + protected void updatePlaybackSpeedButton() { + if(butPlaybackSpeed == null) { return; } - AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - int position = adapterInfo.position; - if(position < navAdapter.getSubscriptionOffset()) { + if (controller == null) { + butPlaybackSpeed.setVisibility(View.GONE); return; } - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.nav_feed_context, menu); - Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); - menu.setHeaderTitle(feed.getTitle()); - // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - final int position = mPosition; - mPosition = -1; // reset - if(position < 0) { - return false; - } - Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); - switch(item.getItemId()) { - case R.id.mark_all_seen_item: - DBWriter.markFeedSeen(feed.getId()); - return true; - case R.id.mark_all_read_item: - DBWriter.markFeedRead(feed.getId()); - return true; - case R.id.remove_item: - final FeedRemover remover = new FeedRemover(this, feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - } - }; - ConfirmationDialog conDialog = new ConfirmationDialog(this, - R.string.remove_feed_label, - R.string.feed_delete_confirmation_msg) { - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - if (controller != null) { - Playable playable = controller.getMedia(); - if (playable != null && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - if (media.getItem().getFeed().getId() == feed.getId()) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - if(controller.getStatus() == PlayerStatus.PLAYING) { - sendBroadcast(new Intent( - PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); - } - } - } - } - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); - return true; - default: - return super.onContextItemSelected(item); - } + updatePlaybackSpeedButtonText(); + ViewCompat.setAlpha(butPlaybackSpeed, controller.canSetPlaybackSpeed() ? 1.0f : 0.5f); + butPlaybackSpeed.setVisibility(View.VISIBLE); } @Override - public void onBackPressed() { - if(isDrawerOpen()) { - drawerLayout.closeDrawer(navDrawer); - } else if (pager == null || pager.getCurrentItem() == 0) { - // If the user is currently looking at the first step, allow the system to handle the - // Back button. This calls finish() on this activity and pops the back stack. - super.onBackPressed(); - } else { - // Otherwise, select the previous step. - pager.setCurrentItem(pager.getCurrentItem() - 1); - } - } - - 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(this); - builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { - if (isChecked) { - hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); - } else { - hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); - } - }); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); - }); - builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - } - - private DBReader.NavDrawerData navDrawerData; - - private void loadData() { - subscription = Observable.fromCallable(() -> DBReader.getNavDrawerData()) - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - navDrawerData = result; - if (navAdapter != null) { - navAdapter.notifyDataSetChanged(); - } - }, error -> { - Log.e(TAG, Log.getStackTraceString(error)); - }); - } - - - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) { - Log.d(TAG, "Received contentUpdate Intent."); - loadData(); - } + protected void updatePlaybackSpeedButtonText() { + if(butPlaybackSpeed == null) { + return; } - }; - - private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { - @Override - public int getCount() { - if (navDrawerData != null) { - return navDrawerData.feeds.size(); - } else { - return 0; - } + if (controller == null) { + butPlaybackSpeed.setVisibility(View.GONE); + return; } - - @Override - public Feed getItem(int position) { - if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { - return navDrawerData.feeds.get(position); - } else { - return null; + float speed = 1.0f; + if(controller.canSetPlaybackSpeed()) { + try { + // we can only retrieve the playback speed from the controller/playback service + // once mediaplayer has been initialized + speed = Float.parseFloat(UserPreferences.getPlaybackSpeed()); + } catch (NumberFormatException e) { + Log.e(TAG, Log.getStackTraceString(e)); + UserPreferences.setPlaybackSpeed(String.valueOf(speed)); } } - - @Override - public int getSelectedItemIndex() { - return -1; - } - - @Override - public int getQueueSize() { - return (navDrawerData != null) ? navDrawerData.queueSize : 0; - } - - @Override - public int getNumberOfNewItems() { - return (navDrawerData != null) ? navDrawerData.numNewItems : 0; - } - - @Override - public int getNumberOfDownloadedItems() { - return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0; - } - - @Override - public int getReclaimableItems() { - return (navDrawerData != null) ? navDrawerData.reclaimableSpace : 0; - } - - @Override - public int getFeedCounter(long feedId) { - return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; - } - }; - - public interface AudioplayerContentFragment { - void onMediaChanged(Playable media); + String speedStr = String.format("%.2fx", speed); + butPlaybackSpeed.setText(speedStr); } - private class AudioplayerPagerAdapter extends FragmentStatePagerAdapter { - - public AudioplayerPagerAdapter(FragmentManager fm) { - super(fm); - } - - private CoverFragment coverFragment; - private ItemDescriptionFragment itemDescriptionFragment; - private ChaptersFragment chaptersFragment; - - public void onMediaChanged(Playable media) { - if(coverFragment != null) { - coverFragment.onMediaChanged(media); - } - if(itemDescriptionFragment != null) { - itemDescriptionFragment.onMediaChanged(media); - } - if(chaptersFragment != null) { - chaptersFragment.onMediaChanged(media); - } - } - - @Nullable - public ChaptersFragment getChaptersFragment() { - return chaptersFragment; + @Override + protected void setupGUI() { + if(isSetup.getAndSet(true)) { + return; } - - @Override - public Fragment getItem(int position) { - Log.d(TAG, "getItem(" + position + ")"); - switch (position) { - case POS_COVER: - if(coverFragment == null) { - coverFragment = CoverFragment.newInstance(media); - } - return coverFragment; - case POS_DESCR: - if(itemDescriptionFragment == null) { - itemDescriptionFragment = ItemDescriptionFragment.newInstance(media, true, true); - } - return itemDescriptionFragment; - case POS_CHAPTERS: - if(chaptersFragment == null) { - chaptersFragment = ChaptersFragment.newInstance(media, controller); + super.setupGUI(); + if(butCastDisconnect != null) { + butCastDisconnect.setVisibility(View.GONE); + } + if(butPlaybackSpeed != null) { + butPlaybackSpeed.setOnClickListener(v -> { + if (controller == null) { + return; + } + if (controller.canSetPlaybackSpeed()) { + String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); + String currentSpeed = UserPreferences.getPlaybackSpeed(); + + // Provide initial value in case the speed list has changed + // out from under us + // and our current speed isn't in the new list + String newSpeed; + if (availableSpeeds.length > 0) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = "1.00"; } - return chaptersFragment; - default: - return null; - } - } - @Override - public int getCount() { - return NUM_CONTENT_FRAGMENTS; + for (int i = 0; i < availableSpeeds.length; i++) { + if (availableSpeeds[i].equals(currentSpeed)) { + if (i == availableSpeeds.length - 1) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = availableSpeeds[i + 1]; + } + break; + } + } + UserPreferences.setPlaybackSpeed(newSpeed); + controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); + } else { + VariableSpeedDialog.showGetPluginDialog(this); + } + }); + butPlaybackSpeed.setOnLongClickListener(v -> { + VariableSpeedDialog.showDialog(this); + return true; + }); + butPlaybackSpeed.setVisibility(View.VISIBLE); } } - } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/CastEnabledActivity.java new file mode 100644 index 000000000..b8856c295 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -0,0 +1,237 @@ +package de.danoeh.antennapod.activity; + +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.CallSuper; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import com.google.android.gms.cast.ApplicationMetadata; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.cast.CastConsumer; +import de.danoeh.antennapod.core.cast.CastManager; +import de.danoeh.antennapod.core.cast.DefaultCastConsumer; +import de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; + +/** + * Activity that allows for showing the MediaRouter button whenever there's a cast device in the + * network. + */ +public abstract class CastEnabledActivity extends AppCompatActivity + implements SharedPreferences.OnSharedPreferenceChangeListener { + public static final String TAG = "CastEnabledActivity"; + + protected CastManager castManager; + protected SwitchableMediaRouteActionProvider mediaRouteActionProvider; + private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()). + registerOnSharedPreferenceChangeListener(this); + + castManager = CastManager.getInstance(); + castManager.addCastConsumer(castConsumer); + castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); + onCastConnectionChanged(castManager.isConnected()); + } + + @Override + protected void onDestroy() { + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) + .unregisterOnSharedPreferenceChangeListener(this); + castManager.removeCastConsumer(castConsumer); + super.onDestroy(); + } + + @Override + @CallSuper + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.cast_enabled, menu); + castButtonVisibilityManager.setMenu(menu); + return true; + } + + @Override + @CallSuper + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + mediaRouteActionProvider = castManager + .addMediaRouterButton(menu.findItem(R.id.media_route_menu_item)); + mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable()); + return true; + } + + @Override + protected void onResume() { + super.onResume(); + castButtonVisibilityManager.setResumed(true); + } + + @Override + protected void onPause() { + super.onPause(); + castButtonVisibilityManager.setResumed(false); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { + boolean newValue = UserPreferences.isCastEnabled(); + Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue); + castButtonVisibilityManager.setPrefEnabled(newValue); + // PlaybackService has its own listener, so if it's active we don't have to take action here. + if (!newValue && !PlaybackService.isRunning) { + CastManager.getInstance().disconnect(); + } + } + } + + CastConsumer castConsumer = new DefaultCastConsumer() { + @Override + public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { + onCastConnectionChanged(true); + } + + @Override + public void onDisconnected() { + onCastConnectionChanged(false); + } + }; + + private void onCastConnectionChanged(boolean connected) { + if (connected) { + castButtonVisibilityManager.onConnected(); + setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); + } else { + castButtonVisibilityManager.onDisconnected(); + setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + } + + /** + * Should be called by any activity or fragment for which the cast button should be shown. + * + * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)} + */ + public final void requestCastButton(int showAsAction) { + castButtonVisibilityManager.requestCastButton(showAsAction); + } + + private class CastButtonVisibilityManager { + private volatile boolean prefEnabled = false; + private volatile boolean viewRequested = false; + private volatile boolean resumed = false; + private volatile boolean connected = false; + private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; + private Menu menu; + + public synchronized void setPrefEnabled(boolean newValue) { + if (prefEnabled != newValue && resumed && (viewRequested || connected)) { + if (newValue) { + castManager.incrementUiCounter(); + } else { + castManager.decrementUiCounter(); + } + } + prefEnabled = newValue; + if (mediaRouteActionProvider != null) { + mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); + } + } + + public synchronized void setResumed(boolean newValue) { + if (resumed == newValue) { + Log.e(TAG, "resumed should never change to the same value"); + return; + } + resumed = newValue; + if (prefEnabled && (viewRequested || connected)) { + if (resumed) { + castManager.incrementUiCounter(); + } else { + castManager.decrementUiCounter(); + } + } + } + + public synchronized void setViewRequested(boolean newValue) { + if (viewRequested != newValue && resumed && prefEnabled && !connected) { + if (newValue) { + castManager.incrementUiCounter(); + } else { + castManager.decrementUiCounter(); + } + } + viewRequested = newValue; + if (mediaRouteActionProvider != null) { + mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); + } + } + + public synchronized void setConnected(boolean newValue) { + if (connected != newValue && resumed && prefEnabled && !prefEnabled) { + if (newValue) { + castManager.incrementUiCounter(); + } else { + castManager.decrementUiCounter(); + } + } + connected = newValue; + if (mediaRouteActionProvider != null) { + mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); + } + } + + public synchronized boolean shouldEnable() { + return prefEnabled && viewRequested; + } + + public void setMenu(Menu menu) { + setViewRequested(false); + showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; + this.menu = menu; + setShowAsAction(); + } + + public void requestCastButton(int showAsAction) { + setViewRequested(true); + this.showAsAction = showAsAction; + setShowAsAction(); + } + + public void onConnected() { + setConnected(true); + setShowAsAction(); + } + + public void onDisconnected() { + setConnected(false); + setShowAsAction(); + } + + private void setShowAsAction() { + if (menu == null) { + Log.d(TAG, "setShowAsAction() without a menu"); + return; + } + MenuItem item = menu.findItem(R.id.media_route_menu_item); + if (item == null) { + Log.e(TAG, "setShowAsAction(), but cast button not inflated"); + return; + } + MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java new file mode 100644 index 000000000..1ca4d095f --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/CastplayerActivity.java @@ -0,0 +1,83 @@ +package de.danoeh.antennapod.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.danoeh.antennapod.core.service.playback.PlaybackService; + +/** + * Activity for controlling the remote playback on a Cast device. + */ +public class CastplayerActivity extends MediaplayerInfoActivity { + public static final String TAG = "CastPlayerActivity"; + + private AtomicBoolean isSetup = new AtomicBoolean(false); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!PlaybackService.isCasting()) { + Intent intent = PlaybackService.getPlayerActivityIntent(this); + if (!intent.getComponent().getClassName().equals(CastplayerActivity.class.getName())) { + finish(); + startActivity(intent); + } + } + } + + @Override + protected void onReloadNotification(int notificationCode) { + if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) { + Log.d(TAG, "ReloadNotification received, switching to Audioplayer now"); + saveCurrentFragment(); + finish(); + startActivity(new Intent(this, AudioplayerActivity.class)); + } else { + super.onReloadNotification(notificationCode); + } + } + + @Override + protected void setupGUI() { + if(isSetup.getAndSet(true)) { + return; + } + super.setupGUI(); + if (butPlaybackSpeed != null) { + butPlaybackSpeed.setVisibility(View.GONE); + } + if (butCastDisconnect != null) { + butCastDisconnect.setOnClickListener(v -> castManager.disconnect()); + butCastDisconnect.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onResume() { + if (!PlaybackService.isCasting()) { + Intent intent = PlaybackService.getPlayerActivityIntent(this); + if (!intent.getComponent().getClassName().equals(CastplayerActivity.class.getName())) { + saveCurrentFragment(); + finish(); + startActivity(intent); + } + } + super.onResume(); + } + + @Override + protected void onBufferStart() { + //sbPosition.setIndeterminate(true); + sbPosition.setEnabled(false); + } + + @Override + protected void onBufferEnd() { + //sbPosition.setIndeterminate(false); + sbPosition.setEnabled(true); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java index 62e85120d..5fd69ef6a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java @@ -243,7 +243,7 @@ public class DirectoryChooserActivity extends AppCompatActivity { @Override public void onEvent(int event, String path) { Log.d(TAG, "FileObserver received event " + event); - runOnUiThread(() -> refreshDirectory()); + runOnUiThread(DirectoryChooserActivity.this::refreshDirectory); } }; } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java index 365c4216d..41b2debdc 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.util.Log; -import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -73,31 +72,25 @@ public class DownloadAuthenticationActivity extends ActionBarActivity { txtvDescription.setText(txtvDescription.getText() + ":\n\n" + request.getTitle()); - butCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } + butCancel.setOnClickListener(v -> { + setResult(Activity.RESULT_CANCELED); + finish(); }); - butConfirm.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String username = etxtUsername.getText().toString(); - String password = etxtPassword.getText().toString(); - request.setUsername(username); - request.setPassword(password); - Intent result = new Intent(); - result.putExtra(RESULT_REQUEST, request); - setResult(Activity.RESULT_OK, result); - - if (sendToDownloadRequester) { - if (BuildConfig.DEBUG) Log.d(TAG, "Sending request to DownloadRequester"); - DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); - } - finish(); + butConfirm.setOnClickListener(v -> { + String username = etxtUsername.getText().toString(); + String password = etxtPassword.getText().toString(); + request.setUsername(username); + request.setPassword(password); + Intent result = new Intent(); + result.putExtra(RESULT_REQUEST, request); + setResult(Activity.RESULT_OK, result); + + if (sendToDownloadRequester) { + if (BuildConfig.DEBUG) Log.d(TAG, "Sending request to DownloadRequester"); + DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); } + finish(); }); } 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 9116decb0..d4356719e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -139,20 +139,14 @@ public class FeedInfoActivity extends ActionBarActivity { Log.d(TAG, "Author is " + feed.getAuthor()); Log.d(TAG, "URL is " + feed.getDownload_url()); FeedPreferences prefs = feed.getPreferences(); - imgvCover.post(new Runnable() { - - @Override - public void run() { - Glide.with(FeedInfoActivity.this) - .load(feed.getImageUri()) - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate() - .into(imgvCover); - } - }); + imgvCover.post(() -> Glide.with(FeedInfoActivity.this) + .load(feed.getImageUri()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(imgvCover)); txtvTitle.setText(feed.getTitle()); String description = feed.getDescription(); @@ -245,7 +239,11 @@ public class FeedInfoActivity extends ActionBarActivity { } } }; - loadTask.execute(feedId); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, feedId); + } else { + loadTask.execute(feedId); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java index 3b10ba4c3..be1c9f9e6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java @@ -9,17 +9,17 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; + +import org.shredzone.flattr4j.exception.FlattrException; + import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.flattr.FlattrUtils; import de.danoeh.antennapod.preferences.PreferenceController; -import org.shredzone.flattr4j.exception.FlattrException; - /** Guides the user through the authentication process */ public class FlattrAuthActivity extends ActionBarActivity { @@ -46,25 +46,19 @@ public class FlattrAuthActivity extends ActionBarActivity { butAuthenticate = (Button) findViewById(R.id.but_authenticate); butReturn = (Button) findViewById(R.id.but_return_home); - butReturn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(FlattrAuthActivity.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - }); + butReturn.setOnClickListener(v -> { + Intent intent = new Intent(FlattrAuthActivity.this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + }); - butAuthenticate.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - try { - FlattrUtils.startAuthProcess(FlattrAuthActivity.this); - } catch (FlattrException e) { - e.printStackTrace(); - } - } - }); + butAuthenticate.setOnClickListener(v -> { + try { + FlattrUtils.startAuthProcess(FlattrAuthActivity.this); + } catch (FlattrException e) { + e.printStackTrace(); + } + }); } public static FlattrAuthActivity getInstance() { 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 bd7da7c03..b7c7d86c7 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -7,7 +7,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.DataSetObserver; -import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -17,11 +16,11 @@ import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.util.TypedValue; import android.view.ContextMenu; +import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; @@ -43,15 +42,13 @@ import de.danoeh.antennapod.core.event.ProgressEvent; import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedMedia; +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.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.StorageUtils; -import de.danoeh.antennapod.core.util.playback.Playable; -import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; @@ -60,6 +57,7 @@ import de.danoeh.antennapod.fragment.ExternalPlayerFragment; import de.danoeh.antennapod.fragment.ItemlistFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.danoeh.antennapod.menuhandler.NavDrawerActivity; import de.danoeh.antennapod.preferences.PreferenceController; import de.greenrobot.event.EventBus; @@ -71,7 +69,7 @@ import rx.schedulers.Schedulers; /** * The activity that is shown when the user launches the app. */ -public class MainActivity extends AppCompatActivity implements NavDrawerActivity { +public class MainActivity extends CastEnabledActivity implements NavDrawerActivity { private static final String TAG = "MainActivity"; @@ -86,6 +84,7 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity 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 EXTRA_FEED_ID = "fragment_feed_id"; public static final String SAVE_BACKSTACK_COUNT = "backstackCount"; public static final String SAVE_TITLE = "title"; @@ -93,9 +92,11 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity public static final String[] NAV_DRAWER_TAGS = { QueueFragment.TAG, EpisodesFragment.TAG, + SubscriptionFragment.TAG, DownloadsFragment.TAG, PlaybackHistoryFragment.TAG, - AddFeedFragment.TAG + AddFeedFragment.TAG, + NavListAdapter.SUBSCRIPTION_LIST_TAG }; private Toolbar toolbar; @@ -121,7 +122,6 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity super.onCreate(savedInstanceState); StorageUtils.checkStorageAvailability(this); setContentView(R.layout.main); - setVolumeControlStream(AudioManager.STREAM_MUSIC); toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -184,7 +184,7 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity loadFragment(lastFragment, null); } else { try { - loadFeedFragmentById(Integer.valueOf(lastFragment), null); + loadFeedFragmentById(Integer.parseInt(lastFragment), null); } catch (NumberFormatException e) { // it's not a number, this happens if we removed // a label from the NAV_DRAWER_TAGS @@ -270,7 +270,7 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity } public void loadFragment(int index, Bundle args) { - Log.d(TAG, "loadFragment(index: " + index + ", args: " + args +")"); + Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")"); if (index < navAdapter.getSubscriptionOffset()) { String tag = navAdapter.getTags().get(index); loadFragment(tag, args); @@ -299,6 +299,10 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity case AddFeedFragment.TAG: fragment = new AddFeedFragment(); break; + case SubscriptionFragment.TAG: + SubscriptionFragment subscriptionFragment = new SubscriptionFragment(); + fragment = subscriptionFragment; + break; default: // default to the queue tag = QueueFragment.TAG; @@ -464,8 +468,9 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity StorageUtils.checkStorageAvailability(this); Intent intent = getIntent(); - if (navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) && - (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG))) { + if (intent.hasExtra(EXTRA_FEED_ID) || + (navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) && + (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) { handleNavIntent(); } loadData(); @@ -500,6 +505,26 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity } @Override + public boolean onCreateOptionsMenu(Menu menu) { + boolean retVal = super.onCreateOptionsMenu(menu); + switch (getLastNavFragment()) { + case QueueFragment.TAG: + case EpisodesFragment.TAG: + requestCastButton(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return retVal; + case DownloadsFragment.TAG: + case PlaybackHistoryFragment.TAG: + case AddFeedFragment.TAG: + case SubscriptionFragment.TAG: + return retVal; + default: + requestCastButton(MenuItem.SHOW_AS_ACTION_NEVER); + return retVal; + } + + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { if (drawerToggle.onOptionsItemSelected(item)) { return true; @@ -565,21 +590,15 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity public void onConfirmButtonPressed( DialogInterface dialog) { dialog.dismiss(); - if (externalPlayerFragment != null) { - PlaybackController controller = externalPlayerFragment.getPlaybackControllerTestingOnly(); - if (controller != null) { - Playable playable = controller.getMedia(); - if (playable != null && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - if (media.getItem().getFeed().getId() == feed.getId()) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - if(controller.getStatus() == PlayerStatus.PLAYING) { - sendBroadcast(new Intent( - PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); - } - } - } + long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); + if (mediaId > 0 && + FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); + if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { + sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); } } remover.executeAsync(); @@ -652,10 +671,23 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity public int getFeedCounter(long feedId) { return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; } + + @Override + public int getFeedCounterSum() { + if(navDrawerData == null) { + return 0; + } + int sum = 0; + for(int counter : navDrawerData.feedCounters.values()) { + sum += counter; + } + return sum; + } + }; private void loadData() { - subscription = Observable.fromCallable(() -> DBReader.getNavDrawerData()) + subscription = Observable.fromCallable(DBReader::getNavDrawerData) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { @@ -674,6 +706,12 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity public void onEvent(QueueEvent event) { Log.d(TAG, "onEvent(" + event + ")"); + // we are only interested in the number of queue items, not download status or position + if(event.action == QueueEvent.Action.DELETED_MEDIA || + event.action == QueueEvent.Action.SORTED || + event.action == QueueEvent.Action.MOVED) { + return; + } loadData(); } @@ -709,15 +747,19 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity private void handleNavIntent() { Log.d(TAG, "handleNavIntent()"); Intent intent = getIntent(); - if (intent.hasExtra(EXTRA_NAV_TYPE) && - intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)) { + if (intent.hasExtra(EXTRA_FEED_ID) || + (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); + long feedId = intent.getLongExtra(EXTRA_FEED_ID, 0); if (index >= 0) { loadFragment(index, args); } else if (tag != null) { loadFragment(tag, args); + } else if(feedId > 0) { + loadFeedFragmentById(feedId, 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 61815757f..71d288725 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -6,13 +6,10 @@ import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; -import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.v4.view.ViewCompat; import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -58,7 +55,7 @@ import rx.schedulers.Schedulers; * Provides general features which are both needed for playing audio and video * files. */ -public abstract class MediaplayerActivity extends AppCompatActivity implements OnSeekBarChangeListener { +public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener { private static final String TAG = "MediaplayerActivity"; private static final String PREFS = "MediaPlayerActivityPreferences"; private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; @@ -68,7 +65,6 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O protected TextView txtvPosition; protected TextView txtvLength; protected SeekBar sbPosition; - protected Button butPlaybackSpeed; protected ImageButton butRev; protected TextView txtvRev; protected ImageButton butPlay; @@ -129,8 +125,8 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O } @Override - public void postStatusMsg(int msg) { - MediaplayerActivity.this.postStatusMsg(msg); + public void postStatusMsg(int msg, boolean showToast) { + MediaplayerActivity.this.postStatusMsg(msg, showToast); } @Override @@ -173,12 +169,21 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O super.setScreenOn(enable); MediaplayerActivity.this.setScreenOn(enable); } + + @Override + public void onSetSpeedAbilityChanged() { + MediaplayerActivity.this.onSetSpeedAbilityChanged(); + } }; + } + protected void onSetSpeedAbilityChanged() { + Log.d(TAG, "onSetSpeedAbilityChanged()"); + updatePlaybackSpeedButton(); } protected void onPlaybackSpeedChange() { - updateButPlaybackSpeed(); + updatePlaybackSpeedButtonText(); } protected void onServiceQueried() { @@ -199,7 +204,6 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O Log.d(TAG, "onCreate()"); StorageUtils.checkStorageAvailability(this); - setVolumeControlStream(AudioManager.STREAM_MUSIC); orientation = getResources().getConfiguration().orientation; getWindow().setFormat(PixelFormat.TRANSPARENT); @@ -207,11 +211,11 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O @Override protected void onPause() { - super.onPause(); if(controller != null) { controller.reinitServiceIfPaused(); controller.pause(); } + super.onPause(); } /** @@ -249,18 +253,16 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O controller.release(); } controller = newPlaybackController(); - if(butPlay != null) { - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); - } } @Override protected void onStop() { - super.onStop(); Log.d(TAG, "onStop()"); if (controller != null) { controller.release(); + controller = null; // prevent leak } + super.onStop(); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @@ -279,6 +281,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); + requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.mediaplayer, menu); return true; @@ -389,18 +392,11 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O .getSleepTimerTimeLeft())); stDialog.positiveText(R.string.disable_sleeptimer_label); stDialog.negativeText(R.string.cancel_label); - stDialog.callback(new MaterialDialog.ButtonCallback() { - @Override - public void onPositive(MaterialDialog dialog) { - dialog.dismiss(); - controller.disableSleepTimer(); - } - - @Override - public void onNegative(MaterialDialog dialog) { - dialog.dismiss(); - } + stDialog.onPositive((dialog, which) -> { + dialog.dismiss(); + controller.disableSleepTimer(); }); + stDialog.onNegative((dialog, which) -> dialog.dismiss()); stDialog.build().show(); } break; @@ -466,9 +462,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O txtvPlaybackSpeed.setText(speedStr); } else if(fromUser) { float speed = Float.valueOf(UserPreferences.getPlaybackSpeed()); - barPlaybackSpeed.post(() -> { - barPlaybackSpeed.setProgress((int) (20 * speed) - 10); - }); + barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress((int) (20 * speed) - 10)); } } @@ -486,9 +480,9 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O barPlaybackSpeed.setProgress((int) (20 * currentSpeed) - 10); final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); - barLeftVolume.setProgress(100); + barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage()); final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right); - barRightVolume.setProgress(100); + barRightVolume.setProgress(UserPreferences.getRightVolumePercentage()); final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono); stereoToMono.setChecked(UserPreferences.stereoToMono()); if (controller != null && !controller.canDownmix()) { @@ -500,14 +494,9 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O barLeftVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - float leftVolume = 1.0f, rightVolume = 1.0f; - if (progress < 100) { - leftVolume = progress / 100.0f; - } - if (barRightVolume.getProgress() < 100) { - rightVolume = barRightVolume.getProgress() / 100.0f; - } - controller.setVolume(leftVolume, rightVolume); + controller.setVolume( + Converter.getVolumeFromPercentage(progress), + Converter.getVolumeFromPercentage(barRightVolume.getProgress())); } @Override @@ -521,14 +510,9 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O barRightVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - float leftVolume = 1.0f, rightVolume = 1.0f; - if (progress < 100) { - rightVolume = progress / 100.0f; - } - if (barLeftVolume.getProgress() < 100) { - leftVolume = barLeftVolume.getProgress() / 100.0f; - } - controller.setVolume(leftVolume, rightVolume); + controller.setVolume( + Converter.getVolumeFromPercentage(barLeftVolume.getProgress()), + Converter.getVolumeFromPercentage(progress)); } @Override @@ -601,7 +585,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O */ protected abstract void onAwaitingVideoSurface(); - protected abstract void postStatusMsg(int resId); + protected abstract void postStatusMsg(int resId, boolean showToast); protected abstract void clearStatusMsg(); @@ -649,25 +633,22 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O if (media != null) { onPositionObserverUpdate(); checkFavorite(); - if(butPlaybackSpeed != null) { - if (controller == null) { - butPlaybackSpeed.setVisibility(View.GONE); - } else { - butPlaybackSpeed.setVisibility(View.VISIBLE); - if (controller.canSetPlaybackSpeed()) { - ViewCompat.setAlpha(butPlaybackSpeed, 1.0f); - } else { - ViewCompat.setAlpha(butPlaybackSpeed, 0.5f); - } - } - updateButPlaybackSpeed(); - } + updatePlaybackSpeedButton(); return true; } else { return false; } } + protected void updatePlaybackSpeedButton() { + // Only meaningful on AudioplayerActivity, where it is overridden. + } + + protected void updatePlaybackSpeedButtonText() { + // Only meaningful on AudioplayerActivity, where it is overridden. + } + + protected void setupGUI() { setContentView(getContentViewResourceId()); sbPosition = (SeekBar) findViewById(R.id.sbPosition); @@ -677,28 +658,29 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); Log.d("timeleft", showTimeLeft ? "true" : "false"); txtvLength = (TextView) findViewById(R.id.txtvLength); - txtvLength.setOnClickListener(v -> { - showTimeLeft = !showTimeLeft; - Playable media = controller.getMedia(); - if (media == null) { - return; - } + if (txtvLength != null) { + txtvLength.setOnClickListener(v -> { + showTimeLeft = !showTimeLeft; + Playable media = controller.getMedia(); + if (media == null) { + return; + } - String length; - if (showTimeLeft) { - length = "-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()); - } else { - length = Converter.getDurationStringLong(media.getDuration()); - } - txtvLength.setText(length); + String length; + if (showTimeLeft) { + length = "-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()); + } else { + length = Converter.getDurationStringLong(media.getDuration()); + } + txtvLength.setText(length); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft); - editor.apply(); - Log.d("timeleft on click", showTimeLeft ? "true" : "false"); - }); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft); + editor.apply(); + Log.d("timeleft on click", showTimeLeft ? "true" : "false"); + }); + } - butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); butRev = (ImageButton) findViewById(R.id.butRev); txtvRev = (TextView) findViewById(R.id.txtvRev); if (txtvRev != null) { @@ -718,52 +700,8 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O // BUTTON SETUP - if(butPlaybackSpeed != null) { - butPlaybackSpeed.setOnClickListener(v -> { - if (controller == null) { - return; - } - if (controller.canSetPlaybackSpeed()) { - String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); - String currentSpeed = UserPreferences.getPlaybackSpeed(); - - // Provide initial value in case the speed list has changed - // out from under us - // and our current speed isn't in the new list - String newSpeed; - if (availableSpeeds.length > 0) { - newSpeed = availableSpeeds[0]; - } else { - newSpeed = "1.00"; - } - - for (int i = 0; i < availableSpeeds.length; i++) { - if (availableSpeeds[i].equals(currentSpeed)) { - if (i == availableSpeeds.length - 1) { - newSpeed = availableSpeeds[0]; - } else { - newSpeed = availableSpeeds[i + 1]; - } - break; - } - } - UserPreferences.setPlaybackSpeed(newSpeed); - controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); - } else { - VariableSpeedDialog.showGetPluginDialog(this); - } - }); - butPlaybackSpeed.setOnLongClickListener(v -> { - VariableSpeedDialog.showDialog(this); - return true; - }); - } - if (butRev != null) { - butRev.setOnClickListener(v -> { - int curr = controller.getPosition(); - controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); - }); + butRev.setOnClickListener(v -> onRewind()); butRev.setOnLongClickListener(new View.OnLongClickListener() { int choice; @@ -800,13 +738,10 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O }); } - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> onPlayPause()); if (butFF != null) { - butFF.setOnClickListener(v -> { - int curr = controller.getPosition(); - controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); - }); + butFF.setOnClickListener(v -> onFastForward()); butFF.setOnLongClickListener(new View.OnLongClickListener() { int choice; @@ -844,12 +779,33 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O } if (butSkip != null) { - butSkip.setOnClickListener(v -> { - sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE)); - }); + butSkip.setOnClickListener(v -> sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE))); } } + protected void onRewind() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); + } + + protected void onPlayPause() { + if(controller == null) { + return; + } + controller.playPause(); + } + + protected void onFastForward() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); + } + protected abstract int getContentViewResourceId(); void handleError(int errorCode) { @@ -880,20 +836,6 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O } } - private void updateButPlaybackSpeed() { - if (controller != null && butPlaybackSpeed != null) { - float speed = 1.0f; - try { - speed = Float.parseFloat(UserPreferences.getPlaybackSpeed()); - } catch(NumberFormatException e) { - Log.e(TAG, Log.getStackTraceString(e)); - UserPreferences.setPlaybackSpeed(String.valueOf(speed)); - } - String speedStr = String.format("%.2fx", speed); - butPlaybackSpeed.setText(speedStr); - } - } - @Override public void onStartTrackingTouch(SeekBar seekBar) { if (controller != null) { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java new file mode 100644 index 000000000..647745e39 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java @@ -0,0 +1,620 @@ +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.os.Build; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ListView; +import android.widget.Toast; + +import com.viewpagerindicator.CirclePageIndicator; + +import java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.ChaptersListAdapter; +import de.danoeh.antennapod.adapter.NavListAdapter; +import de.danoeh.antennapod.core.asynctask.FeedRemover; +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.FeedMedia; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.fragment.AddFeedFragment; +import de.danoeh.antennapod.fragment.ChaptersFragment; +import de.danoeh.antennapod.fragment.CoverFragment; +import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; +import de.danoeh.antennapod.fragment.ItemDescriptionFragment; +import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; +import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.preferences.PreferenceController; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Activity for playing files that do not require a video surface. + */ +public abstract class MediaplayerInfoActivity extends MediaplayerActivity implements NavDrawerActivity { + + private static final int POS_COVER = 0; + private static final int POS_DESCR = 1; + private static final int POS_CHAPTERS = 2; + private static final int NUM_CONTENT_FRAGMENTS = 3; + + final String TAG = "MediaplayerInfoActivity"; + private static final String PREFS = "AudioPlayerActivityPreferences"; + private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition"; + + public static final String[] NAV_DRAWER_TAGS = { + QueueFragment.TAG, + EpisodesFragment.TAG, + SubscriptionFragment.TAG, + DownloadsFragment.TAG, + PlaybackHistoryFragment.TAG, + AddFeedFragment.TAG, + NavListAdapter.SUBSCRIPTION_LIST_TAG + }; + + protected Button butPlaybackSpeed; + protected ImageButton butCastDisconnect; + private DrawerLayout drawerLayout; + private NavListAdapter navAdapter; + private ListView navList; + private View navDrawer; + private ActionBarDrawerToggle drawerToggle; + private int mPosition = -1; + + private Playable media; + private ViewPager pager; + private MediaplayerInfoPagerAdapter pagerAdapter; + + private Subscription subscription; + + @Override + protected void onStop() { + super.onStop(); + Log.d(TAG, "onStop()"); + if(pagerAdapter != null) { + pagerAdapter.setController(null); + } + if(subscription != null) { + subscription.unsubscribe(); + } + EventDistributor.getInstance().unregister(contentUpdate); + saveCurrentFragment(); + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy()"); + super.onDestroy(); + // don't risk creating memory leaks + drawerLayout = null; + navAdapter = null; + navList = null; + navDrawer = null; + drawerToggle = null; + pager = null; + pagerAdapter = null; + } + + @Override + protected void chooseTheme() { + setTheme(UserPreferences.getNoTitleTheme()); + } + + protected void saveCurrentFragment() { + if(pager == null) { + return; + } + Log.d(TAG, "Saving preferences"); + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + prefs.edit() + .putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, pager.getCurrentItem()) + .commit(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if(drawerToggle != null) { + drawerToggle.onConfigurationChanged(newConfig); + } + } + + private void loadLastFragment() { + Log.d(TAG, "Restoring instance state"); + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + int lastPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1); + pager.setCurrentItem(lastPosition); + } + + @Override + protected void onResume() { + super.onResume(); + if(pagerAdapter != null && controller != null && controller.getMedia() != media) { + media = controller.getMedia(); + pagerAdapter.onMediaChanged(media); + pagerAdapter.setController(controller); + } + + EventDistributor.getInstance().register(contentUpdate); + loadData(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + + @Override + protected void onAwaitingVideoSurface() { + Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player"); + startActivity(new Intent(this, VideoplayerActivity.class)); + } + + @Override + protected void postStatusMsg(int resId, boolean showToast) { + if (resId == R.string.player_preparing_msg + || resId == R.string.player_seeking_msg + || resId == R.string.player_buffering_msg) { + // TODO Show progress bar here + } + if (showToast) { + Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); + } + } + + @Override + protected void clearStatusMsg() { + // TODO Hide progress bar here + } + + + @Override + protected void setupGUI() { + super.setupGUI(); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(""); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + findViewById(R.id.shadow).setVisibility(View.GONE); + AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appBar); + float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); + appBarLayout.setElevation(px); + } + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + navList = (ListView) findViewById(R.id.nav_list); + navDrawer = findViewById(R.id.nav_layout); + + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); + drawerToggle.setDrawerIndicatorEnabled(false); + drawerLayout.setDrawerListener(drawerToggle); + + navAdapter = new NavListAdapter(itemAccess, this); + navList.setAdapter(navAdapter); + navList.setOnItemClickListener((parent, view, position, id) -> { + int viewType = parent.getAdapter().getItemViewType(position); + if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { + Intent intent = new Intent(MediaplayerInfoActivity.this, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, position); + startActivity(intent); + } + drawerLayout.closeDrawer(navDrawer); + }); + navList.setOnItemLongClickListener((parent, view, position, id) -> { + if (position < navAdapter.getTags().size()) { + showDrawerPreferencesDialog(); + return true; + } else { + mPosition = position; + return false; + } + }); + registerForContextMenu(navList); + drawerToggle.syncState(); + + findViewById(R.id.nav_settings).setOnClickListener(v -> { + drawerLayout.closeDrawer(navDrawer); + startActivity(new Intent(MediaplayerInfoActivity.this, PreferenceController.getPreferenceActivity())); + }); + + butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); + butCastDisconnect = (ImageButton) findViewById(R.id.butCastDisconnect); + + pager = (ViewPager) findViewById(R.id.pager); + pagerAdapter = new MediaplayerInfoPagerAdapter(getSupportFragmentManager(), media); + pagerAdapter.setController(controller); + pager.setAdapter(pagerAdapter); + CirclePageIndicator pageIndicator = (CirclePageIndicator) findViewById(R.id.page_indicator); + pageIndicator.setViewPager(pager); + loadLastFragment(); + pager.onSaveInstanceState(); + } + + @Override + protected void onPositionObserverUpdate() { + super.onPositionObserverUpdate(); + notifyMediaPositionChanged(); + } + + @Override + protected boolean loadMediaInfo() { + if (!super.loadMediaInfo()) { + return false; + } + if(controller.getMedia() != media) { + media = controller.getMedia(); + pagerAdapter.onMediaChanged(media); + } + return true; + } + + public void notifyMediaPositionChanged() { + if(pagerAdapter == null) { + return; + } + ChaptersFragment chaptersFragment = pagerAdapter.getChaptersFragment(); + if(chaptersFragment != null) { + ChaptersListAdapter adapter = (ChaptersListAdapter) chaptersFragment.getListAdapter(); + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + } + + @Override + protected void onReloadNotification(int notificationCode) { + if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) { + Log.d(TAG, "ReloadNotification received, switching to Videoplayer now"); + finish(); + startActivity(new Intent(this, VideoplayerActivity.class)); + + } + } + + @Override + protected void onBufferStart() { + postStatusMsg(R.string.player_buffering_msg, false); + } + + @Override + protected void onBufferEnd() { + clearStatusMsg(); + } + + public PlaybackController getPlaybackController() { + return controller; + } + + @Override + public boolean isDrawerOpen() { + return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); + } + + @Override + protected int getContentViewResourceId() { + return R.layout.mediaplayerinfo_activity; + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return drawerToggle != null && drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if(v.getId() != R.id.nav_list) { + return; + } + AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + int position = adapterInfo.position; + if(position < navAdapter.getSubscriptionOffset()) { + return; + } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + menu.setHeaderTitle(feed.getTitle()); + // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int position = mPosition; + mPosition = -1; // reset + if(position < 0) { + return false; + } + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + switch(item.getItemId()) { + case R.id.mark_all_seen_item: + DBWriter.markFeedSeen(feed.getId()); + return true; + case R.id.mark_all_read_item: + DBWriter.markFeedRead(feed.getId()); + return true; + case R.id.remove_item: + final FeedRemover remover = new FeedRemover(this, feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(this, + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + if (controller != null) { + Playable playable = controller.getMedia(); + if (playable != null && playable instanceof FeedMedia) { + FeedMedia media = (FeedMedia) playable; + if (media.getItem().getFeed().getId() == feed.getId()) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + if(controller.getStatus() == PlayerStatus.PLAYING) { + sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); + } + } + } + } + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public void onBackPressed() { + if(isDrawerOpen()) { + drawerLayout.closeDrawer(navDrawer); + } else if (pager == null || pager.getCurrentItem() == 0) { + // If the user is currently looking at the first step, allow the system to handle the + // Back button. This calls finish() on this activity and pops the back stack. + super.onBackPressed(); + } else { + // Otherwise, select the previous step. + pager.setCurrentItem(pager.getCurrentItem() - 1); + } + } + + 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(this); + builder.setTitle(R.string.drawer_preferences); + builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + + private DBReader.NavDrawerData navDrawerData; + + private void loadData() { + subscription = Observable.fromCallable(DBReader::getNavDrawerData) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + navDrawerData = result; + if (navAdapter != null) { + navAdapter.notifyDataSetChanged(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) { + Log.d(TAG, "Received contentUpdate Intent."); + loadData(); + } + } + }; + + private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { + @Override + public int getCount() { + if (navDrawerData != null) { + return navDrawerData.feeds.size(); + } else { + return 0; + } + } + + @Override + public Feed getItem(int position) { + if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { + return navDrawerData.feeds.get(position); + } else { + return null; + } + } + + @Override + public int getSelectedItemIndex() { + return -1; + } + + @Override + public int getQueueSize() { + return (navDrawerData != null) ? navDrawerData.queueSize : 0; + } + + @Override + public int getNumberOfNewItems() { + return (navDrawerData != null) ? navDrawerData.numNewItems : 0; + } + + @Override + public int getNumberOfDownloadedItems() { + return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0; + } + + @Override + public int getReclaimableItems() { + return (navDrawerData != null) ? navDrawerData.reclaimableSpace : 0; + } + + @Override + public int getFeedCounter(long feedId) { + return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; + } + + @Override + public int getFeedCounterSum() { + if(navDrawerData == null) { + return 0; + } + int sum = 0; + for(int counter : navDrawerData.feedCounters.values()) { + sum += counter; + } + return sum; + } + }; + + public interface MediaplayerInfoContentFragment { + void onMediaChanged(Playable media); + } + + private static class MediaplayerInfoPagerAdapter extends FragmentStatePagerAdapter { + + private static final String TAG = "MPInfoPagerAdapter"; + + private Playable media; + private PlaybackController controller; + + public MediaplayerInfoPagerAdapter(FragmentManager fm, Playable media) { + super(fm); + this.media = media; + } + + private CoverFragment coverFragment; + private ItemDescriptionFragment itemDescriptionFragment; + private ChaptersFragment chaptersFragment; + + public void onMediaChanged(Playable media) { + Log.d(TAG, "media changing to " + ((media != null) ? media.getEpisodeTitle() : "null")); + this.media = media; + if(coverFragment != null) { + coverFragment.onMediaChanged(media); + } + if(itemDescriptionFragment != null) { + itemDescriptionFragment.onMediaChanged(media); + } + if(chaptersFragment != null) { + chaptersFragment.onMediaChanged(media); + } + } + + public void setController(PlaybackController controller) { + this.controller = controller; + if(chaptersFragment != null) { + chaptersFragment.setController(controller); + } + } + + @Nullable + public ChaptersFragment getChaptersFragment() { + return chaptersFragment; + } + + @Override + public Fragment getItem(int position) { + Log.d(TAG, "getItem(" + position + ")"); + switch (position) { + case POS_COVER: + if(coverFragment == null) { + coverFragment = CoverFragment.newInstance(media); + } + return coverFragment; + case POS_DESCR: + if(itemDescriptionFragment == null) { + itemDescriptionFragment = ItemDescriptionFragment.newInstance(media, true, true); + } + return itemDescriptionFragment; + case POS_CHAPTERS: + if(chaptersFragment == null) { + chaptersFragment = ChaptersFragment.newInstance(media); + chaptersFragment.setController(controller); + } + return chaptersFragment; + default: + return null; + } + } + + @Override + public int getCount() { + return NUM_CONTENT_FRAGMENTS; + } + } +} 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 c7426c006..a53f9bdb8 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -7,8 +7,8 @@ import android.content.Intent; import android.os.Bundle; import android.os.Looper; import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBarActivity; import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -79,7 +79,7 @@ import rx.schedulers.Schedulers; * If the feed cannot be downloaded or parsed, an error dialog will be displayed * and the activity will finish as soon as the error dialog is closed. */ -public class OnlineFeedViewActivity extends ActionBarActivity { +public class OnlineFeedViewActivity extends AppCompatActivity { private static final String TAG = "OnlineFeedViewActivity"; @@ -328,6 +328,7 @@ public class OnlineFeedViewActivity extends ActionBarActivity { subscriber.onError(e); } } catch (Exception e) { + Log.e(TAG, Log.getStackTraceString(e)); subscriber.onError(e); } finally { boolean rc = new File(feed.getFile_url()).delete(); @@ -408,18 +409,25 @@ public class OnlineFeedViewActivity extends ActionBarActivity { description.setText(feed.getDescription()); subscribeButton.setOnClickListener(v -> { - try { + if(feed != null && feedInFeedlist(feed)) { + Intent intent = new Intent(OnlineFeedViewActivity.this, MainActivity.class); + // feed.getId() is always 0, we have to retrieve the id from the feed list from + // the database + intent.putExtra(MainActivity.EXTRA_FEED_ID, getFeedId(feed)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } else { Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle()); f.setPreferences(feed.getPreferences()); this.feed = f; - - DownloadRequester.getInstance().downloadFeed(this, f); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(OnlineFeedViewActivity.this, - e.getMessage()); + try { + DownloadRequester.getInstance().downloadFeed(this, f); + } catch (DownloadRequestException e) { + Log.e(TAG, Log.getStackTraceString(e)); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage()); + } + setSubscribeButtonState(feed); } - setSubscribeButtonState(feed); }); if (alternateFeedUrls.isEmpty()) { @@ -462,8 +470,8 @@ public class OnlineFeedViewActivity extends ActionBarActivity { subscribeButton.setEnabled(false); subscribeButton.setText(R.string.downloading_label); } else if (feedInFeedlist(feed)) { - subscribeButton.setEnabled(false); - subscribeButton.setText(R.string.subscribed_label); + subscribeButton.setEnabled(true); + subscribeButton.setText(R.string.open_podcast); } else { subscribeButton.setEnabled(true); subscribeButton.setText(R.string.subscribe_label); @@ -483,6 +491,18 @@ public class OnlineFeedViewActivity extends ActionBarActivity { return false; } + private long getFeedId(Feed feed) { + if (feeds == null || feed == null) { + return 0; + } + for (Feed f : feeds) { + if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { + return f.getId(); + } + } + return 0; + } + private void showErrorDialog(String errorMsg) { assert(Looper.myLooper() == Looper.getMainLooper()); // run on UI thread if (!isFinishing() && !isPaused) { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java index 8a9ec58d3..bc1a40b11 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java @@ -2,28 +2,27 @@ package de.danoeh.antennapod.activity; import android.content.Intent; import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.util.SparseBooleanArray; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.preferences.UserPreferences; import java.util.ArrayList; import java.util.List; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.opml.OpmlElement; +import de.danoeh.antennapod.core.preferences.UserPreferences; + /** * Displays the feeds that the OPML-Importer has read and lets the user choose * which feeds he wants to import. */ -public class OpmlFeedChooserActivity extends ActionBarActivity { +public class OpmlFeedChooserActivity extends AppCompatActivity { private static final String TAG = "OpmlFeedChooserActivity"; public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems"; @@ -33,6 +32,9 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { private ListView feedlist; private ArrayAdapter<String> listAdapter; + private MenuItem selectAll; + private MenuItem deselectAll; + @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getTheme()); @@ -44,45 +46,54 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { feedlist = (ListView) findViewById(R.id.feedlist); feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - listAdapter = new ArrayAdapter<String>(this, + listAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice, getTitleList()); feedlist.setAdapter(listAdapter); - - butCancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setResult(RESULT_CANCELED); - finish(); + feedlist.setOnItemClickListener((parent, view, position, id) -> { + SparseBooleanArray checked = feedlist.getCheckedItemPositions(); + int checkedCount = 0; + for (int i = 0; i < checked.size(); i++) { + if (checked.valueAt(i)) { + checkedCount++; + } + } + if(checkedCount == listAdapter.getCount()) { + selectAll.setVisible(false); + deselectAll.setVisible(true); + } else { + deselectAll.setVisible(false); + selectAll.setVisible(true); } }); - butConfirm.setOnClickListener(new OnClickListener() { + butCancel.setOnClickListener(v -> { + setResult(RESULT_CANCELED); + finish(); + }); - @Override - public void onClick(View v) { - Intent intent = new Intent(); - SparseBooleanArray checked = feedlist.getCheckedItemPositions(); + butConfirm.setOnClickListener(v -> { + Intent intent = new Intent(); + SparseBooleanArray checked = feedlist.getCheckedItemPositions(); - int checkedCount = 0; - // Get number of checked items - for (int i = 0; i < checked.size(); i++) { - if (checked.valueAt(i)) { - checkedCount++; - } + int checkedCount = 0; + // Get number of checked items + for (int i = 0; i < checked.size(); i++) { + if (checked.valueAt(i)) { + checkedCount++; } - int[] selection = new int[checkedCount]; - for (int i = 0, collected = 0; collected < checkedCount; i++) { - if (checked.valueAt(i)) { - selection[collected] = checked.keyAt(i); - collected++; - } + } + int[] selection = new int[checkedCount]; + for (int i = 0, collected = 0; collected < checkedCount; i++) { + if (checked.valueAt(i)) { + selection[collected] = checked.keyAt(i); + collected++; } - intent.putExtra(EXTRA_SELECTED_ITEMS, selection); - setResult(RESULT_OK, intent); - finish(); } + intent.putExtra(EXTRA_SELECTED_ITEMS, selection); + setResult(RESULT_OK, intent); + finish(); }); } @@ -93,7 +104,6 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { for (OpmlElement element : OpmlImportHolder.getReadElements()) { result.add(element.getText()); } - } return result; } @@ -101,13 +111,11 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE, - R.string.select_all_label), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE, - R.string.deselect_all_label), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.opml_selection_options, menu); + selectAll = menu.findItem(R.id.select_all_item); + deselectAll = menu.findItem(R.id.deselect_all_item); + deselectAll.setVisible(false); return true; } @@ -115,10 +123,14 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.select_all_item: + selectAll.setVisible(false); selectAllItems(true); + deselectAll.setVisible(true); return true; case R.id.deselect_all_item: + deselectAll.setVisible(false); selectAllItems(false); + selectAll.setVisible(true); return true; default: return false; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java index 46dabec12..8726af281 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -4,8 +4,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; +import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import com.afollestad.materialdialogs.MaterialDialog; @@ -25,13 +26,13 @@ import de.danoeh.antennapod.core.util.LangUtils; /** * Base activity for Opml Import - e.g. with code what to do afterwards * */ -public class OpmlImportBaseActivity extends ActionBarActivity { +public class OpmlImportBaseActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; private OpmlImportWorker importWorker; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; - private Uri uri; + @Nullable private Uri uri; /** * Handles the choices made by the user in the OpmlFeedChooserActivity and @@ -67,7 +68,14 @@ public class OpmlImportBaseActivity extends ActionBarActivity { } } - protected void importUri(Uri uri) { + protected void importUri(@Nullable Uri uri) { + if(uri == null) { + new MaterialDialog.Builder(this) + .content(R.string.opml_import_error_no_file) + .positiveText(android.R.string.ok) + .show(); + return; + } this.uri = uri; if(uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); @@ -127,8 +135,9 @@ public class OpmlImportBaseActivity extends ActionBarActivity { importWorker.executeAsync(); } catch (Exception e) { Log.d(TAG, Log.getStackTraceString(e)); + String message = getString(R.string.opml_reader_error); new MaterialDialog.Builder(this) - .content("Cannot open OPML file: " + e.getMessage()) + .content(message + " " + e.getMessage()) .positiveText(android.R.string.ok) .show(); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java index ab4b0d0ee..02e16a7b5 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java @@ -18,6 +18,9 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); Uri uri = getIntent().getData(); + if(uri.toString().startsWith("/")) { + uri = Uri.parse("file://" + uri.toString()); + } importUri(uri); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java index 15d97cc2c..b2dab7f68 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java @@ -53,7 +53,7 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { intentPickAction = new Intent(Intent.ACTION_PICK); intentPickAction.setData(Uri.parse("file://")); - if(false == IntentUtils.isCallable(getApplicationContext(), intentPickAction)) { + if(!IntentUtils.isCallable(getApplicationContext(), intentPickAction)) { intentPickAction.setData(null); if(false == IntentUtils.isCallable(getApplicationContext(), intentPickAction)) { txtvHeaderExplanation1.setVisibility(View.GONE); @@ -70,7 +70,7 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); intentGetContentAction.setType("*/*"); - if(false == IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { + if(!IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { txtvHeaderExplanation2.setVisibility(View.GONE); txtvExplanation2.setVisibility(View.GONE); findViewById(R.id.divider2).setVisibility(View.GONE); @@ -135,6 +135,9 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == CHOOSE_OPML_FILE) { Uri uri = data.getData(); + if(uri.toString().startsWith("/")) { + uri = Uri.parse("file://" + uri.toString()); + } importUri(uri); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index 80ccb7c99..ba22a42b4 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -8,7 +8,7 @@ import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceFragment; import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; @@ -24,7 +24,7 @@ import de.danoeh.antennapod.preferences.PreferenceController; * PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see * PreferenceController. */ -public class PreferenceActivity extends ActionBarActivity { +public class PreferenceActivity extends AppCompatActivity { private PreferenceController preferenceController; private MainFragment prefFragment; @@ -49,7 +49,7 @@ public class PreferenceActivity extends ActionBarActivity { protected void onCreate(Bundle savedInstanceState) { // This must be the FIRST thing we do, otherwise other code may not have the // reference it needs - instance = new WeakReference<PreferenceActivity>(this); + instance = new WeakReference<>(this); setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java new file mode 100644 index 000000000..0254617e4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/StatisticsActivity.java @@ -0,0 +1,107 @@ +package de.danoeh.antennapod.activity; + +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.StatisticsListAdapter; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.util.Converter; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Displays the 'statistics' screen + */ +public class StatisticsActivity extends AppCompatActivity + implements AdapterView.OnItemClickListener { + + private static final String TAG = StatisticsActivity.class.getSimpleName(); + + private Subscription subscription; + private TextView totalTimeTextView; + private ListView feedStatisticsList; + private ProgressBar progressBar; + private StatisticsListAdapter listAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayShowHomeEnabled(true); + setContentView(R.layout.statistics_activity); + + totalTimeTextView = (TextView) findViewById(R.id.total_time); + feedStatisticsList = (ListView) findViewById(R.id.statistics_list); + progressBar = (ProgressBar) findViewById(R.id.progressBar); + listAdapter = new StatisticsListAdapter(this); + feedStatisticsList.setAdapter(listAdapter); + feedStatisticsList.setOnItemClickListener(this); + } + + @Override + public void onResume() { + super.onResume(); + progressBar.setVisibility(View.VISIBLE); + totalTimeTextView.setVisibility(View.GONE); + feedStatisticsList.setVisibility(View.GONE); + loadStats(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void loadStats() { + if(subscription != null) { + subscription.unsubscribe(); + } + subscription = Observable.fromCallable(() -> DBReader.getStatistics()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result != null) { + totalTimeTextView.setText(Converter + .shortLocalizedDuration(this, result.totalTime)); + listAdapter.update(result.feedTime); + progressBar.setVisibility(View.GONE); + totalTimeTextView.setVisibility(View.VISIBLE); + feedStatisticsList.setVisibility(View.VISIBLE); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + DBReader.StatisticsItem stats = listAdapter.getItem(position); + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(stats.feed.getTitle()); + dialog.setMessage(getString(R.string.statistics_details_dialog, + stats.episodesStarted, + stats.episodes, + Converter.shortLocalizedDuration(this, stats.timePlayed), + Converter.shortLocalizedDuration(this, stats.time))); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.show(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java index e980764ec..f22507f4c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java @@ -132,7 +132,8 @@ public class StorageErrorActivity extends AppCompatActivity { List<String> folders = new ArrayList<>(mediaDirs.length); List<CharSequence> choices = new ArrayList<>(mediaDirs.length); for(int i=0; i < mediaDirs.length; i++) { - if(mediaDirs[i] == null) { + File dir = mediaDirs[i]; + if(dir == null || !dir.exists() || !dir.canRead() || !dir.canWrite()) { continue; } String path = mediaDirs[i].getAbsolutePath(); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index ee459dbc6..a4ffebae2 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -41,6 +41,7 @@ public class VideoplayerActivity extends MediaplayerActivity { */ private boolean videoControlsShowing = true; private boolean videoSurfaceCreated = false; + private boolean destroyingDueToReload = false; private VideoControlsHider videoControlsHider = new VideoControlsHider(this); @@ -83,23 +84,30 @@ public class VideoplayerActivity extends MediaplayerActivity { launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true); startService(launchIntent); + } else if (PlaybackService.isCasting()) { + Intent intent = PlaybackService.getPlayerActivityIntent(this); + if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) { + destroyingDueToReload = true; + finish(); + startActivity(intent); + } } } @Override protected void onPause() { - super.onPause(); videoControlsHider.stop(); if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) { controller.pause(); } + super.onPause(); } @Override protected void onDestroy() { - super.onDestroy(); videoControlsHider.stop(); videoControlsHider = null; + super.onDestroy(); } @Override @@ -159,7 +167,7 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override - protected void postStatusMsg(int resId) { + protected void postStatusMsg(int resId, boolean showToast) { if (resId == R.string.player_preparing_msg) { progressIndicator.setVisibility(View.VISIBLE); } else { @@ -202,6 +210,24 @@ public class VideoplayerActivity extends MediaplayerActivity { videoControlsShowing = !videoControlsShowing; } + @Override + protected void onRewind() { + super.onRewind(); + setupVideoControlsToggler(); + } + + @Override + protected void onPlayPause() { + super.onPlayPause(); + setupVideoControlsToggler(); + } + + @Override + protected void onFastForward() { + super.onFastForward(); + setupVideoControlsToggler(); + } + private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -218,7 +244,7 @@ public class VideoplayerActivity extends MediaplayerActivity { if (controller.serviceAvailable()) { controller.setVideoSurface(holder); } else { - Log.e(TAG, "Could'nt attach surface to mediaplayer - reference to service was null"); + Log.e(TAG, "Couldn't attach surface to mediaplayer - reference to service was null"); } } @@ -228,7 +254,9 @@ public class VideoplayerActivity extends MediaplayerActivity { public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "Videosurface was destroyed"); videoSurfaceCreated = false; - controller.notifyVideoSurfaceAbandoned(); + if (!destroyingDueToReload) { + controller.notifyVideoSurfaceAbandoned(); + } } }; @@ -237,8 +265,14 @@ public class VideoplayerActivity extends MediaplayerActivity { protected void onReloadNotification(int notificationCode) { if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) { Log.d(TAG, "ReloadNotification received, switching to Audioplayer now"); + destroyingDueToReload = true; finish(); startActivity(new Intent(this, AudioplayerActivity.class)); + } else if (notificationCode == PlaybackService.EXTRA_CODE_CAST) { + Log.d(TAG, "ReloadNotification received, switching to Castplayer now"); + destroyingDueToReload = true; + finish(); + startActivity(new Intent(this, CastplayerActivity.class)); } } @@ -312,7 +346,7 @@ public class VideoplayerActivity extends MediaplayerActivity { private static class VideoControlsHider extends Handler { - private static final int DELAY = 5000; + private static final int DELAY = 2500; private WeakReference<VideoplayerActivity> activity; 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 28c2b7206..d46a3d6c2 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 @@ -4,8 +4,8 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; -import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.LayoutInflater; @@ -20,7 +20,6 @@ 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; @@ -42,7 +41,7 @@ import de.danoeh.antennapod.core.service.GpodnetSyncService; * Step 3: Choose from a list of actions */ public class GpodnetAuthenticationActivity extends ActionBarActivity { - private static final String TAG = "GpodnetAuthenticationActivity"; + private static final String TAG = "GpodnetAuthActivity"; private static final String CURRENT_STEP = "current_step"; @@ -122,7 +121,7 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { final String passwordStr = password.getText().toString(); if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials"); - new AsyncTask<GpodnetService, Void, Void>() { + AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() { volatile Exception exception; @@ -144,7 +143,7 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { if (exception == null) { advance(); } else { - txtvError.setText(exception.getMessage()); + txtvError.setText(exception.getCause().getMessage()); txtvError.setVisibility(View.VISIBLE); } } @@ -161,7 +160,12 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { } return null; } - }.execute(service); + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service); + } else { + authTask.execute(); + } } }); } @@ -177,7 +181,7 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { // load device list - final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<List<GpodnetDevice>>(); + final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>(); new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() { private volatile Exception exception; @@ -194,17 +198,18 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) { super.onPostExecute(gpodnetDevices); if (gpodnetDevices != null) { - List<String> deviceNames = new ArrayList<String>(); + List<String> deviceNames = new ArrayList<>(); for (GpodnetDevice device : gpodnetDevices) { deviceNames.add(device.getCaption()); } - spinnerDevices.setAdapter(new ArrayAdapter<String>(GpodnetAuthenticationActivity.this, + spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this, android.R.layout.simple_spinner_dropdown_item, deviceNames)); spinnerDevices.setEnabled(true); if (!deviceNames.isEmpty()) { chooseDevice.setEnabled(true); } devices.set(gpodnetDevices); + deviceID.setText(generateDeviceID(gpodnetDevices)); createNewDevice.setEnabled(true); } } @@ -225,7 +230,7 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { createNewDevice.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (checkDeviceIDText(deviceID, txtvError, devices.get())) { + if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) { final String deviceStr = deviceID.getText().toString(); final String captionStr = caption.getText().toString(); @@ -273,45 +278,60 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { } }); - deviceID.setText(generateDeviceID()); - chooseDevice.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final int position = spinnerDevices.getSelectedItemPosition(); - if (position != AdapterView.INVALID_POSITION) { - selectedDevice = devices.get().get(position); - advance(); - } + chooseDevice.setOnClickListener(v -> { + final int position = spinnerDevices.getSelectedItemPosition(); + if (position != AdapterView.INVALID_POSITION) { + selectedDevice = devices.get().get(position); + advance(); } }); } - private String generateDeviceID() { - final int DEVICE_ID_LENGTH = 10; - StringBuilder buffer = new StringBuilder(DEVICE_ID_LENGTH); - SecureRandom random = new SecureRandom(); - for (int i = 0; i < DEVICE_ID_LENGTH; i++) { - buffer.append(random.nextInt(10)); + private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) { + // devices names must be of a certain form: + // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices + // This is more restrictive than needed, but I think it makes for more readable names. + String baseId = Build.MODEL.replaceAll("\\W", ""); + String id = baseId; + int num = 0; + + while (isDeviceWithIdInList(id, gpodnetDevices)) { + id = baseId + "_" + num; + num++; + } + + return id; + } + private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) { + if (gpodnetDevices == null) { + return false; + } + for (GpodnetDevice device : gpodnetDevices) { + if (device.getId().equals(id)) { + return true; + } } - return buffer.toString(); + return false; } - private boolean checkDeviceIDText(EditText deviceID, TextView txtvError, List<GpodnetDevice> devices) { + private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) { String text = deviceID.getText().toString(); if (text.length() == 0) { txtvError.setText(R.string.gpodnetauth_device_errorEmpty); txtvError.setVisibility(View.VISIBLE); return false; + } else if (caption.length() == 0) { + txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty); + txtvError.setVisibility(View.VISIBLE); + return false; } else { if (devices != null) { - for (GpodnetDevice device : devices) { - if (device.getId().equals(text)) { - txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed); - txtvError.setVisibility(View.VISIBLE); - return false; - } + if (isDeviceWithIdInList(text, devices)) { + txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed); + txtvError.setVisibility(View.VISIBLE); + return false; } txtvError.setVisibility(View.GONE); return true; @@ -325,20 +345,14 @@ public class GpodnetAuthenticationActivity extends ActionBarActivity { final Button sync = (Button) view.findViewById(R.id.butSyncNow); final Button back = (Button) view.findViewById(R.id.butGoMainscreen); - sync.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this); - finish(); - } + sync.setOnClickListener(v -> { + GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this); + finish(); }); - back.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } + back.setOnClickListener(v -> { + Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); }); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonCallback.java b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonCallback.java index a75789815..66e6f9a00 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonCallback.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ActionButtonCallback.java @@ -4,5 +4,5 @@ import de.danoeh.antennapod.core.feed.FeedItem; public interface ActionButtonCallback { /** Is called when the action button of a list item has been pressed. */ - abstract void onActionButtonPressed(FeedItem item); + void onActionButtonPressed(FeedItem item); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java b/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java index 935a0dcd4..8aaf0055a 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java @@ -50,7 +50,7 @@ public class AdapterUtils { Log.d(TAG, "size: " + media.getSize()); if (media.getSize() > 0) { txtvPos.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isDownloadAllowed() && false == media.checkedOnSizeButUnknown()) { + } else if(NetworkUtils.isDownloadAllowed() && !media.checkedOnSizeButUnknown()) { txtvPos.setText("{fa-spinner}"); Iconify.addIcons(txtvPos); NetworkUtils.getFeedMediaSizeObservable(media) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java index 07f895468..43a00f7c5 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -1,10 +1,10 @@ package de.danoeh.antennapod.adapter; -import android.graphics.drawable.Drawable; -import android.net.Uri; +import android.os.Build; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; +import android.text.Layout; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -15,13 +15,11 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; import com.joanzapata.iconify.Iconify; import com.nineoldandroids.view.ViewHelper; @@ -84,8 +82,12 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR .inflate(R.layout.new_episodes_listitem, parent, false); Holder holder = new Holder(view); holder.container = (FrameLayout) view.findViewById(R.id.container); + holder.content = (LinearLayout) view.findViewById(R.id.content); holder.placeholder = (TextView) view.findViewById(R.id.txtvPlaceholder); holder.title = (TextView) view.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } holder.pubDate = (TextView) view .findViewById(R.id.txtvPublished); holder.statusUnread = view.findViewById(R.id.statusUnread); @@ -121,11 +123,16 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.title.setText(item.getTitle()); String pubDateStr = DateUtils.formatAbbrev(mainActivityRef.get(), item.getPubDate()); holder.pubDate.setText(pubDateStr); - if (showOnlyNewEpisodes || false == item.isNew()) { + if (showOnlyNewEpisodes || !item.isNew()) { holder.statusUnread.setVisibility(View.INVISIBLE); } else { holder.statusUnread.setVisibility(View.VISIBLE); } + if(item.isPlayed()) { + ViewHelper.setAlpha(holder.content, 0.5f); + } else { + ViewHelper.setAlpha(holder.content, 1.0f); + } FeedMedia media = item.getMedia(); if (media != null) { @@ -135,7 +142,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); } else if (media.getSize() > 0) { holder.txtvDuration.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isDownloadAllowed() && false == media.checkedOnSizeButUnknown()) { + } else if(NetworkUtils.isDownloadAllowed() && !media.checkedOnSizeButUnknown()) { holder.txtvDuration.setText("{fa-spinner}"); Iconify.addIcons(holder.txtvDuration); NetworkUtils.getFeedMediaSizeObservable(media) @@ -197,7 +204,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .fitCenter() .dontAnimate() - .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover)); + .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover, mainActivityRef.get())); } @Override @@ -221,44 +228,6 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR return pos; } - private class CoverTarget extends GlideDrawableImageViewTarget { - - private final WeakReference<Uri> fallback; - private final WeakReference<TextView> placeholder; - private final WeakReference<ImageView> cover; - - public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { - super(imgvCover); - fallback = new WeakReference<>(fallbackUri); - placeholder = new WeakReference<>(txtvPlaceholder); - cover = new WeakReference<>(imgvCover); - } - - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - Uri fallbackUri = fallback.get(); - TextView txtvPlaceholder = placeholder.get(); - ImageView imgvCover = cover.get(); - if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { - Glide.with(mainActivityRef.get()) - .load(fallbackUri) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate() - .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); - } - } - - @Override - public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { - super.onResourceReady(drawable, anim); - TextView txtvPlaceholder = placeholder.get(); - if(txtvPlaceholder != null) { - txtvPlaceholder.setVisibility(View.INVISIBLE); - } - } - } - private View.OnClickListener secondaryActionListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -271,6 +240,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR implements View.OnClickListener, View.OnCreateContextMenuListener, ItemTouchHelperViewHolder { + LinearLayout content; FrameLayout container; TextView placeholder; TextView title; @@ -295,7 +265,8 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR public void onClick(View v) { MainActivity mainActivity = mainActivityRef.get(); if (mainActivity != null) { - mainActivity.loadChildFragment(ItemFragment.newInstance(item.getId())); + long[] ids = itemAccess.getItemsIds().toArray(); + mainActivity.loadChildFragment(ItemFragment.newInstance(ids, position)); } } @@ -331,8 +302,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR item1.setVisible(visible); } }; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, - itemAccess.getQueueIds(), itemAccess.getFavoritesIds()); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); } } @@ -343,14 +313,12 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR FeedItem getItem(int position); + LongList getItemsIds(); + int getItemDownloadProgressPercent(FeedItem item); boolean isInQueue(FeedItem item); - LongList getQueueIds(); - - LongList getFavoritesIds(); - } /** diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java index 8bde1097b..37e00ab74 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -109,7 +109,7 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { link[0].onClick(widget); - } else if (action == MotionEvent.ACTION_DOWN) { + } else if (action == MotionEvent.ACTION_DOWN){ Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/CoverTarget.java b/app/src/main/java/de/danoeh/antennapod/adapter/CoverTarget.java new file mode 100644 index 000000000..ed0c08086 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/CoverTarget.java @@ -0,0 +1,58 @@ +package de.danoeh.antennapod.adapter; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; + +import java.lang.ref.WeakReference; + +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.glide.ApGlideSettings; + +class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference<Uri> fallback; + private final WeakReference<TextView> placeholder; + private final WeakReference<ImageView> cover; + private final WeakReference<MainActivity> mainActivity; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover, MainActivity activity) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + mainActivity = new WeakReference<>(activity); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if (fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + MainActivity activity = mainActivity.get(); + Glide.with(activity) + .load(fallbackUri) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover, activity)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if (txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } +} 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 469a807e1..00ab96f6c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java @@ -1,9 +1,7 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.support.v7.app.AlertDialog; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; 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 9ff80424c..e271b5eed 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -1,7 +1,9 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.os.Build; import android.support.v4.content.ContextCompat; +import android.text.Layout; import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; @@ -11,8 +13,8 @@ import android.widget.BaseAdapter; import android.widget.TextView; import android.widget.Toast; -import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconButton; +import com.joanzapata.iconify.widget.IconTextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; @@ -48,13 +50,15 @@ 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.icon = (IconTextView) convertView.findViewById(R.id.txtvIcon); holder.retry = (IconButton) convertView.findViewById(R.id.btnRetry); holder.date = (TextView) convertView.findViewById(R.id.txtvDate); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } holder.type = (TextView) convertView.findViewById(R.id.txtvType); - holder.reason = (TextView) convertView - .findViewById(R.id.txtvReason); + holder.reason = (TextView) convertView.findViewById(R.id.txtvReason); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); @@ -78,14 +82,12 @@ public class DownloadLogAdapter extends BaseAdapter { holder.icon.setTextColor(ContextCompat.getColor(convertView.getContext(), R.color.download_success_green)); holder.icon.setText("{fa-check-circle}"); - Iconify.addIcons(holder.icon); holder.retry.setVisibility(View.GONE); holder.reason.setVisibility(View.GONE); } else { holder.icon.setTextColor(ContextCompat.getColor(convertView.getContext(), R.color.download_failed_red)); holder.icon.setText("{fa-times-circle}"); - Iconify.addIcons(holder.icon); String reasonText = status.getReason().getErrorString(context); if (status.getReasonDetailed() != null) { reasonText += ": " + status.getReasonDetailed(); @@ -160,7 +162,7 @@ public class DownloadLogAdapter extends BaseAdapter { } static class Holder { - TextView icon; + IconTextView icon; IconButton retry; TextView title; TextView type; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 53dedd496..6d0beff9e 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.os.Build; +import android.text.Layout; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -60,6 +62,9 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { parent, false); holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } holder.txtvSize = (TextView) convertView.findViewById(R.id.txtvSize); holder.queueStatus = (ImageView) convertView.findViewById(R.id.imgvInPlaylist); holder.pubDate = (TextView) convertView diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java index 4a0ff1f12..e1efdaa7b 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -131,11 +131,11 @@ public class DownloadlistAdapter extends BaseAdapter { } public interface ItemAccess { - public int getCount(); + int getCount(); - public Downloader getItem(int position); + Downloader getItem(int position); - public void onSecondaryActionClick(Downloader downloader); + void onSecondaryActionClick(Downloader downloader); } } 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 7ca8d6a31..4e9c5d71b 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -2,7 +2,9 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; import android.support.v4.content.ContextCompat; +import android.text.Layout; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -95,8 +97,10 @@ public class FeedItemlistAdapter extends BaseAdapter { convertView = inflater.inflate(R.layout.feeditemlist_item, parent, false); holder.container = (LinearLayout) convertView .findViewById(R.id.container); - holder.title = (TextView) convertView - .findViewById(R.id.txtvItemname); + holder.title = (TextView) convertView.findViewById(R.id.txtvItemname); + if(Build.VERSION.SDK_INT >= 23) { + holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } holder.lenSize = (TextView) convertView .findViewById(R.id.txtvLenSize); holder.butAction = (ImageButton) convertView @@ -147,6 +151,8 @@ public class FeedItemlistAdapter extends BaseAdapter { String pubDateStr = DateUtils.formatAbbrev(context, item.getPubDate()); holder.published.setText(pubDateStr); + boolean isInQueue = item.isTagged(FeedItem.TAG_QUEUE); + FeedMedia media = item.getMedia(); if (media == null) { holder.episodeProgress.setVisibility(View.GONE); @@ -157,7 +163,7 @@ public class FeedItemlistAdapter extends BaseAdapter { AdapterUtils.updateEpisodePlaybackProgress(item, holder.lenSize, holder.episodeProgress); - if (itemAccess.isInQueue(item)) { + if (isInQueue) { holder.inPlaylist.setVisibility(View.VISIBLE); } else { holder.inPlaylist.setVisibility(View.INVISIBLE); @@ -189,17 +195,15 @@ public class FeedItemlistAdapter extends BaseAdapter { holder.type.setImageBitmap(null); holder.type.setVisibility(View.GONE); } + typeDrawables.recycle(); if(media.isCurrentlyPlaying()) { - if(media.isCurrentlyPlaying()) { - holder.container.setBackgroundColor(playingBackGroundColor); - } else { - holder.container.setBackgroundColor(normalBackGroundColor); - } + holder.container.setBackgroundColor(playingBackGroundColor); + } else { + holder.container.setBackgroundColor(normalBackGroundColor); } } - boolean isInQueue = itemAccess.isInQueue(item); actionButtonUtils.configureActionButton(holder.butAction, item, isInQueue); holder.butAction.setFocusable(false); holder.butAction.setTag(item); @@ -233,8 +237,6 @@ public class FeedItemlistAdapter extends BaseAdapter { public interface ItemAccess { - boolean isInQueue(FeedItem item); - int getItemDownloadProgressPercent(FeedItem item); int getCount(); 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 6d7e6dcac..75c858ec6 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -38,6 +38,7 @@ import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.NewEpisodesFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.fragment.SubscriptionFragment; /** * BaseAdapter for the navigation drawer @@ -49,11 +50,18 @@ public class NavListAdapter extends BaseAdapter public static final int VIEW_TYPE_SECTION_DIVIDER = 1; public static final int VIEW_TYPE_SUBSCRIPTION = 2; + /** + * a tag used as a placeholder to indicate if the subscription list should be displayed or not + * This tag doesn't correspond to any specific activity. + */ + public static final String SUBSCRIPTION_LIST_TAG = "SubscriptionList"; + private static List<String> tags; private static String[] titles; private ItemAccess itemAccess; private Context context; + private boolean showSubscriptionList = true; public NavListAdapter(ItemAccess itemAccess, Context context) { this.itemAccess = itemAccess; @@ -73,11 +81,23 @@ public class NavListAdapter extends BaseAdapter } private void loadItems() { - List<String> newTags = new ArrayList<String>(Arrays.asList(MainActivity.NAV_DRAWER_TAGS)); + List<String> newTags = new ArrayList<>(Arrays.asList(MainActivity.NAV_DRAWER_TAGS)); List<String> hiddenFragments = UserPreferences.getHiddenDrawerItems(); for(String hidden : hiddenFragments) { newTags.remove(hidden); } + + if (newTags.contains(SUBSCRIPTION_LIST_TAG)) { + // we never want SUBSCRIPTION_LIST_TAG to be in 'tags' + // since it doesn't actually correspond to a position in the list, but is + // a placeholder that indicates if we should show the subscription list in the + // nav drawer at all. + showSubscriptionList = true; + newTags.remove(SUBSCRIPTION_LIST_TAG); + } else { + showSubscriptionList = false; + } + tags = newTags; notifyDataSetChanged(); } @@ -108,6 +128,9 @@ public class NavListAdapter extends BaseAdapter case PlaybackHistoryFragment.TAG: icon = R.attr.ic_history; break; + case SubscriptionFragment.TAG: + icon = R.attr.ic_folder; + break; case AddFeedFragment.TAG: icon = R.attr.content_new; break; @@ -127,7 +150,11 @@ public class NavListAdapter extends BaseAdapter @Override public int getCount() { - return getSubscriptionOffset() + itemAccess.getCount(); + int baseCount = getSubscriptionOffset(); + if (showSubscriptionList) { + baseCount += itemAccess.getCount(); + } + return baseCount; } @Override @@ -177,7 +204,7 @@ public class NavListAdapter extends BaseAdapter } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { v = getSectionDividerView(convertView, parent); } else { - v = getFeedView(position - getSubscriptionOffset(), convertView, parent); + v = getFeedView(position, convertView, parent); } if (v != null && viewType != VIEW_TYPE_SECTION_DIVIDER) { TextView txtvTitle = (TextView) v.findViewById(R.id.txtvTitle); @@ -226,6 +253,14 @@ public class NavListAdapter extends BaseAdapter } else { holder.count.setVisibility(View.GONE); } + } else if (tag.equals(SubscriptionFragment.TAG)) { + int sum = itemAccess.getFeedCounterSum(); + if (sum > 0) { + holder.count.setVisibility(View.VISIBLE); + holder.count.setText(String.valueOf(sum)); + } else { + holder.count.setVisibility(View.GONE); + } } else if(tag.equals(DownloadsFragment.TAG) && UserPreferences.isEnableAutodownload()) { int epCacheSize = UserPreferences.getEpisodeCacheSize(); // don't count episodes that can be reclaimed @@ -236,13 +271,13 @@ public class NavListAdapter extends BaseAdapter holder.count.setText("{md-disc-full 150%}"); Iconify.addIcons(holder.count); holder.count.setVisibility(View.VISIBLE); - holder.count.setOnClickListener(v -> { + holder.count.setOnClickListener(v -> new AlertDialog.Builder(context) .setTitle(R.string.episode_cache_full_title) .setMessage(R.string.episode_cache_full_message) .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) - .show(); - }); + .show() + ); } else { holder.count.setVisibility(View.GONE); } @@ -267,10 +302,11 @@ public class NavListAdapter extends BaseAdapter return convertView; } - private View getFeedView(int feedPos, View convertView, ViewGroup parent) { - FeedHolder holder; + private View getFeedView(int position, View convertView, ViewGroup parent) { + int feedPos = position - getSubscriptionOffset(); Feed feed = itemAccess.getItem(feedPos); + FeedHolder holder; if (convertView == null) { holder = new FeedHolder(); LayoutInflater inflater = (LayoutInflater) context @@ -298,7 +334,6 @@ public class NavListAdapter extends BaseAdapter holder.title.setText(feed.getTitle()); - if(feed.hasLastUpdateFailed()) { RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams(); p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure); @@ -312,7 +347,11 @@ public class NavListAdapter extends BaseAdapter if(counter > 0) { holder.count.setVisibility(View.VISIBLE); holder.count.setText(String.valueOf(counter)); - holder.count.setTypeface(holder.title.getTypeface()); + if (itemAccess.getSelectedItemIndex() == position) { + holder.count.setTypeface(null, Typeface.BOLD); + } else { + holder.count.setTypeface(null, Typeface.NORMAL); + } } else { holder.count.setVisibility(View.GONE); } @@ -341,6 +380,7 @@ public class NavListAdapter extends BaseAdapter int getNumberOfDownloadedItems(); int getReclaimableItems(); int getFeedCounter(long feedId); + int getFeedCounterSum(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index 6cf61f90b..796ac4184 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -1,12 +1,12 @@ package de.danoeh.antennapod.adapter; -import android.graphics.drawable.Drawable; -import android.net.Uri; +import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; +import android.text.Layout; import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; @@ -23,12 +23,11 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; import com.joanzapata.iconify.Iconify; import com.nineoldandroids.view.ViewHelper; +import org.apache.commons.lang3.ArrayUtils; + import java.lang.ref.WeakReference; import de.danoeh.antennapod.R; @@ -144,6 +143,9 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap placeholder = (TextView) v.findViewById(R.id.txtvPlaceholder); cover = (ImageView) v.findViewById(R.id.imgvCover); title = (TextView) v.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } pubDate = (TextView) v.findViewById(R.id.txtvPubDate); progressLeft = (TextView) v.findViewById(R.id.txtvProgressLeft); progressRight = (TextView) v.findViewById(R.id.txtvProgressRight); @@ -165,7 +167,9 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap public void onClick(View v) { MainActivity activity = mainActivity.get(); if (activity != null) { - activity.loadChildFragment(ItemFragment.newInstance(item.getId())); + long[] ids = itemAccess.getQueueIds().toArray(); + int position = ArrayUtils.indexOf(ids, item.getId()); + activity.loadChildFragment(ItemFragment.newInstance(ids, position)); } } @@ -189,8 +193,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap item1.setVisible(visible); } }; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, - itemAccess.getQueueIds(), itemAccess.getFavoritesIds()); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, itemAccess.getQueueIds()); } @Override @@ -258,7 +261,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap } else { if(media.getSize() > 0) { progressLeft.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isDownloadAllowed() && false == media.checkedOnSizeButUnknown()) { + } else if(NetworkUtils.isDownloadAllowed() && !media.checkedOnSizeButUnknown()) { progressLeft.setText("{fa-spinner}"); Iconify.addIcons(progressLeft); NetworkUtils.getFeedMediaSizeObservable(media) @@ -297,48 +300,9 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .fitCenter() .dontAnimate() - .into(new CoverTarget(item.getFeed().getImageUri(), placeholder, cover)); - } - - } - - - private class CoverTarget extends GlideDrawableImageViewTarget { - - private final WeakReference<Uri> fallback; - private final WeakReference<TextView> placeholder; - private final WeakReference<ImageView> cover; - - public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { - super(imgvCover); - fallback = new WeakReference<>(fallbackUri); - placeholder = new WeakReference<>(txtvPlaceholder); - cover = new WeakReference<>(imgvCover); + .into(new CoverTarget(item.getFeed().getImageUri(), placeholder, cover, mainActivity.get())); } - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - Uri fallbackUri = fallback.get(); - TextView txtvPlaceholder = placeholder.get(); - ImageView imgvCover = cover.get(); - if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { - Glide.with(mainActivity.get()) - .load(fallbackUri) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate() - .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); - } - } - - @Override - public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { - super.onResourceReady(drawable, anim); - TextView txtvPlaceholder = placeholder.get(); - if(txtvPlaceholder != null) { - txtvPlaceholder.setVisibility(View.INVISIBLE); - } - } } private View.OnClickListener secondaryActionListener = new View.OnClickListener() { @@ -357,7 +321,6 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap long getItemDownloadSize(FeedItem item); int getItemDownloadProgressPercent(FeedItem item); LongList getQueueIds(); - LongList getFavoritesIds(); } /** diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java index 83f5dcb4d..a68ef01d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.os.Build; +import android.text.Layout; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -9,7 +11,6 @@ import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; @@ -61,6 +62,9 @@ public class SearchlistAdapter extends BaseAdapter { convertView = inflater.inflate(R.layout.searchlist_item, parent, false); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } holder.cover = (ImageView) convertView .findViewById(R.id.imgvFeedimage); holder.subtitle = (TextView) convertView @@ -112,7 +116,7 @@ public class SearchlistAdapter extends BaseAdapter { TextView subtitle; } - public static interface ItemAccess { + public interface ItemAccess { int getCount(); SearchResult getItem(int position); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java new file mode 100644 index 000000000..7fb1472ad --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -0,0 +1,97 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.widget.IconTextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.util.Converter; + +/** + * Adapter for the statistics list + */ +public class StatisticsListAdapter extends BaseAdapter { + private Context context; + List<DBReader.StatisticsItem> feedTime = new ArrayList<>(); + + public StatisticsListAdapter(Context context) { + this.context = context; + } + + + @Override + public int getCount() { + return feedTime.size(); + } + + @Override + public DBReader.StatisticsItem getItem(int position) { + return feedTime.get(position); + } + + @Override + public long getItemId(int position) { + return feedTime.get(position).feed.getId(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + StatisticsHolder holder; + Feed feed = feedTime.get(position).feed; + + if (convertView == null) { + holder = new StatisticsHolder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + convertView = inflater.inflate(R.layout.statistics_listitem, parent, false); + + holder.image = (ImageView) convertView.findViewById(R.id.imgvCover); + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.time = (TextView) convertView.findViewById(R.id.txtvTime); + convertView.setTag(holder); + } else { + holder = (StatisticsHolder) convertView.getTag(); + } + + Glide.with(context) + .load(feed.getImageUri()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(holder.image); + + holder.title.setText(feed.getTitle()); + holder.time.setText(Converter.shortLocalizedDuration(context, + feedTime.get(position).timePlayed)); + return convertView; + } + + public void update(List<DBReader.StatisticsItem> feedTime) { + this.feedTime = feedTime; + notifyDataSetChanged(); + } + + static class StatisticsHolder { + ImageView image; + TextView title; + TextView time; + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java new file mode 100644 index 000000000..e2561804e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -0,0 +1,141 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.net.Uri; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import java.lang.ref.WeakReference; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.fragment.AddFeedFragment; +import de.danoeh.antennapod.fragment.ItemlistFragment; +import jp.shts.android.library.TriangleLabelView; + +/** + * Adapter for subscriptions + */ +public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnItemClickListener { + + /** placeholder object that indicates item should be added */ + public static final Object ADD_ITEM_OBJ = new Object(); + + /** the position in the view that holds the add item */ + private static final int ADD_POSITION = 0; + private static final String TAG = "SubscriptionsAdapter"; + + private final WeakReference<MainActivity> mainActivityRef; + private final ItemAccess itemAccess; + + public SubscriptionsAdapter(MainActivity mainActivity, ItemAccess itemAccess) { + this.mainActivityRef = new WeakReference<>(mainActivity); + this.itemAccess = itemAccess; + } + + private int getAdjustedPosition(int origPosition) { + return origPosition - 1; + } + + @Override + public int getCount() { + return 1 + itemAccess.getCount(); + } + + @Override + public Object getItem(int position) { + if (position == ADD_POSITION) { + return ADD_ITEM_OBJ; + } + return itemAccess.getItem(getAdjustedPosition(position)); + } + + @Override + public long getItemId(int position) { + if (position == ADD_POSITION) { + return 0; + } + return itemAccess.getItem(getAdjustedPosition(position)).getId(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Holder holder; + + if (convertView == null) { + holder = new Holder(); + + LayoutInflater layoutInflater = + (LayoutInflater) mainActivityRef.get().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = layoutInflater.inflate(R.layout.subscription_item, parent, false); + holder.feedTitle = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.imageView = (ImageView) convertView.findViewById(R.id.imgvCover); + holder.count = (TriangleLabelView) convertView.findViewById(R.id.triangleCountView); + + + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + if (position == ADD_POSITION) { + holder.feedTitle.setText("{md-add 500%}\n\n" + mainActivityRef.get().getString(R.string.add_feed_label)); + holder.feedTitle.setVisibility(View.VISIBLE); + // prevent any accidental re-use of old values (not sure how that would happen...) + holder.count.setPrimaryText(""); + // make it go away, we don't need it for add feed + holder.count.setVisibility(View.INVISIBLE); + return convertView; + } + + final Feed feed = (Feed) getItem(position); + if (feed == null) return null; + + holder.feedTitle.setText(feed.getTitle()); + holder.feedTitle.setVisibility(View.VISIBLE); + holder.count.setPrimaryText(String.valueOf(itemAccess.getFeedCounter(feed.getId()))); + holder.count.setVisibility(View.VISIBLE); + Glide.with(mainActivityRef.get()) + .load(feed.getImageUri()) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, holder.feedTitle, holder.imageView, mainActivityRef.get())); + + return convertView; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (position == ADD_POSITION) { + mainActivityRef.get().loadChildFragment(new AddFeedFragment()); + } else { + Fragment fragment = ItemlistFragment.newInstance(getItemId(position)); + mainActivityRef.get().loadChildFragment(fragment); + } + } + + static class Holder { + public TextView feedTitle; + public ImageView imageView; + public TriangleLabelView count; + } + + public interface ItemAccess { + int getCount(); + Feed getItem(int position); + int getFeedCounter(long feedId); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java index 743f9fc86..aea3d583f 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java @@ -9,7 +9,6 @@ import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.StringUtils; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index 47ac4c757..e9756b467 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -167,7 +167,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { for(int i=0; imageUrl == null && i < images.length(); i++) { JSONObject image = images.getJSONObject(i); String height = image.getJSONObject("attributes").getString("height"); - if(Integer.valueOf(height) >= 100) { + if(Integer.parseInt(height) >= 100) { imageUrl = image.getString("label"); } } diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java index 5c24c2822..13abb26ea 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlExportWorker.java @@ -3,7 +3,6 @@ package de.danoeh.antennapod.asynctask; import android.annotation.SuppressLint; import android.app.ProgressDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; @@ -80,14 +79,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { progDialog.dismiss(); AlertDialog.Builder alert = new AlertDialog.Builder(context) .setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - } - }); + (dialog, which) -> dialog.dismiss()); if (exception != null) { alert.setTitle(R.string.export_error_label); alert.setMessage(exception.getMessage()); diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java index cc27b6c9d..1cb653f01 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java @@ -43,9 +43,8 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { DownloadRequester requester = DownloadRequester.getInstance(); - for (int idx = 0; idx < selection.length; idx++) { - OpmlElement element = OpmlImportHolder.getReadElements().get( - selection[idx]); + for (int selected : selection) { + OpmlElement element = OpmlImportHolder.getReadElements().get(selected); Feed feed = new Feed(element.getXmlUrl(), null, element.getText()); try { diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index 86636485d..45ec6c5fa 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -3,21 +3,20 @@ package de.danoeh.antennapod.asynctask; import android.annotation.SuppressLint; import android.app.ProgressDialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.os.AsyncTask; import android.support.v7.app.AlertDialog; import android.util.Log; -import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.opml.OpmlReader; + import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.opml.OpmlElement; +import de.danoeh.antennapod.core.opml.OpmlReader; + public class OpmlImportWorker extends AsyncTask<Void, Void, ArrayList<OpmlElement>> { private static final String TAG = "OpmlImportWorker"; diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java index 9f8af1142..c3f7ae9c8 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.config; import de.danoeh.antennapod.core.DBTasksCallbacks; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.APCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APDownloadAlgorithm; import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm; import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm; diff --git a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java index 997befe99..1ab60ef61 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java @@ -5,6 +5,7 @@ import android.content.Intent; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity; +import de.danoeh.antennapod.activity.CastplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; import de.danoeh.antennapod.core.PlaybackServiceCallbacks; import de.danoeh.antennapod.core.feed.MediaType; @@ -12,7 +13,10 @@ import de.danoeh.antennapod.core.feed.MediaType; public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { @Override - public Intent getPlayerActivityIntent(Context context, MediaType mediaType) { + public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) { + if (remotePlayback) { + return new Intent(context, CastplayerActivity.class); + } if (mediaType == MediaType.VIDEO) { return new Intent(context, VideoplayerActivity.class); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java index bdb2d68ba..6f9e221ec 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java @@ -2,13 +2,13 @@ package de.danoeh.antennapod.dialog; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.os.Bundle; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; + import de.danoeh.antennapod.R; /** @@ -58,26 +58,13 @@ public abstract class AuthenticationDialog extends Dialog { if (passwordInitialValue != null) { etxtPassword.setText(passwordInitialValue); } - setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - onCancelled(); - } - }); - butCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - cancel(); - } - }); - butConfirm.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onConfirmed(etxtUsername.getText().toString(), - etxtPassword.getText().toString(), - showSaveCredentialsCheckbox && saveUsernamePassword.isChecked()); - dismiss(); - } + setOnCancelListener(dialog -> onCancelled()); + butCancel.setOnClickListener(v -> cancel()); + butConfirm.setOnClickListener(v -> { + onConfirmed(etxtUsername.getText().toString(), + etxtPassword.getText().toString(), + showSaveCredentialsCheckbox && saveUsernamePassword.isChecked()); + dismiss(); }); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java index 75b1bc8d2..93425949c 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java @@ -3,7 +3,6 @@ package de.danoeh.antennapod.dialog; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; -import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.CheckBox; @@ -42,12 +41,9 @@ public class AutoFlattrPreferenceDialog { setStatusMsgText(activity, txtvStatus, initialValue); skbPercent.setProgress(initialValue); - chkAutoFlattr.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - skbPercent.setEnabled(chkAutoFlattr.isChecked()); - txtvStatus.setEnabled(chkAutoFlattr.isChecked()); - } + chkAutoFlattr.setOnClickListener(v -> { + skbPercent.setEnabled(chkAutoFlattr.isChecked()); + txtvStatus.setEnabled(chkAutoFlattr.isChecked()); }); skbPercent.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @@ -69,20 +65,14 @@ public class AutoFlattrPreferenceDialog { builder.setTitle(R.string.pref_auto_flattr_title) .setView(view) - .setPositiveButton(R.string.confirm_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - float progDouble = ((float) skbPercent.getProgress()) / 100.0f; - callback.onConfirmed(chkAutoFlattr.isChecked(), progDouble); - dialog.dismiss(); - } + .setPositiveButton(R.string.confirm_label, (dialog, which) -> { + float progDouble = ((float) skbPercent.getProgress()) / 100.0f; + callback.onConfirmed(chkAutoFlattr.isChecked(), progDouble); + dialog.dismiss(); }) - .setNegativeButton(R.string.cancel_label, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - callback.onCancelled(); - dialog.dismiss(); - } + .setNegativeButton(R.string.cancel_label, (dialog, which) -> { + callback.onCancelled(); + dialog.dismiss(); }) .setCancelable(false).show(); } @@ -97,10 +87,10 @@ public class AutoFlattrPreferenceDialog { } } - public static interface AutoFlattrPreferenceDialogInterface { - public void onCancelled(); + public interface AutoFlattrPreferenceDialogInterface { + void onCancelled(); - public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue); + void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java index ac7a9efee..577a3ecbe 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -54,7 +54,7 @@ public class EpisodesApplyActionFragment extends Fragment { private final Map<Long,FeedItem> idMap = new ArrayMap<>(); private final List<FeedItem> episodes = new ArrayList<>(); private int actions; - private final List<String> titles = new ArrayList(); + private final List<String> titles = new ArrayList<>(); private final LongList checkedIds = new LongList(); private MenuItem mSelectToggle; @@ -285,9 +285,9 @@ public class EpisodesApplyActionFragment extends Fragment { private void sortByDuration(final boolean reverse) { Collections.sort(episodes, (lhs, rhs) -> { int ordering; - if (false == lhs.hasMedia()) { + if (!lhs.hasMedia()) { ordering = 1; - } else if (false == rhs.hasMedia()) { + } else if (!rhs.hasMedia()) { ordering = -1; } else { ordering = lhs.getMedia().getDuration() - rhs.getMedia().getDuration(); @@ -304,7 +304,7 @@ public class EpisodesApplyActionFragment extends Fragment { private void checkAll() { for (FeedItem episode : episodes) { - if(false == checkedIds.contains(episode.getId())) { + if(!checkedIds.contains(episode.getId())) { checkedIds.add(episode.getId()); } } @@ -391,14 +391,14 @@ public class EpisodesApplyActionFragment extends Fragment { private void downloadChecked() { // download the check episodes in the same order as they are currently displayed - List<FeedItem> toDownload = new ArrayList<FeedItem>(checkedIds.size()); + List<FeedItem> toDownload = new ArrayList<>(checkedIds.size()); for(FeedItem episode : episodes) { if(checkedIds.contains(episode.getId())) { toDownload.add(episode); } } try { - DBTasks.downloadFeedItems(getActivity(), toDownload.toArray(new FeedItem[0])); + DBTasks.downloadFeedItems(getActivity(), toDownload.toArray(new FeedItem[toDownload.size()])); } catch (DownloadRequestException e) { e.printStackTrace(); DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java index 5f531e88f..b50e21d15 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.dialog; import android.content.Context; -import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.InputType; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java new file mode 100644 index 000000000..98a4b5356 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java @@ -0,0 +1,320 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.content.ContextCompat; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.materialdialogs.internal.MDButton; +import com.squareup.okhttp.Credentials; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.service.download.ProxyConfig; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +public class ProxyDialog { + + private static final String TAG = "ProxyDialog"; + + private Context context; + + private MaterialDialog dialog; + + private Spinner spType; + private EditText etHost; + private EditText etPort; + private EditText etUsername; + private EditText etPassword; + + private boolean testSuccessful = false; + private TextView txtvMessage; + private Subscription subscription; + + public ProxyDialog(Context context) { + this.context = context; + } + + public Dialog createDialog() { + dialog = new MaterialDialog.Builder(context) + .title(R.string.pref_proxy_title) + .customView(R.layout.proxy_settings, true) + .positiveText(R.string.proxy_test_label) + .negativeText(R.string.cancel_label) + .onPositive((dialog1, which) -> { + if(!testSuccessful) { + dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); + test(); + return; + } + String type = (String) ((Spinner) dialog1.findViewById(R.id.spType)).getSelectedItem(); + ProxyConfig proxy; + if(Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { + proxy = ProxyConfig.direct(); + } else { + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + if(TextUtils.isEmpty(username)) { + username = null; + } + String password = etPassword.getText().toString(); + if(TextUtils.isEmpty(password)) { + password = null; + } + int portValue = 0; + if(!TextUtils.isEmpty(port)) { + portValue = Integer.valueOf(port); + } + proxy = ProxyConfig.http(host, portValue, username, password); + } + UserPreferences.setProxyConfig(proxy); + AntennapodHttpClient.reinit(); + dialog.dismiss(); + }) + .onNegative((dialog1, which) -> dialog1.dismiss()) + .autoDismiss(false) + .build(); + View view = dialog.getCustomView(); + spType = (Spinner) view.findViewById(R.id.spType); + String[] types = { Proxy.Type.DIRECT.name(), Proxy.Type.HTTP.name() }; + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, types); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spType.setAdapter(adapter); + ProxyConfig proxyConfig = UserPreferences.getProxyConfig(); + spType.setSelection(adapter.getPosition(proxyConfig.type.name())); + etHost = (EditText) view.findViewById(R.id.etHost); + if(!TextUtils.isEmpty(proxyConfig.host)) { + etHost.setText(proxyConfig.host); + } + etHost.addTextChangedListener(requireTestOnChange); + etPort = (EditText) view.findViewById(R.id.etPort); + if(proxyConfig.port > 0) { + etPort.setText(String.valueOf(proxyConfig.port)); + } + etPort.addTextChangedListener(requireTestOnChange); + etUsername = (EditText) view.findViewById(R.id.etUsername); + if(!TextUtils.isEmpty(proxyConfig.username)) { + etUsername.setText(proxyConfig.username); + } + etUsername.addTextChangedListener(requireTestOnChange); + etPassword = (EditText) view.findViewById(R.id.etPassword); + if(!TextUtils.isEmpty(proxyConfig.password)) { + etPassword.setText(proxyConfig.username); + } + etPassword.addTextChangedListener(requireTestOnChange); + if(proxyConfig.type == Proxy.Type.DIRECT) { + enableSettings(false); + setTestRequired(false); + } + spType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + enableSettings(position > 0); + setTestRequired(position > 0); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + enableSettings(false); + } + }); + txtvMessage = (TextView) view.findViewById(R.id.txtvMessage); + checkValidity(); + return dialog; + } + + private final TextWatcher requireTestOnChange = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + setTestRequired(true); + } + }; + + private void enableSettings(boolean enable) { + etHost.setEnabled(enable); + etPort.setEnabled(enable); + etUsername.setEnabled(enable); + etPassword.setEnabled(enable); + } + + private boolean checkValidity() { + boolean valid = true; + if(spType.getSelectedItemPosition() > 0) { + valid &= checkHost(); + } + valid &= checkPort(); + return valid; + } + + private boolean checkHost() { + String host = etHost.getText().toString(); + if(host.length() == 0) { + etHost.setError(context.getString(R.string.proxy_host_empty_error)); + return false; + } + if(!"localhost".equals(host) && !Patterns.DOMAIN_NAME.matcher(host).matches()) { + etHost.setError(context.getString(R.string.proxy_host_invalid_error)); + return false; + } + return true; + } + + private boolean checkPort() { + int port = getPort(); + if(port < 0 && port > 65535) { + etPort.setError(context.getString(R.string.proxy_port_invalid_error)); + return false; + } + return true; + } + + private int getPort() { + String port = etPort.getText().toString(); + if(port.length() > 0) { + try { + return Integer.parseInt(port); + } catch(NumberFormatException e) { + // ignore + } + } + return 0; + } + + private void setTestRequired(boolean required) { + if(required) { + testSuccessful = false; + MDButton button = dialog.getActionButton(DialogAction.POSITIVE); + button.setText(context.getText(R.string.proxy_test_label)); + button.setEnabled(true); + } else { + testSuccessful = true; + MDButton button = dialog.getActionButton(DialogAction.POSITIVE); + button.setText(context.getText(android.R.string.ok)); + button.setEnabled(true); + } + } + + private void test() { + if(subscription != null) { + subscription.unsubscribe(); + } + if(!checkValidity()) { + setTestRequired(true); + return; + } + TypedArray res = context.getTheme().obtainStyledAttributes(new int[] { android.R.attr.textColorPrimary }); + int textColorPrimary = res.getColor(0, 0); + res.recycle(); + String checking = context.getString(R.string.proxy_checking); + txtvMessage.setTextColor(textColorPrimary); + txtvMessage.setText("{fa-circle-o-notch spin} " + checking); + txtvMessage.setVisibility(View.VISIBLE); + subscription = Observable.create(new Observable.OnSubscribe<Response>() { + @Override + public void call(Subscriber<? super Response> subscriber) { + String type = (String) spType.getSelectedItem(); + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + String password = etPassword.getText().toString(); + int portValue = 8080; + if(!TextUtils.isEmpty(port)) { + portValue = Integer.valueOf(port); + } + SocketAddress address = InetSocketAddress.createUnresolved(host, portValue); + Proxy.Type proxyType = Proxy.Type.valueOf(type.toUpperCase()); + Proxy proxy = new Proxy(proxyType, address); + OkHttpClient client = AntennapodHttpClient.newHttpClient(); + client.setConnectTimeout(10, TimeUnit.SECONDS); + client.setProxy(proxy); + client.interceptors().clear(); + if(!TextUtils.isEmpty(username)) { + String credentials = Credentials.basic(username, password); + client.interceptors().add(chain -> { + Request request = chain.request().newBuilder() + .header("Proxy-Authorization", credentials).build(); + return chain.proceed(request); + }); + } + Request request = new Request.Builder() + .url("http://www.google.com") + .head() + .build(); + try { + Response response = client.newCall(request).execute(); + subscriber.onNext(response); + } catch(IOException e) { + subscriber.onError(e); + } + subscriber.onCompleted(); + } + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + response -> { + int colorId; + String icon; + String result; + if(response.isSuccessful()) { + colorId = R.color.download_success_green; + icon = "{fa-check}"; + result = context.getString(R.string.proxy_test_successful); + } else { + colorId = R.color.download_failed_red; + icon = "{fa-close}"; + result = context.getString(R.string.proxy_test_failed); + } + int color = ContextCompat.getColor(context, colorId); + txtvMessage.setTextColor(color); + String message = String.format("%s %s: %s", icon, result, response.message()); + txtvMessage.setText(message); + setTestRequired(!response.isSuccessful()); + }, + error -> { + String icon = "{fa-close}"; + String result = context.getString(R.string.proxy_test_failed); + int color = ContextCompat.getColor(context, R.color.download_failed_red); + txtvMessage.setTextColor(color); + String message = String.format("%s %s: %s", icon, result, error.getMessage()); + txtvMessage.setText(message); + setTestRequired(true); + } + ); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java index ed0db92a4..64fc1fda4 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RatingDialog.java @@ -94,11 +94,7 @@ public class RatingDialog { long firstDate = mPreferences.getLong(KEY_FIRST_START_DATE, now); long diff = now - firstDate; long diffDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); - if (diffDays >= AFTER_DAYS) { - return true; - } else { - return false; - } + return diffDays >= AFTER_DAYS; } @Nullable @@ -107,30 +103,16 @@ public class RatingDialog { if(context == null) { return null; } - MaterialDialog dialog = new MaterialDialog.Builder(context) + return new MaterialDialog.Builder(context) .title(R.string.rating_title) .content(R.string.rating_message) .positiveText(R.string.rating_now_label) .negativeText(R.string.rating_never_label) .neutralText(R.string.rating_later_label) - .callback(new MaterialDialog.ButtonCallback() { - @Override - public void onPositive(MaterialDialog dialog) { - rateNow(); - } - - @Override - public void onNegative(MaterialDialog dialog) { - saveRated(); - } - - @Override - public void onNeutral(MaterialDialog dialog) { - resetStartDate(); - } - }) + .onPositive((dialog, which) -> rateNow()) + .onNegative((dialog, which) -> saveRated()) + .onNeutral((dialog, which) -> resetStartDate()) .cancelListener(dialog1 -> resetStartDate()) .build(); - return dialog; } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index 930079e40..8a13a75d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -1,17 +1,13 @@ package de.danoeh.antennapod.dialog; -import android.app.Dialog; import android.content.Context; import android.content.SharedPreferences; -import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; -import android.view.Window; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; -import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Spinner; @@ -58,14 +54,8 @@ public abstract class SleepTimerDialog { builder.customView(R.layout.time_dialog, false); builder.positiveText(R.string.set_sleeptimer_label); builder.negativeText(R.string.cancel_label); - builder.callback(new MaterialDialog.ButtonCallback() { - @Override - public void onNegative(MaterialDialog dialog) { - dialog.dismiss(); - } - - @Override - public void onPositive(MaterialDialog dialog) { + builder.onNegative((dialog, which) -> dialog.dismiss()); + builder.onPositive((dialog, which) -> { try { savePreferences(); long input = readTimeMillis(); @@ -77,8 +67,7 @@ public abstract class SleepTimerDialog { Toast.LENGTH_LONG); toast.show(); } - } - }); + }); dialog = builder.build(); View view = dialog.getView(); @@ -138,7 +127,7 @@ public abstract class SleepTimerDialog { private long readTimeMillis() { TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; - long value = Long.valueOf(etxtTime.getText().toString()); + long value = Long.parseLong(etxtTime.getText().toString()); return selectedUnit.toMillis(value); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index 3ed82b9bd..2bf9c4e7a 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -102,8 +102,8 @@ public class VariableSpeedDialog { builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { int choiceCount = 0; - for (int i = 0; i < speedChecked.length; i++) { - if (speedChecked[i]) { + for (boolean checked : speedChecked) { + if (checked) { choiceCount++; } } 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 d979dc382..45364ca07 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -47,36 +47,18 @@ public class AddFeedFragment extends Fragment { final MainActivity activity = (MainActivity) getActivity(); activity.getSupportActionBar().setTitle(R.string.add_feed_label); - butSearchITunes.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - activity.loadChildFragment(new ItunesSearchFragment()); - } - }); + butSearchITunes.setOnClickListener(v -> activity.loadChildFragment(new ItunesSearchFragment())); - butBrowserGpoddernet.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - activity.loadChildFragment(new GpodnetMainFragment()); - } - }); + butBrowserGpoddernet.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); - butOpmlImport.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(getActivity(), - OpmlImportFromPathActivity.class)); - } - }); + butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), + OpmlImportFromPathActivity.class))); - butConfirm.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString()); - intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); - startActivity(intent); - } + butConfirm.setOnClickListener(v -> { + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString()); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); + startActivity(intent); }); return root; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index aa2879330..8ae7f1cf9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -24,7 +23,6 @@ import android.widget.Toast; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; @@ -72,17 +70,15 @@ public class AllEpisodesFragment extends Fragment { private static final String PREF_SCROLL_OFFSET = "scroll_offset"; protected RecyclerView recyclerView; - private AllEpisodesRecycleAdapter listAdapter; + protected AllEpisodesRecycleAdapter listAdapter; private ProgressBar progLoading; - private List<FeedItem> episodes; + protected List<FeedItem> episodes; private List<Downloader> downloaderList; private boolean itemsLoaded = false; private boolean viewsCreated = false; - private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>(); - private boolean isUpdatingFeeds; protected Subscription subscription; @@ -101,7 +97,6 @@ public class AllEpisodesFragment extends Fragment { public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); - this.activity.set((MainActivity) getActivity()); if (viewsCreated && itemsLoaded) { onFragmentLoaded(); } @@ -133,12 +128,6 @@ public class AllEpisodesFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.activity.set((MainActivity) getActivity()); - } - - @Override public void onDestroyView() { super.onDestroyView(); resetViewState(); @@ -176,18 +165,13 @@ public class AllEpisodesFragment extends Fragment { } protected void resetViewState() { - listAdapter = null; - activity.set(null); viewsCreated = false; + listAdapter = null; } - private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { - @Override - public boolean isRefreshing() { - return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); - } - }; + private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = + () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -325,7 +309,7 @@ public class AllEpisodesFragment extends Fragment { viewsCreated = true; - if (itemsLoaded && activity.get() != null) { + if (itemsLoaded) { onFragmentLoaded(); } @@ -334,7 +318,7 @@ public class AllEpisodesFragment extends Fragment { private void onFragmentLoaded() { if (listAdapter == null) { - MainActivity mainActivity = activity.get(); + MainActivity mainActivity = (MainActivity) getActivity(); listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, new DefaultActionButtonCallback(mainActivity), showOnlyNewEpisodes()); listAdapter.setHasStableIds(true); @@ -365,6 +349,18 @@ public class AllEpisodesFragment extends Fragment { } @Override + public LongList getItemsIds() { + if(episodes == null) { + return new LongList(0); + } + LongList ids = new LongList(episodes.size()); + for(FeedItem episode : episodes) { + ids.add(episode.getId()); + } + return ids; + } + + @Override public int getItemDownloadProgressPercent(FeedItem item) { if (downloaderList != null) { for (Downloader downloader : downloaderList) { @@ -379,39 +375,9 @@ public class AllEpisodesFragment extends Fragment { @Override public boolean isInQueue(FeedItem item) { - if (item != null) { - return item.isTagged(FeedItem.TAG_QUEUE); - } - return false; + return item != null && item.isTagged(FeedItem.TAG_QUEUE); } - @Override - public LongList getQueueIds() { - LongList queueIds = new LongList(); - if(episodes == null) { - return queueIds; - } - for(FeedItem item : episodes) { - if(item.isTagged(FeedItem.TAG_QUEUE)) { - queueIds.add(item.getId()); - } - } - return queueIds; - } - - @Override - public LongList getFavoritesIds() { - LongList favoritesIds = new LongList(); - if(episodes == null) { - return favoritesIds; - } - for(FeedItem item : episodes) { - if(item.isTagged(FeedItem.TAG_FAVORITE)) { - favoritesIds.add(item.getId()); - } - } - return favoritesIds; - } }; public void onEventMainThread(FeedItemEvent event) { @@ -471,7 +437,7 @@ public class AllEpisodesFragment extends Fragment { recyclerView.setVisibility(View.GONE); progLoading.setVisibility(View.VISIBLE); } - subscription = Observable.fromCallable(() -> loadData()) + subscription = Observable.fromCallable(this::loadData) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> { @@ -480,7 +446,7 @@ public class AllEpisodesFragment extends Fragment { if (data != null) { episodes = data; itemsLoaded = true; - if (viewsCreated && activity.get() != null) { + if (viewsCreated) { onFragmentLoaded(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index 96abdd835..77e66f3b0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -2,28 +2,30 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; import android.support.v4.app.ListFragment; +import android.util.Log; import android.view.View; import android.widget.ListView; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; +import de.danoeh.antennapod.activity.MediaplayerInfoActivity.MediaplayerInfoContentFragment; import de.danoeh.antennapod.adapter.ChaptersListAdapter; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; -public class ChaptersFragment extends ListFragment implements AudioplayerContentFragment { +public class ChaptersFragment extends ListFragment implements MediaplayerInfoContentFragment { + + private static final String TAG = "ChaptersFragment"; private Playable media; private PlaybackController controller; private ChaptersListAdapter adapter; - public static ChaptersFragment newInstance(Playable media, PlaybackController controller) { + public static ChaptersFragment newInstance(Playable media) { ChaptersFragment f = new ChaptersFragment(); f.media = media; - f.controller = controller; return f; } @@ -37,6 +39,10 @@ public class ChaptersFragment extends ListFragment implements AudioplayerContent lv.setPadding(0, vertPadding, 0, vertPadding); adapter = new ChaptersListAdapter(getActivity(), 0, pos -> { + if(controller == null) { + Log.d(TAG, "controller is null"); + return; + } Chapter chapter = (Chapter) getListAdapter().getItem(pos); controller.seekToChapter(chapter); }); @@ -58,6 +64,7 @@ public class ChaptersFragment extends ListFragment implements AudioplayerContent public void onDestroy() { super.onDestroy(); adapter = null; + controller = null; } @Override @@ -74,4 +81,9 @@ public class ChaptersFragment extends ListFragment implements AudioplayerContent setEmptyText(null); } } + + public void setController(PlaybackController controller) { + this.controller = controller; + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index a53c85f1c..d14265f70 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -25,6 +25,7 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import rx.Observable; import rx.Subscription; @@ -116,10 +117,9 @@ public class CompletedDownloadsFragment extends ListFragment { @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); - FeedItem item = listAdapter.getItem(position - l.getHeaderViewsCount()); - if (item != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId())); - } + position -= l.getHeaderViewsCount(); + long[] ids = FeedItemUtil.getIds(items); + ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); } private void onFragmentLoaded() { @@ -205,7 +205,7 @@ public class CompletedDownloadsFragment extends ListFragment { if (items == null && viewCreated) { setListShown(false); } - subscription = Observable.fromCallable(() -> DBReader.getDownloadedItems()) + subscription = Observable.fromCallable(DBReader::getDownloadedItems) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index d3b97f9df..943ddeec7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -12,14 +12,14 @@ import android.widget.TextView; import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; +import de.danoeh.antennapod.activity.MediaplayerInfoActivity.MediaplayerInfoContentFragment; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.playback.Playable; /** * Displays the cover and the title of a FeedItem. */ -public class CoverFragment extends Fragment implements AudioplayerContentFragment { +public class CoverFragment extends Fragment implements MediaplayerInfoContentFragment { private static final String TAG = "CoverFragment"; private static final String ARG_PLAYABLE = "arg.playable"; 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 b470d379a..93527b149 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -154,7 +154,7 @@ public class DownloadLogFragment extends ListFragment { if(subscription != null) { subscription.unsubscribe(); } - subscription = Observable.fromCallable(() -> DBReader.getDownloadLog()) + subscription = Observable.fromCallable(DBReader::getDownloadLog) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 80a9bf0b3..758f8095d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -5,7 +5,6 @@ import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; @@ -53,16 +52,12 @@ public class ExternalPlayerFragment extends Fragment { mFeedName = (TextView) root.findViewById(R.id.txtvAuthor); mProgressBar = (ProgressBar) root.findViewById(R.id.episodeProgress); - fragmentLayout.setOnClickListener(new OnClickListener() { + fragmentLayout.setOnClickListener(v -> { + Log.d(TAG, "layoutInfo was clicked"); - @Override - public void onClick(View v) { - Log.d(TAG, "layoutInfo was clicked"); - - if (controller != null && controller.getMedia() != null) { - startActivity(PlaybackService.getPlayerActivityIntent( - getActivity(), controller.getMedia())); - } + if (controller != null && controller.getMedia() != null) { + startActivity(PlaybackService.getPlayerActivityIntent( + getActivity(), controller.getMedia())); } }); return root; @@ -72,7 +67,11 @@ public class ExternalPlayerFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); controller = setupPlaybackController(); - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> { + if(controller != null) { + controller.playPause(); + } + }); } private PlaybackController setupPlaybackController() { @@ -88,7 +87,6 @@ public class ExternalPlayerFragment extends Fragment { return butPlay; } - @Override public boolean loadMediaInfo() { ExternalPlayerFragment fragment = ExternalPlayerFragment.this; @@ -145,8 +143,11 @@ public class ExternalPlayerFragment extends Fragment { } controller = setupPlaybackController(); if (butPlay != null) { - butPlay.setOnClickListener(controller - .newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> { + if(controller != null) { + controller.playPause(); + } + }); } controller.init(); } @@ -171,7 +172,7 @@ public class ExternalPlayerFragment extends Fragment { .into(imgvCover); fragmentLayout.setVisibility(View.VISIBLE); - if (controller.isPlayingVideo()) { + if (controller.isPlayingVideoLocally()) { butPlay.setVisibility(View.GONE); } else { butPlay.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index c1c1aab6c..55d28cadb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -27,8 +27,8 @@ import android.webkit.WebViewClient; import android.widget.Toast; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AudioplayerActivity; -import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; +import de.danoeh.antennapod.activity.MediaplayerInfoActivity; +import de.danoeh.antennapod.activity.MediaplayerInfoActivity.MediaplayerInfoContentFragment; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; @@ -47,7 +47,7 @@ import rx.schedulers.Schedulers; /** * Displays the description of a Playable object in a Webview. */ -public class ItemDescriptionFragment extends Fragment implements AudioplayerContentFragment { +public class ItemDescriptionFragment extends Fragment implements MediaplayerInfoContentFragment { private static final String TAG = "ItemDescriptionFragment"; @@ -111,7 +111,7 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "Creating view"); - webvDescription = new WebView(getActivity()); + webvDescription = new WebView(getActivity().getApplicationContext()); if (Build.VERSION.SDK_INT >= 11) { webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @@ -148,7 +148,7 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont super.onPageFinished(view, url); Log.d(TAG, "Page finished"); // Restoring the scroll position might not always work - view.postDelayed(() -> restoreFromPreference(), 50); + view.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); } }); @@ -318,8 +318,7 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont private String loadData() { Timeline timeline = new Timeline(getActivity(), shownotesProvider); - String data = timeline.processShownotes(highlightTimecodes); - return data; + return timeline.processShownotes(highlightTimecodes); } @Override @@ -372,8 +371,8 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont private void onTimecodeLinkSelected(String link) { int time = Timeline.getTimecodeLinkTime(link); - if (getActivity() != null && getActivity() instanceof AudioplayerActivity) { - PlaybackController pc = ((AudioplayerActivity) getActivity()).getPlaybackController(); + if (getActivity() != null && getActivity() instanceof MediaplayerInfoActivity) { + PlaybackController pc = ((MediaplayerInfoActivity) getActivity()).getPlaybackController(); if (pc != null) { pc.seekTo(time); } 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 1382929af..e721af47d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.annotation.TargetApi; import android.content.ClipData; import android.content.Context; import android.content.Intent; @@ -10,6 +9,8 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; +import android.support.v4.view.GestureDetectorCompat; +import android.text.Layout; import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; @@ -17,12 +18,14 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -36,6 +39,7 @@ import org.apache.commons.lang3.ArrayUtils; import java.util.List; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.core.event.DownloadEvent; @@ -57,10 +61,11 @@ import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.view.OnSwipeGesture; +import de.danoeh.antennapod.view.SwipeGestureDetector; import de.greenrobot.event.EventBus; import rx.Observable; import rx.Subscription; @@ -70,13 +75,17 @@ import rx.schedulers.Schedulers; /** * Displays information about a FeedItem and actions. */ -public class ItemFragment extends Fragment { +public class ItemFragment extends Fragment implements OnSwipeGesture { private static final String TAG = "ItemFragment"; private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE; - private static final String ARG_FEEDITEM = "feeditem"; + private static final String ARG_FEEDITEMS = "feeditems"; + private static final String ARG_FEEDITEM_POS = "feeditem_pos"; + + private GestureDetectorCompat headerGestureDetector; + private GestureDetectorCompat webviewGestureDetector; /** * Creates a new instance of an ItemFragment @@ -85,23 +94,35 @@ public class ItemFragment extends Fragment { * @return The ItemFragment instance */ public static ItemFragment newInstance(long feeditem) { + return newInstance(new long[] { feeditem }, 0); + } + + /** + * Creates a new instance of an ItemFragment + * + * @param feeditems The IDs of the FeedItems that belong to the same list + * @param feedItemPos The position of the FeedItem that is currently shown + * @return The ItemFragment instance + */ + public static ItemFragment newInstance(long[] feeditems, int feedItemPos) { ItemFragment fragment = new ItemFragment(); Bundle args = new Bundle(); - args.putLong(ARG_FEEDITEM, feeditem); + args.putLongArray(ARG_FEEDITEMS, feeditems); + args.putInt(ARG_FEEDITEM_POS, feedItemPos); fragment.setArguments(args); return fragment; } private boolean itemsLoaded = false; - private long itemID; + private long[] feedItems; + private int feedItemPos; private FeedItem item; - private LongList queue; - private LongList favorites; private String webviewData; private List<Downloader> downloaderList; private ViewGroup root; private WebView webvDescription; + private TextView txtvPodcast; private TextView txtvTitle; private TextView txtvDuration; private TextView txtvPublished; @@ -125,17 +146,37 @@ public class ItemFragment extends Fragment { setRetainInstance(true); setHasOptionsMenu(true); - itemID = getArguments().getLong(ARG_FEEDITEM, -1); + feedItems = getArguments().getLongArray(ARG_FEEDITEMS); + feedItemPos = getArguments().getInt(ARG_FEEDITEM_POS); + + headerGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this)); + webviewGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this) { + // necessary for the longclick context menu to work properly + @Override + public boolean onDown(MotionEvent e) { + return false; + } + }); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View layout = inflater.inflate(R.layout.feeditem_fragment, container, false); root = (ViewGroup) layout.findViewById(R.id.content_root); + + LinearLayout header = (LinearLayout) root.findViewById(R.id.header); + if(feedItems.length > 0) { + header.setOnTouchListener((v, event) -> headerGestureDetector.onTouchEvent(event)); + } + + txtvPodcast = (TextView) layout.findViewById(R.id.txtvPodcast); + txtvPodcast.setOnClickListener(v -> openPodcast()); txtvTitle = (TextView) layout.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } txtvDuration = (TextView) layout.findViewById(R.id.txtvDuration); txtvPublished = (TextView) layout.findViewById(R.id.txtvPublished); if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448 @@ -153,7 +194,10 @@ public class ItemFragment extends Fragment { webvDescription.getSettings().setLayoutAlgorithm( WebSettings.LayoutAlgorithm.NARROW_COLUMNS); webvDescription.getSettings().setLoadWithOverviewMode(true); - webvDescription.setOnLongClickListener(webViewLongClickListener); + if(feedItems.length > 0) { + webvDescription.setOnLongClickListener(webViewLongClickListener); + } + webvDescription.setOnTouchListener((v, event) -> webviewGestureDetector.onTouchEvent(event)); webvDescription.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { @@ -167,6 +211,7 @@ public class ItemFragment extends Fragment { registerForContextMenu(webvDescription); imgvCover = (ImageView) layout.findViewById(R.id.imgvCover); + imgvCover.setOnClickListener(v -> openPodcast()); progbarDownload = (ProgressBar) layout.findViewById(R.id.progbarDownload); progbarLoading = (ProgressBar) layout.findViewById(R.id.progbarLoading); butAction1 = (IconButton) layout.findViewById(R.id.butAction1); @@ -219,6 +264,7 @@ public class ItemFragment extends Fragment { EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().registerSticky(this); if(itemsLoaded) { + progbarLoading.setVisibility(View.GONE); updateAppearance(); } } @@ -243,29 +289,55 @@ public class ItemFragment extends Fragment { } @Override + public boolean onSwipeLeftToRight() { + Log.d(TAG, "onSwipeLeftToRight()"); + feedItemPos = feedItemPos - 1; + if(feedItemPos < 0) { + feedItemPos = feedItems.length - 1; + } + load(); + return true; + } + + @Override + public boolean onSwipeRightToLeft() { + Log.d(TAG, "onSwipeRightToLeft()"); + feedItemPos = (feedItemPos + 1) % feedItems.length; + load(); + return true; + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if(!isAdded() || item == null) { return; } + ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); inflater.inflate(R.menu.feeditem_options, menu); popupMenu = menu; if (item.hasMedia()) { - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue, favorites); + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null); } else { // these are already available via button1 and button2 - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue, favorites, + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null, R.id.mark_read_item, R.id.visit_website_item); } } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { - try { - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); - return true; + switch(menuItem.getItemId()) { + case R.id.open_podcast: + openPodcast(); + return true; + default: + try { + return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } } } @@ -281,10 +353,8 @@ public class ItemFragment extends Fragment { private void onFragmentLoaded() { - progbarLoading.setVisibility(View.INVISIBLE); if (webviewData != null) { - webvDescription.loadDataWithBaseURL(null, webviewData, "text/html", - "utf-8", "about:blank"); + webvDescription.loadDataWithBaseURL(null, webviewData, "text/html", "utf-8", "about:blank"); } updateAppearance(); } @@ -295,6 +365,7 @@ public class ItemFragment extends Fragment { return; } getActivity().supportInvalidateOptionsMenu(); + txtvPodcast.setText(item.getFeed().getTitle()); txtvTitle.setText(item.getTitle()); if (item.getPubDate() != null) { @@ -452,22 +523,15 @@ public class ItemFragment extends Fragment { } } - public void onEventMainThread(QueueEvent event) { - if(event.contains(itemID)) { - load(); - } - } - - public void onEventMainThread(FavoritesEvent event) { - if(event.item.getId() == itemID) { - load(); - } + private void openPodcast() { + Fragment fragment = ItemlistFragment.newInstance(item.getFeedId()); + ((MainActivity)getActivity()).loadChildFragment(fragment); } public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); for(FeedItem item : event.items) { - if(itemID == item.getId()) { + if(feedItems[feedItemPos] == item.getId()) { load(); return; } @@ -503,43 +567,27 @@ public class ItemFragment extends Fragment { if(subscription != null) { subscription.unsubscribe(); } - subscription = Observable.fromCallable(() -> loadInBackground()) + progbarLoading.setVisibility(View.VISIBLE); + subscription = Observable.fromCallable(this::loadInBackground) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { - item = (FeedItem) result[0]; - queue = (LongList) result[1]; - favorites = (LongList) result[2]; - if (!itemsLoaded) { - itemsLoaded = true; - onFragmentLoaded(); - } else { - updateAppearance(); - } + progbarLoading.setVisibility(View.GONE); + item = result; + itemsLoaded = true; + onFragmentLoaded(); }, error -> { Log.e(TAG, Log.getStackTraceString(error)); }); } - private Object[] loadInBackground() { - FeedItem feedItem = DBReader.getFeedItem(itemID); + private FeedItem loadInBackground() { + FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]); if (feedItem != null) { Timeline t = new Timeline(getActivity(), feedItem); webviewData = t.processShownotes(false); } - LongList queue; - if(feedItem.isTagged(FeedItem.TAG_QUEUE)) { - queue = LongList.of(feedItem.getId()); - } else { - queue = new LongList(0); - } - LongList favorites; - if(feedItem.isTagged(FeedItem.TAG_FAVORITE)) { - favorites = LongList.of(feedItem.getId()); - } else { - favorites = new LongList(0); - } - return new Object[] { feedItem, queue, favorites }; + return feedItem; } } 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 303d273bc..3194d7cab 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -47,9 +47,7 @@ import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FavoritesEvent; import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedEvent; @@ -66,7 +64,6 @@ import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; @@ -98,8 +95,6 @@ public class ItemlistFragment extends ListFragment { private long feedID; private Feed feed; - private LongList queuedItemsIds; - private LongList favoritedItemsId; private boolean itemsLoaded = false; private boolean viewsCreated = false; @@ -185,11 +180,7 @@ public class ItemlistFragment extends ListFragment { private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() { @Override public boolean isRefreshing() { - if (feed != null && DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFile(feed)) { - return true; - } else { - return false; - } + return feed != null && DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFile(feed); } }; @@ -325,8 +316,7 @@ public class ItemlistFragment extends ListFragment { contextMenu = menu; lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, queuedItemsIds, - favoritedItemsId); + FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); } @Override @@ -378,22 +368,11 @@ public class ItemlistFragment extends ListFragment { if(adapter == null) { return; } - FeedItem selection = adapter.getItem(position - l.getHeaderViewsCount()); - if (selection != null) { - MainActivity activity = (MainActivity) getActivity(); - activity.loadChildFragment(ItemFragment.newInstance(selection.getId())); - activity.getSupportActionBar().setTitle(feed.getTitle()); - } - } - - public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); - loadItems(); - } - - public void onEvent(FavoritesEvent event) { - Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); - loadItems(); + position -= l.getHeaderViewsCount(); + MainActivity activity = (MainActivity) getActivity(); + long[] ids = FeedItemUtil.getIds(feed.getItems()); + activity.loadChildFragment(ItemFragment.newInstance(ids, position)); + activity.getSupportActionBar().setTitle(feed.getTitle()); } public void onEvent(FeedEvent event) { @@ -405,7 +384,6 @@ public class ItemlistFragment extends ListFragment { public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - boolean queueChanged = false; if(feed == null || feed.getItems() == null || adapter == null) { return; } @@ -605,11 +583,6 @@ public class ItemlistFragment extends ListFragment { } @Override - public boolean isInQueue(FeedItem item) { - return (queuedItemsIds != null) && queuedItemsIds.contains(item.getId()); - } - - @Override public int getItemDownloadProgressPercent(FeedItem item) { if (downloaderList != null) { for (Downloader downloader : downloaderList) { @@ -628,14 +601,12 @@ public class ItemlistFragment extends ListFragment { if(subscription != null) { subscription.unsubscribe(); } - subscription = Observable.fromCallable(() -> loadData()) + subscription = Observable.fromCallable(this::loadData) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { if (result != null) { - feed = (Feed) result[0]; - queuedItemsIds = (LongList) result[1]; - favoritedItemsId = (LongList) result[2]; + feed = result; itemsLoaded = true; if (viewsCreated) { onFragmentLoaded(); @@ -646,15 +617,14 @@ public class ItemlistFragment extends ListFragment { }); } - private Object[] loadData() { + private Feed loadData() { Feed feed = DBReader.getFeed(feedID); + DBReader.loadAdditionalFeedItemListData(feed.getItems()); if(feed != null && feed.getItemFilter() != null) { FeedItemFilter filter = feed.getItemFilter(); feed.setItems(filter.filter(feed.getItems())); } - LongList queuedItemsIds = DBReader.getQueueIDList(); - LongList favoritedItemsId = DBReader.getFavoriteIDList(); - return new Object[] { feed, queuedItemsIds, favoritedItemsId }; + return feed; } } 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 b996e1cb3..7ef070f21 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -14,12 +14,13 @@ import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; -import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.core.event.FeedItemEvent; 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.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.FeedItemUtil; /** @@ -39,17 +40,27 @@ public class NewEpisodesFragment extends AllEpisodesFragment { @Override protected String getPrefName() { return PREF_NAME; } - public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); - loadItems(); - } - @Override protected void resetViewState() { super.resetViewState(); } @Override + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if(episodes == null) { + return; + } + for(FeedItem item : event.items) { + int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId()); + if(pos >= 0 && item.isTagged(FeedItem.TAG_QUEUE)) { + episodes.remove(pos); + listAdapter.notifyItemRemoved(pos); + } + } + } + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = super.onCreateViewHelper(inflater, container, savedInstanceState, R.layout.all_episodes_fragment); 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 c5b77fae2..49c68c732 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.support.v4.app.ListFragment; -import android.support.v4.util.Pair; import android.support.v4.view.MenuItemCompat; import android.util.Log; import android.view.Menu; @@ -21,14 +20,14 @@ import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.core.event.FeedItemEvent; 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.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.core.util.FeedItemUtil; import de.greenrobot.event.EventBus; import rx.Observable; import rx.Subscription; @@ -43,7 +42,6 @@ public class PlaybackHistoryFragment extends ListFragment { EventDistributor.PLAYER_STATUS_UPDATE; private List<FeedItem> playbackHistory; - private LongList queue; private FeedItemlistAdapter adapter; private boolean itemsLoaded = false; @@ -140,10 +138,9 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); - FeedItem item = adapter.getItem(position - l.getHeaderViewsCount()); - if (item != null) { - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId())); - } + position -= l.getHeaderViewsCount(); + long[] ids = FeedItemUtil.getIds(playbackHistory); + ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); } @Override @@ -187,9 +184,18 @@ public class PlaybackHistoryFragment extends ListFragment { } } - public void onEvent(QueueEvent event) { - Log.d(TAG, "onEvent(" + event + ")"); - loadItems(); + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if(playbackHistory == null) { + return; + } + for(FeedItem item : event.items) { + int pos = FeedItemUtil.indexOfItemWithId(playbackHistory, item.getId()); + if(pos >= 0) { + loadItems(); + return; + } + } } private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @@ -218,10 +224,6 @@ public class PlaybackHistoryFragment extends ListFragment { } private FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { - @Override - public boolean isInQueue(FeedItem item) { - return (queue != null) ? queue.contains(item.getId()) : false; - } @Override public int getItemDownloadProgressPercent(FeedItem item) { @@ -255,13 +257,12 @@ public class PlaybackHistoryFragment extends ListFragment { if(subscription != null) { subscription.unsubscribe(); } - subscription = Observable.fromCallable(() -> loadData()) + subscription = Observable.fromCallable(this::loadData) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { if (result != null) { - playbackHistory = result.first; - queue = result.second; + playbackHistory = result; itemsLoaded = true; if (viewsCreated) { onFragmentLoaded(); @@ -272,11 +273,10 @@ public class PlaybackHistoryFragment extends ListFragment { }); } - private Pair<List<FeedItem>, LongList> loadData() { + private List<FeedItem> loadData() { List<FeedItem> history = DBReader.getPlaybackHistory(); - LongList queue = DBReader.getQueueIDList(); DBReader.loadAdditionalFeedItemListData(history); - return Pair.create(history, queue); + return history; } } 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 e5bb1d895..08e681c99 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -230,9 +230,8 @@ public class QueueFragment extends Fragment { resetViewState(); } - private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = () -> { - return DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); - }; + private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = + () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -410,12 +409,12 @@ public class QueueFragment extends Fragment { Log.d(TAG, "remove(" + position + ")"); final FeedItem item = queue.get(position); final boolean isRead = item.isPlayed(); - DBWriter.markItemPlayed(FeedItem.PLAYED, item.getId()); + DBWriter.markItemPlayed(FeedItem.PLAYED, false, item.getId()); DBWriter.removeQueueItem(getActivity(), item, true); Snackbar snackbar = Snackbar.make(root, getString(R.string.marked_as_read_label), Snackbar.LENGTH_LONG); snackbar.setAction(getString(R.string.undo), v -> { DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false); - if(false == isRead) { + if(!isRead) { DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); } }); @@ -424,12 +423,12 @@ public class QueueFragment extends Fragment { @Override public boolean isLongPressDragEnabled() { - return false == UserPreferences.isQueueLocked(); + return !UserPreferences.isQueueLocked(); } @Override public boolean isItemViewSwipeEnabled() { - return false == UserPreferences.isQueueLocked(); + return !UserPreferences.isQueueLocked(); } @Override @@ -569,20 +568,6 @@ public class QueueFragment extends Fragment { public LongList getQueueIds() { return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0); } - - @Override - public LongList getFavoritesIds() { - LongList favoritesIds = new LongList(); - if(queue == null) { - return favoritesIds; - } - for(FeedItem item : queue) { - if(item.isTagged(FeedItem.TAG_FAVORITE)) { - favoritesIds.add(item.getId()); - } - } - return favoritesIds; - } }; private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @@ -608,7 +593,7 @@ public class QueueFragment extends Fragment { txtvEmpty.setVisibility(View.GONE); progLoading.setVisibility(View.VISIBLE); } - subscription = Observable.fromCallable(() -> DBReader.getQueue()) + subscription = Observable.fromCallable(DBReader::getQueue) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(items -> { 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 dbd18163c..510909379 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -208,7 +208,7 @@ public class SearchFragment extends ListFragment { if (viewCreated && !itemsLoaded) { setListShown(false); } - subscription = Observable.fromCallable(() -> performSearch()) + subscription = Observable.fromCallable(this::performSearch) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java new file mode 100644 index 000000000..a314419ac --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -0,0 +1,238 @@ +package de.danoeh.antennapod.fragment; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.GridView; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.SubscriptionsAdapter; +import de.danoeh.antennapod.core.asynctask.FeedRemover; +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.preferences.PlaybackPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.FeedItemUtil; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Fragment for displaying feed subscriptions + */ +public class SubscriptionFragment extends Fragment { + + public static final String TAG = "SubscriptionFragment"; + + private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; + + private GridView subscriptionGridLayout; + private DBReader.NavDrawerData navDrawerData; + private SubscriptionsAdapter subscriptionAdapter; + + private int mPosition = -1; + + + public SubscriptionFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + + // So, we certainly *don't* have an options menu, + // but unless we say we do, old options menus sometimes + // persist. mfietz thinks this causes the ActionBar to be invalidated + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_subscriptions, container, false); + subscriptionGridLayout = (GridView) root.findViewById(R.id.subscriptions_grid); + registerForContextMenu(subscriptionGridLayout); + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + subscriptionAdapter = new SubscriptionsAdapter((MainActivity)getActivity(), itemAccess); + + subscriptionGridLayout.setAdapter(subscriptionAdapter); + + loadSubscriptions(); + + subscriptionGridLayout.setOnItemClickListener(subscriptionAdapter); + + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.subscriptions_label); + } + + EventDistributor.getInstance().register(contentUpdate); + } + + private void loadSubscriptions() { + Observable.fromCallable(() -> DBReader.getNavDrawerData()) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + navDrawerData = result; + subscriptionAdapter.notifyDataSetChanged(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + int position = adapterInfo.position; + + Object selectedObject = subscriptionAdapter.getItem(position); + if (selectedObject.equals(SubscriptionsAdapter.ADD_ITEM_OBJ)) { + mPosition = position; + return; + } + + Feed feed = (Feed)selectedObject; + + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + + menu.setHeaderTitle(feed.getTitle()); + + mPosition = position; + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + + final int position = mPosition; + mPosition = -1; // reset + if(position < 0) { + return false; + } + + Object selectedObject = subscriptionAdapter.getItem(position); + if (selectedObject.equals(SubscriptionsAdapter.ADD_ITEM_OBJ)) { + // this is the add object, do nothing + return false; + } + + Feed feed = (Feed)selectedObject; + switch(item.getItemId()) { + case R.id.mark_all_seen_item: + Observable.fromCallable(() -> DBWriter.markFeedSeen(feed.getId())) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + loadSubscriptions(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + return true; + case R.id.mark_all_read_item: + Observable.fromCallable(() -> DBWriter.markFeedRead(feed.getId())) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + loadSubscriptions(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + return true; + case R.id.remove_item: + final FeedRemover remover = new FeedRemover(getContext(), feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + loadSubscriptions(); + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(getContext(), + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); + if (mediaId > 0 && + FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); + if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { + getActivity().sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); + } + } + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public void onResume() { + super.onResume(); + loadSubscriptions(); + } + + 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."); + loadSubscriptions(); + } + } + }; + + private SubscriptionsAdapter.ItemAccess itemAccess = new SubscriptionsAdapter.ItemAccess() { + @Override + public int getCount() { + if (navDrawerData != null) { + return navDrawerData.feeds.size(); + } else { + return 0; + } + } + + @Override + public Feed getItem(int position) { + if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { + return navDrawerData.feeds.get(position); + } else { + return null; + } + } + + @Override + public int getFeedCounter(long feedId) { + return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; + } + }; +} 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 204f36956..15e9c9943 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 @@ -14,7 +14,6 @@ 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; @@ -84,18 +83,9 @@ public abstract class PodcastListFragment extends Fragment { txtvError = (TextView) root.findViewById(R.id.txtvError); butRetry = (Button) root.findViewById(R.id.butRetry); - gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - onPodcastSelected((GpodnetPodcast) gridView.getAdapter().getItem(position)); - } - }); - butRetry.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - loadData(); - } - }); + gridView.setOnItemClickListener((parent, view, position, id) -> + onPodcastSelected((GpodnetPodcast) gridView.getAdapter().getItem(position))); + butRetry.setOnClickListener(v -> loadData()); loadData(); return root; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java index 133bb0281..1b1b61efb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SuggestionListFragment.java @@ -1,13 +1,13 @@ package de.danoeh.antennapod.fragment.gpodnet; +import java.util.Collections; +import java.util.List; + 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.core.preferences.GpodnetPreferences; -import java.util.ArrayList; -import java.util.List; - /** * Displays suggestions from gpodder.net */ @@ -20,7 +20,7 @@ public class SuggestionListFragment extends PodcastListFragment { service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); return service.getSuggestions(SUGGESTIONS_COUNT); } else { - return new ArrayList<GpodnetPodcast>(); + return Collections.emptyList(); } } } 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 338f02e61..d2c7f32dd 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 @@ -11,7 +11,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; import android.widget.TextView; import java.util.List; @@ -65,13 +64,10 @@ public class TagListFragment extends ListFragment { public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position); - MainActivity activity = (MainActivity) getActivity(); - activity.loadChildFragment(TagFragment.newInstance(tag)); - } + getListView().setOnItemClickListener((parent, view1, position, id) -> { + GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position); + MainActivity activity = (MainActivity) getActivity(); + activity.loadChildFragment(TagFragment.newInstance(tag)); }); startLoadTask(); 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 58fe8afbf..b80213459 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.menuhandler; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.support.annotation.Nullable; import android.util.Log; import android.widget.Toast; @@ -41,7 +42,7 @@ public class FeedItemMenuHandler { * menu-object and call setVisibility(visibility) on the returned * MenuItem object. */ - abstract void setItemVisibility(int id, boolean visible); + void setItemVisibility(int id, boolean visible); } /** @@ -54,14 +55,14 @@ public class FeedItemMenuHandler { * @param showExtendedMenu True if MenuItems that let the user share information about * the FeedItem and visit its website should be set visible. This * parameter should be set to false if the menu space is limited. - * @param queueAccess Used for testing if the queue contains the selected item + * @param queueAccess Used for testing if the queue contains the selected item; only used for + * move to top/bottom in the queue * @return Returns true if selectedItem is not null. */ public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem, boolean showExtendedMenu, - LongList queueAccess, - LongList favorites) { + @Nullable LongList queueAccess) { if (selectedItem == null) { return false; } @@ -72,10 +73,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.skip_episode_item, false); } - boolean isInQueue = false; - if(queueAccess != null) { - isInQueue = queueAccess.contains(selectedItem.getId()); - } + boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); if(queueAccess == null || queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId()) { mi.setItemVisibility(R.id.move_to_top_item, false); } @@ -98,7 +96,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.share_download_url_item, false); mi.setItemVisibility(R.id.share_download_url_with_position_item, false); } - if(false == hasMedia || selectedItem.getMedia().getPosition() <= 0) { + if(!hasMedia || selectedItem.getMedia().getPosition() <= 0) { mi.setItemVisibility(R.id.share_link_with_position_item, false); mi.setItemVisibility(R.id.share_download_url_with_position_item, false); } @@ -113,7 +111,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.reset_position, false); } - if(false == UserPreferences.isEnableAutodownload()) { + if(!UserPreferences.isEnableAutodownload()) { mi.setItemVisibility(R.id.activate_auto_download, false); mi.setItemVisibility(R.id.deactivate_auto_download, false); } else if(selectedItem.getAutoDownload()) { @@ -126,7 +124,7 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.support_item, false); } - boolean isFavorite = favorites != null && favorites.contains(selectedItem.getId()); + boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); mi.setItemVisibility(R.id.add_to_favorites_item, !isFavorite); mi.setItemVisibility(R.id.remove_from_favorites_item, isFavorite); @@ -144,9 +142,8 @@ public class FeedItemMenuHandler { FeedItem selectedItem, boolean showExtendedMenu, LongList queueAccess, - LongList favorites, int... excludeIds) { - boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess, favorites); + boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess); if (rc && excludeIds != null) { for (int id : excludeIds) { mi.setItemVisibility(id, false); @@ -224,7 +221,7 @@ public class FeedItemMenuHandler { context.startActivity(intent); } else { Toast.makeText(context, context.getString(R.string.download_error_malformed_url), - Toast.LENGTH_SHORT); + Toast.LENGTH_SHORT).show(); } break; case R.id.support_item: 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 38030f4ea..ab7d0e7c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -92,7 +92,7 @@ public class FeedMenuHandler { context.startActivity(intent); } else { Toast.makeText(context, context.getString(R.string.download_error_malformed_url), - Toast.LENGTH_SHORT); + Toast.LENGTH_SHORT).show(); } break; case R.id.support_item: 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 0d2ff8a75..4c28b197d 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -39,6 +39,7 @@ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuIte queueLock.setTitle(de.danoeh.antennapod.R.string.lock_queue); queueLock.setIcon(ta.getDrawable(1)); } + ta.recycle(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java index 6ceaaada4..c973990c9 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/NavDrawerActivity.java @@ -5,5 +5,5 @@ package de.danoeh.antennapod.menuhandler; */ public interface NavDrawerActivity { - public boolean isDrawerOpen(); + boolean isDrawerOpen(); } 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 c563d278f..6c03dc7ae 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -4,6 +4,7 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.TimePickerDialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -19,6 +20,7 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; @@ -29,8 +31,11 @@ import android.text.format.DateFormat; import android.util.Log; import android.widget.EditText; import android.widget.Toast; +import android.widget.ListView; import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; import org.apache.commons.lang3.ArrayUtils; @@ -49,15 +54,18 @@ import de.danoeh.antennapod.activity.DirectoryChooserActivity; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.activity.PreferenceActivityGingerbread; +import de.danoeh.antennapod.activity.StatisticsActivity; import de.danoeh.antennapod.asynctask.OpmlExportWorker; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.GpodnetSyncService; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.flattr.FlattrUtils; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; +import de.danoeh.antennapod.dialog.ProxyDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; /** @@ -73,15 +81,21 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; public static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs"; public static final String PREF_OPML_EXPORT = "prefOpmlExport"; + public static final String STATISTICS = "statistics"; public static final String PREF_ABOUT = "prefAbout"; public static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; public static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; public static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher"; public static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; public static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; + public static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; public static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; public static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + public static final String PREF_PROXY = "prefProxy"; + public static final String PREF_KNOWN_ISSUES = "prefKnownIssues"; + public static final String PREF_FAQ = "prefFaq"; + public static final String PREF_SEND_CRASH_REPORT = "prefSendCrashReport"; private final PreferenceUI ui; @@ -149,6 +163,12 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc return true; } ); + ui.findPreference(PreferenceController.STATISTICS).setOnPreferenceClickListener( + preference -> { + activity.startActivity(new Intent(activity, StatisticsActivity.class)); + return true; + } + ); ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener( preference -> { new OpmlExportWorker(activity).executeAsync(); @@ -177,20 +197,17 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ); ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) .setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - if (Build.VERSION.SDK_INT >= 19) { - showChooseDataFolderDialog(); - } else { - Intent intent = new Intent(activity, DirectoryChooserActivity.class); - activity.startActivityForResult(intent, - DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); - } - return true; + preference -> { + if (Build.VERSION.SDK_INT >= 19) { + showChooseDataFolderDialog(); + } else { + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, + DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); } - } - ); + return true; + } + ); ui.findPreference(UserPreferences.PREF_THEME) .setOnPreferenceChangeListener( (preference, newValue) -> { @@ -208,6 +225,12 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc return true; }); + ui.findPreference(UserPreferences.PREF_COMPACT_NOTIFICATION_BUTTONS) + .setOnPreferenceClickListener(preference -> { + showNotificationButtonsDialog(); + return true; + }); + ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL) .setOnPreferenceClickListener(preference -> { showUpdateIntervalTimePreferencesDialog(); @@ -241,7 +264,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc (preference, o) -> { if (o instanceof String) { try { - int value = Integer.valueOf((String) o); + int value = Integer.parseInt((String) o); if (1 <= value && value <= 50) { setParallelDownloadsText(value); return true; @@ -268,7 +291,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc public void afterTextChanged(Editable s) { if (s.length() > 0) { try { - int value = Integer.valueOf(s.toString()); + int value = Integer.parseInt(s.toString()); if (value <= 0) { ev.setText("1"); } else if (value > 50) { @@ -309,6 +332,14 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc dialog.show(); return true; }); + ui.findPreference(PreferenceController.PREF_GPODNET_SYNC). + setOnPreferenceClickListener(preference -> { + GpodnetSyncService.sendSyncIntent(ui.getActivity().getApplicationContext()); + Toast toast = Toast.makeText(ui.getActivity(), R.string.pref_gpodnet_sync_started, + Toast.LENGTH_SHORT); + toast.show(); + return true; + }); ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setOnPreferenceClickListener( preference -> { GpodnetPreferences.logout(); @@ -343,7 +374,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( (preference, o) -> { if (o instanceof String) { - int newValue = Integer.valueOf((String) o) * 1024 * 1024; + int newValue = Integer.parseInt((String) o) * 1024 * 1024; if (newValue != UserPreferences.getImageCacheSize()) { AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity()); dialog.setTitle(android.R.string.dialog_alert_title); @@ -356,7 +387,20 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc return false; } ); - ui.findPreference("prefSendCrashReport").setOnPreferenceClickListener(preference -> { + ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> { + ProxyDialog dialog = new ProxyDialog(ui.getActivity()); + dialog.createDialog().show(); + return true; + }); + ui.findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> { + openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug"); + return true; + }); + ui.findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { + openInBrowser("http://antennapod.org/faq.html"); + return true; + }); + ui.findPreference(PREF_SEND_CRASH_REPORT).setOnPreferenceClickListener(preference -> { Intent emailIntent = new Intent(Intent.ACTION_SEND); emailIntent.setType("text/plain"); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"}); @@ -368,12 +412,38 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ui.getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); return true; }); + //checks whether Google Play Services is installed on the device (condition necessary for Cast support) + ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { + if (o instanceof Boolean && ((Boolean) o)) { + final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(ui.getActivity()); + if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { + return true; + } else { + GoogleApiAvailability.getInstance() + .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) + .show(); + return false; + } + } + return true; + }); buildEpisodeCleanupPreference(); buildSmartMarkAsPlayedPreference(); buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences.isEnableAutodownloadWifiFilter()); } + private void openInBrowser(String url) { + try { + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + ui.getActivity().startActivity(myIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(ui.getActivity(), R.string.pref_no_browser_found, Toast.LENGTH_LONG).show(); + Log.e(TAG, Log.getStackTraceString(e)); + } + } + public void onResume() { checkItemVisibility(); setUpdateIntervalText(); @@ -423,7 +493,16 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc final boolean loggedIn = GpodnetPreferences.loggedIn(); ui.findPreference(PreferenceController.PREF_GPODNET_LOGIN).setEnabled(!loggedIn); ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn); + ui.findPreference(PreferenceController.PREF_GPODNET_SYNC).setEnabled(loggedIn); ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setEnabled(loggedIn); + if(loggedIn) { + String format = ui.getActivity().getString(R.string.pref_gpodnet_login_status); + String summary = String.format(format, GpodnetPreferences.getUsername(), + GpodnetPreferences.getDeviceID()); + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary)); + } else { + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(null); + } ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); } @@ -514,7 +593,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(autoDownload); setSelectedNetworksEnabled(autoDownload && UserPreferences.isEnableAutodownloadWifiFilter()); - ui.findPreference("prefSendCrashReport").setEnabled(CrashReportWriter.getFile().exists()); + ui.findPreference(PREF_SEND_CRASH_REPORT).setEnabled(CrashReportWriter.getFile().exists()); if (Build.VERSION.SDK_INT >= 16) { ui.findPreference(UserPreferences.PREF_SONIC).setEnabled(true); @@ -598,7 +677,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc Preference.OnPreferenceClickListener clickListener = preference -> { if (preference instanceof CheckBoxPreference) { String key = preference.getKey(); - ArrayList<String> prefValuesList = new ArrayList<String>( + List<String> prefValuesList = new ArrayList<>( Arrays.asList(UserPreferences .getAutodownloadSelectedNetworks()) ); @@ -607,10 +686,10 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc Log.d(TAG, "Selected network " + key + ". New state: " + newValue); int index = prefValuesList.indexOf(key); - if (index >= 0 && newValue == false) { + if (index >= 0 && !newValue) { // remove network prefValuesList.remove(index); - } else if (index < 0 && newValue == true) { + } else if (index < 0 && newValue) { prefValuesList.add(key); } @@ -646,9 +725,9 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc if (selectedNetworks != null) { PreferenceScreen prefScreen = (PreferenceScreen) ui.findPreference(PreferenceController.AUTO_DL_PREF_SCREEN); - for (int i = 0; i < selectedNetworks.length; i++) { - if (selectedNetworks[i] != null) { - prefScreen.removePreference(selectedNetworks[i]); + for (CheckBoxPreference network : selectedNetworks) { + if (network != null) { + prefScreen.removePreference(network); } } } @@ -682,6 +761,52 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc builder.create().show(); } + private void showNotificationButtonsDialog() { + final Context context = ui.getActivity(); + final List<Integer> preferredButtons = UserPreferences.getCompactNotificationButtons(); + final String[] allButtonNames = context.getResources().getStringArray( + R.array.compact_notification_buttons_options); + boolean[] checked = new boolean[allButtonNames.length]; // booleans default to false in java + + for(int i=0; i < checked.length; i++) { + if(preferredButtons.contains(i)) { + checked[i] = true; + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(String.format(context.getResources().getString( + R.string.pref_compact_notification_buttons_dialog_title), 2)); + builder.setMultiChoiceItems(allButtonNames, checked, (dialog, which, isChecked) -> { + checked[which] = isChecked; + + if (isChecked) { + if (preferredButtons.size() < 2) { + preferredButtons.add(which); + } else { + // Only allow a maximum of two selections. This is because the notification + // on the lock screen can only display 3 buttons, and the play/pause button + // is always included. + checked[which] = false; + ListView selectionView = ((AlertDialog) dialog).getListView(); + selectionView.setItemChecked(which, false); + Snackbar.make( + selectionView, + String.format(context.getResources().getString( + R.string.pref_compact_notification_buttons_dialog_error), 2), + Snackbar.LENGTH_SHORT).show(); + } + } else { + preferredButtons.remove((Integer) which); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setCompactNotificationButtons(preferredButtons); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + // CHOOSE DATA FOLDER private void requestPermission() { @@ -712,7 +837,8 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc List<String> folders = new ArrayList<>(mediaDirs.length); List<CharSequence> choices = new ArrayList<>(mediaDirs.length); for(int i=0; i < mediaDirs.length; i++) { - if(mediaDirs[i] == null) { + File dir = mediaDirs[i]; + if(dir == null || !dir.exists() || !dir.canRead() || !dir.canWrite()) { continue; } String path = mediaDirs[i].getAbsolutePath(); @@ -781,7 +907,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc checkedItem = ArrayUtils.indexOf(values, currIntervalStr); } builder1.setSingleChoiceItems(entries, checkedItem, (dialog1, which1) -> { - int hours = Integer.valueOf(values[which1]); + int hours = Integer.parseInt(values[which1]); UserPreferences.setUpdateInterval(hours); dialog1.dismiss(); setUpdateIntervalText(); diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index 665ddc3b5..1075117dd 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -8,6 +8,7 @@ import android.net.NetworkInfo; import android.text.TextUtils; import android.util.Log; +import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.NetworkUtils; @@ -20,7 +21,8 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { if (TextUtils.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { Log.d(TAG, "Received intent"); - if (NetworkUtils.autodownloadNetworkAvailable()) { + ClientConfig.initialize(context); + if (NetworkUtils.autodownloadNetworkAvailable()) { Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java index 7000827c6..f0d4014ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.receiver; +import java.util.Arrays; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; @@ -44,14 +46,14 @@ public class PlayerWidget extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + appWidgetIds + "]"); + Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]"); startUpdate(context); } @Override public void onDisabled(Context context) { super.onDisabled(context); - Log.d(TAG, "Widet disabled"); + Log.d(TAG, "Widget disabled"); setEnabled(context, false); stopUpdate(context); } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java index f050e031d..339a4f0f7 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java @@ -3,10 +3,9 @@ package de.danoeh.antennapod.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.BatteryManager; import android.util.Log; -import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequester; @@ -25,6 +24,7 @@ public class PowerConnectionReceiver extends BroadcastReceiver { Log.d(TAG, "charging intent: " + action); + ClientConfig.initialize(context); if (Intent.ACTION_POWER_CONNECTED.equals(action)) { Log.d(TAG, "charging, starting auto-download"); // we're plugged in, this is a great time to auto-download if everything else is diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java index 8201fe3e2..a373c5353 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/SPAReceiver.java @@ -9,8 +9,8 @@ import android.widget.Toast; import java.util.Arrays; -import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; @@ -27,29 +27,30 @@ public class SPAReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_SP_APPS_QUERY_FEEDS_REPSONSE)) { - if (BuildConfig.DEBUG) Log.d(TAG, "Received SP_APPS_QUERY_RESPONSE"); - if (intent.hasExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA)) { - String[] feedUrls = intent.getStringArrayExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA); - if (feedUrls != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Received feeds list: " + Arrays.toString(feedUrls)); - for (String url : feedUrls) { - Feed f = new Feed(url, null); - try { - DownloadRequester.getInstance().downloadFeed(context, f); - } catch (DownloadRequestException e) { - Log.e(TAG, "Error while trying to add feed " + url); - e.printStackTrace(); - } - } - Toast.makeText(context, R.string.sp_apps_importing_feeds_msg, Toast.LENGTH_LONG).show(); - - } else { - Log.e(TAG, "Received invalid SP_APPS_QUERY_REPSONSE: extra was null"); - } - } else { - Log.e(TAG, "Received invalid SP_APPS_QUERY_RESPONSE: Contains no extra"); + if (!TextUtils.equals(intent.getAction(), ACTION_SP_APPS_QUERY_FEEDS_REPSONSE)) { + return; + } + Log.d(TAG, "Received SP_APPS_QUERY_RESPONSE"); + if (!intent.hasExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA)) { + Log.e(TAG, "Received invalid SP_APPS_QUERY_RESPONSE: Contains no extra"); + return; + } + String[] feedUrls = intent.getStringArrayExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA); + if (feedUrls == null) { + Log.e(TAG, "Received invalid SP_APPS_QUERY_REPSONSE: extra was null"); + return; + } + Log.d(TAG, "Received feeds list: " + Arrays.toString(feedUrls)); + ClientConfig.initialize(context); + for (String url : feedUrls) { + Feed f = new Feed(url, null); + try { + DownloadRequester.getInstance().downloadFeed(context, f); + } catch (DownloadRequestException e) { + Log.e(TAG, "Error while trying to add feed " + url); + e.printStackTrace(); } } + Toast.makeText(context, R.string.sp_apps_importing_feeds_msg, Toast.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/OnSwipeGesture.java b/app/src/main/java/de/danoeh/antennapod/view/OnSwipeGesture.java new file mode 100644 index 000000000..eb72a7135 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/OnSwipeGesture.java @@ -0,0 +1,9 @@ +package de.danoeh.antennapod.view; + +public interface OnSwipeGesture { + + boolean onSwipeLeftToRight(); + + boolean onSwipeRightToLeft(); + +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java new file mode 100644 index 000000000..27b6ee2bc --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java @@ -0,0 +1,32 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * From http://stackoverflow.com/a/19449488/6839 + */ +public class SquareImageView extends ImageView { + + public SquareImageView(Context context) { + super(context); + } + + public SquareImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + setMeasuredDimension(width, width); + } + +}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java b/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java new file mode 100644 index 000000000..f4ee092df --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java @@ -0,0 +1,44 @@ +package de.danoeh.antennapod.view; + +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; + +public class SwipeGestureDetector extends GestureDetector.SimpleOnGestureListener { + + private static final String TAG = "SwipeGestureDetector"; + + private static final int SWIPE_MIN_DISTANCE = 120; + private static final int SWIPE_MAX_OFF_PATH = 250; + private static final int SWIPE_THRESHOLD_VELOCITY = 200; + + private final OnSwipeGesture callback; + + public SwipeGestureDetector(OnSwipeGesture callback) { + this.callback = callback; + } + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + try { + if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) + return false; + if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE + && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + return callback.onSwipeRightToLeft(); + } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE + && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) { + return callback.onSwipeLeftToRight(); + } + } catch (Exception e) { + Log.d(TAG, Log.getStackTraceString(e)); + } + return false; + } + +} diff --git a/app/src/main/res/layout/downloadlog_item.xml b/app/src/main/res/layout/downloadlog_item.xml index 20d879933..7b4773bca 100644 --- a/app/src/main/res/layout/downloadlog_item.xml +++ b/app/src/main/res/layout/downloadlog_item.xml @@ -10,14 +10,13 @@ android:paddingBottom="8dp" tools:background="@android:color/darker_gray"> - <TextView + <com.joanzapata.iconify.widget.IconTextView 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" /> <com.joanzapata.iconify.widget.IconButton diff --git a/app/src/main/res/layout/external_player_fragment.xml b/app/src/main/res/layout/external_player_fragment.xml index ac55b4c40..fb7abde55 100644 --- a/app/src/main/res/layout/external_player_fragment.xml +++ b/app/src/main/res/layout/external_player_fragment.xml @@ -37,6 +37,18 @@ android:indeterminate="false" tools:progress="100"/> + <ImageButton + android:id="@+id/butPlay" + android:layout_width="52dp" + android:layout_height="52dp" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_below="@id/episodeProgress" + android:layout_centerVertical="true" + android:contentDescription="@string/pause_label" + android:background="?attr/selectableItemBackground" + tools:src="@drawable/ic_play_arrow_white_36dp"/> + <TextView android:id="@+id/txtvTitle" android:layout_width="wrap_content" @@ -72,18 +84,6 @@ android:maxLines="1" tools:text="Episode author that is too long and will cause the text to wrap"/> - <ImageButton - android:id="@+id/butPlay" - android:layout_width="52dp" - android:layout_height="52dp" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_below="@id/episodeProgress" - android:layout_centerVertical="true" - android:contentDescription="@string/pause_label" - android:background="?attr/selectableItemBackground" - tools:src="@drawable/ic_play_arrow_white_36dp"/> - </RelativeLayout> </LinearLayout> diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml index c18e5fd6c..a6ce221db 100644 --- a/app/src/main/res/layout/feeditem_fragment.xml +++ b/app/src/main/res/layout/feeditem_fragment.xml @@ -31,24 +31,34 @@ android:layout_alignParentStart="true" android:layout_width="50dp" android:layout_height="50dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp" android:contentDescription="@string/cover_label" android:gravity="center_vertical" tools:src="@drawable/ic_stat_antenna_default" tools:background="@android:color/holo_green_dark" /> <TextView - android:id="@+id/txtvTitle" + android:id="@+id/txtvPodcast" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="16dp" android:layout_alignTop="@id/imgvCover" android:layout_toRightOf="@id/imgvCover" - android:includeFontPadding="false" + tools:text="Podcast title" + tools:background="@android:color/holo_green_dark" /> + + <TextView + android:id="@+id/txtvTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/txtvPodcast" + android:layout_toRightOf="@id/imgvCover" android:textSize="16sp" android:textColor="?android:attr/textColorPrimary" android:ellipsize="end" android:maxLines="5" - tools:text="Podcast title" + tools:text="Episode title" tools:background="@android:color/holo_green_dark" /> <TextView @@ -58,7 +68,6 @@ android:layout_height="wrap_content" android:layout_toRightOf="@id/imgvCover" android:layout_below="@id/txtvTitle" - android:layout_marginLeft="16dp" tools:text="00:42:23" tools:background="@android:color/holo_green_dark"/> @@ -70,7 +79,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/txtvTitle" android:layout_marginLeft="8dp" - tools:text="Jan\n23" + tools:text="Jan 23" tools:background="@android:color/holo_green_dark" /> </RelativeLayout> diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml new file mode 100644 index 000000000..a716cecb6 --- /dev/null +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <GridView + android:id="@+id/subscriptions_grid" + android:layout_width="match_parent" + android:numColumns="3" + android:horizontalSpacing="2dp" + android:verticalSpacing="2dp" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal"> + </GridView> +</LinearLayout> diff --git a/app/src/main/res/layout/audioplayer_activity.xml b/app/src/main/res/layout/mediaplayerinfo_activity.xml index fb4f995a2..0f68b503e 100644 --- a/app/src/main/res/layout/audioplayer_activity.xml +++ b/app/src/main/res/layout/mediaplayerinfo_activity.xml @@ -152,6 +152,21 @@ android:src="?attr/av_fast_forward" android:textSize="@dimen/text_size_medium" android:textAllCaps="false" + tools:visibility="gone" + tools:background="@android:color/holo_green_dark" /> + + <ImageButton + android:id="@+id/butCastDisconnect" + android:layout_width="@dimen/audioplayer_playercontrols_length" + android:layout_height="@dimen/audioplayer_playercontrols_length" + android:layout_toLeftOf="@id/butRev" + android:background="?attr/selectableItemBackground" + android:contentDescription="@string/cast_disconnect_label" + android:src="?attr/ic_cast_disconnect" + android:scaleType="fitCenter" + android:visibility="gone" + tools:visibility="visible" + tools:src="@drawable/ic_cast_disconnect_white_36dp" tools:background="@android:color/holo_green_dark" /> <ImageButton diff --git a/app/src/main/res/layout/new_episodes_listitem.xml b/app/src/main/res/layout/new_episodes_listitem.xml index 0f1e873f3..944711aec 100644 --- a/app/src/main/res/layout/new_episodes_listitem.xml +++ b/app/src/main/res/layout/new_episodes_listitem.xml @@ -8,6 +8,7 @@ android:layout_height="wrap_content"> <LinearLayout + android:id="@+id/content" android:layout_width="match_parent" android:layout_height="@dimen/listitem_threeline_height" android:background="?attr/selectableItemBackground" diff --git a/app/src/main/res/layout/proxy_settings.xml b/app/src/main/res/layout/proxy_settings.xml new file mode 100644 index 000000000..983325030 --- /dev/null +++ b/app/src/main/res/layout/proxy_settings.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/txtvType" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/proxy_type_label" + android:textColor="?android:attr/textColorSecondary" /> + + <Spinner + android:id="@+id/spType" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/txtvHost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/host_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etHost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textUri" + android:hint="www.example.com" /> + + <TextView + android:id="@+id/txtvPort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/port_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etPort" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="8080" + android:inputType="number" /> + + <TextView + android:id="@+id/txtvUsername" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:singleLine="true" + android:text="@string/username_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etUsername" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/optional_hint" /> + + <TextView + android:id="@+id/txtvPassword" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/password_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etPassword" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/optional_hint" + android:inputType="textPassword" /> + + <com.joanzapata.iconify.widget.IconTextView + android:id="@+id/txtvMessage" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:visibility="invisible" + android:gravity="center"/> + +</LinearLayout> diff --git a/app/src/main/res/layout/statistics_activity.xml b/app/src/main/res/layout/statistics_activity.xml new file mode 100644 index 000000000..4a72dc7de --- /dev/null +++ b/app/src/main/res/layout/statistics_activity.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/total_time_listened_to_podcasts" + android:gravity="center_horizontal"/> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/progressBar" + android:layout_gravity="center_horizontal" + style="?android:attr/progressBarStyleLarge"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/total_time" + android:gravity="center_horizontal" + android:textSize="45sp"/> + + <ListView + android:id="@+id/statistics_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + 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" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/statistics_listitem.xml b/app/src/main/res/layout/statistics_listitem.xml new file mode 100644 index 000000000..20e01bf32 --- /dev/null +++ b/app/src/main/res/layout/statistics_listitem.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="@dimen/listitem_iconwithtext_height" + android:paddingRight="@dimen/listitem_threeline_verticalpadding" + tools:background="@android:color/darker_gray"> + + <ImageView + android:id="@+id/imgvCover" + android:contentDescription="@string/cover_label" + android:layout_width="@dimen/thumbnail_length_navlist" + android:layout_height="@dimen/thumbnail_length_navlist" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true" + android:adjustViewBounds="true" + android:cropToPadding="true" + android:scaleType="centerCrop" + 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/txtvTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/list_vertical_padding" + android:lines="1" + android:textColor="?android:attr/textColorTertiary" + android:textSize="@dimen/text_size_navdrawer" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + tools:text="23" + tools:background="@android:color/holo_green_dark"/> + + <TextView + android:id="@+id/txtvTitle" + android:lines="1" + android:ellipsize="end" + android:singleLine="true" + android:layout_centerVertical="true" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_navdrawer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/listitem_iconwithtext_textleftpadding" + android:layout_toRightOf="@id/imgvCover" + android:layout_toLeftOf="@id/txtvTime" + android:layout_alignWithParentIfMissing="true" + tools:text="Navigation feed item title" + tools:background="@android:color/holo_green_dark"/> + + +</RelativeLayout> diff --git a/app/src/main/res/layout/subscription_item.xml b/app/src/main/res/layout/subscription_item.xml new file mode 100644 index 000000000..19b9943e4 --- /dev/null +++ b/app/src/main/res/layout/subscription_item.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <de.danoeh.antennapod.view.SquareImageView + android:id="@+id/imgvCover" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + tools:src="@drawable/ic_launcher" /> + + <com.joanzapata.iconify.widget.IconTextView + android:id="@+id/txtvTitle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:ellipsize="end" + android:gravity="center" + android:background="@color/light_gray" + tools:text="@string/app_name" /> + + <jp.shts.android.library.TriangleLabelView + android:id="@+id/triangleCountView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" + app:backgroundColor="@color/antennapod_blue" + app:corner="rightTop" + app:primaryText="Test" + app:primaryTextColor="@color/white" + app:primaryTextSize="12sp" + android:layout_gravity="right|top"/> + +</FrameLayout> diff --git a/app/src/main/res/layout/videoplayer_activity.xml b/app/src/main/res/layout/videoplayer_activity.xml index 2e1097fb8..4db663e19 100644 --- a/app/src/main/res/layout/videoplayer_activity.xml +++ b/app/src/main/res/layout/videoplayer_activity.xml @@ -32,7 +32,7 @@ android:layout_height="wrap_content" android:layout_margin="8dp" android:background="@drawable/overlay_button_circle_background" - android:contentDescription="@string/pause_label" + android:contentDescription="@string/rewind_label" android:src="@drawable/ic_av_rewind_80dp" /> <ImageButton @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_margin="8dp" android:background="@drawable/overlay_button_circle_background" - android:contentDescription="@string/pause_label" + android:contentDescription="@string/fast_forward_label" android:src="@drawable/ic_av_fast_forward_80dp" /> </LinearLayout> diff --git a/app/src/main/res/menu/cast_enabled.xml b/app/src/main/res/menu/cast_enabled.xml new file mode 100644 index 000000000..d6e85c311 --- /dev/null +++ b/app/src/main/res/menu/cast_enabled.xml @@ -0,0 +1,10 @@ +<?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/media_route_menu_item" + android:title="@string/cast_media_route_menu_title" + custom:actionProviderClass="de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider" + custom:showAsAction="ifRoom"/> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/feeditem_options.xml b/app/src/main/res/menu/feeditem_options.xml index 898081486..511758c2e 100644 --- a/app/src/main/res/menu/feeditem_options.xml +++ b/app/src/main/res/menu/feeditem_options.xml @@ -93,4 +93,10 @@ android:title="@string/support_label"> </item> -</menu>
\ No newline at end of file + <item + android:id="@+id/open_podcast" + custom:showAsAction="collapseActionView" + android:title="@string/open_podcast"> + </item> + +</menu> diff --git a/app/src/main/res/menu/opml_selection_options.xml b/app/src/main/res/menu/opml_selection_options.xml new file mode 100644 index 000000000..26d2a0519 --- /dev/null +++ b/app/src/main/res/menu/opml_selection_options.xml @@ -0,0 +1,19 @@ +<?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/select_all_item" + android:icon="?attr/ic_check_box_outline" + android:title="@string/select_all_label" + custom:showAsAction="ifRoom"> + </item> + + <item + android:id="@id/deselect_all_item" + android:icon="?attr/ic_check_box" + android:title="@string/deselect_all_label" + custom:showAsAction="ifRoom"> + </item> + +</menu> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3ecd79b1e..57829e3e1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -49,6 +49,10 @@ android:key="prefPersistNotify" android:summary="@string/pref_persistNotify_sum" android:title="@string/pref_persistNotify_title"/> + <Preference + android:key="prefCompactNotificationButtons" + android:summary="@string/pref_compact_notification_buttons_sum" + android:title="@string/pref_compact_notification_buttons_title"/> <de.danoeh.antennapod.preferences.SwitchCompatPreference android:defaultValue="true" android:enabled="true" @@ -185,7 +189,7 @@ android:title="@string/pref_automatic_download_title" android:defaultValue="false"/> <com.afollestad.materialdialogs.prefs.MaterialListPreference - android:defaultValue="20" + android:defaultValue="25" android:entries="@array/episode_cache_size_entries" android:key="prefEpisodeCacheSize" android:title="@string/pref_episode_cache_title" @@ -200,8 +204,11 @@ android:key="prefEnableAutoDownloadWifiFilter" android:title="@string/pref_autodl_wifi_filter_title" android:summary="@string/pref_autodl_wifi_filter_sum"/> - </PreferenceScreen> + <Preference + android:key="prefProxy" + android:summary="@string/pref_proxy_sum" + android:title="@string/pref_proxy_title" /> </PreferenceCategory> @@ -240,6 +247,10 @@ android:title="@string/pref_gpodnet_setlogin_information_title" android:summary="@string/pref_gpodnet_setlogin_information_sum"/> <Preference + android:key="pref_gpodnet_sync" + android:title="@string/pref_gpodnet_sync_title" + android:summary="@string/pref_gpodnet_sync_sum"/> + <Preference android:key="pref_gpodnet_logout" android:title="@string/pref_gpodnet_logout_title"/> <Preference @@ -247,7 +258,7 @@ android:title="@string/pref_gpodnet_sethostname_title"/> </PreferenceScreen> </PreferenceCategory> - <PreferenceCategory android:title="@string/other_pref"> + <PreferenceCategory android:title="@string/storage_pref"> <Preference android:title="@string/choose_data_directory" android:key="prefChooseDataDir"/> @@ -258,16 +269,38 @@ android:key="prefImageCacheSize" android:summary="@string/pref_image_cache_size_sum" android:defaultValue="100"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/other_pref"> <Preference android:key="prefOpmlExport" android:title="@string/opml_export_label"/> <Preference - android:key="prefAbout" - android:title="@string/about_pref"/> + android:key="statistics" + android:title="@string/statistics_label"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/project_pref"> + <Preference + android:key="prefFaq" + android:title="@string/pref_faq"/> + <Preference + android:key="prefKnownIssues" + android:title="@string/pref_known_issues"/> <Preference android:key="prefSendCrashReport" android:title="@string/crash_report_title" android:summary="@string/crash_report_sum"/> + <Preference + android:key="prefAbout" + android:title="@string/about_pref"/> + </PreferenceCategory> + + <PreferenceCategory android:title="@string/experimental_pref"> + <de.danoeh.antennapod.preferences.SwitchCompatPreference + android:defaultValue="false" + android:enabled="true" + android:key="prefCast" + android:summary="@string/pref_cast_message" + android:title="@string/pref_cast_title"/> </PreferenceCategory> </PreferenceScreen> diff --git a/app/src/main/templates/about.html b/app/src/main/templates/about.html index 222429b17..5c7a3cbd0 100644 --- a/app/src/main/templates/about.html +++ b/app/src/main/templates/about.html @@ -100,6 +100,9 @@ licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0.txt">(View)</a <h2>StackBlur <a href="https://github.com/kikoso/android-stackblur">(Link)</a></h2> by Enrique López Mañas, licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0.txt">(View)</a> +<h2>Triangle Label View <a href="https://github.com/shts/TriangleLabelView">(Link)</a></h2> +by Shota Saito, licensed under the Apache 2.0 license <a href="LICENSE_TRIANGLE_LABEL_VIEW.txt">(View)</a> + <h2>AntennaPod-AudioPlayer <a href="https://github.com/AntennaPod/AntennaPod-AudioPlayer/">(Link)</a></h2> by the AntennaPod team, licensed under the Apache 2.0 license <a href="LICENSE_APACHE-2.0.txt">(View)</a> |