diff options
author | ByteHamster <info@bytehamster.com> | 2020-02-21 19:02:53 +0100 |
---|---|---|
committer | ByteHamster <info@bytehamster.com> | 2020-02-21 19:02:53 +0100 |
commit | b3ea96e7b3d3409a02da64f9e93511cc3400709a (patch) | |
tree | 529b4b72831a8c9e2daac74839d71f70636e095c /app | |
parent | 7b5435082042dc77de6e3fb5f59bf55fc71d3aa8 (diff) | |
parent | 657d19ccc2c1e3de6555c5c220605bb59225e450 (diff) | |
download | AntennaPod-b3ea96e7b3d3409a02da64f9e93511cc3400709a.zip |
Merge branch 'develop' into speed-indicator-view
Diffstat (limited to 'app')
166 files changed, 3548 insertions, 4459 deletions
diff --git a/app/build.gradle b/app/build.gradle index 498111189..771ef5b95 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { // "1.2.3-SNAPSHOT" -> 1020300 // "1.2.3-RC4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 1080008 - versionName "1.8.0-RC8" + versionCode 1080195 + versionName "1.8.1" testApplicationId "de.test.antennapod" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" generatedDensities = [] @@ -161,7 +161,6 @@ dependencies { implementation "com.joanzapata.iconify:android-iconify-fontawesome:$iconifyVersion" implementation "com.joanzapata.iconify:android-iconify-material:$iconifyVersion" implementation "com.yqritc:recyclerview-flexibledivider:$recyclerviewFlexibledividerVersion" - implementation "com.githang:viewpagerindicator:2.5.1@aar" implementation "com.github.shts:TriangleLabelView:$triangleLabelViewVersion" implementation 'com.leinardi.android:speed-dial:3.0.0' implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java index bbd8a705c..6ae14a58e 100644 --- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -116,8 +116,8 @@ public class EspressoTestUtils { String[] sharedPreferencesFileNames = new File(root, "shared_prefs").list(); for (String fileName : sharedPreferencesFileNames) { System.out.println("Cleared database: " + fileName); - InstrumentationRegistry.getTargetContext(). - getSharedPreferences(fileName.replace(".xml", ""), Context.MODE_PRIVATE).edit().clear().commit(); + InstrumentationRegistry.getTargetContext().getSharedPreferences( + fileName.replace(".xml", ""), Context.MODE_PRIVATE).edit().clear().commit(); } } diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java index cc380813e..efd6070bb 100644 --- a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java @@ -228,7 +228,7 @@ public class PlaybackTest { final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); Matcher<View> allEpisodesMatcher = allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2)); onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000)); - onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.butSecondaryAction))); + onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton))); FeedMedia media = episodes.get(0).getMedia(); Awaitility.await().atMost(1, TimeUnit.SECONDS).until( @@ -244,7 +244,7 @@ public class PlaybackTest { Matcher<View> queueMatcher = allOf(withId(R.id.recyclerView), isDisplayed(), hasMinimumChildCount(2)); onView(isRoot()).perform(waitForView(queueMatcher, 1000)); - onView(queueMatcher).perform(actionOnItemAtPosition(itemIdx, clickChildViewWithId(R.id.butSecondaryAction))); + onView(queueMatcher).perform(actionOnItemAtPosition(itemIdx, clickChildViewWithId(R.id.secondaryActionButton))); FeedMedia media = queue.get(itemIdx).getMedia(); Awaitility.await().atMost(1, TimeUnit.SECONDS).until( 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 c17324750..d4af79645 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 @@ -3,7 +3,8 @@ package de.test.antennapod.service.playback; import android.content.Context; import androidx.test.filters.MediumTest; -import androidx.test.platform.app.InstrumentationRegistry; + +import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.test.antennapod.EspressoTestUtils; import junit.framework.AssertionFailedError; @@ -125,7 +126,7 @@ public class PlaybackServiceMediaPlayerTest { private Playable writeTestPlayable(String downloadUrl, String fileUrl) { final Context c = getInstrumentation().getTargetContext(); Feed f = new Feed(0, null, "f", "l", "d", null, null, null, null, "i", null, null, "l", false); - FeedPreferences prefs = new FeedPreferences(f.getId(), false, FeedPreferences.AutoDeleteAction.NO, null, null); + FeedPreferences prefs = new FeedPreferences(f.getId(), false, FeedPreferences.AutoDeleteAction.NO, VolumeAdaptionSetting.OFF, null, null); f.setPreferences(prefs); f.setItems(new ArrayList<>()); FeedItem i = new FeedItem(0, "t", "i", "l", new Date(), FeedItem.UNPLAYED, f); diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java index ebf59fea7..840a7d01f 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java @@ -58,7 +58,8 @@ class DBTestUtils { List<Chapter> chapters = new ArrayList<>(); item.setChapters(chapters); for (int k = 0; k < numChapters; k++) { - chapters.add(new SimpleChapter(k, "item " + j + " chapter " + k, item, "http://example.com")); + chapters.add(new SimpleChapter(k, "item " + j + " chapter " + k, + "http://example.com", "http://example.com/image.png")); } } f.getItems().add(item); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java index df32ed3c0..194d51a3c 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java @@ -66,8 +66,8 @@ public class SpeedChangeTest { List<FeedItem> queue = DBReader.getQueue(); PlaybackPreferences.writeMediaPlaying(queue.get(0).getMedia(), PlayerStatus.PAUSED, false); - UserPreferences.setPlaybackSpeedArray(new String[] {"1.00", "2.00", "3.00"}); - availableSpeeds = UserPreferences.getPlaybackSpeedArray(); + availableSpeeds = new String[] {"1.00", "2.00", "3.00"}; + UserPreferences.setPlaybackSpeedArray(availableSpeeds); EspressoTestUtils.tryKillPlaybackService(); activityRule.launchActivity(new Intent()); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java index 3f0bf6967..8f1000d43 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java @@ -64,6 +64,7 @@ public class TextOnlyFeedsTest { openNavDrawer(); onDrawerItem(withText(feed.getTitle())).perform(scrollTo(), click()); onView(withText(feed.getItemAtIndex(0).getTitle())).perform(click()); + onView(isRoot()).perform(waitForView(withText(R.string.mark_read_no_media_label), 3000)); onView(withText(R.string.mark_read_no_media_label)).perform(click()); onView(isRoot()).perform(waitForView(allOf(withText(R.string.mark_read_no_media_label), not(isDisplayed())), 3000)); } diff --git a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java b/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java index 5bc2f7bd8..4893d7d82 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java @@ -5,6 +5,8 @@ import de.danoeh.antennapod.core.util.URLChecker; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Test class for URLChecker @@ -119,4 +121,30 @@ public class URLCheckerTest { final String out = URLChecker.prepareURL(in, null); assertEquals("http://example.com", out); } + + @Test + public void testUrlEqualsSame() { + assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test")); + assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test/")); + assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com//test")); + assertTrue(URLChecker.urlEquals("https://www.example.com", "https://www.example.com/")); + assertTrue(URLChecker.urlEquals("https://www.example.com", "http://www.example.com")); + assertTrue(URLChecker.urlEquals("http://www.example.com/", "https://www.example.com/")); + assertTrue(URLChecker.urlEquals("https://www.example.com/?id=42", "https://www.example.com/?id=42")); + assertTrue(URLChecker.urlEquals("https://example.com/podcast%20test", "https://example.com/podcast test")); + assertTrue(URLChecker.urlEquals("https://example.com/?a=podcast%20test", "https://example.com/?a=podcast test")); + assertTrue(URLChecker.urlEquals("https://example.com/?", "https://example.com/")); + assertTrue(URLChecker.urlEquals("https://example.com/?", "https://example.com")); + assertTrue(URLChecker.urlEquals("https://Example.com", "https://example.com")); + assertTrue(URLChecker.urlEquals("https://example.com/test", "https://example.com/Test")); + } + + @Test + public void testUrlEqualsDifferent() { + assertFalse(URLChecker.urlEquals("https://www.example.com/test", "https://www.example2.com/test")); + assertFalse(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.de/test")); + assertFalse(URLChecker.urlEquals("https://example.com/", "https://otherpodcast.example.com/")); + assertFalse(URLChecker.urlEquals("https://www.example.com/?id=42&a=b", "https://www.example.com/?id=43&a=b")); + assertFalse(URLChecker.urlEquals("https://example.com/podcast%25test", "https://example.com/podcast test")); + } } diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java index c42695a57..59b9ceaca 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java @@ -26,7 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** - * Test class for timeline + * Test class for timeline. */ @SmallTest public class TimelineTest { @@ -34,7 +34,7 @@ public class TimelineTest { private Context context; @Before - public void setUp() throws Exception { + public void setUp() { context = InstrumentationRegistry.getTargetContext(); } @@ -49,128 +49,151 @@ public class TimelineTest { } @Test - public void testProcessShownotesAddTimecodeHHMMSSNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeHHMMSSNoChapters() { final String timeStr = "10:11:12"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeHHMMSSMoreThen24HoursNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeHHMMSSMoreThen24HoursNoChapters() { final String timeStr = "25:00:00"; final long time = 25 * 60 * 60 * 1000; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeHHMMNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeHHMMNoChapters() { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeMMSSNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeMMSSNoChapters() { final String timeStr = "10:11"; final long time = 10 * 60 * 1000 + 11 * 1000; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 11 * 60 * 1000); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", 11 * 60 * 1000); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeHMMSSNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeHMMSSNoChapters() { final String timeStr = "2:11:12"; final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; - - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeMSSNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeMSSNoChapters() { final String timeStr = "1:12"; final long time = 60 * 1000 + 12 * 1000; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 2 * 60 * 1000); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStr + " here.</p>", 2 * 60 * 1000); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() throws Exception { + public void testProcessShownotesAddNoTimecodeDuration() { + final String timeStr = "2:11:12"; + final int time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000; + String originalText = "<p> Some test text with a timecode " + timeStr + " here.</p>"; + Playable p = newTestPlayable(null, originalText, time); + Timeline t = new Timeline(context, p); + String res = t.processShownotes(); + Document d = Jsoup.parse(res); + assertEquals("Should not parse time codes that equal duration", 0, d.body().getElementsByTag("a").size()); + } + + @Test + public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() { final String[] timeStrings = new String[]{ "10:12", "1:10:12" }; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 60 * 60 * 1000); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 60 * 60 * 1000); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); - checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings); + String res = t.processShownotes(); + checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, + 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings); } @Test - public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() throws Exception { + public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() { // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS. final String[] timeStrings = new String[]{ "10:12", "2:12" }; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings); } @Test - public void testProcessShownotesAddTimecodeParentheses() throws Exception { + public void testProcessShownotesAddTimecodeParentheses() { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode (" + timeStr + ") here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode (" + + timeStr + ") here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeBrackets() throws Exception { + public void testProcessShownotesAddTimecodeBrackets() { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode [" + timeStr + "] here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode [" + + timeStr + "] here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAddTimecodeAngleBrackets() throws Exception { + public void testProcessShownotesAddTimecodeAngleBrackets() { final String timeStr = "10:11"; final long time = 3600 * 1000 * 10 + 60 * 1000 * 11; - Playable p = newTestPlayable(null, "<p> Some test text with a timecode <" + timeStr + "> here.</p>", Integer.MAX_VALUE); + Playable p = newTestPlayable(null, "<p> Some test text with a timecode <" + + timeStr + "> here.</p>", Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[]{time}, new String[]{timeStr}); } @Test - public void testProcessShownotesAndInvalidTimecode() throws Exception { + public void testProcessShownotesAndInvalidTimecode() { final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"}; StringBuilder shownotes = new StringBuilder("<p> Some test text with timecodes "); @@ -181,7 +204,7 @@ public class TimelineTest { Playable p = newTestPlayable(null, shownotes.toString(), Integer.MAX_VALUE); Timeline t = new Timeline(context, p); - String res = t.processShownotes(true); + String res = t.processShownotes(); checkLinkCorrect(res, new long[0], new String[0]); } @@ -197,14 +220,15 @@ public class TimelineTest { assertTrue(href.endsWith(String.valueOf(timecodes[countedLinks]))); assertEquals(timecodeStr[countedLinks], text); countedLinks++; - assertTrue("Contains too many links: " + countedLinks + " > " + timecodes.length, countedLinks <= timecodes.length); + assertTrue("Contains too many links: " + countedLinks + " > " + + timecodes.length, countedLinks <= timecodes.length); } } assertEquals(timecodes.length, countedLinks); } @Test - public void testIsTimecodeLink() throws Exception { + public void testIsTimecodeLink() { assertFalse(Timeline.isTimecodeLink(null)); assertFalse(Timeline.isTimecodeLink("http://antennapod/timecode/123123")); assertFalse(Timeline.isTimecodeLink("antennapod://timecode/")); @@ -215,7 +239,7 @@ public class TimelineTest { } @Test - public void testGetTimecodeLinkTime() throws Exception { + public void testGetTimecodeLinkTime() { assertEquals(-1, Timeline.getTimecodeLinkTime(null)); assertEquals(-1, Timeline.getTimecodeLinkTime("http://timecode/123")); assertEquals(123, Timeline.getTimecodeLinkTime("antennapod://timecode/123")); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ad68fcfe3..62335b5e6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,19 +33,18 @@ android:label="@string/app_name" android:backupAgent=".core.backup.OpmlBackupAgent" android:restoreAnyVersion="true" + android:theme="@style/Theme.AntennaPod.Splash" android:usesCleartextTraffic="true" android:logo="@mipmap/ic_launcher"> - <activity - android:name=".activity.WidgetConfigActivity" - android:label="@string/widget_settings"> - <intent-filter> - <action android:name="android.appwidget.action.APPWIDGET_CONFIGUR"/> - </intent-filter> - </activity> + <meta-data android:name="android.webkit.WebView.MetricsOptOut" + android:value="true"/> <meta-data android:name="com.google.android.gms.car.notification.SmallIcon" - android:resource="@drawable/ic_antenna" /> + android:resource="@drawable/ic_antenna"/> + <meta-data + android:name="com.google.android.gms.car.application" + android:resource="@xml/automotive_app_desc"/> <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/> @@ -53,12 +52,7 @@ <activity android:name=".activity.SplashActivity" android:label="@string/app_name" - android:configChanges="keyboardHidden|orientation|screenSize" - android:theme="@style/Theme.AntennaPod.Dark.Splash"> - <!-- android:launchMode="singleTask" removed for #2948, so that - when app is launched again, the app will go to the last activity users use - (if the app has been used recently, e.g., last 30 minutes), - rather than always go to MainActivity (launched by SplashActivity here) --> + android:configChanges="keyboardHidden|orientation|screenSize"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -71,12 +65,14 @@ "android.intent.category.DEFAULT" /> </intent-filter> </activity> + <activity android:name=".activity.MainActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:launchMode="singleTask" android:label="@string/app_name"> </activity> + <activity android:name=".activity.AudioplayerActivity" android:launchMode="singleTop"> @@ -93,6 +89,7 @@ <data android:mimeType="audio/*"/> </intent-filter> </activity> + <activity android:name=".activity.CastplayerActivity" android:launchMode="singleTop"> @@ -114,6 +111,14 @@ android:value="de.danoeh.antennapod.activity.MainActivity"/> </activity> + <activity + android:name=".activity.WidgetConfigActivity" + android:label="@string/widget_settings"> + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_CONFIGUR"/> + </intent-filter> + </activity> + <service android:name=".core.service.PlayerWidgetJobService" android:permission="android.permission.BIND_JOB_SERVICE" @@ -141,19 +146,7 @@ <activity android:name=".activity.StorageErrorActivity"> </activity> <activity - android:name=".activity.ImportExportActivity" - android:label="@string/import_export"> - <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|screenSize" - android:label="@string/opml_import_label"> - </activity> - <activity - android:name=".activity.OpmlImportFromIntentActivity" + android:name=".activity.OpmlImportActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/opml_import_label"> <intent-filter> @@ -359,10 +352,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> - - <meta-data - android:name="com.google.android.gms.car.application" - android:resource="@xml/automotive_app_desc"/> </application> </manifest> diff --git a/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java index 061ea9ae2..ab4d564df 100644 --- a/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java +++ b/app/src/main/java/de/danoeh/antennapod/CrashReportWriter.java @@ -35,7 +35,9 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(path)); - out.println(getSystemInfo()); + out.println("[ Crash info ]"); + out.println("Time: " + new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.getDefault()).format(new Date())); + out.println("AntennaPod version: " + BuildConfig.VERSION_NAME); out.println(); out.println("[ StackTrace ]"); ex.printStackTrace(out); @@ -54,7 +56,6 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler { "\nAntennaPod version: " + BuildConfig.VERSION_NAME + "\nModel: " + Build.MODEL + "\nDevice: " + Build.DEVICE + - "\nProduct: " + Build.PRODUCT + - "\nTime: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()) + "\n"; + "\nProduct: " + Build.PRODUCT; } } 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 f24b13a9f..2a747bc9e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -1,28 +1,25 @@ package de.danoeh.antennapod.activity; import android.content.Intent; -import androidx.core.view.ViewCompat; import android.text.TextUtils; import android.util.Log; import android.view.View; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; - import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; +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.dialog.VariableSpeedDialog; +import java.text.DecimalFormat; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Activity for playing audio files. */ public class AudioplayerActivity extends MediaplayerInfoActivity { private static final String TAG = "AudioPlayerActivity"; + private static final float EPSILON = 0.001f; private final AtomicBoolean isSetup = new AtomicBoolean(false); @@ -33,8 +30,8 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { playExternalMedia(getIntent(), MediaType.AUDIO); } else if (PlaybackService.isCasting()) { Intent intent = PlaybackService.getPlayerActivityIntent(this); - if (intent.getComponent() != null && - !intent.getComponent().getClassName().equals(AudioplayerActivity.class.getName())) { + if (intent.getComponent() != null + && !intent.getComponent().getClassName().equals(AudioplayerActivity.class.getName())) { saveCurrentFragment(); finish(); startActivity(intent); @@ -57,7 +54,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { @Override protected void updatePlaybackSpeedButton() { - if(butPlaybackSpeed == null) { + if (butPlaybackSpeed == null) { return; } if (controller == null) { @@ -66,7 +63,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { return; } updatePlaybackSpeedButtonText(); - ViewCompat.setAlpha(butPlaybackSpeed, controller.canSetPlaybackSpeed() ? 1.0f : 0.5f); + butPlaybackSpeed.setAlpha(controller.canSetPlaybackSpeed() ? 1.0f : 0.5f); butPlaybackSpeed.setVisibility(View.VISIBLE); txtvPlaybackSpeed.setVisibility(View.VISIBLE); } @@ -92,54 +89,40 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { @Override protected void setupGUI() { - if(isSetup.getAndSet(true)) { + if (isSetup.getAndSet(true)) { return; } super.setupGUI(); - if(butCastDisconnect != null) { + if (butCastDisconnect != null) { butCastDisconnect.setVisibility(View.GONE); } - if(butPlaybackSpeed != null) { + if (butPlaybackSpeed != null) { butPlaybackSpeed.setOnClickListener(v -> { if (controller == null) { return; } if (controller.canSetPlaybackSpeed()) { - String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); - DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); - format.setDecimalSeparator('.'); + float[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); + float currentSpeed = controller.getCurrentPlaybackSpeedMultiplier(); - float currentSpeedValue = controller.getCurrentPlaybackSpeedMultiplier(); - String currentSpeed = new DecimalFormat("0.00", format).format(currentSpeedValue); + int newSpeedIndex = 0; + while (newSpeedIndex < availableSpeeds.length + && availableSpeeds[newSpeedIndex] < currentSpeed + EPSILON) { + newSpeedIndex++; + } - // 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) { + float newSpeed; + if (availableSpeeds.length == 0) { + newSpeed = 1.0f; + } else if (newSpeedIndex == availableSpeeds.length) { newSpeed = availableSpeeds[0]; } else { - newSpeed = "1.00"; + newSpeed = availableSpeeds[newSpeedIndex]; } - 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; - } - } - - try { - PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(Float.parseFloat(newSpeed)); - } catch (NumberFormatException e) { - // Well this was awkward... - } + PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(newSpeed); UserPreferences.setPlaybackSpeed(newSpeed); - controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); + controller.setPlaybackSpeed(newSpeed); onPositionObserverUpdate(); } else { VariableSpeedDialog.showGetPluginDialog(this); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java index 666eacfa8..01413bb79 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java @@ -30,16 +30,17 @@ public class BugReportActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); setContentView(R.layout.bug_report); - TextView crashDetailsText = findViewById(R.id.crash_report_logs); + String crashDetailsText = CrashReportWriter.getSystemInfo() + "\n\n"; + TextView crashDetailsTextView = findViewById(R.id.crash_report_logs); try { File crashFile = CrashReportWriter.getFile(); - String crashReportContent = IOUtils.toString(new FileInputStream(crashFile), Charset.forName("UTF-8")); - crashDetailsText.setText(crashReportContent); + crashDetailsText += IOUtils.toString(new FileInputStream(crashFile), Charset.forName("UTF-8")); } catch (IOException e) { e.printStackTrace(); - crashDetailsText.setText("No crash report recorded\n" + CrashReportWriter.getSystemInfo()); + crashDetailsText += "No crash report recorded"; } + crashDetailsTextView.setText(crashDetailsText); findViewById(R.id.btn_open_bug_tracker).setOnClickListener(v -> { IntentUtils.openInBrowser(BugReportActivity.this, "https://github.com/AntennaPod/AntennaPod/issues"); @@ -47,7 +48,7 @@ public class BugReportActivity extends AppCompatActivity { findViewById(R.id.btn_copy_log).setOnClickListener(v -> { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsText.getText()); + ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsTextView.getText()); clipboard.setPrimaryClip(clip); Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show(); }); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java deleted file mode 100644 index f85a1cd77..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/ImportExportActivity.java +++ /dev/null @@ -1,218 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ComponentName; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.ParcelFileDescriptor; -import com.google.android.material.snackbar.Snackbar; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.view.MenuItem; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.FileChannel; -import java.util.Arrays; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.PodDBAdapter; - -/** - * Displays the 'import/export' screen - */ -public class ImportExportActivity extends AppCompatActivity { - private static final int REQUEST_CODE_RESTORE = 43; - private static final int REQUEST_CODE_BACKUP_DOCUMENT = 44; - private static final String EXPORT_FILENAME = "AntennaPodBackup.db"; - private static final String TAG = ImportExportActivity.class.getSimpleName(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayShowHomeEnabled(true); - } - setContentView(R.layout.import_export_activity); - - findViewById(R.id.button_export).setOnClickListener(view -> backup()); - findViewById(R.id.button_import).setOnClickListener(view -> restore()); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void backup() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/x-sqlite3") - .putExtra(Intent.EXTRA_TITLE, EXPORT_FILENAME); - - startActivityForResult(intent, REQUEST_CODE_BACKUP_DOCUMENT); - } else { - try { - File sd = Environment.getExternalStorageDirectory(); - File backupDB = new File(sd, EXPORT_FILENAME); - writeBackupTo(new FileOutputStream(backupDB)); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } - } - } - - private void restore() { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - startActivityForResult(intent, REQUEST_CODE_RESTORE); - } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - startActivityForResult(Intent.createChooser(intent, - getString(R.string.import_select_file)), REQUEST_CODE_RESTORE); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent resultData) { - if (resultCode != RESULT_OK || resultData == null) { - return; - } - Uri uri = resultData.getData(); - - if (requestCode == REQUEST_CODE_RESTORE) { - restoreFrom(uri); - } else if (requestCode == REQUEST_CODE_BACKUP_DOCUMENT) { - backupToDocument(uri); - } - } - - private void restoreFrom(Uri inputUri) { - InputStream inputStream = null; - try { - if (!validateDB(inputUri)) { - displayBadFileDialog(); - return; - } - - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - inputStream = getContentResolver().openInputStream(inputUri); - FileUtils.copyInputStreamToFile(inputStream, currentDB); - displayImportSuccessDialog(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - private static final byte[] SQLITE3_MAGIC = "SQLite format 3\0".getBytes(); - private boolean validateDB(Uri inputUri) throws IOException { - try (InputStream inputStream = getContentResolver().openInputStream(inputUri)) { - byte[] magicBuf = new byte[SQLITE3_MAGIC.length]; - if (inputStream.read(magicBuf) == magicBuf.length) { - return Arrays.equals(SQLITE3_MAGIC, magicBuf); - } - } - - return false; - } - - private void displayBadFileDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_bad_file) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, ((dialogInterface, i) -> { - // do nothing - })) - .show(); - } - - private void displayImportSuccessDialog() { - AlertDialog.Builder d = new AlertDialog.Builder(ImportExportActivity.this); - d.setMessage(R.string.import_ok); - d.setCancelable(false); - d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { - Intent intent = new Intent(getApplicationContext(), SplashActivity.class); - ComponentName cn = intent.getComponent(); - Intent mainIntent = Intent.makeRestartActivityTask(cn); - startActivity(mainIntent); - }); - d.show(); - } - - private void backupToDocument(Uri uri) { - ParcelFileDescriptor pfd = null; - FileOutputStream fileOutputStream = null; - try { - pfd = getContentResolver().openFileDescriptor(uri, "w"); - fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); - writeBackupTo(fileOutputStream); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(fileOutputStream); - - if (pfd != null) { - try { - pfd.close(); - } catch (IOException e) { - Log.d(TAG, "Unable to close ParcelFileDescriptor"); - } - } - } - } - - private void writeBackupTo(FileOutputStream outFileStream) { - FileChannel src = null; - FileChannel dst = null; - try { - File currentDB = getDatabasePath(PodDBAdapter.DATABASE_NAME); - - if (currentDB.exists()) { - src = new FileInputStream(currentDB).getChannel(); - dst = outFileStream.getChannel(); - dst.transferFrom(src, 0, src.size()); - - Snackbar.make(findViewById(R.id.import_export_layout), - R.string.export_ok, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.import_export_layout), - "Can not access current database", Snackbar.LENGTH_SHORT).show(); - } - } catch (IOException e) { - Log.e(TAG, Log.getStackTraceString(e)); - Snackbar.make(findViewById(R.id.import_export_layout), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show(); - } finally { - IOUtils.closeQuietly(src); - IOUtils.closeQuietly(dst); - } - } -} 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 fab84078e..62fd4b515 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -111,20 +111,13 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi private Toolbar toolbar; private ExternalPlayerFragment externalPlayerFragment; private DrawerLayout drawerLayout; - private View navDrawer; private ListView navList; private NavListAdapter navAdapter; private int mPosition = -1; - private ActionBarDrawerToggle drawerToggle; - private CharSequence currentTitle; - - private ProgressDialog pd; - private Disposable disposable; - private long lastBackButtonPressTime = 0; @NonNull @@ -282,10 +275,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); } - public List<Feed> getFeeds() { - return (navDrawerData != null) ? navDrawerData.feeds : null; - } - private void loadFragment(int index, Bundle args) { Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")"); if (index < navAdapter.getSubscriptionOffset()) { @@ -518,9 +507,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi if (disposable != null) { disposable.dispose(); } - if(pd != null) { - pd.dismiss(); - } } @@ -849,9 +835,4 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi super.onNewIntent(intent); setIntent(intent); } - - @VisibleForTesting - public void updateNavDrawer() { - navAdapter.notifyDataSetChanged(); - } } 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 538ed1231..a5ae5e58c 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -358,10 +358,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); } - boolean sleepTimerSet = controller.sleepTimerActive(); - boolean sleepTimerNotSet = controller.sleepTimerNotActive(); - menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); - menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); + menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive()); + menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive()); if (this instanceof AudioplayerActivity) { int[] attrs = {R.attr.action_bar_icon_color}; @@ -422,30 +420,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements .show(); } break; - case R.id.disable_sleeptimer_item: - if (controller.serviceAvailable()) { - - new AlertDialog.Builder(this) - .setTitle(R.string.sleep_timer_label) - .setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())) - .setPositiveButton(R.string.disable_sleeptimer_label, (dialog, which) - -> controller.disableSleepTimer()) - .setNegativeButton(R.string.cancel_label, null) - .show(); - } - break; + case R.id.disable_sleeptimer_item: // Fall-through case R.id.set_sleeptimer_item: - if (controller.serviceAvailable()) { - SleepTimerDialog td = new SleepTimerDialog(this) { - @Override - public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) { - controller.setSleepTimer(millis, shakeToReset, vibrate); - } - }; - td.createNewDialog().show(); - } + new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog"); break; case R.id.audio_controls: boolean isPlayingVideo = controller.getMedia().getMediaType() == MediaType.VIDEO; @@ -793,7 +770,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private float prog; @Override - public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) { + public void onProgressChanged(SeekBar seekBar,int progress, boolean fromUser) { if (controller == null || txtvLength == null) { return; } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java index 43423f4a5..1e1aad712 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java @@ -28,8 +28,6 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -import com.viewpagerindicator.CirclePageIndicator; - import java.util.List; import de.danoeh.antennapod.R; @@ -38,6 +36,7 @@ import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.MessageEvent; +import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -59,6 +58,7 @@ 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.view.PagerIndicatorView; import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -103,6 +103,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem private int mPosition = -1; private ViewPager pager; + private PagerIndicatorView pageIndicator; private MediaplayerInfoPagerAdapter pagerAdapter; private Disposable disposable; @@ -211,12 +212,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem 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 = findViewById(R.id.appBar); - float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); - appBarLayout.setElevation(px); - } drawerLayout = findViewById(R.id.drawer_layout); navList = findViewById(R.id.nav_list); navDrawer = findViewById(R.id.nav_layout); @@ -262,8 +257,10 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem pager.setOffscreenPageLimit(3); pagerAdapter = new MediaplayerInfoPagerAdapter(getSupportFragmentManager()); pager.setAdapter(pagerAdapter); - CirclePageIndicator pageIndicator = findViewById(R.id.page_indicator); + pageIndicator = findViewById(R.id.page_indicator); pageIndicator.setViewPager(pager); + pageIndicator.setOnClickListener(v + -> pager.setCurrentItem((pager.getCurrentItem() + 1) % pager.getChildCount())); loadLastFragment(); pager.onSaveInstanceState(); @@ -271,6 +268,16 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem } @Override + boolean loadMediaInfo() { + if (controller != null && controller.getMedia() != null) { + List<Chapter> chapters = controller.getMedia().getChapters(); + boolean hasChapters = chapters != null && !chapters.isEmpty(); + pageIndicator.setDisabledPage(hasChapters ? -1 : 2); + } + return super.loadMediaInfo(); + } + + @Override protected void onReloadNotification(int notificationCode) { if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) { Log.d(TAG, "ReloadNotification received, switching to Videoplayer now"); 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 50a8d0965..3621f0b44 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -7,14 +7,8 @@ import android.content.Intent; import android.graphics.LightingColorFilter; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.UiThread; -import androidx.core.app.NavUtils; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; -import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; @@ -25,29 +19,22 @@ import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.annotation.UiThread; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.NavUtils; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; - -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import org.apache.commons.lang3.StringUtils; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -73,6 +60,16 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * Downloads a feed from a feed URL and parses it. Subclasses can display the @@ -126,11 +123,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedUrl == null) { Log.e(TAG, "feedUrl is null."); - new AlertDialog.Builder(OnlineFeedViewActivity.this). - setNeutralButton(android.R.string.ok, - (dialog, which) -> finish()). - setTitle(R.string.error_label). - setMessage(R.string.null_value_podcast_error).create().show(); + new AlertDialog.Builder(OnlineFeedViewActivity.this) + .setNeutralButton(android.R.string.ok, (dialog, which) -> finish()) + .setTitle(R.string.error_label) + .setMessage(R.string.null_value_podcast_error) + .show(); } else { Log.d(TAG, "Activity was started with url " + feedUrl); setLoadingLayout(); @@ -230,7 +227,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { url = URLChecker.prepareURL(url); feed = new Feed(url, null); if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, username, password)); + feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password)); } String fileUrl = new File(getExternalCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); @@ -368,8 +365,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { findViewById(R.id.feedDisplay).setVisibility(View.VISIBLE); this.feed = feed; this.selectedDownloadUrl = feed.getDownload_url(); - listView.setSelector(android.R.color.transparent); - listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); ImageView cover = findViewById(R.id.imgvCover); ImageView headerBackground = findViewById(R.id.imgvBackground); @@ -382,6 +377,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { View header = View.inflate(this, R.layout.onlinefeedview_header, null); listView.addHeaderView(header); + listView.setSelector(android.R.color.transparent); + listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); + TextView description = header.findViewById(R.id.txtvDescription); subscribeButton = findViewById(R.id.butSubscribe); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index d7a4b9517..03e6b89db 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -4,35 +4,53 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; +import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; - -import org.apache.commons.lang3.ArrayUtils; - -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; - +import androidx.core.app.ActivityCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.core.export.opml.OpmlElement; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.LangUtils; +import org.apache.commons.lang3.ArrayUtils; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; /** - * Base activity for Opml Import - e.g. with code what to do afterwards + * Activity for Opml Import. * */ -public class OpmlImportBaseActivity extends AppCompatActivity { - +public class OpmlImportActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; - private OpmlImportWorker importWorker; @Nullable private Uri uri; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + + Uri uri = getIntent().getData(); + if (uri != null && uri.toString().startsWith("/")) { + uri = Uri.parse("file://" + uri.toString()); + } else { + String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); + if (extraText != null) { + uri = Uri.parse(extraText); + } + } + importUri(uri); + } + /** * Handles the choices made by the user in the OpmlFeedChooserActivity and * starts the OpmlFeedQueuer if necessary. @@ -42,9 +60,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Received result"); if (resultCode == RESULT_CANCELED) { Log.d(TAG, "Activity was cancelled"); - if (finishWhenCanceled()) { - finish(); - } + finish(); } else { int[] selected = data.getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS); if (selected != null && selected.length > 0) { @@ -53,7 +69,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { @Override protected void onPostExecute(Void result) { super.onPostExecute(result); - Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class); + Intent intent = new Intent(OpmlImportActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); @@ -68,7 +84,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } void importUri(@Nullable Uri uri) { - if(uri == null) { + if (uri == null) { new AlertDialog.Builder(this) .setMessage(R.string.opml_import_error_no_file) .setPositiveButton(android.R.string.ok, null) @@ -76,9 +92,10 @@ public class OpmlImportBaseActivity extends AppCompatActivity { return; } this.uri = uri; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { - int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { + int permission = ActivityCompat.checkSelfPermission(this, + android.Manifest.permission.READ_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { requestPermission(); return; @@ -93,9 +110,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { } @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { return; } @@ -113,8 +129,8 @@ public class OpmlImportBaseActivity extends AppCompatActivity { /** Starts the import process. */ private void startImport() { try { - Reader mReader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); - importWorker = new OpmlImportWorker(this, mReader) { + Reader reader = new InputStreamReader(getContentResolver().openInputStream(uri), LangUtils.UTF_8); + OpmlImportWorker importWorker = new OpmlImportWorker(this, reader) { @Override protected void onPostExecute(ArrayList<OpmlElement> result) { @@ -123,7 +139,7 @@ public class OpmlImportBaseActivity extends AppCompatActivity { Log.d(TAG, "Parsing was successful"); OpmlImportHolder.setReadElements(result); startActivityForResult(new Intent( - OpmlImportBaseActivity.this, + OpmlImportActivity.this, OpmlFeedChooserActivity.class), 0); } else { Log.d(TAG, "Parser error occurred"); @@ -140,10 +156,4 @@ public class OpmlImportBaseActivity extends AppCompatActivity { .show(); } } - - boolean finishWhenCanceled() { - return false; - } - - } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java deleted file mode 100644 index 557510808..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; - -import de.danoeh.antennapod.core.preferences.UserPreferences; - -/** - * Lets the user start the OPML-import process. - */ -public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromIntentAct"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - Uri uri = getIntent().getData(); - if (uri != null && uri.toString().startsWith("/")) { - uri = Uri.parse("file://" + uri.toString()); - } else { - String extraText = getIntent().getStringExtra(Intent.EXTRA_TEXT); - if(extraText != null) { - uri = Uri.parse(extraText); - } - } - importUri(uri); - } - - @Override - protected boolean finishWhenCanceled() { - return true; - } - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java deleted file mode 100644 index 158e3b7a4..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ /dev/null @@ -1,111 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.StorageUtils; - -/** - * Lets the user start the OPML-import process from a path - */ -public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { - - private static final String TAG = "OpmlImportFromPathAct"; - - private static final int CHOOSE_OPML_FILE = 1; - - private Intent intentGetContentAction; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.opml_import); - - final TextView txtvHeaderExplanation = findViewById(R.id.txtvHeadingExplanation); - final TextView txtvExplanation = findViewById(R.id.txtvExplanation); - final TextView txtvHeaderExplanationOpenWith = findViewById(R.id.txtvHeadingExplanationOpenWith); - - Button butChooseFilesystem = findViewById(R.id.butChooseFileFromFilesystem); - butChooseFilesystem.setOnClickListener(v -> chooseFileFromExternal()); - - int nextOption = 1; - String optionLabel = getString(R.string.opml_import_option); - intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); - intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentGetContentAction.setType("*/*"); - - if (IntentUtils.isCallable(getApplicationContext(), intentGetContentAction)) { - txtvHeaderExplanation.setText(String.format(optionLabel, nextOption)); - nextOption++; - } else { - txtvHeaderExplanation.setVisibility(View.GONE); - txtvExplanation.setVisibility(View.GONE); - findViewById(R.id.divider).setVisibility(View.GONE); - butChooseFilesystem.setVisibility(View.GONE); - } - - txtvHeaderExplanationOpenWith.setText(String.format(optionLabel, nextOption)); - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return false; - } - } - - private void chooseFileFromExternal() { - try { - startActivityForResult(intentGetContentAction, CHOOSE_OPML_FILE); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - /** - * Gets the path of the file chosen with chooseFileToImport() - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK && requestCode == CHOOSE_OPML_FILE) { - Uri uri = data.getData(); - if(uri != null && 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 a0f9bf6d8..dd91b7e2a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -16,6 +16,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment; +import de.danoeh.antennapod.fragment.preferences.ImportExportPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.IntegrationsPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment; @@ -59,6 +60,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new NetworkPreferencesFragment(); } else if (screen == R.xml.preferences_storage) { prefFragment = new StoragePreferencesFragment(); + } else if (screen == R.xml.preferences_import_export) { + prefFragment = new ImportExportPreferencesFragment(); } else if (screen == R.xml.preferences_autodownload) { prefFragment = new AutoDownloadPreferencesFragment(); } else if (screen == R.xml.preferences_gpodder) { @@ -79,6 +82,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.playback_pref; case R.xml.preferences_storage: return R.string.storage_pref; + case R.xml.preferences_import_export: + return R.string.import_export_pref; case R.xml.preferences_user_interface: return R.string.user_interface_label; case R.xml.preferences_integrations: 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 0c6ae2645..8fb7aa73f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -98,7 +98,7 @@ public class VideoplayerActivity extends MediaplayerActivity { } @Override - public void onUserLeaveHint () { + public void onUserLeaveHint() { if (!PictureInPictureUtil.isInPictureInPictureMode(this) && UserPreferences.getVideoBackgroundBehavior() == UserPreferences.VideoBackgroundBehavior.PICTURE_IN_PICTURE) { compatEnterPictureInPicture(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java b/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java deleted file mode 100644 index 315b3a173..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AdapterUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.util.Log; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.joanzapata.iconify.Iconify; - -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.NetworkUtils; - -/** - * Utility methods for adapters - */ -class AdapterUtils { - - private static final String TAG = AdapterUtils.class.getSimpleName(); - - private AdapterUtils() { - - } - - /** - * Updates the contents of the TextView that shows the current playback position and the ProgressBar. - */ - static void updateEpisodePlaybackProgress(FeedItem item, TextView txtvPos, ProgressBar episodeProgress) { - FeedMedia media = item.getMedia(); - episodeProgress.setVisibility(View.GONE); - if (media == null) { - txtvPos.setVisibility(View.GONE); - return; - } else { - txtvPos.setVisibility(View.VISIBLE); - } - - FeedItem.State state = item.getState(); - if (state == FeedItem.State.PLAYING - || state == FeedItem.State.IN_PROGRESS) { - if (media.getDuration() > 0) { - episodeProgress.setVisibility(View.VISIBLE); - episodeProgress.setProgress((int) (((double) media - .getPosition()) / media.getDuration() * 100)); - txtvPos.setText(Converter.getDurationStringLong(media.getDuration() - - media.getPosition())); - } - } else if (!media.isDownloaded()) { - if (media.getSize() > 0) { - txtvPos.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isEpisodeHeadDownloadAllowed() && !media.checkedOnSizeButUnknown()) { - txtvPos.setText("{fa-spinner}"); - Iconify.addIcons(txtvPos); - NetworkUtils.getFeedMediaSizeObservable(media) - .subscribe( - size -> { - if (size > 0) { - txtvPos.setText(Converter.byteToString(size)); - } else { - txtvPos.setText(""); - } - }, error -> { - txtvPos.setText(""); - Log.e(TAG, Log.getStackTraceString(error)); - }); - } else { - txtvPos.setText(""); - } - } else { - txtvPos.setText(Converter.getDurationStringLong(media.getDuration())); - } - } -} 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 f1946d9a8..71c872de2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -1,204 +1,73 @@ package de.danoeh.antennapod.adapter; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.ItemTouchHelper; -import android.text.Layout; -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.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.joanzapata.iconify.Iconify; - -import java.lang.ref.WeakReference; -import java.util.List; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -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.feed.util.ImageResourceUtils; -import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; -import de.danoeh.antennapod.fragment.ItemFragment; +import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.fragment.ItemPagerFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; +import org.apache.commons.lang3.ArrayUtils; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; /** - * List adapter for the list of new episodes + * List adapter for the list of new episodes. */ -public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesRecycleAdapter.Holder> { - - private static final String TAG = AllEpisodesRecycleAdapter.class.getSimpleName(); +public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder> + implements View.OnCreateContextMenuListener { private final WeakReference<MainActivity> mainActivityRef; - private final ItemAccess itemAccess; - private final boolean showOnlyNewEpisodes; + private List<FeedItem> episodes = new ArrayList<>(); private FeedItem selectedItem; - private final int playingBackGroundColor; - private final int normalBackGroundColor; - - public AllEpisodesRecycleAdapter(MainActivity mainActivity, - ItemAccess itemAccess, - boolean showOnlyNewEpisodes) { + public AllEpisodesRecycleAdapter(MainActivity mainActivity) { super(); this.mainActivityRef = new WeakReference<>(mainActivity); - this.itemAccess = itemAccess; - this.showOnlyNewEpisodes = showOnlyNewEpisodes; + } - playingBackGroundColor = ThemeUtils.getColorFromAttr(mainActivity, R.attr.currently_playing_background); - normalBackGroundColor = ContextCompat.getColor(mainActivity, android.R.color.transparent); + public void updateItems(List<FeedItem> items) { + episodes = items; + notifyDataSetChanged(); } + @NonNull @Override - public Holder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.new_episodes_listitem, parent, false); - Holder holder = new Holder(view); - holder.container = view.findViewById(R.id.container); - holder.content = view.findViewById(R.id.content); - holder.placeholder = view.findViewById(R.id.txtvPlaceholder); - holder.title = view.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - holder.pubDate = view - .findViewById(R.id.txtvPublished); - holder.statusUnread = view.findViewById(R.id.statusUnread); - holder.butSecondary = view - .findViewById(R.id.butSecondaryAction); - holder.queueStatus = view - .findViewById(R.id.imgvInPlaylist); - holder.progress = view - .findViewById(R.id.pbar_progress); - holder.cover = view.findViewById(R.id.imgvCover); - holder.txtvDuration = view.findViewById(R.id.txtvDuration); - holder.item = null; - holder.mainActivityRef = mainActivityRef; - // so we can grab this later - view.setTag(holder); - - return holder; + public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + EpisodeItemViewHolder viewHolder = new EpisodeItemViewHolder(mainActivityRef.get(), parent); + viewHolder.dragHandle.setVisibility(View.GONE); + return viewHolder; } @Override - public void onBindViewHolder(final Holder holder, int position) { - final FeedItem item = itemAccess.getItem(position); - if (item == null) return; + public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { + FeedItem item = episodes.get(pos); + holder.bind(item); holder.itemView.setOnLongClickListener(v -> { - this.selectedItem = item; + selectedItem = item; return false; }); - holder.item = item; - holder.placeholder.setVisibility(View.VISIBLE); - holder.placeholder.setText(item.getFeed().getTitle()); - holder.title.setText(item.getTitle()); - String pubDateStr = DateUtils.formatAbbrev(mainActivityRef.get(), item.getPubDate()); - holder.pubDate.setText(pubDateStr); - if (showOnlyNewEpisodes || !item.isNew()) { - holder.statusUnread.setVisibility(View.INVISIBLE); - } else { - holder.statusUnread.setVisibility(View.VISIBLE); - } - if(item.isPlayed()) { - holder.content.setAlpha(0.5f); - } else { - holder.content.setAlpha(1.0f); - } - - FeedMedia media = item.getMedia(); - if (media != null) { - final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); - - if (media.getDuration() > 0) { - holder.txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); - } else if (media.getSize() > 0) { - holder.txtvDuration.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isEpisodeHeadDownloadAllowed() && !media.checkedOnSizeButUnknown()) { - holder.txtvDuration.setText("{fa-spinner}"); - Iconify.addIcons(holder.txtvDuration); - NetworkUtils.getFeedMediaSizeObservable(media) - .subscribe( - size -> { - if (size > 0) { - holder.txtvDuration.setText(Converter.byteToString(size)); - } else { - holder.txtvDuration.setText(""); - } - }, error -> { - holder.txtvDuration.setText(""); - Log.e(TAG, Log.getStackTraceString(error)); - }); - } else { - holder.txtvDuration.setText(""); - } - - FeedItem.State state = item.getState(); - if (isDownloadingMedia) { - holder.progress.setVisibility(View.VISIBLE); - // item is being downloaded - holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); - } else if (state == FeedItem.State.PLAYING - || state == FeedItem.State.IN_PROGRESS) { - if (media.getDuration() > 0) { - int progress = (int) (100.0 * media.getPosition() / media.getDuration()); - holder.progress.setProgress(progress); - holder.progress.setVisibility(View.VISIBLE); - } - } else { - holder.progress.setVisibility(View.INVISIBLE); + holder.itemView.setOnClickListener(v -> { + MainActivity activity = mainActivityRef.get(); + if (activity != null) { + long[] ids = FeedItemUtil.getIds(episodes); + int position = ArrayUtils.indexOf(ids, item.getId()); + activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); } - - if (media.isCurrentlyPlaying()) { - holder.container.setBackgroundColor(playingBackGroundColor); - } else { - holder.container.setBackgroundColor(normalBackGroundColor); - } - } else { - holder.progress.setVisibility(View.INVISIBLE); - holder.txtvDuration.setVisibility(View.GONE); - } - - boolean isInQueue = itemAccess.isInQueue(item); - if (isInQueue) { - holder.queueStatus.setVisibility(View.VISIBLE); - } else { - holder.queueStatus.setVisibility(View.INVISIBLE); - } - - ItemActionButton actionButton = ItemActionButton.forItem(item, isInQueue); - actionButton.configure(holder.butSecondary, mainActivityRef.get()); - - holder.butSecondary.setFocusable(false); - holder.butSecondary.setTag(item); - - new CoverLoader(mainActivityRef.get()) - .withUri(ImageResourceUtils.getImageLocation(item)) - .withFallbackUri(item.getFeed().getImageLocation()) - .withPlaceholderView(holder.placeholder) - .withCoverView(holder.cover) - .load(); + }); + holder.itemView.setOnCreateContextMenuListener(this); + holder.hideSeparatorIfNecessary(); } @Nullable @@ -208,98 +77,21 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR @Override public long getItemId(int position) { - FeedItem item = itemAccess.getItem(position); + FeedItem item = episodes.get(position); return item != null ? item.getId() : RecyclerView.NO_POSITION; } @Override public int getItemCount() { - return itemAccess.getCount(); + return episodes.size(); } - public class Holder extends RecyclerView.ViewHolder - implements View.OnClickListener, - View.OnCreateContextMenuListener, - ItemTouchHelperViewHolder { - LinearLayout content; - FrameLayout container; - TextView placeholder; - TextView title; - TextView pubDate; - View statusUnread; - ImageView queueStatus; - ImageView cover; - ProgressBar progress; - TextView txtvDuration; - ImageButton butSecondary; - FeedItem item; - WeakReference<MainActivity> mainActivityRef; - - public Holder(View itemView) { - super(itemView); - itemView.setOnClickListener(this); - itemView.setOnCreateContextMenuListener(this); - } - - @Override - public void onClick(View v) { - MainActivity mainActivity = mainActivityRef.get(); - if (mainActivity != null) { - LongList itemIds = itemAccess.getItemsIds(); - long[] ids = itemIds.toArray(); - mainActivity.loadChildFragment(ItemPagerFragment.newInstance(ids, itemIds.indexOf(item.getId()))); - } - } - - @Override - public void onItemSelected() { - itemView.setAlpha(0.5f); - } - - @Override - public void onItemClear() { - itemView.setAlpha(1.0f); - } - - public FeedItem getFeedItem() { return item; } - - @Override - public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - FeedItem item = itemAccess.getItem(getAdapterPosition()); - - MenuInflater inflater = mainActivityRef.get().getMenuInflater(); - inflater.inflate(R.menu.feeditemlist_context, menu); - - if (item != null) { - menu.setHeaderTitle(item.getTitle()); - } - FeedItemMenuHandler.onPrepareMenu(menu, item); - } - - public boolean isCurrentlyPlayingItem() { - return item.getMedia() != null && item.getMedia().isCurrentlyPlaying(); - } - - public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { - progress.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); - } - - } - - public interface ItemAccess { - - int getCount(); - - FeedItem getItem(int position); - - LongList getItemsIds(); - - int getItemDownloadProgressPercent(FeedItem item); - - boolean isInQueue(FeedItem item); - - LongList getQueueIds(); - + @Override + public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + MenuInflater inflater = mainActivityRef.get().getMenuInflater(); + inflater.inflate(R.menu.feeditemlist_context, menu); + menu.setHeaderTitle(selectedItem.getTitle()); + FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item); } /** 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 f6e6da8b4..1171acaa5 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -1,37 +1,37 @@ package de.danoeh.antennapod.adapter; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.Spanned; -import android.text.style.ClickableSpan; -import android.text.util.Linkify; +import android.text.TextUtils; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.FitCenter; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; +import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.EmbeddedChapterImage; +import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.core.util.playback.Playable; public class ChaptersListAdapter extends ArrayAdapter<Chapter> { - private static final String TAG = "ChapterListAdapter"; private Playable media; - - private int defaultTextColor; private final Callback callback; + private int currentChapterIndex = -1; + private boolean hasImages = false; public ChaptersListAdapter(Context context, int textViewResourceId, Callback callback) { super(context, textViewResourceId); @@ -40,6 +40,15 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { public void setMedia(Playable media) { this.media = media; + hasImages = false; + if (media.getChapters() != null) { + for (Chapter chapter : media.getChapters()) { + if (!ignoreChapter(chapter) && !TextUtils.isEmpty(chapter.getImageUrl())) { + hasImages = true; + } + } + } + notifyDataSetChanged(); } @NonNull @@ -58,11 +67,12 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { convertView = inflater.inflate(R.layout.simplechapter_item, parent, false); holder.view = convertView; holder.title = convertView.findViewById(R.id.txtvTitle); - defaultTextColor = holder.title.getTextColors().getDefaultColor(); holder.start = convertView.findViewById(R.id.txtvStart); holder.link = convertView.findViewById(R.id.txtvLink); + holder.image = convertView.findViewById(R.id.imgvCover); holder.duration = convertView.findViewById(R.id.txtvDuration); - holder.butPlayChapter = convertView.findViewById(R.id.butPlayChapter); + holder.secondaryActionButton = convertView.findViewById(R.id.secondaryActionButton); + holder.secondaryActionIcon = convertView.findViewById(R.id.secondaryActionIcon); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); @@ -82,73 +92,43 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { holder.duration.setText(getContext().getString(R.string.chapter_duration, Converter.getDurationStringLong((int) duration))); - if (sc.getLink() != null) { + if (sc.getLink() == null) { + holder.link.setVisibility(View.GONE); + } else { holder.link.setVisibility(View.VISIBLE); holder.link.setText(sc.getLink()); - Linkify.addLinks(holder.link, Linkify.WEB_URLS); - } else { - holder.link.setVisibility(View.GONE); + holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), sc.getLink())); } - holder.link.setMovementMethod(null); - holder.link.setOnTouchListener((v, event) -> { - // from - // http://stackoverflow.com/questions/7236840/android-textview-linkify-intercepts-with-parent-view-gestures - TextView widget = (TextView) v; - Object text = widget.getText(); - if (text instanceof Spanned) { - Spannable buffer = (Spannable) text; - - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - ClickableSpan[] link = buffer.getSpans(off, off, - ClickableSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); - } else if (action == MotionEvent.ACTION_DOWN) { - Selection.setSelection(buffer, - buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0])); - } - return true; - } - } - - } - - return false; - - }); - holder.butPlayChapter.setOnClickListener(v -> { + holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(getContext(), R.attr.av_play)); + holder.secondaryActionButton.setOnClickListener(v -> { if (callback != null) { callback.onPlayChapterButtonClicked(position); } }); - Chapter current = ChapterUtils.getCurrentChapter(media); - if (current == sc) { + if (position == currentChapterIndex) { int playingBackGroundColor = ThemeUtils.getColorFromAttr(getContext(), R.attr.currently_playing_background); holder.view.setBackgroundColor(playingBackGroundColor); } else { holder.view.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent)); - holder.title.setTextColor(defaultTextColor); - holder.start.setTextColor(defaultTextColor); + } + + if (hasImages) { + holder.image.setVisibility(View.VISIBLE); + if (TextUtils.isEmpty(sc.getImageUrl())) { + Glide.with(getContext()).clear(holder.image); + } else { + Glide.with(getContext()) + .load(EmbeddedChapterImage.getModelFor(media, position)) + .apply(new RequestOptions() + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .dontAnimate() + .transforms(new FitCenter(), new RoundedCorners((int) + (4 * getContext().getResources().getDisplayMetrics().density)))) + .into(holder.image); + } + } else { + holder.image.setVisibility(View.GONE); } return convertView; @@ -160,7 +140,9 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { TextView start; TextView link; TextView duration; - ImageButton butPlayChapter; + ImageView image; + View secondaryActionButton; + ImageView secondaryActionIcon; } @Override @@ -178,6 +160,11 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> { return counter; } + public void notifyChapterChanged(int newChapterIndex) { + currentChapterIndex = newChapterIndex; + notifyDataSetChanged(); + } + private boolean ignoreChapter(Chapter c) { return media.getDuration() > 0 && media.getDuration() < c.getStart(); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java index 098e9a616..5acc25bee 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java @@ -24,7 +24,6 @@ public class CoverLoader { private TextView txtvPlaceholder; private ImageView imgvCover; private MainActivity activity; - private int errorResource = -1; public CoverLoader(MainActivity activity) { this.activity = activity; @@ -45,11 +44,6 @@ public class CoverLoader { return this; } - public CoverLoader withError(int errorResource) { - this.errorResource = errorResource; - return this; - } - public CoverLoader withPlaceholderView(TextView placeholderView) { txtvPlaceholder = placeholderView; return this; @@ -61,10 +55,6 @@ public class CoverLoader { .fitCenter() .dontAnimate(); - if (errorResource != -1) { - options = options.error(errorResource); - } - RequestBuilder<Drawable> builder = Glide.with(activity) .load(uri) .apply(options); @@ -84,6 +74,9 @@ public class CoverLoader { public CoverTarget(TextView txtvPlaceholder, ImageView imgvCover) { super(imgvCover); + if (txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.VISIBLE); + } placeholder = new WeakReference<>(txtvPlaceholder); cover = new WeakReference<>(imgvCover); } 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 4d66fd486..9206cebea 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -26,171 +26,139 @@ import de.danoeh.antennapod.core.storage.DBReader; 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.ThemeUtils; +import de.danoeh.antennapod.view.viewholder.DownloadItemViewHolder; +import de.danoeh.antennapod.view.viewholder.FeedViewHolder; -/** Displays a list of DownloadStatus entries. */ +/** + * Displays a list of DownloadStatus entries. + */ public class DownloadLogAdapter extends BaseAdapter { + private static final String TAG = "DownloadLogAdapter"; - private static final String TAG = "DownloadLogAdapter"; - - private final Context context; - + private final Context context; private final ItemAccess itemAccess; - public DownloadLogAdapter(Context context, ItemAccess itemAccess) { - super(); + public DownloadLogAdapter(Context context, ItemAccess itemAccess) { + super(); this.itemAccess = itemAccess; - this.context = context; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - DownloadStatus status = getItem(position); - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.downloadlog_item, parent, false); - holder.icon = convertView.findViewById(R.id.txtvIcon); - holder.retry = convertView.findViewById(R.id.btnRetry); - holder.date = convertView.findViewById(R.id.txtvDate); - holder.title = convertView.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - holder.type = convertView.findViewById(R.id.txtvType); - holder.reason = convertView.findViewById(R.id.txtvReason); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { - holder.type.setText(R.string.download_type_feed); - } else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - holder.type.setText(R.string.download_type_media); - } - if (status.getTitle() != null) { - holder.title.setText(status.getTitle()); - } else { - holder.title.setText(R.string.download_log_title_unknown); - } - holder.date.setText(DateUtils.getRelativeTimeSpanString( - status.getCompletionDate().getTime(), - System.currentTimeMillis(), 0, 0)); - if (status.isSuccessful()) { - holder.icon.setTextColor(ContextCompat.getColor(convertView.getContext(), - R.color.download_success_green)); - holder.icon.setText("{fa-check-circle}"); - 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}"); - String reasonText = status.getReason().getErrorString(context); - if (status.getReasonDetailed() != null) { - reasonText += ": " + status.getReasonDetailed(); - } - holder.reason.setText(reasonText); - holder.reason.setVisibility(View.VISIBLE); - if(!newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) { - holder.retry.setVisibility(View.VISIBLE); - holder.retry.setOnClickListener(clickListener); - ButtonHolder btnHolder; - if(holder.retry.getTag() != null) { - btnHolder = (ButtonHolder) holder.retry.getTag(); - } else { - btnHolder = new ButtonHolder(); - } - btnHolder.typeId = status.getFeedfileType(); - btnHolder.id = status.getFeedfileId(); - holder.retry.setTag(btnHolder); - } else { - holder.retry.setVisibility(View.GONE); - holder.retry.setOnClickListener(null); - holder.retry.setTag(null); - } - } - - return convertView; - } - - private final View.OnClickListener clickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - ButtonHolder holder = (ButtonHolder) v.getTag(); - if(holder.typeId == Feed.FEEDFILETYPE_FEED) { - Feed feed = DBReader.getFeed(holder.id); - if (feed != null) { - try { - DBTasks.forceRefreshFeed(context, feed); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } else { - Log.wtf(TAG, "Could not find feed for feed id: " + holder.id); - } - } else if(holder.typeId == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { - FeedMedia media = DBReader.getFeedMedia(holder.id); - if (media != null) { - try { - DownloadRequester.getInstance().downloadMedia(context, media.getItem()); - Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); - } - } else { - Log.wtf(TAG, "Could not find media for id: " + holder.id); - } - } else { - Log.wtf(TAG, "Unexpected type id: " + holder.typeId); - } - v.setVisibility(View.GONE); - } - }; - - private boolean newerWasSuccessful(int position, int feedTypeId, long id) { - for (int i = 0; i < position; i++) { - DownloadStatus status = getItem(i); - if (status.getFeedfileType() == feedTypeId && status.getFeedfileId() == id && - status.isSuccessful()) return true; - } - return false; - } - - static class Holder { - IconTextView icon; - IconButton retry; - TextView title; - TextView type; - TextView date; - TextView reason; - } - - static class ButtonHolder { - int typeId; - long id; - } - - @Override - public int getCount() { + this.context = context; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + DownloadItemViewHolder holder; + if (convertView == null) { + holder = new DownloadItemViewHolder(context, parent); + } else { + holder = (DownloadItemViewHolder) convertView.getTag(); + } + + DownloadStatus status = getItem(position); + if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { + holder.type.setText(R.string.download_type_feed); + } else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + holder.type.setText(R.string.download_type_media); + } + + if (status.getTitle() != null) { + holder.title.setText(status.getTitle()); + } else { + holder.title.setText(R.string.download_log_title_unknown); + } + holder.date.setText(DateUtils.getRelativeTimeSpanString(status.getCompletionDate().getTime(), + System.currentTimeMillis(), 0, 0)); + + if (status.isSuccessful()) { + holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_success_green)); + holder.icon.setText("{fa-check-circle}"); + holder.secondaryActionButton.setVisibility(View.INVISIBLE); + holder.reason.setVisibility(View.GONE); + } else { + holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_failed_red)); + holder.icon.setText("{fa-times-circle}"); + String reasonText = status.getReason().getErrorString(context); + if (status.getReasonDetailed() != null) { + reasonText += ": " + status.getReasonDetailed(); + } + holder.reason.setText(reasonText); + holder.reason.setVisibility(View.VISIBLE); + + if (newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) { + holder.secondaryActionButton.setVisibility(View.INVISIBLE); + holder.secondaryActionButton.setOnClickListener(null); + holder.secondaryActionButton.setTag(null); + } else { + holder.secondaryActionIcon.setImageResource( + ThemeUtils.getDrawableFromAttr(context, R.attr.navigation_refresh)); + holder.secondaryActionButton.setVisibility(View.VISIBLE); + + if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { + holder.secondaryActionButton.setOnClickListener(v -> { + holder.secondaryActionButton.setVisibility(View.INVISIBLE); + Feed feed = DBReader.getFeed(status.getFeedfileId()); + if (feed == null) { + Log.e(TAG, "Could not find feed for feed id: " + status.getFeedfileId()); + return; + } + try { + DBTasks.forceRefreshFeed(context, feed); + } catch (DownloadRequestException e) { + e.printStackTrace(); + } + }); + } else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + holder.secondaryActionButton.setOnClickListener(v -> { + holder.secondaryActionButton.setVisibility(View.INVISIBLE); + FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId()); + if (media == null) { + Log.e(TAG, "Could not find feed media for feed id: " + status.getFeedfileId()); + return; + } + try { + DownloadRequester.getInstance().downloadMedia(context, media.getItem()); + Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(context, e.getMessage()); + } + }); + } + } + } + + return holder.itemView; + } + + private boolean newerWasSuccessful(int position, int feedTypeId, long id) { + for (int i = 0; i < position; i++) { + DownloadStatus status = getItem(i); + if (status.getFeedfileType() == feedTypeId && status.getFeedfileId() == id && status.isSuccessful()) { + return true; + } + } + return false; + } + + @Override + public int getCount() { return itemAccess.getCount(); - } + } - @Override - public DownloadStatus getItem(int position) { + @Override + public DownloadStatus getItem(int position) { return itemAccess.getItem(position); - } + } - @Override - public long getItemId(int position) { - return position; - } + @Override + public long getItemId(int position) { + return position; + } public interface ItemAccess { - int getCount(); - DownloadStatus getItem(int position); + int getCount(); + + DownloadStatus getItem(int position); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java index d29db5622..c49d2f39d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadStatisticsListAdapter.java @@ -3,11 +3,14 @@ package de.danoeh.antennapod.adapter; import android.content.Context; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.view.PieChartView; + +import java.util.List; /** - * Adapter for the download statistics list + * Adapter for the download statistics list. */ public class DownloadStatisticsListAdapter extends StatisticsListAdapter { @@ -21,25 +24,23 @@ public class DownloadStatisticsListAdapter extends StatisticsListAdapter { } @Override - void onBindHeaderViewHolder(HeaderHolder holder) { - long totalDownloadSize = 0; + String getHeaderValue() { + return Converter.byteToString((long) pieChartData.getSum()); + } - for (DBReader.StatisticsItem item: statisticsData.feeds) { - totalDownloadSize = totalDownloadSize + item.totalDownloadSize; - } - holder.totalTime.setText(Converter.byteToString(totalDownloadSize)); - float[] dataValues = new float[statisticsData.feeds.size()]; - for (int i = 0; i < statisticsData.feeds.size(); i++) { - DBReader.StatisticsItem item = statisticsData.feeds.get(i); + @Override + PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData) { + float[] dataValues = new float[statisticsData.size()]; + for (int i = 0; i < statisticsData.size(); i++) { + StatisticsItem item = statisticsData.get(i); dataValues[i] = item.totalDownloadSize; } - holder.pieChart.setData(dataValues); + return new PieChartView.PieChartData(dataValues); } @Override - void onBindFeedViewHolder(StatisticsHolder holder, int position) { - DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1); - holder.value.setText(Converter.byteToString(statsItem.totalDownloadSize)); + void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem item) { + holder.value.setText(Converter.byteToString(item.totalDownloadSize)); } } 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 b083908a8..307dabf14 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -1,37 +1,25 @@ 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; import android.widget.BaseAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; - -import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; +import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; /** - * Shows a list of downloaded episodes + * Shows a list of downloaded episodes. */ public class DownloadedEpisodesListAdapter extends BaseAdapter { - private final Context context; + private final MainActivity activity; private final ItemAccess itemAccess; - public DownloadedEpisodesListAdapter(Context context, ItemAccess itemAccess) { + public DownloadedEpisodesListAdapter(MainActivity activity, ItemAccess itemAccess) { super(); - this.context = context; + this.activity = activity; this.itemAccess = itemAccess; } @@ -52,77 +40,21 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = getItem(position); - if (item == null) return null; - + EpisodeItemViewHolder holder; if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.downloaded_episodeslist_item, - parent, false); - holder.imageView = convertView.findViewById(R.id.imgvImage); - holder.title = convertView.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - holder.txtvSize = convertView.findViewById(R.id.txtvSize); - holder.queueStatus = convertView.findViewById(R.id.imgvInPlaylist); - holder.pubDate = convertView - .findViewById(R.id.txtvPublished); - holder.butSecondary = convertView - .findViewById(R.id.butSecondaryAction); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - Glide.with(context) - .load(ImageResourceUtils.getImageLocation(item)) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(holder.imageView); - - if(item.isPlayed()) { - convertView.setAlpha(0.5f); + holder = new EpisodeItemViewHolder(activity, parent); } else { - convertView.setAlpha(1.0f); + holder = (EpisodeItemViewHolder) convertView.getTag(); } - holder.title.setText(item.getTitle()); - holder.txtvSize.setText(Converter.byteToString(item.getMedia().getSize())); - holder.queueStatus.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE); - String pubDateStr = DateUtils.formatAbbrev(context, item.getPubDate()); - holder.pubDate.setText(pubDateStr); - - holder.butSecondary.setFocusable(false); - holder.butSecondary.setTag(item); - holder.butSecondary.setOnClickListener(secondaryActionListener); - - return convertView; - } - - private final View.OnClickListener secondaryActionListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - FeedItem item = (FeedItem) v.getTag(); - itemAccess.onFeedItemSecondaryAction(item); - } - }; - + final FeedItem item = getItem(position); + holder.bind(item); + holder.dragHandle.setVisibility(View.GONE); + holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(activity, R.attr.content_discard)); + holder.secondaryActionButton.setOnClickListener(v -> itemAccess.onFeedItemSecondaryAction(item)); + holder.hideSeparatorIfNecessary(); - static class Holder { - ImageView imageView; - TextView title; - TextView txtvSize; - ImageView queueStatus; - TextView pubDate; - ImageButton butSecondary; + return holder.itemView; } public interface ItemAccess { 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 b0ee87b7e..fd9b76b9c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -5,23 +5,25 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.ImageButton; -import android.widget.ProgressBar; +import android.widget.ImageView; import android.widget.TextView; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.view.CircularProgressBar; public class DownloadlistAdapter extends BaseAdapter { private final ItemAccess itemAccess; private final Context context; - public DownloadlistAdapter(Context context, - ItemAccess itemAccess) { + public DownloadlistAdapter(Context context, ItemAccess itemAccess) { super(); this.context = context; this.itemAccess = itemAccess; @@ -47,47 +49,44 @@ public class DownloadlistAdapter extends BaseAdapter { Holder holder; Downloader downloader = getItem(position); DownloadRequest request = downloader.getDownloadRequest(); - // Inflate layout if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.downloadlist_item, parent, false); holder.title = convertView.findViewById(R.id.txtvTitle); - holder.downloaded = convertView - .findViewById(R.id.txtvDownloaded); - holder.percent = convertView - .findViewById(R.id.txtvPercent); - holder.progbar = convertView - .findViewById(R.id.progProgress); - holder.butSecondary = convertView - .findViewById(R.id.butSecondaryAction); - + holder.status = convertView.findViewById(R.id.txtvStatus); + holder.secondaryActionButton = convertView.findViewById(R.id.secondaryActionButton); + holder.secondaryActionIcon = convertView.findViewById(R.id.secondaryActionIcon); + holder.secondaryActionProgress = convertView.findViewById(R.id.secondaryActionProgress); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); } holder.title.setText(request.getTitle()); - - holder.progbar.setIndeterminate(request.getSoFar() <= 0); - - String strDownloaded = Converter.byteToString(request.getSoFar()); - if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) { - strDownloaded += " / " + Converter.byteToString(request.getSize()); - holder.percent.setText(request.getProgressPercent() + "%"); - holder.progbar.setProgress(request.getProgressPercent()); - holder.percent.setVisibility(View.VISIBLE); + holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.navigation_cancel)); + holder.secondaryActionButton.setTag(downloader); + holder.secondaryActionButton.setOnClickListener(butSecondaryListener); + holder.secondaryActionProgress.setPercentage(0, request); + + String status = ""; + if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { + status += context.getString(R.string.download_type_feed); + } else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + status += context.getString(R.string.download_type_media); + } + status += " · "; + if (request.getSoFar() <= 0) { + status += context.getString(R.string.download_queued); } else { - holder.progbar.setProgress(0); - holder.percent.setVisibility(View.INVISIBLE); + status += Converter.byteToString(request.getSoFar()); + if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) { + status += " / " + Converter.byteToString(request.getSize()); + holder.secondaryActionProgress.setPercentage( + 0.01f * Math.max(1, request.getProgressPercent()), request); + } } - - holder.downloaded.setText(strDownloaded); - - holder.butSecondary.setFocusable(false); - holder.butSecondary.setTag(downloader); - holder.butSecondary.setOnClickListener(butSecondaryListener); + holder.status.setText(status); return convertView; } @@ -102,10 +101,10 @@ public class DownloadlistAdapter extends BaseAdapter { static class Holder { TextView title; - TextView downloaded; - TextView percent; - ProgressBar progbar; - ImageButton butSecondary; + TextView status; + View secondaryActionButton; + ImageView secondaryActionIcon; + CircularProgressBar secondaryActionProgress; } public interface ItemAccess { 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 aec0f0c91..a5cfcb3e7 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -1,33 +1,17 @@ package de.danoeh.antennapod.adapter; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import androidx.core.content.ContextCompat; -import android.text.Layout; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Adapter; import android.widget.BaseAdapter; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; +import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -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.LongList; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; +import de.danoeh.antennapod.view.viewholder.FeedComponentViewHolder; +import de.danoeh.antennapod.view.viewholder.FeedViewHolder; /** * List adapter for items of feeds that the user has already subscribed to. @@ -35,27 +19,19 @@ import de.danoeh.antennapod.core.util.ThemeUtils; public class FeedItemlistAdapter extends BaseAdapter { private final ItemAccess itemAccess; - private final Context context; - private final boolean showFeedtitle; - /** true if played items should be made partially transparent */ + private final MainActivity activity; private final boolean makePlayedItemsTransparent; - private final int playingBackGroundColor; - private final int normalBackGroundColor; + private final boolean showIcons; private int currentlyPlayingItem = -1; - public FeedItemlistAdapter(Context context, - ItemAccess itemAccess, - boolean showFeedtitle, - boolean makePlayedItemsTransparent) { + public FeedItemlistAdapter(MainActivity activity, ItemAccess itemAccess, + boolean showIcons, boolean makePlayedItemsTransparent) { super(); - this.context = context; + this.activity = activity; this.itemAccess = itemAccess; - this.showFeedtitle = showFeedtitle; + this.showIcons = showIcons; this.makePlayedItemsTransparent = makePlayedItemsTransparent; - - playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background); - normalBackGroundColor = ContextCompat.getColor(context, android.R.color.transparent); } @Override @@ -70,135 +46,56 @@ public class FeedItemlistAdapter extends BaseAdapter { } @Override - public FeedItem getItem(int position) { + public FeedComponent getItem(int position) { return itemAccess.getItem(position); } @Override - @SuppressWarnings("ResourceType") public View getView(final int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = getItem(position); - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.feeditemlist_item, parent, false); - holder.container = convertView - .findViewById(R.id.container); - holder.title = convertView.findViewById(R.id.txtvItemname); - if(Build.VERSION.SDK_INT >= 23) { - holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - holder.lenSize = convertView - .findViewById(R.id.txtvLenSize); - holder.butAction = convertView - .findViewById(R.id.butSecondaryAction); - holder.published = convertView - .findViewById(R.id.txtvPublished); - holder.inPlaylist = convertView - .findViewById(R.id.imgvInPlaylist); - holder.type = convertView.findViewById(R.id.imgvType); - holder.statusUnread = convertView - .findViewById(R.id.statusUnread); - holder.episodeProgress = convertView - .findViewById(R.id.pbar_episode_progress); - - convertView.setTag(holder); + final FeedComponent item = getItem(position); + if (item instanceof Feed) { + return getView((Feed) item, convertView, parent); } else { - holder = (Holder) convertView.getTag(); - } - - if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) { - convertView.setVisibility(View.VISIBLE); - - StringBuilder buffer = new StringBuilder(item.getTitle()); - if (showFeedtitle) { - buffer.append(" ("); - buffer.append(item.getFeed().getTitle()); - buffer.append(")"); + final FeedItem feeditem = (FeedItem) item; + if (feeditem.getMedia() != null && feeditem.getMedia().isCurrentlyPlaying()) { + currentlyPlayingItem = position; } - holder.title.setText(buffer.toString()); + return getView(feeditem, convertView, parent); + } + } - if(item.isNew()) { - holder.statusUnread.setVisibility(View.VISIBLE); - } else { - holder.statusUnread.setVisibility(View.INVISIBLE); - } - if(item.isPlayed() && makePlayedItemsTransparent) { - convertView.setAlpha(0.5f); - } else { - convertView.setAlpha(1.0f); - } + private View getView(Feed item, View convertView, ViewGroup parent) { + FeedViewHolder holder; + if (convertView == null || !(convertView.getTag() instanceof FeedViewHolder)) { + holder = new FeedViewHolder(activity, parent); + } else { + holder = (FeedViewHolder) convertView.getTag(); + } + holder.bind(item); + return holder.itemView; + } - 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.INVISIBLE); - holder.inPlaylist.setVisibility(View.INVISIBLE); - holder.type.setVisibility(View.INVISIBLE); - holder.lenSize.setVisibility(View.INVISIBLE); - } else { - - AdapterUtils.updateEpisodePlaybackProgress(item, holder.lenSize, holder.episodeProgress); - - if (isInQueue) { - holder.inPlaylist.setVisibility(View.VISIBLE); - } else { - holder.inPlaylist.setVisibility(View.INVISIBLE); - } - - if (DownloadRequester.getInstance().isDownloadingFile(item.getMedia())) { - holder.episodeProgress.setVisibility(View.VISIBLE); - holder.episodeProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); - } else { - if(media.getPosition() == 0) { - holder.episodeProgress.setVisibility(View.INVISIBLE); - } - } - - TypedArray typeDrawables = context.obtainStyledAttributes( - new int[]{R.attr.type_audio, R.attr.type_video}); - final int[] labels = new int[]{R.string.media_type_audio_label, R.string.media_type_video_label}; - - MediaType mediaType = item.getMedia().getMediaType(); - if (mediaType == MediaType.AUDIO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(0)); - holder.type.setContentDescription(context.getString(labels[0])); - holder.type.setVisibility(View.VISIBLE); - } else if (mediaType == MediaType.VIDEO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(1)); - holder.type.setContentDescription(context.getString(labels[1])); - holder.type.setVisibility(View.VISIBLE); - } else { - holder.type.setImageBitmap(null); - holder.type.setVisibility(View.GONE); - } - typeDrawables.recycle(); - - if (media.isCurrentlyPlaying()) { - holder.container.setBackgroundColor(playingBackGroundColor); - currentlyPlayingItem = position; - } else { - holder.container.setBackgroundColor(normalBackGroundColor); - } - } + private View getView(final FeedItem item, View convertView, ViewGroup parent) { + EpisodeItemViewHolder holder; + if (convertView == null || !(convertView.getTag() instanceof EpisodeItemViewHolder)) { + holder = new EpisodeItemViewHolder(activity, parent); + } else { + holder = (EpisodeItemViewHolder) convertView.getTag(); + } - ItemActionButton actionButton = ItemActionButton.forItem(item, isInQueue); - actionButton.configure(holder.butAction, context); + if (!showIcons) { + holder.coverHolder.setVisibility(View.GONE); + } - holder.butAction.setFocusable(false); - holder.butAction.setTag(item); + holder.bind(item); + holder.dragHandle.setVisibility(View.GONE); - } else { - convertView.setVisibility(View.GONE); + if (!makePlayedItemsTransparent) { + holder.itemView.setAlpha(1.0f); } - return convertView; + + holder.hideSeparatorIfNecessary(); + return holder.itemView; } public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event, ListView listView) { @@ -208,35 +105,15 @@ public class FeedItemlistAdapter extends BaseAdapter { if (view == null) { return; } - Holder holder = (Holder) view.getTag(); - holder.episodeProgress.setVisibility(View.VISIBLE); - holder.episodeProgress.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); - holder.lenSize.setText(Converter.getDurationStringLong(event.getDuration() - event.getPosition())); + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) view.getTag(); + holder.notifyPlaybackPositionUpdated(event); } } - static class Holder { - LinearLayout container; - TextView title; - TextView published; - TextView lenSize; - ImageView type; - ImageView inPlaylist; - ImageButton butAction; - View statusUnread; - ProgressBar episodeProgress; - } - public interface ItemAccess { - - int getItemDownloadProgressPercent(FeedItem item); - int getCount(); - FeedItem getItem(int position); - - LongList getQueueIds(); - + FeedComponent getItem(int position); } } 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 50b11a15b..befe142cc 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -7,7 +7,6 @@ import android.content.res.TypedArray; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; -import androidx.appcompat.app.AlertDialog; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; @@ -16,33 +15,28 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; - +import androidx.appcompat.app.AlertDialog; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconTextView; - -import org.apache.commons.lang3.ArrayUtils; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.AddFeedFragment; -import de.danoeh.antennapod.fragment.AllEpisodesFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.fragment.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; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; /** * BaseAdapter for the navigation drawer @@ -112,7 +106,7 @@ public class NavListAdapter extends BaseAdapter private Drawable getDrawable(String tag) { Activity context = activity.get(); - if(context == null) { + if (context == null) { return null; } int icon; @@ -120,15 +114,9 @@ public class NavListAdapter extends BaseAdapter case QueueFragment.TAG: icon = R.attr.stat_playlist; break; - case NewEpisodesFragment.TAG: - icon = R.attr.ic_new; - break; case EpisodesFragment.TAG: icon = R.attr.feed; break; - case AllEpisodesFragment.TAG: - icon = R.attr.feed; - break; case DownloadsFragment.TAG: icon = R.attr.av_download; break; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java index ce6ad2f83..c5a73c53e 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java @@ -4,11 +4,14 @@ import android.content.Context; import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.view.PieChartView; + +import java.util.List; /** - * Adapter for the playback statistics list + * Adapter for the playback statistics list. */ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { @@ -28,20 +31,22 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { } @Override - void onBindHeaderViewHolder(HeaderHolder holder) { - long time = countAll ? statisticsData.totalTimeCountAll : statisticsData.totalTime; - holder.totalTime.setText(Converter.shortLocalizedDuration(context, time)); - float[] dataValues = new float[statisticsData.feeds.size()]; - for (int i = 0; i < statisticsData.feeds.size(); i++) { - DBReader.StatisticsItem item = statisticsData.feeds.get(i); + String getHeaderValue() { + return Converter.shortLocalizedDuration(context, (long) pieChartData.getSum()); + } + + @Override + PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData) { + float[] dataValues = new float[statisticsData.size()]; + for (int i = 0; i < statisticsData.size(); i++) { + StatisticsItem item = statisticsData.get(i); dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed; } - holder.pieChart.setData(dataValues); + return new PieChartView.PieChartData(dataValues); } @Override - void onBindFeedViewHolder(StatisticsHolder holder, int position) { - DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1); + void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem statsItem) { long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed; holder.value.setText(Converter.shortLocalizedDuration(context, time)); 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 2a39321ef..456f45fd2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -1,56 +1,34 @@ package de.danoeh.antennapod.adapter; -import android.os.Build; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.core.view.MotionEventCompat; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.ItemTouchHelper; -import android.text.Layout; -import android.text.TextUtils; +import android.annotation.SuppressLint; import android.util.Log; import android.view.ContextMenu; -import android.view.LayoutInflater; import android.view.MenuInflater; -import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.joanzapata.iconify.Iconify; - -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.fragment.ItemPagerFragment; -import org.apache.commons.lang3.ArrayUtils; - -import java.lang.ref.WeakReference; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.MotionEventCompat; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.fragment.ItemPagerFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; +import org.apache.commons.lang3.ArrayUtils; + +import java.lang.ref.WeakReference; /** * List adapter for the queue. */ -public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdapter.ViewHolder> { - - private static final String TAG = QueueRecyclerAdapter.class.getSimpleName(); +public class QueueRecyclerAdapter extends RecyclerView.Adapter<EpisodeItemViewHolder> implements View.OnCreateContextMenuListener { + private static final String TAG = "QueueRecyclerAdapter"; private final WeakReference<MainActivity> mainActivity; private final ItemAccess itemAccess; @@ -60,9 +38,6 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap private FeedItem selectedItem; - private final int playingBackGroundColor; - private final int normalBackGroundColor; - public QueueRecyclerAdapter(MainActivity mainActivity, ItemAccess itemAccess, ItemTouchHelper itemTouchHelper) { @@ -71,9 +46,6 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap this.itemAccess = itemAccess; this.itemTouchHelper = itemTouchHelper; locked = UserPreferences.isQueueLocked(); - - playingBackGroundColor = ThemeUtils.getColorFromAttr(mainActivity, R.attr.currently_playing_background); - normalBackGroundColor = ContextCompat.getColor(mainActivity, android.R.color.transparent); } public void setLocked(boolean locked) { @@ -81,20 +53,49 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap notifyDataSetChanged(); } + @NonNull @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.queue_listitem, parent, false); - return new ViewHolder(view); + public EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new EpisodeItemViewHolder(mainActivity.get(), parent); } @Override - public void onBindViewHolder(ViewHolder holder, int pos) { + @SuppressLint("ClickableViewAccessibility") + public void onBindViewHolder(EpisodeItemViewHolder holder, int pos) { FeedItem item = itemAccess.getItem(pos); holder.bind(item); + holder.dragHandle.setVisibility(locked ? View.GONE : View.VISIBLE); holder.itemView.setOnLongClickListener(v -> { selectedItem = item; return false; }); + holder.itemView.setOnClickListener(v -> { + MainActivity activity = mainActivity.get(); + if (activity != null) { + long[] ids = itemAccess.getQueueIds().toArray(); + int position = ArrayUtils.indexOf(ids, item.getId()); + activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); + } + }); + + View.OnTouchListener startDragTouchListener = (v1, event) -> { + if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + Log.d(TAG, "startDrag()"); + itemTouchHelper.startDrag(holder); + } + return false; + }; + if (!locked) { + holder.dragHandle.setOnTouchListener(startDragTouchListener); + holder.coverHolder.setOnTouchListener(startDragTouchListener); + } else { + holder.dragHandle.setOnTouchListener(null); + holder.coverHolder.setOnTouchListener(null); + } + + holder.itemView.setOnCreateContextMenuListener(this); + holder.isInQueue.setVisibility(View.GONE); + holder.hideSeparatorIfNecessary(); } @Nullable @@ -112,211 +113,30 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap return itemAccess.getCount(); } - public class ViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener, - View.OnCreateContextMenuListener, - ItemTouchHelperViewHolder { - - private final FrameLayout container; - private final ImageView dragHandle; - private final TextView placeholder; - private final ImageView cover; - private final TextView title; - private final TextView pubDate; - private final TextView progressLeft; - private final TextView progressRight; - private final ProgressBar progressBar; - private final ImageButton butSecondary; - - private FeedItem item; - - public ViewHolder(View v) { - super(v); - container = v.findViewById(R.id.container); - dragHandle = v.findViewById(R.id.drag_handle); - placeholder = v.findViewById(R.id.txtvPlaceholder); - cover = v.findViewById(R.id.imgvCover); - title = v.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - pubDate = v.findViewById(R.id.txtvPubDate); - progressLeft = v.findViewById(R.id.txtvProgressLeft); - progressRight = v.findViewById(R.id.txtvProgressRight); - butSecondary = v.findViewById(R.id.butSecondaryAction); - progressBar = v.findViewById(R.id.progressBar); - v.setTag(this); - v.setOnClickListener(this); - v.setOnCreateContextMenuListener(this); - dragHandle.setOnTouchListener((v1, event) -> { - if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { - Log.d(TAG, "startDrag()"); - itemTouchHelper.startDrag(ViewHolder.this); - } - return false; - }); - } - - @Override - public void onClick(View v) { - MainActivity activity = mainActivity.get(); - if (activity != null) { - long[] ids = itemAccess.getQueueIds().toArray(); - int position = ArrayUtils.indexOf(ids, item.getId()); - activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position)); - } - } - - @Override - public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - FeedItem item = itemAccess.getItem(getAdapterPosition()); - - MenuInflater inflater = mainActivity.get().getMenuInflater(); - inflater.inflate(R.menu.queue_context, menu); // queue-specific menu items - inflater.inflate(R.menu.feeditemlist_context, menu); // generic menu items for item feeds - - if (item != null) { - menu.setHeaderTitle(item.getTitle()); - } - - FeedItemMenuHandler.onPrepareMenu(menu, item, - R.id.skip_episode_item); // Skip Episode is not useful in Queue, so hide it. - // Queue-specific menu preparation - final boolean keepSorted = UserPreferences.isQueueKeepSorted(); - final LongList queueAccess = itemAccess.getQueueIds(); - if (queueAccess.size() == 0 || queueAccess.get(0) == item.getId() || keepSorted) { - menu.findItem(R.id.move_to_top_item).setVisible(false); - } - if (queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == item.getId() || keepSorted) { - menu.findItem(R.id.move_to_bottom_item).setVisible(false); - } - } - - @Override - public void onItemSelected() { - itemView.setAlpha(0.5f); - } - - @Override - public void onItemClear() { - itemView.setAlpha(1.0f); - } - - public void bind(FeedItem item) { - this.item = item; - if(locked) { - dragHandle.setVisibility(View.GONE); - } else { - dragHandle.setVisibility(View.VISIBLE); - } - - placeholder.setText(item.getFeed().getTitle()); - - title.setText(item.getTitle()); - FeedMedia media = item.getMedia(); - - title.setText(item.getTitle()); - String pubDateStr = DateUtils.formatAbbrev(mainActivity.get(), item.getPubDate()); - int index = 0; - if(countMatches(pubDateStr, ' ') == 1 || countMatches(pubDateStr, ' ') == 2) { - index = pubDateStr.lastIndexOf(' '); - } else if(countMatches(pubDateStr, '.') == 2) { - index = pubDateStr.lastIndexOf('.'); - } else if(countMatches(pubDateStr, '-') == 2) { - index = pubDateStr.lastIndexOf('-'); - } else if(countMatches(pubDateStr, '/') == 2) { - index = pubDateStr.lastIndexOf('/'); - } - if(index > 0) { - pubDateStr = pubDateStr.substring(0, index+1).trim() + "\n" + pubDateStr.substring(index+1); - } - pubDate.setText(pubDateStr); - - if (media != null) { - final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); - FeedItem.State state = item.getState(); - if (isDownloadingMedia) { - progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item))); - if(itemAccess.getItemDownloadSize(item) > 0) { - progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item))); - } else { - progressRight.setText(Converter.byteToString(media.getSize())); - } - progressBar.setProgress(itemAccess.getItemDownloadProgressPercent(item)); - progressBar.setVisibility(View.VISIBLE); - } else if (state == FeedItem.State.PLAYING - || state == FeedItem.State.IN_PROGRESS) { - if (media.getDuration() > 0) { - int progress = (int) (100.0 * media.getPosition() / media.getDuration()); - progressBar.setProgress(progress); - progressBar.setVisibility(View.VISIBLE); - progressLeft.setText(Converter - .getDurationStringLong(media.getPosition())); - progressRight.setText(Converter.getDurationStringLong(media.getDuration())); - } - } else { - if(media.getSize() > 0) { - progressLeft.setText(Converter.byteToString(media.getSize())); - } else if(NetworkUtils.isEpisodeHeadDownloadAllowed() && !media.checkedOnSizeButUnknown()) { - progressLeft.setText("{fa-spinner}"); - Iconify.addIcons(progressLeft); - NetworkUtils.getFeedMediaSizeObservable(media) - .subscribe( - size -> { - if (size > 0) { - progressLeft.setText(Converter.byteToString(size)); - } else { - progressLeft.setText(""); - } - }, error -> { - progressLeft.setText(""); - Log.e(TAG, Log.getStackTraceString(error)); - }); - } else { - progressLeft.setText(""); - } - progressRight.setText(Converter.getDurationStringLong(media.getDuration())); - progressBar.setVisibility(View.INVISIBLE); - } - - if(media.isCurrentlyPlaying()) { - container.setBackgroundColor(playingBackGroundColor); - } else { - container.setBackgroundColor(normalBackGroundColor); - } - } - - ItemActionButton actionButton = ItemActionButton.forItem(item, true); - actionButton.configure(butSecondary, mainActivity.get()); - - butSecondary.setFocusable(false); - butSecondary.setTag(item); - - new CoverLoader(mainActivity.get()) - .withUri(ImageResourceUtils.getImageLocation(item)) - .withFallbackUri(item.getFeed().getImageLocation()) - .withPlaceholderView(placeholder) - .withCoverView(cover) - .load(); - } - - public boolean isCurrentlyPlayingItem() { - return item.getMedia() != null && item.getMedia().isCurrentlyPlaying(); + @Override + public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + MenuInflater inflater = mainActivity.get().getMenuInflater(); + inflater.inflate(R.menu.queue_context, menu); // queue-specific menu items + inflater.inflate(R.menu.feeditemlist_context, menu); // generic menu items for item feeds + + menu.setHeaderTitle(selectedItem.getTitle()); + FeedItemMenuHandler.onPrepareMenu(menu, selectedItem, R.id.skip_episode_item); + // Queue-specific menu preparation + final boolean keepSorted = UserPreferences.isQueueKeepSorted(); + final LongList queueAccess = itemAccess.getQueueIds(); + if (queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId() || keepSorted) { + menu.findItem(R.id.move_to_top_item).setVisible(false); } - - public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { - progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); - progressLeft.setText(Converter.getDurationStringLong(event.getPosition())); - progressRight.setText(Converter.getDurationStringLong(event.getDuration())); + if (queueAccess.size() == 0 || queueAccess.get(queueAccess.size() - 1) == selectedItem.getId() || keepSorted) { + menu.findItem(R.id.move_to_bottom_item).setVisible(false); } } public interface ItemAccess { FeedItem getItem(int position); + int getCount(); - long getItemDownloadedBytes(FeedItem item); - long getItemDownloadSize(FeedItem item); - int getItemDownloadProgressPercent(FeedItem item); + LongList getQueueIds(); } @@ -341,18 +161,4 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap */ void onItemClear(); } - - // Oh Xiaomi, I hate you so much. How did you manage to fuck this up? - private static int countMatches(final CharSequence str, final char ch) { - if (TextUtils.isEmpty(str)) { - return 0; - } - int count = 0; - for (int i = 0; i < str.length(); i++) { - if (ch == str.charAt(i)) { - count++; - } - } - return count; - } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java deleted file mode 100644 index ad23478d6..000000000 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ /dev/null @@ -1,127 +0,0 @@ -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; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; - -import com.bumptech.glide.request.RequestOptions; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.feed.FeedComponent; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.SearchResult; -import de.danoeh.antennapod.core.glide.ApGlideSettings; - -/** - * List adapter for search activity. - */ -public class SearchlistAdapter extends BaseAdapter { - - private final Context context; - private final ItemAccess itemAccess; - - - public SearchlistAdapter(Context context, ItemAccess itemAccess) { - this.context = context; - this.itemAccess = itemAccess; - } - - @Override - public int getCount() { - return itemAccess.getCount(); - } - - @Override - public SearchResult getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Holder holder; - SearchResult result = getItem(position); - FeedComponent component = result.getComponent(); - - // Inflate Layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.searchlist_item, parent, false); - holder.title = convertView.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - holder.title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - holder.cover = convertView - .findViewById(R.id.imgvFeedimage); - holder.subtitle = convertView - .findViewById(R.id.txtvSubtitle); - - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - if (component.getClass() == Feed.class) { - final Feed feed = (Feed) component; - holder.title.setText(feed.getTitle()); - holder.subtitle.setVisibility(View.GONE); - - Glide.with(context) - .load(feed.getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(holder.cover); - - } else if (component.getClass() == FeedItem.class) { - final FeedItem item = (FeedItem) component; - holder.title.setText(item.getTitle()); - holder.subtitle.setText(result.getLocation().getDescription()); - - convertView.setAlpha(item.isPlayed() ? 0.5f : 1.0f); - - Glide.with(context) - .load(item.getFeed().getImageLocation()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate()) - .into(holder.cover); - - } - - return convertView; - } - - static class Holder { - ImageView cover; - TextView title; - TextView subtitle; - } - - 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 index 76ae3c3d9..5f019d1db 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -14,17 +14,20 @@ import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.view.PieChartView; +import java.util.List; + /** - * Parent Adapter for the playback and download statistics list + * Parent Adapter for the playback and download statistics list. */ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int TYPE_HEADER = 0; private static final int TYPE_FEED = 1; final Context context; - DBReader.StatisticsData statisticsData; + private List<StatisticsItem> statisticsData; + PieChartView.PieChartData pieChartData; StatisticsListAdapter(Context context) { this.context = context; @@ -32,14 +35,14 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle @Override public int getItemCount() { - return statisticsData.feeds.size() + 1; + return statisticsData.size() + 1; } - public DBReader.StatisticsItem getItem(int position) { + public StatisticsItem getItem(int position) { if (position == 0) { return null; } - return statisticsData.feeds.get(position - 1); + return statisticsData.get(position - 1); } @Override @@ -63,10 +66,12 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder h, int position) { if (getItemViewType(position) == TYPE_HEADER) { - onBindHeaderViewHolder((HeaderHolder) h); + HeaderHolder holder = (HeaderHolder) h; + holder.pieChart.setData(pieChartData); + holder.totalTime.setText(getHeaderValue()); } else { StatisticsHolder holder = (StatisticsHolder) h; - DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1); + StatisticsItem statsItem = statisticsData.get(position - 1); Glide.with(context) .load(statsItem.feed.getImageLocation()) .apply(new RequestOptions() @@ -78,12 +83,14 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle .into(holder.image); holder.title.setText(statsItem.feed.getTitle()); - onBindFeedViewHolder(holder, position); + holder.chip.setTextColor(pieChartData.getColorOfItem(position - 1)); + onBindFeedViewHolder(holder, statsItem); } } - public void update(DBReader.StatisticsData statistics) { - this.statisticsData = statistics; + public void update(List<StatisticsItem> statistics) { + statisticsData = statistics; + pieChartData = generateChartData(statistics); notifyDataSetChanged(); } @@ -102,18 +109,22 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle ImageView image; TextView title; TextView value; + TextView chip; StatisticsHolder(View itemView) { super(itemView); image = itemView.findViewById(R.id.imgvCover); title = itemView.findViewById(R.id.txtvTitle); value = itemView.findViewById(R.id.txtvValue); + chip = itemView.findViewById(R.id.chip); } } abstract int getHeaderCaptionResourceId(); - abstract void onBindHeaderViewHolder(HeaderHolder holder); + abstract String getHeaderValue(); + + abstract PieChartView.PieChartData generateChartData(List<StatisticsItem> statisticsData); - abstract void onBindFeedViewHolder(StatisticsHolder holder, int position); + abstract void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem item); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java index 3141c6046..a012211a5 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -98,7 +98,6 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI .withUri(feed.getImageLocation()) .withPlaceholderView(holder.feedTitle) .withCoverView(holder.imageView) - .withError(R.color.light_gray) .load(); return convertView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java index 861c6a4be..47eab7d05 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.adapter.actionbutton; import android.content.Context; import android.content.res.TypedArray; +import android.widget.ImageView; import androidx.annotation.AttrRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; @@ -33,7 +34,7 @@ public abstract class ItemActionButton { } @NonNull - public static ItemActionButton forItem(@NonNull FeedItem item, boolean isInQueue) { + public static ItemActionButton forItem(@NonNull FeedItem item, boolean isInQueue, boolean allowStream) { final FeedMedia media = item.getMedia(); if (media == null) { return new MarkAsPlayedActionButton(item); @@ -44,23 +45,23 @@ public abstract class ItemActionButton { return new PlayActionButton(item); } else if (isDownloadingMedia) { return new CancelDownloadActionButton(item); - } else if (UserPreferences.streamOverDownload()) { + } else if (UserPreferences.streamOverDownload() && allowStream) { return new StreamActionButton(item); - } else if (MobileDownloadHelper.userAllowedMobileDownloads() || !MobileDownloadHelper.userChoseAddToQueue() || isInQueue) { + } else if (MobileDownloadHelper.userAllowedMobileDownloads() + || !MobileDownloadHelper.userChoseAddToQueue() || isInQueue) { return new DownloadActionButton(item, isInQueue); } else { return new AddToQueueActionButton(item); } } - public void configure(@NonNull ImageButton button, Context context) { - TypedArray drawables = context.obtainStyledAttributes(new int[]{getDrawable()}); - + public void configure(@NonNull View button, @NonNull ImageView icon, Context context) { button.setVisibility(getVisibility()); button.setContentDescription(context.getString(getLabel())); - button.setImageDrawable(drawables.getDrawable(0)); button.setOnClickListener((view) -> onClick(context)); + TypedArray drawables = context.obtainStyledAttributes(new int[]{getDrawable()}); + icon.setImageDrawable(drawables.getDrawable(0)); drawables.recycle(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java index 339a98dfa..3ac05e842 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java @@ -45,7 +45,7 @@ public class DocumentFileExportWorker { throw new IOException(); } writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8); - exportWriter.writeDocument(DBReader.getFeedList(), writer); + exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { subscriber.onError(e); diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java index 40b101ddf..f81a52402 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.asynctask; +import android.content.Context; import androidx.annotation.NonNull; import android.util.Log; @@ -25,15 +26,17 @@ public class ExportWorker { private final @NonNull ExportWriter exportWriter; private final @NonNull File output; + private final Context context; - public ExportWorker(@NonNull ExportWriter exportWriter) { + public ExportWorker(@NonNull ExportWriter exportWriter, Context context) { this(exportWriter, new File(UserPreferences.getDataFolder(EXPORT_DIR), - DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension())); + DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension()), context); } - private ExportWorker(@NonNull ExportWriter exportWriter, @NonNull File output) { + private ExportWorker(@NonNull ExportWriter exportWriter, @NonNull File output, Context context) { this.exportWriter = exportWriter; this.output = output; + this.context = context; } public Observable<File> exportObservable() { @@ -45,7 +48,7 @@ public class ExportWorker { OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); - exportWriter.writeDocument(DBReader.getFeedList(), writer); + exportWriter.writeDocument(DBReader.getFeedList(), writer, context); subscriber.onNext(output); } catch (IOException e) { subscriber.onError(e); 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 b88b58537..e037eb392 100644 --- a/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/app/src/main/java/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -16,84 +16,78 @@ import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.export.opml.OpmlElement; import de.danoeh.antennapod.core.export.opml.OpmlReader; -public class OpmlImportWorker extends - AsyncTask<Void, Void, ArrayList<OpmlElement>> { - private static final String TAG = "OpmlImportWorker"; +public class OpmlImportWorker extends AsyncTask<Void, Void, ArrayList<OpmlElement>> { + private static final String TAG = "OpmlImportWorker"; - private final Context context; - private Exception exception; + private final Context context; + private Exception exception; + private ProgressDialog progDialog; - private ProgressDialog progDialog; - - private final Reader mReader; + private final Reader reader; public OpmlImportWorker(Context context, Reader reader) { super(); this.context = context; - this.mReader=reader; + this.reader = reader; } - @Override - protected ArrayList<OpmlElement> doInBackground(Void... params) { - Log.d(TAG, "Starting background work"); + @Override + protected ArrayList<OpmlElement> doInBackground(Void... params) { + Log.d(TAG, "Starting background work"); - if (mReader==null) { + if (reader == null) { return null; } - OpmlReader opmlReader = new OpmlReader(); - try { - ArrayList<OpmlElement> result = opmlReader.readDocument(mReader); - mReader.close(); - return result; - } catch (XmlPullParserException e) { - e.printStackTrace(); - exception = e; - return null; - } catch (IOException e) { - e.printStackTrace(); - exception = e; - return null; - } - - } - - @Override - protected void onPostExecute(ArrayList<OpmlElement> result) { - if (mReader != null) { + OpmlReader opmlReader = new OpmlReader(); + try { + ArrayList<OpmlElement> result = opmlReader.readDocument(reader); + reader.close(); + return result; + } catch (XmlPullParserException e) { + e.printStackTrace(); + exception = e; + return null; + } catch (IOException e) { + e.printStackTrace(); + exception = e; + return null; + } + + } + + @Override + protected void onPostExecute(ArrayList<OpmlElement> result) { + if (reader != null) { try { - mReader.close(); + reader.close(); } catch (IOException e) { e.printStackTrace(); } } - progDialog.dismiss(); - if (exception != null) { - Log.d(TAG, "An error occurred while trying to parse the opml document"); - AlertDialog.Builder alert = new AlertDialog.Builder(context); - alert.setTitle(R.string.error_label); - alert.setMessage(context.getString(R.string.opml_reader_error) - + exception.getMessage()); - alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.create().show(); - } - } - - @Override - protected void onPreExecute() { - progDialog = new ProgressDialog(context); - progDialog.setMessage(context.getString(R.string.reading_opml_label)); - progDialog.setIndeterminate(true); - progDialog.setCancelable(false); - progDialog.show(); - } - - public boolean wasSuccessful() { - return exception != null; - } - - public void executeAsync() { - executeOnExecutor(THREAD_POOL_EXECUTOR); - } + progDialog.dismiss(); + if (exception != null) { + Log.d(TAG, "An error occurred while trying to parse the opml document"); + AlertDialog.Builder alert = new AlertDialog.Builder(context); + alert.setTitle(R.string.error_label); + alert.setMessage(context.getString(R.string.opml_reader_error) + + exception.getMessage()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.create().show(); + } + } + + @Override + protected void onPreExecute() { + progDialog = new ProgressDialog(context); + progDialog.setMessage(context.getString(R.string.please_wait)); + progDialog.setIndeterminate(true); + progDialog.setCancelable(false); + progDialog.show(); + } + + public void executeAsync() { + executeOnExecutor(THREAD_POOL_EXECUTOR); + } } 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 34b102ca8..cbba1637f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -245,7 +245,7 @@ public class EpisodesApplyActionFragment extends Fragment { } @Override - public void onPrepareOptionsMenu (Menu menu) { + public void onPrepareOptionsMenu(Menu menu) { // Prepare icon for select toggle button int[] icon = new int[1]; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index 3e4e40a5b..4e3068e7b 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -10,7 +10,6 @@ import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import java.util.Locale; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -19,9 +18,6 @@ import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; public class PlaybackControlsDialog extends DialogFragment { - private static final float PLAYBACK_SPEED_STEP = 0.05f; - private static final float DEFAULT_MIN_PLAYBACK_SPEED = 0.5f; - private static final float DEFAULT_MAX_PLAYBACK_SPEED = 2.5f; private static final String ARGUMENT_IS_PLAYING_VIDEO = "isPlayingVideo"; private PlaybackController controller; @@ -77,11 +73,11 @@ public class PlaybackControlsDialog extends DialogFragment { } private void setupUi() { - final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed); - final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed); + final SeekBar barPlaybackSpeed = dialog.findViewById(R.id.playback_speed); + final Button butDecSpeed = dialog.findViewById(R.id.butDecSpeed); butDecSpeed.setOnClickListener(v -> { if (controller != null && controller.canSetPlaybackSpeed()) { - barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 1); + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 2); } else { VariableSpeedDialog.showGetPluginDialog(getContext()); } @@ -89,45 +85,35 @@ public class PlaybackControlsDialog extends DialogFragment { final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed); butIncSpeed.setOnClickListener(v -> { if (controller != null && controller.canSetPlaybackSpeed()) { - barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 1); + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 2); } else { VariableSpeedDialog.showGetPluginDialog(getContext()); } }); - final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed); + final TextView txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); float currentSpeed = getCurrentSpeed(); - String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray(); - final float minPlaybackSpeed = availableSpeeds.length > 1 ? - Float.valueOf(availableSpeeds[0]) : DEFAULT_MIN_PLAYBACK_SPEED; - float maxPlaybackSpeed = availableSpeeds.length > 1 ? - Float.valueOf(availableSpeeds[availableSpeeds.length - 1]) : DEFAULT_MAX_PLAYBACK_SPEED; - int progressMax = (int) ((maxPlaybackSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP); - barPlaybackSpeed.setMax(progressMax); - txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed)); barPlaybackSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (controller != null && controller.canSetPlaybackSpeed()) { - float playbackSpeed = progress * PLAYBACK_SPEED_STEP + minPlaybackSpeed; + float playbackSpeed = (progress + 10) / 20.0f; controller.setPlaybackSpeed(playbackSpeed); - String speedPref = String.format(Locale.US, "%.2f", playbackSpeed); PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(playbackSpeed); if (isPlayingVideo) { - UserPreferences.setVideoPlaybackSpeed(speedPref); + UserPreferences.setVideoPlaybackSpeed(playbackSpeed); } else { - UserPreferences.setPlaybackSpeed(speedPref); + UserPreferences.setPlaybackSpeed(playbackSpeed); } String speedStr = String.format("%.2fx", playbackSpeed); txtvPlaybackSpeed.setText(speedStr); } else if (fromUser) { float speed = getCurrentSpeed(); - barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress( - (int) ((speed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP))); + barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress(Math.round((20 * speed) - 10))); } } @@ -142,7 +128,7 @@ public class PlaybackControlsDialog extends DialogFragment { public void onStopTrackingTouch(SeekBar seekBar) { } }); - barPlaybackSpeed.setProgress(Math.round((currentSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP)); + barPlaybackSpeed.setProgress(Math.round((20 * currentSpeed) - 10)); final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage()); 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 8d176c708..d36f97c7a 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -1,67 +1,101 @@ package de.danoeh.antennapod.dialog; +import android.app.Dialog; import android.content.Context; +import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; - +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; -import org.greenrobot.eventbus.EventBus; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; -public abstract class SleepTimerDialog { - - private static final String TAG = SleepTimerDialog.class.getSimpleName(); +import java.util.concurrent.TimeUnit; - private final Context context; +public class SleepTimerDialog extends DialogFragment { + private PlaybackController controller; + private Disposable timeUpdater; - private AlertDialog dialog; private EditText etxtTime; private Spinner spTimeUnit; private CheckBox cbShakeToReset; private CheckBox cbVibrate; private CheckBox chAutoEnable; + private LinearLayout timeSetup; + private LinearLayout timeDisplay; + private TextView time; + public SleepTimerDialog() { - protected SleepTimerDialog(Context context) { - this.context = context; } - public AlertDialog createNewDialog() { - View content = View.inflate(context, R.layout.time_dialog, null); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.set_sleeptimer_label); - builder.setView(content); - builder.setNegativeButton(R.string.cancel_label, (dialog, which) -> dialog.dismiss()); - builder.setPositiveButton(R.string.set_sleeptimer_label, (dialog, which) -> { - try { - savePreferences(); - long input = SleepTimerPreferences.timerMillis(); - onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked()); - dialog.dismiss(); - } catch (NumberFormatException e) { - e.printStackTrace(); - Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input, - Toast.LENGTH_LONG); - toast.show(); + @Override + public void onStart() { + super.onStart(); + controller = new PlaybackController(getActivity(), false) { + @Override + public void setupGUI() { + updateTime(); } - }); - dialog = builder.create(); + + @Override + public void onSleepTimerUpdate() { + updateTime(); + } + }; + controller.init(); + timeUpdater = Observable.interval(1, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(tick -> updateTime()); + } + + @Override + public void onStop() { + super.onStop(); + if (controller != null) { + controller.release(); + } + if (timeUpdater != null) { + timeUpdater.dispose(); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View content = View.inflate(getContext(), R.layout.time_dialog, null); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.sleep_timer_label); + builder.setView(content); + builder.setPositiveButton(android.R.string.ok, null); etxtTime = content.findViewById(R.id.etxtTime); spTimeUnit = content.findViewById(R.id.spTimeUnit); cbShakeToReset = content.findViewById(R.id.cbShakeToReset); cbVibrate = content.findViewById(R.id.cbVibrate); chAutoEnable = content.findViewById(R.id.chAutoEnable); + timeSetup = content.findViewById(R.id.timeSetup); + timeDisplay = content.findViewById(R.id.timeDisplay); + time = content.findViewById(R.id.time); + AlertDialog dialog = builder.create(); etxtTime.setText(SleepTimerPreferences.lastTimerValue()); etxtTime.addTextChangedListener(new TextWatcher() { @Override @@ -78,15 +112,15 @@ public abstract class SleepTimerDialog { } }); etxtTime.postDelayed(() -> { - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT); }, 100); String[] spinnerContent = new String[] { - context.getString(R.string.time_seconds), - context.getString(R.string.time_minutes), - context.getString(R.string.time_hours) }; - ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(context, + getString(R.string.time_seconds), + getString(R.string.time_minutes), + getString(R.string.time_hours) }; + ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, spinnerContent); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spTimeUnit.setAdapter(spinnerAdapter); @@ -96,16 +130,33 @@ public abstract class SleepTimerDialog { cbVibrate.setChecked(SleepTimerPreferences.vibrate()); chAutoEnable.setChecked(SleepTimerPreferences.autoEnable()); - chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) -> { - SleepTimerPreferences.setAutoEnable(isChecked); - int messageString = isChecked ? R.string.sleep_timer_enabled_label : R.string.sleep_timer_disabled_label; - EventBus.getDefault().post(new MessageEvent(context.getString(messageString))); + chAutoEnable.setOnCheckedChangeListener((compoundButton, isChecked) + -> SleepTimerPreferences.setAutoEnable(isChecked)); + Button disableButton = content.findViewById(R.id.disableSleeptimerButton); + disableButton.setOnClickListener(v -> { + if (controller != null) { + controller.disableSleepTimer(); + } + }); + Button setButton = content.findViewById(R.id.setSleeptimerButton); + setButton.setOnClickListener(v -> { + if (!PlaybackService.isRunning) { + Toast.makeText(getContext(), R.string.no_media_playing_label, Toast.LENGTH_LONG).show(); + } + try { + savePreferences(); + long time = SleepTimerPreferences.timerMillis(); + if (controller != null) { + controller.setSleepTimer(time, cbShakeToReset.isChecked(), cbVibrate.isChecked()); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + Toast.makeText(getContext(), R.string.time_dialog_invalid_input, Toast.LENGTH_LONG).show(); + } }); return dialog; } - public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate); - private void savePreferences() { SleepTimerPreferences.setLastTimer(etxtTime.getText().toString(), spTimeUnit.getSelectedItemPosition()); @@ -114,4 +165,12 @@ public abstract class SleepTimerDialog { SleepTimerPreferences.setAutoEnable(chAutoEnable.isChecked()); } + private void updateTime() { + if (controller == null) { + return; + } + timeSetup.setVisibility(controller.sleepTimerActive() ? View.GONE : View.VISIBLE); + timeDisplay.setVisibility(controller.sleepTimerActive() ? View.VISIBLE : View.GONE); + time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft())); + } } 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 ef624ebe6..3a6ba183f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -6,8 +6,12 @@ import androidx.appcompat.app.AlertDialog; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; public class VariableSpeedDialog { @@ -43,15 +47,22 @@ public class VariableSpeedDialog { } private static void showSpeedSelectorDialog(final Context context) { + DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US); + format.setDecimalSeparator('.'); + DecimalFormat speedFormat = new DecimalFormat("0.00", format); + final String[] speedValues = context.getResources().getStringArray( R.array.playback_speed_values); // According to Java spec these get initialized to false on creation final boolean[] speedChecked = new boolean[speedValues.length]; - // Build the "isChecked" array so that multiChoice dialog is - // populated correctly - List<String> selectedSpeedList = Arrays.asList(UserPreferences - .getPlaybackSpeedArray()); + // Build the "isChecked" array so that multiChoice dialog is populated correctly + List<String> selectedSpeedList = new ArrayList<>(); + float[] selectedSpeeds = UserPreferences.getPlaybackSpeedArray(); + for (float speed : selectedSpeeds) { + selectedSpeedList.add(speedFormat.format(speed)); + } + for (int i = 0; i < speedValues.length; i++) { speedChecked[i] = selectedSpeedList.contains(speedValues[i]); } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java index 79ccd9532..ee318c706 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.discovery; import android.content.Context; +import android.util.Log; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; @@ -21,6 +23,7 @@ import java.util.List; import java.util.Locale; public class ItunesTopListLoader { + private static final String TAG = "ITunesTopListLoader"; private final Context context; public ItunesTopListLoader(Context context) { @@ -29,11 +32,11 @@ public class ItunesTopListLoader { public Single<List<PodcastSearchResult>> loadToplist(int limit) { return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) emitter -> { - String lang = Locale.getDefault().getLanguage(); + String country = Locale.getDefault().getCountry(); OkHttpClient client = AntennapodHttpClient.getHttpClient(); String feedString; try { - feedString = getTopListFeed(client, lang, limit); + feedString = getTopListFeed(client, country, limit); } catch (IOException e) { feedString = getTopListFeed(client, "us", limit); } @@ -74,11 +77,12 @@ public class ItunesTopListLoader { .observeOn(AndroidSchedulers.mainThread()); } - private String getTopListFeed(OkHttpClient client, String language, int limit) throws IOException { - String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit="+limit+"/explicit=true/json"; + private String getTopListFeed(OkHttpClient client, String country, int limit) throws IOException { + String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit=" + limit + "/explicit=true/json"; + Log.d(TAG, "Feed URL " + String.format(url, country)); Request.Builder httpReq = new Request.Builder() .header("User-Agent", ClientConfig.USER_AGENT) - .url(String.format(url, language)); + .url(String.format(url, country)); try (Response response = client.newCall(httpReq.build()).execute()) { if (response.isSuccessful()) { @@ -95,7 +99,7 @@ public class ItunesTopListLoader { JSONArray entries = feed.getJSONArray("entry"); List<PodcastSearchResult> results = new ArrayList<>(); - for (int i=0; i < entries.length(); i++) { + for (int i = 0; i < entries.length(); i++) { JSONObject json = entries.getJSONObject(i); results.add(PodcastSearchResult.fromItunesToplist(json)); } 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 2cfe7c1e8..343cf76ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -1,7 +1,11 @@ package de.danoeh.antennapod.fragment; +import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.util.Log; import androidx.fragment.app.Fragment; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -14,7 +18,7 @@ import android.widget.EditText; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; +import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; /** @@ -28,6 +32,7 @@ public class AddFeedFragment extends Fragment { * Preset value for url text field. */ private static final String ARG_FEED_URL = "feedurl"; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; private EditText combinedFeedSearchBox; private MainActivity activity; @@ -44,8 +49,16 @@ public class AddFeedFragment extends Fragment { setupSeachBox(root); View butOpmlImport = root.findViewById(R.id.btn_opml_import); - butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), - OpmlImportFromPathActivity.class))); + butOpmlImport.setOnClickListener(v -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + }); root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch()); return root; } @@ -130,4 +143,18 @@ public class AddFeedFragment extends Fragment { // persist. mfietz thinks this causes the ActionBar to be invalidated setHasOptionsMenu(true); } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } } 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 e4276b3b9..216c8cc57 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -39,11 +39,6 @@ public class AllEpisodesFragment extends EpisodesListFragment { } @Override - protected boolean showOnlyNewEpisodes() { - return false; - } - - @Override protected String getPrefName() { return PREF_NAME; } @@ -67,7 +62,7 @@ public class AllEpisodesFragment extends EpisodesListFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.findItem(R.id.filter_items).setVisible(true); - menu.findItem(R.id.mark_all_read_item).setVisible(!episodes.isEmpty()); + menu.findItem(R.id.mark_all_read_item).setVisible(true); } @Override 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 2df28b262..9940ccbdd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -6,12 +6,16 @@ import android.util.Log; import android.view.View; import android.widget.ListView; +import de.danoeh.antennapod.core.util.ChapterUtils; import java.util.List; import java.util.ListIterator; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChaptersListAdapter; +import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.Chapter; +import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; @@ -20,14 +24,17 @@ import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; public class ChaptersFragment extends ListFragment { private static final String TAG = "ChaptersFragment"; private ChaptersListAdapter adapter; private PlaybackController controller; private Disposable disposable; - private EmptyViewHandler emptyView; - + private int focusedChapter = -1; + private Playable media; @Override public void onViewCreated(View view, Bundle savedInstanceState) { @@ -38,15 +45,19 @@ public class ChaptersFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); - emptyView = new EmptyViewHandler(getContext()); + EmptyViewHandler emptyView = new EmptyViewHandler(getContext()); emptyView.attachToListView(lv); emptyView.setIcon(R.attr.ic_bookmark); emptyView.setTitle(R.string.no_chapters_head_label); emptyView.setMessage(R.string.no_chapters_label); adapter = new ChaptersListAdapter(getActivity(), 0, pos -> { + if (controller.getStatus() != PlayerStatus.PLAYING) { + controller.playPause(); + } Chapter chapter = (Chapter) getListAdapter().getItem(pos); controller.seekToChapter(chapter); + updateChapterSelection(pos); }); setListAdapter(adapter); } @@ -67,7 +78,7 @@ public class ChaptersFragment extends ListFragment { } }; controller.init(); - + EventBus.getDefault().register(this); loadMediaInfo(); } @@ -85,22 +96,19 @@ public class ChaptersFragment extends ListFragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + updateChapterSelection(getCurrentChapter(media)); } private int getCurrentChapter(Playable media) { - if (media == null || media.getChapters() == null || media.getChapters().size() == 0 || controller == null) { + if (controller == null) { return -1; } - int currentPosition = controller.getPosition(); - - List<Chapter> chapters = media.getChapters(); - for (final ListIterator<Chapter> it = chapters.listIterator(); it.hasNext(); ) { - Chapter chapter = it.next(); - if (chapter.getStart() > currentPosition) { - return it.previousIndex() - 1; - } - } - return chapters.size() - 1; + return ChapterUtils.getCurrentChapterIndex(media, controller.getPosition()); } private void loadMediaInfo() { @@ -108,27 +116,42 @@ public class ChaptersFragment extends ListFragment { disposable.dispose(); } disposable = Maybe.create(emitter -> { - Playable media = controller.getMedia(); - if (media != null) { - emitter.onSuccess(media); - } else { - emitter.onComplete(); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(media -> onMediaChanged((Playable) media), - error -> Log.e(TAG, Log.getStackTraceString(error))); + Playable media = controller.getMedia(); + if (media != null) { + emitter.onSuccess(media); + } else { + emitter.onComplete(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(media -> onMediaChanged((Playable) media), + error -> Log.e(TAG, Log.getStackTraceString(error))); } private void onMediaChanged(Playable media) { - if (adapter != null) { - adapter.setMedia(media); - adapter.notifyDataSetChanged(); + this.media = media; + focusedChapter = -1; + if (adapter == null) { + return; + } + adapter.setMedia(media); + adapter.notifyDataSetChanged(); + int positionOfCurrentChapter = getCurrentChapter(media); + updateChapterSelection(positionOfCurrentChapter); + } + + private void updateChapterSelection(int position) { + if (adapter == null) { + return; + } - int positionOfCurrentChapter = getCurrentChapter(media); - if (positionOfCurrentChapter != -1) { - getListView().setSelection(positionOfCurrentChapter); + if (position != -1 && focusedChapter != position) { + focusedChapter = position; + adapter.notifyChapterChanged(focusedChapter); + if (getListView().getFirstVisiblePosition() >= position + || getListView().getLastVisiblePosition() <= position) { + getListView().setSelectionFromTop(position, 100); } } } 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 7f70daaec..101c5da27 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -52,7 +52,7 @@ public class CompletedDownloadsFragment extends ListFragment { addVerticalPadding(); addEmptyView(); - listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess); + listAdapter = new DownloadedEpisodesListAdapter((MainActivity) getActivity(), itemAccess); setListAdapter(listAdapter); setListShown(false); EventBus.getDefault().register(this); 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 5467d71a8..cc1868321 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -1,27 +1,36 @@ package de.danoeh.antennapod.fragment; +import android.graphics.drawable.Drawable; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; - +import com.bumptech.glide.load.resource.bitmap.FitCenter; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.util.ChapterUtils; +import de.danoeh.antennapod.core.util.EmbeddedChapterImage; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Displays the cover and the title of a FeedItem. @@ -36,6 +45,8 @@ public class CoverFragment extends Fragment { private ImageView imgvCover; private PlaybackController controller; private Disposable disposable; + private int displayedChapterIndex = -2; + private Playable media; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -53,30 +64,27 @@ public class CoverFragment extends Fragment { if (disposable != null) { disposable.dispose(); } - disposable = Maybe.create(emitter -> { - Playable media = controller.getMedia(); - if (media != null) { - emitter.onSuccess(media); - } else { - emitter.onComplete(); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(media -> displayMediaInfo((Playable) media), - error -> Log.e(TAG, Log.getStackTraceString(error))); + disposable = Maybe.<Playable>create(emitter -> { + Playable media = controller.getMedia(); + if (media != null) { + emitter.onSuccess(media); + } else { + emitter.onComplete(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(media -> { + this.media = media; + displayMediaInfo(media); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); } private void displayMediaInfo(@NonNull Playable media) { txtvPodcastTitle.setText(media.getFeedTitle()); txtvEpisodeTitle.setText(media.getEpisodeTitle()); - Glide.with(this) - .load(ImageResourceUtils.getImageLocation(media)) - .apply(new RequestOptions() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .dontAnimate() - .fitCenter()) - .into(imgvCover); + displayedChapterIndex = -2; // Force refresh + displayCoverImage(media.getPosition()); } @Override @@ -99,6 +107,7 @@ public class CoverFragment extends Fragment { }; controller.init(); loadMediaInfo(); + EventBus.getDefault().register(this); } @Override @@ -106,6 +115,44 @@ public class CoverFragment extends Fragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(PlaybackPositionEvent event) { + if (controller == null) { + return; + } + displayCoverImage(event.getPosition()); + } + + private void displayCoverImage(int position) { + int chapter = ChapterUtils.getCurrentChapterIndex(media, position); + if (chapter != displayedChapterIndex) { + displayedChapterIndex = chapter; + + RequestBuilder<Drawable> cover = Glide.with(this) + .load(ImageResourceUtils.getImageLocation(media)) + .apply(new RequestOptions() + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .dontAnimate() + .transforms(new FitCenter(), + new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)))); + if (chapter == -1 || TextUtils.isEmpty(media.getChapters().get(chapter).getImageUrl())) { + cover.into(imgvCover); + } else { + Glide.with(this) + .load(EmbeddedChapterImage.getModelFor(media, chapter)) + .apply(new RequestOptions() + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .dontAnimate() + .transforms(new FitCenter(), + new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)))) + .thumbnail(cover) + .error(cover) + .into(imgvCover); + } + } } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java index 8cdec9f38..49398d104 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesFragment.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; +import androidx.annotation.NonNull; import com.google.android.material.tabs.TabLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -47,7 +48,7 @@ public class EpisodesFragment extends Fragment { View rootView = inflater.inflate(R.layout.pager_fragment, container, false); viewPager = rootView.findViewById(R.id.viewpager); - viewPager.setAdapter(new EpisodesPagerAdapter(getChildFragmentManager(), getResources())); + viewPager.setAdapter(new EpisodesPagerAdapter()); // Give the TabLayout the ViewPager tabLayout = rootView.findViewById(R.id.sliding_tabs); @@ -76,23 +77,23 @@ public class EpisodesFragment extends Fragment { viewPager.setCurrentItem(lastPosition); } - public static class EpisodesPagerAdapter extends FragmentPagerAdapter { + public class EpisodesPagerAdapter extends FragmentPagerAdapter { - private final Resources resources; - private final EpisodesListFragment[] fragments = { - new NewEpisodesFragment(), - new AllEpisodesFragment(), - new FavoriteEpisodesFragment() - }; - - public EpisodesPagerAdapter(FragmentManager fm, Resources resources) { - super(fm); - this.resources = resources; + public EpisodesPagerAdapter() { + super(getChildFragmentManager()); } @Override + @NonNull public Fragment getItem(int position) { - return fragments[position]; + switch (position) { + case 0: + return new NewEpisodesFragment(); + case 1: + return new AllEpisodesFragment(); + default: + return new FavoriteEpisodesFragment(); + } } @Override @@ -104,23 +105,14 @@ public class EpisodesFragment extends Fragment { public CharSequence getPageTitle(int position) { switch (position) { case POS_ALL_EPISODES: - return resources.getString(R.string.all_episodes_short_label); + return getString(R.string.all_episodes_short_label); case POS_NEW_EPISODES: - return resources.getString(R.string.new_episodes_label); + return getString(R.string.new_episodes_label); case POS_FAV_EPISODES: - return resources.getString(R.string.favorite_episodes_label); + return getString(R.string.favorite_episodes_label); default: return super.getPageTitle(position); } } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - super.setPrimaryItem(container, position, object); - for (int i = 0; i < TOTAL_COUNT; i++) { - // Invalidating the OptionsMenu is only allowed for the currently active fragment - fragments[i].isMenuInvalidationAllowed = (i == position); - } - } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index b6d6dd9bb..7258aec8e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -28,6 +28,7 @@ import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -43,14 +44,11 @@ import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; 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.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBWriter; 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.download.AutoUpdateManager; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; @@ -82,20 +80,13 @@ public abstract class EpisodesListFragment extends Fragment { @NonNull List<FeedItem> episodes = new ArrayList<>(); - @NonNull - private List<Downloader> downloaderList = new ArrayList<>(); - - private boolean isUpdatingFeeds; - boolean isMenuInvalidationAllowed = false; + private volatile boolean isUpdatingFeeds; + private boolean isMenuVisible = true; protected Disposable disposable; private LinearLayoutManager layoutManager; protected TextView txtvInformation; - boolean showOnlyNewEpisodes() { - return false; - } - String getPrefName() { return DEFAULT_PREF_NAME; } @@ -165,6 +156,12 @@ public abstract class EpisodesListFragment extends Fragment { () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); @Override + public void setMenuVisibility(final boolean visible) { + super.setMenuVisibility(visible); + isMenuVisible = visible; + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (!isAdded()) { return; @@ -174,7 +171,7 @@ public abstract class EpisodesListFragment extends Fragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - sv.setQueryHint(getString(R.string.search_hint)); + sv.setQueryHint(getString(R.string.search_label)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { @@ -347,14 +344,16 @@ public abstract class EpisodesListFragment extends Fragment { } protected void onFragmentLoaded(List<FeedItem> episodes) { - listAdapter.notifyDataSetChanged(); - if (episodes.size() == 0) { createRecycleAdapter(recyclerView, emptyView); + } else { + listAdapter.updateItems(episodes); } restoreScrollPosition(); - requireActivity().invalidateOptionsMenu(); + if (isMenuVisible && isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + requireActivity().invalidateOptionsMenu(); + } } /** @@ -363,66 +362,13 @@ public abstract class EpisodesListFragment extends Fragment { */ private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) { MainActivity mainActivity = (MainActivity) getActivity(); - listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes()); + listAdapter = new AllEpisodesRecycleAdapter(mainActivity); listAdapter.setHasStableIds(true); + listAdapter.updateItems(episodes); recyclerView.setAdapter(listAdapter); emptyViewHandler.updateAdapter(listAdapter); } - private final AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() { - - @Override - public int getCount() { - return episodes.size(); - } - - @Override - public FeedItem getItem(int position) { - if (0 <= position && position < episodes.size()) { - return episodes.get(position); - } - return null; - } - - @Override - public LongList getItemsIds() { - LongList ids = new LongList(episodes.size()); - for (FeedItem episode : episodes) { - ids.add(episode.getId()); - } - return ids; - } - - @Override - public int getItemDownloadProgressPercent(FeedItem item) { - for (Downloader downloader : downloaderList) { - DownloadRequest downloadRequest = downloader.getDownloadRequest(); - if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloadRequest.getFeedfileId() == item.getMedia().getId()) { - return downloadRequest.getProgressPercent(); - } - } - return 0; - } - - @Override - public boolean isInQueue(FeedItem item) { - return item != null && item.isTagged(FeedItem.TAG_QUEUE); - } - - @Override - public LongList getQueueIds() { - LongList queueIds = new LongList(); - for (FeedItem item : episodes) { - if (item.isTagged(FeedItem.TAG_QUEUE)) { - queueIds.add(item.getId()); - } - } - return queueIds; - } - - }; - @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); @@ -444,8 +390,7 @@ public abstract class EpisodesListFragment extends Fragment { public void onEventMainThread(PlaybackPositionEvent event) { if (listAdapter != null) { for (int i = 0; i < listAdapter.getItemCount(); i++) { - AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) - recyclerView.findViewHolderForAdapterPosition(i); + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); if (holder != null && holder.isCurrentlyPlayingItem()) { holder.notifyPlaybackPositionUpdated(event); break; @@ -462,8 +407,7 @@ public abstract class EpisodesListFragment extends Fragment { public void onEventMainThread(DownloadEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; - downloaderList = update.downloaders; - if (isMenuInvalidationAllowed && event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { + if (isMenuVisible && event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { requireActivity().invalidateOptionsMenu(); } if (update.mediaIds.length > 0) { @@ -478,7 +422,7 @@ public abstract class EpisodesListFragment extends Fragment { private void updateUi() { loadItems(); - if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { + if (isMenuVisible && isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { requireActivity().invalidateOptionsMenu(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index 87a555cfd..4953ccc6c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -10,12 +10,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import org.greenrobot.eventbus.Subscribe; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.core.event.FavoritesEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; @@ -31,11 +31,6 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment { private static final String PREF_NAME = "PrefFavoriteEpisodesFragment"; @Override - protected boolean showOnlyNewEpisodes() { - return true; - } - - @Override protected String getPrefName() { return PREF_NAME; } @@ -63,8 +58,8 @@ public class FavoriteEpisodesFragment extends EpisodesListFragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder; - Log.d(TAG, String.format("remove(%s)", holder.getItemId())); + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) viewHolder; + Log.d(TAG, String.format("remove(%s)", holder.getFeedItem().getId())); if (disposable != null) { disposable.dispose(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index 081f4d604..b9afa6d57 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -89,25 +89,18 @@ public class FeedItemlistFragment extends ListFragment { private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; private FeedItemlistAdapter adapter; - private ContextMenu contextMenu; private AdapterView.AdapterContextMenuInfo lastMenuInfo = null; + private MoreContentListFooterUtil listFooter; private long feedID; private Feed feed; - private boolean headerCreated = false; - - private List<Downloader> downloaderList; - - private MoreContentListFooterUtil listFooter; - private boolean isUpdatingFeed; private TextView txtvTitle; private IconTextView txtvFailure; private ImageView imgvBackground; private ImageView imgvCover; - private TextView txtvInformation; private Disposable disposable; @@ -186,7 +179,7 @@ public class FeedItemlistFragment extends ListFragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - sv.setQueryHint(getString(R.string.search_hint)); + sv.setQueryHint(getString(R.string.search_label)); searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { @@ -296,7 +289,7 @@ public class FeedItemlistFragment extends ListFragment { AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; // because of addHeaderView(), positions are increased by 1! - FeedItem item = itemAccess.getItem(adapterInfo.position-1); + FeedItem item = (FeedItem) itemAccess.getItem(adapterInfo.position - 1); MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.feeditemlist_context, menu); @@ -305,7 +298,6 @@ public class FeedItemlistFragment extends ListFragment { menu.setHeaderTitle(item.getTitle()); } - contextMenu = menu; lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; FeedItemMenuHandler.onPrepareMenu(menu, item); } @@ -313,11 +305,11 @@ public class FeedItemlistFragment extends ListFragment { @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - if(menuInfo == null) { + if (menuInfo == null) { menuInfo = lastMenuInfo; } // because of addHeaderView(), positions are increased by 1! - FeedItem selectedItem = itemAccess.getItem(menuInfo.position-1); + FeedItem selectedItem = feed.getItemAtIndex(menuInfo.position - 1); if (selectedItem == null) { Log.i(TAG, "Selected item at position " + menuInfo.position + " was null, ignoring selection"); @@ -366,7 +358,6 @@ public class FeedItemlistFragment extends ListFragment { public void onEventMainThread(DownloadEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; - downloaderList = update.downloaders; if (event.hasChangedFeedUpdateStatus(isUpdatingFeed)) { updateProgressBarVisibility(); } @@ -424,7 +415,7 @@ public class FeedItemlistFragment extends ListFragment { setListAdapter(null); setupHeaderView(); setupFooterView(); - adapter = new FeedItemlistAdapter(getActivity(), itemAccess, false, true); + adapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, false, true); setListAdapter(adapter); } refreshHeaderView(); @@ -575,39 +566,12 @@ public class FeedItemlistFragment extends ListFragment { } @Override - public LongList getQueueIds() { - LongList queueIds = new LongList(); - if(feed == null) { - return queueIds; - } - for(FeedItem item : feed.getItems()) { - if(item.isTagged(FeedItem.TAG_QUEUE)) { - queueIds.add(item.getId()); - } - } - return queueIds; - } - - @Override public int getCount() { return (feed != null) ? feed.getNumOfItems() : 0; } - @Override - public int getItemDownloadProgressPercent(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - return downloader.getDownloadRequest().getProgressPercent(); - } - } - } - return 0; - } }; - private void loadItems() { if(disposable != null) { disposable.dispose(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index bb1f8f8e9..8ff71e114 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -3,16 +3,19 @@ package de.danoeh.antennapod.fragment; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import androidx.preference.SwitchPreference; +import android.util.Log; import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; -import android.util.Log; +import androidx.preference.SwitchPreference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; +import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedFilter; import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; @@ -23,6 +26,8 @@ import io.reactivex.MaybeOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; + import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; @@ -32,7 +37,8 @@ import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL; public class FeedSettingsFragment extends PreferenceFragmentCompat { private static final CharSequence PREF_EPISODE_FILTER = "episodeFilter"; private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; - private static final DecimalFormat decimalFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); + private static final DecimalFormat SPEED_FORMAT = + new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; private static final String TAG = "FeedSettingsFragment"; @@ -73,11 +79,13 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { setupAutoDownloadPreference(); setupKeepUpdatedPreference(); setupAutoDeletePreference(); + setupVolumeReductionPreferences(); setupAuthentificationPreference(); setupEpisodeFilterPreference(); setupPlaybackSpeedPreference(); updateAutoDeleteSummary(); + updateVolumeReductionValue(); updateAutoDownloadEnabled(); updatePlaybackSpeedPreference(); }, error -> Log.d(TAG, Log.getStackTraceString(error)), () -> { }); @@ -109,10 +117,9 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { private void setupPlaybackSpeedPreference() { ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); - String[] speeds = UserPreferences.getPlaybackSpeedArray(); - + final String[] speeds = getResources().getStringArray(R.array.playback_speed_values); String[] values = new String[speeds.length + 1]; - values[0] = decimalFormat.format(SPEED_USE_GLOBAL); + values[0] = SPEED_FORMAT.format(SPEED_USE_GLOBAL); String[] entries = new String[speeds.length + 1]; entries[0] = getString(R.string.feed_auto_download_global); @@ -122,11 +129,12 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { feedPlaybackSpeedPreference.setEntryValues(values); feedPlaybackSpeedPreference.setEntries(entries); - feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); feed.savePreferences(); updatePlaybackSpeedPreference(); + EventBus.getDefault().post( + new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); return false; }); } @@ -184,7 +192,7 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); float speedValue = feedPreferences.getFeedPlaybackSpeed(); - feedPlaybackSpeedPreference.setValue(decimalFormat.format(speedValue)); + feedPlaybackSpeedPreference.setValue(SPEED_FORMAT.format(speedValue)); } private void updateAutoDeleteSummary() { @@ -206,6 +214,44 @@ public class FeedSettingsFragment extends PreferenceFragmentCompat { } } + private void setupVolumeReductionPreferences() { + ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction"); + volumeReductionPreference.setOnPreferenceChangeListener((preference, newValue) -> { + switch ((String) newValue) { + case "off": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.OFF); + break; + case "light": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.LIGHT_REDUCTION); + break; + case "heavy": + feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.HEAVY_REDUCTION); + break; + } + feed.savePreferences(); + updateVolumeReductionValue(); + EventBus.getDefault().post( + new VolumeAdaptionChangedEvent(feedPreferences.getVolumeAdaptionSetting(), feed.getId())); + return false; + }); + } + + private void updateVolumeReductionValue() { + ListPreference volumeReductionPreference = (ListPreference) findPreference("volumeReduction"); + + switch (feedPreferences.getVolumeAdaptionSetting()) { + case OFF: + volumeReductionPreference.setValue("off"); + break; + case LIGHT_REDUCTION: + volumeReductionPreference.setValue("light"); + break; + case HEAVY_REDUCTION: + volumeReductionPreference.setValue("heavy"); + break; + } + } + private void setupKeepUpdatedPreference() { SwitchPreference pref = (SwitchPreference) findPreference("keepUpdated"); 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 85978b761..256615199 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -1,40 +1,18 @@ package de.danoeh.antennapod.fragment; -import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ClipData; -import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; import android.util.Log; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Toast; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MediaplayerInfoActivity; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.ShareUtils; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; +import de.danoeh.antennapod.view.ShownotesWebView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -44,72 +22,29 @@ import io.reactivex.schedulers.Schedulers; * Displays the description of a Playable object in a Webview. */ public class ItemDescriptionFragment extends Fragment { - private static final String TAG = "ItemDescriptionFragment"; private static final String PREF = "ItemDescriptionFragmentPrefs"; private static final String PREF_SCROLL_Y = "prefScrollY"; private static final String PREF_PLAYABLE_ID = "prefPlayableId"; - private WebView webvDescription; + private ShownotesWebView webvDescription; private Disposable webViewLoader; private PlaybackController controller; - /** - * URL that was selected via long-press. - */ - private String selectedURL; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "Creating view"); - webvDescription = new WebView(getActivity().getApplicationContext()); - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - - TypedArray ta = getActivity().getTheme().obtainStyledAttributes(new int[] - {android.R.attr.colorBackground}); - boolean black = UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark - || UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack; - int backgroundColor = ta.getColor(0, black ? Color.BLACK : Color.WHITE); - - ta.recycle(); - webvDescription.setBackgroundColor(backgroundColor); - if (!NetworkUtils.networkAvailable()) { - webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); - // Use cached resources, even if they have expired - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - - webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); - webvDescription.getSettings().setLoadWithOverviewMode(true); - webvDescription.setOnLongClickListener(webViewLongClickListener); - webvDescription.setWebViewClient(new WebViewClient() { - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (Timeline.isTimecodeLink(url)) { - onTimecodeLinkSelected(url); - } else { - IntentUtils.openInBrowser(getContext(), url); - } - return true; + webvDescription = new ShownotesWebView(getActivity().getApplicationContext()); + webvDescription.setTimecodeSelectedListener(time -> { + if (controller != null) { + controller.seekTo(time); } - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - Log.d(TAG, "Page finished"); - // Restoring the scroll position might not always work - view.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); - } - }); - + webvDescription.setPageFinishedListener(() -> { + // Restoring the scroll position might not always work + webvDescription.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); + }); registerForContextMenu(webvDescription); return webvDescription; } @@ -127,91 +62,14 @@ public class ItemDescriptionFragment extends Fragment { } } - private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - WebView.HitTestResult r = webvDescription.getHitTestResult(); - if (r != null - && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); - selectedURL = r.getExtra(); - webvDescription.showContextMenu(); - return true; - } - selectedURL = null; - return false; - } - }; - - @SuppressLint("NewApi") @Override public boolean onContextItemSelected(MenuItem item) { - boolean handled = selectedURL != null; - if (selectedURL != null) { - switch (item.getItemId()) { - case R.id.open_in_browser_item: - IntentUtils.openInBrowser(getContext(), selectedURL); - break; - case R.id.share_url_item: - ShareUtils.shareLink(getActivity(), selectedURL); - break; - case R.id.copy_url_item: - ClipData clipData = ClipData.newPlainText(selectedURL, - selectedURL); - android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - Toast t = Toast.makeText(getActivity(), - R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - break; - case R.id.go_to_position_item: - if (Timeline.isTimecodeLink(selectedURL)) { - onTimecodeLinkSelected(selectedURL); - } else { - Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedURL); - } - break; - default: - handled = false; - break; - - } - selectedURL = null; - } - return handled; - - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - if (selectedURL != null) { - super.onCreateContextMenu(menu, v, menuInfo); - if (Timeline.isTimecodeLink(selectedURL)) { - menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, - R.string.go_to_position_label); - menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedURL))); - } else { - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); - } - menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, - R.string.copy_url_label); - menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, - R.string.share_url_label); - menu.setHeaderTitle(selectedURL); - } - } + return webvDescription.onContextItemSelected(item); } private void load() { Log.d(TAG, "load()"); - if(webViewLoader != null) { + if (webViewLoader != null) { webViewLoader.dispose(); } webViewLoader = Observable.fromCallable(this::loadData) @@ -227,7 +85,7 @@ public class ItemDescriptionFragment extends Fragment { @NonNull private String loadData() { Timeline timeline = new Timeline(getActivity(), controller.getMedia()); - return timeline.processShownotes(true); + return timeline.processShownotes(); } @Override @@ -238,8 +96,7 @@ public class ItemDescriptionFragment extends Fragment { private void savePreference() { Log.d(TAG, "Saving preferences"); - SharedPreferences prefs = getActivity().getSharedPreferences(PREF, - Activity.MODE_PRIVATE); + SharedPreferences prefs = getActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if (controller != null && controller.getMedia() != null && webvDescription != null) { Log.d(TAG, "Saving scroll position: " + webvDescription.getScrollY()); @@ -251,15 +108,14 @@ public class ItemDescriptionFragment extends Fragment { editor.putInt(PREF_SCROLL_Y, -1); editor.putString(PREF_PLAYABLE_ID, ""); } - editor.commit(); + editor.apply(); } private boolean restoreFromPreference() { Log.d(TAG, "Restoring from preferences"); Activity activity = getActivity(); if (activity != null) { - SharedPreferences prefs = activity.getSharedPreferences( - PREF, Activity.MODE_PRIVATE); + SharedPreferences prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE); String id = prefs.getString(PREF_PLAYABLE_ID, ""); int scrollY = prefs.getInt(PREF_SCROLL_Y, -1); if (controller != null && scrollY != -1 && controller.getMedia() != null @@ -274,16 +130,6 @@ public class ItemDescriptionFragment extends Fragment { return false; } - private void onTimecodeLinkSelected(String link) { - int time = Timeline.getTimecodeLinkTime(link); - if (getActivity() != null && getActivity() instanceof MediaplayerInfoActivity) { - PlaybackController pc = ((MediaplayerInfoActivity) getActivity()).getPlaybackController(); - if (pc != null) { - pc.seekTo(time); - } - } - } - @Override public void onStart() { super.onStart(); 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 7a3d034f1..e263d2b3e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -1,9 +1,7 @@ package de.danoeh.antennapod.fragment; -import android.content.ClipData; import android.content.Context; import android.content.Intent; -import android.content.res.TypedArray; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -11,31 +9,24 @@ import android.text.Layout; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; -import android.view.ContextMenu; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.AttrRes; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.FitCenter; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; -import com.joanzapata.iconify.Iconify; -import com.joanzapata.iconify.widget.IconButton; +import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; @@ -47,7 +38,6 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; @@ -55,10 +45,9 @@ import de.danoeh.antennapod.core.storage.DBWriter; 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.NetworkUtils; -import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.Timeline; +import de.danoeh.antennapod.view.ShownotesWebView; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -99,7 +88,7 @@ public class ItemFragment extends Fragment { private List<Downloader> downloaderList; private ViewGroup root; - private WebView webvDescription; + private ShownotesWebView webvDescription; private TextView txtvPodcast; private TextView txtvTitle; private TextView txtvDuration; @@ -107,15 +96,15 @@ public class ItemFragment extends Fragment { private ImageView imgvCover; private ProgressBar progbarDownload; private ProgressBar progbarLoading; - private Button butAction1; - private Button butAction2; + private TextView butAction1Text; + private TextView butAction2Text; + private ImageView butAction1Icon; + private ImageView butAction2Icon; + private View butAction1; + private View butAction2; private Disposable disposable; - - /** - * URL that was selected via long-press. - */ - private String selectedURL; + private PlaybackController controller; @Override public void onCreate(Bundle savedInstanceState) { @@ -134,7 +123,7 @@ public class ItemFragment extends Fragment { txtvPodcast = layout.findViewById(R.id.txtvPodcast); txtvPodcast.setOnClickListener(v -> openPodcast()); txtvTitle = layout.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23) { txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); } txtvDuration = layout.findViewById(R.id.txtvDuration); @@ -143,31 +132,11 @@ public class ItemFragment extends Fragment { txtvTitle.setEllipsize(TextUtils.TruncateAt.END); } webvDescription = layout.findViewById(R.id.webvDescription); - if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark || - UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - webvDescription.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black)); - } - if (!NetworkUtils.networkAvailable()) { - webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); - // Use cached resources, even if they have expired - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm( - WebSettings.LayoutAlgorithm.NARROW_COLUMNS); - webvDescription.getSettings().setLoadWithOverviewMode(true); - webvDescription.setOnLongClickListener(webViewLongClickListener); - - webvDescription.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - IntentUtils.openInBrowser(getContext(), url); - return true; + webvDescription.setTimecodeSelectedListener(time -> { + if (controller != null && item.getMedia().getIdentifier().equals(controller.getMedia().getIdentifier())) { + controller.seekTo(time); + } else { + Snackbar.make(getView(), R.string.play_this_to_seek_position, Snackbar.LENGTH_LONG).show(); } }); registerForContextMenu(webvDescription); @@ -178,12 +147,16 @@ public class ItemFragment extends Fragment { progbarLoading = layout.findViewById(R.id.progbarLoading); butAction1 = layout.findViewById(R.id.butAction1); butAction2 = layout.findViewById(R.id.butAction2); + butAction1Icon = layout.findViewById(R.id.butAction1Icon); + butAction2Icon = layout.findViewById(R.id.butAction2Icon); + butAction1Text = layout.findViewById(R.id.butAction1Text); + butAction2Text = layout.findViewById(R.id.butAction2Text); butAction1.setOnClickListener(v -> { if (item == null) { return; } - ItemActionButton actionButton = ItemActionButton.forItem(item, item.isTagged(FeedItem.TAG_QUEUE)); + ItemActionButton actionButton = ItemActionButton.forItem(item, item.isTagged(FeedItem.TAG_QUEUE), false); actionButton.onClick(getActivity()); FeedMedia media = item.getMedia(); @@ -230,6 +203,8 @@ public class ItemFragment extends Fragment { public void onStart() { super.onStart(); EventBus.getDefault().register(this); + controller = new PlaybackController(getActivity(), false); + controller.init(); } @Override @@ -245,12 +220,13 @@ public class ItemFragment extends Fragment { public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); + controller.release(); } @Override public void onDestroyView() { super.onDestroyView(); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } if (webvDescription != null && root != null) { @@ -282,10 +258,10 @@ public class ItemFragment extends Fragment { Glide.with(getActivity()) .load(ImageResourceUtils.getImageLocation(item)) .apply(new RequestOptions() - .placeholder(R.color.light_gray) .error(R.color.light_gray) .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() + .transforms(new FitCenter(), + new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density))) .dontAnimate()) .into(imgvCover); @@ -301,22 +277,22 @@ public class ItemFragment extends Fragment { } FeedMedia media = item.getMedia(); - @AttrRes int butAction1Icon = 0; - @StringRes int butAction1Text = 0; - @AttrRes int butAction2Icon = 0; - @StringRes int butAction2Text = 0; + @AttrRes int butAction1IconRes = 0; + @StringRes int butAction1TextRes = 0; + @AttrRes int butAction2IconRes = 0; + @StringRes int butAction2TextRes = 0; if (media == null) { if (!item.isPlayed()) { - butAction1Icon = R.attr.navigation_accept; + butAction1IconRes = R.attr.navigation_accept; if (item.hasMedia()) { - butAction1Text = R.string.mark_read_label; + butAction1TextRes = R.string.mark_read_label; } else { - butAction1Text = R.string.mark_read_no_media_label; + butAction1TextRes = R.string.mark_read_no_media_label; } } if (item.getLink() != null) { - butAction2Icon = R.attr.location_web_site; - butAction2Text = R.string.visit_website_label; + butAction2IconRes = R.attr.location_web_site; + butAction2TextRes = R.string.visit_website_label; } } else { if (media.getDuration() > 0) { @@ -324,116 +300,54 @@ public class ItemFragment extends Fragment { } boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); if (!media.isDownloaded()) { - butAction2Icon = R.attr.action_stream; - butAction2Text = R.string.stream_label; + butAction2IconRes = R.attr.action_stream; + butAction2TextRes = R.string.stream_label; } else { - butAction2Icon = R.attr.content_discard; - butAction2Text = R.string.delete_label; + butAction2IconRes = R.attr.content_discard; + butAction2TextRes = R.string.delete_label; } if (isDownloading) { - butAction1Icon = R.attr.navigation_cancel; - butAction1Text = R.string.cancel_label; + butAction1IconRes = R.attr.navigation_cancel; + butAction1TextRes = R.string.cancel_label; } else if (media.isDownloaded()) { - butAction1Icon = R.attr.av_play; - butAction1Text = R.string.play_label; + butAction1IconRes = R.attr.av_play; + butAction1TextRes = R.string.play_label; } else { - butAction1Icon = R.attr.av_download; - butAction1Text = R.string.download_label; + butAction1IconRes = R.attr.av_download; + butAction1TextRes = R.string.download_label; } } - if (butAction1Icon != 0 && butAction1Text != 0) { - butAction1.setText(butAction1Text); - butAction1.setTransformationMethod(null); + if (butAction1IconRes != 0 && butAction1TextRes != 0) { + butAction1Text.setText(butAction1TextRes); + butAction1Text.setTransformationMethod(null); TypedValue typedValue = new TypedValue(); - getContext().getTheme().resolveAttribute(butAction1Icon, typedValue, true); - butAction1.setCompoundDrawablesWithIntrinsicBounds(typedValue.resourceId, 0, 0, 0); + getContext().getTheme().resolveAttribute(butAction1IconRes, typedValue, true); + butAction1Icon.setImageResource(typedValue.resourceId); butAction1.setVisibility(View.VISIBLE); } else { butAction1.setVisibility(View.INVISIBLE); } - if (butAction2Icon != 0 && butAction2Text != 0) { - butAction2.setText(butAction2Text); - butAction2.setTransformationMethod(null); + if (butAction2IconRes != 0 && butAction2TextRes != 0) { + butAction2Text.setText(butAction2TextRes); + butAction2Text.setTransformationMethod(null); TypedValue typedValue = new TypedValue(); - getContext().getTheme().resolveAttribute(butAction2Icon, typedValue, true); - butAction2.setCompoundDrawablesWithIntrinsicBounds(typedValue.resourceId, 0, 0, 0); + getContext().getTheme().resolveAttribute(butAction2IconRes, typedValue, true); + butAction2Icon.setImageResource(typedValue.resourceId); butAction2.setVisibility(View.VISIBLE); } else { butAction2.setVisibility(View.INVISIBLE); } } - private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - WebView.HitTestResult r = webvDescription.getHitTestResult(); - if (r != null - && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); - selectedURL = r.getExtra(); - webvDescription.showContextMenu(); - return true; - } - selectedURL = null; - return false; - } - }; - @Override public boolean onContextItemSelected(MenuItem item) { - boolean handled = selectedURL != null; - if (selectedURL != null) { - switch (item.getItemId()) { - case R.id.open_in_browser_item: - IntentUtils.openInBrowser(getContext(), selectedURL); - break; - case R.id.share_url_item: - ShareUtils.shareLink(getActivity(), selectedURL); - break; - case R.id.copy_url_item: - ClipData clipData = ClipData.newPlainText(selectedURL, - selectedURL); - android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - Toast t = Toast.makeText(getActivity(), - R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - break; - default: - handled = false; - break; - - } - selectedURL = null; - } - return handled; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenu.ContextMenuInfo menuInfo) { - if (selectedURL != null) { - super.onCreateContextMenu(menu, v, menuInfo); - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); - } - menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, - R.string.copy_url_label); - menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, - R.string.share_url_label); - menu.setHeaderTitle(selectedURL); - } + return webvDescription.onContextItemSelected(item); } private void openPodcast() { Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); - ((MainActivity)getActivity()).loadChildFragment(fragment); + ((MainActivity) getActivity()).loadChildFragment(fragment); } @Subscribe(threadMode = ThreadMode.MAIN) @@ -452,11 +366,11 @@ public class ItemFragment extends Fragment { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if(item == null || item.getMedia() == null) { + if (item == null || item.getMedia() == null) { return; } long mediaId = item.getMedia().getId(); - if(ArrayUtils.contains(update.mediaIds, mediaId)) { + if (ArrayUtils.contains(update.mediaIds, mediaId)) { if (itemsLoaded && getActivity() != null) { updateAppearance(); } @@ -490,7 +404,7 @@ public class ItemFragment extends Fragment { Context context = getContext(); if (feedItem != null && context != null) { Timeline t = new Timeline(context, feedItem); - webviewData = t.processShownotes(false); + webviewData = t.processShownotes(); } return feedItem; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java index b9e961535..51d1c7ba9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java @@ -16,6 +16,7 @@ import androidx.viewpager.widget.ViewPager; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.CastEnabledActivity; import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.Flavors; @@ -24,6 +25,9 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Displays information about a list of FeedItems. @@ -104,12 +108,14 @@ public class ItemPagerFragment extends Fragment { } }); + EventBus.getDefault().register(this); return layout; } @Override public void onDestroyView() { super.onDestroyView(); + EventBus.getDefault().unregister(this); if (disposable != null) { disposable.dispose(); } @@ -162,6 +168,17 @@ public class ItemPagerFragment extends Fragment { } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEventMainThread(FeedItemEvent event) { + for (FeedItem item : event.items) { + if (this.item != null && this.item.getId() == item.getId()) { + this.item = item; + getActivity().invalidateOptionsMenu(); + return; + } + } + } + private void openPodcast() { Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); ((MainActivity) getActivity()).loadChildFragment(fragment); 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 bd3fd06b0..94f71894b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -16,6 +16,7 @@ import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; /** * Like 'EpisodesFragment' except that it only shows new episodes and @@ -27,11 +28,6 @@ public class NewEpisodesFragment extends EpisodesListFragment { private static final String PREF_NAME = "PrefNewEpisodesFragment"; @Override - protected boolean showOnlyNewEpisodes() { - return true; - } - - @Override protected String getPrefName() { return PREF_NAME; } @@ -44,7 +40,7 @@ public class NewEpisodesFragment extends EpisodesListFragment { @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.remove_all_new_flags_item).setVisible(!episodes.isEmpty()); + menu.findItem(R.id.remove_all_new_flags_item).setVisible(true); } @NonNull @@ -63,7 +59,7 @@ public class NewEpisodesFragment extends EpisodesListFragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder; + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) viewHolder; FeedItemMenuHandler.removeNewFlagWithUndo(NewEpisodesFragment.this, holder.getFeedItem()); } 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 a97e3dae8..923a6325c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -73,7 +73,7 @@ public class PlaybackHistoryFragment extends ListFragment { // played items shoudln't be transparent for this fragment since, *all* items // in this fragment will, by definition, be played. So it serves no purpose and can make // it harder to read. - adapter = new FeedItemlistAdapter(getActivity(), itemAccess, true, false); + adapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, true, false); setListAdapter(adapter); } @@ -93,7 +93,7 @@ public class PlaybackHistoryFragment extends ListFragment { } } - @Subscribe(sticky = true) + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEvent(DownloadEvent event) { Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; @@ -181,19 +181,6 @@ public class PlaybackHistoryFragment extends ListFragment { private final FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { @Override - public int getItemDownloadProgressPercent(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - return downloader.getDownloadRequest().getProgressPercent(); - } - } - } - return 0; - } - - @Override public int getCount() { return (playbackHistory != null) ? playbackHistory.size() : 0; } @@ -206,20 +193,6 @@ public class PlaybackHistoryFragment extends ListFragment { return null; } } - - @Override - public LongList getQueueIds() { - LongList queueIds = new LongList(); - if(playbackHistory == null) { - return queueIds; - } - for (FeedItem item : playbackHistory) { - if (item.isTagged(FeedItem.TAG_QUEUE)) { - queueIds.add(item.getId()); - } - } - return queueIds; - } }; private void loadItems() { 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 83c16a9ff..36c837a25 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -27,6 +27,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator; import com.google.android.material.snackbar.Snackbar; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; +import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -45,11 +46,9 @@ import de.danoeh.antennapod.core.event.PlayerStatusEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; @@ -84,7 +83,6 @@ public class QueueFragment extends Fragment { private ProgressBar progLoading; private List<FeedItem> queue; - private List<Downloader> downloaderList; private boolean isUpdatingFeeds = false; @@ -196,7 +194,6 @@ public class QueueFragment extends Fragment { public void onEventMainThread(DownloadEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; - downloaderList = update.downloaders; if (event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) { getActivity().invalidateOptionsMenu(); } @@ -214,7 +211,7 @@ public class QueueFragment extends Fragment { public void onEventMainThread(PlaybackPositionEvent event) { if (recyclerAdapter != null) { for (int i = 0; i < recyclerAdapter.getItemCount(); i++) { - QueueRecyclerAdapter.ViewHolder holder = (QueueRecyclerAdapter.ViewHolder) + EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i); if (holder != null && holder.isCurrentlyPlayingItem()) { holder.notifyPlaybackPositionUpdated(event); @@ -289,7 +286,7 @@ public class QueueFragment extends Fragment { MenuItem searchItem = menu.findItem(R.id.action_search); final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - sv.setQueryHint(getString(R.string.search_hint)); + sv.setQueryHint(getString(R.string.search_label)); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { @@ -472,15 +469,19 @@ public class QueueFragment extends Fragment { return super.onContextItemSelected(item); } + int position = FeedItemUtil.indexOfItemWithId(queue, selectedItem.getId()); + if (position < 0) { + Log.i(TAG, "Selected item no longer exist, ignoring selection"); + return super.onContextItemSelected(item); + } + switch(item.getItemId()) { case R.id.move_to_top_item: - int position = FeedItemUtil.indexOfItemWithId(queue, selectedItem.getId()); queue.add(0, queue.remove(position)); recyclerAdapter.notifyItemMoved(position, 0); DBWriter.moveQueueItemToTop(selectedItem.getId(), true); return true; case R.id.move_to_bottom_item: - position = FeedItemUtil.indexOfItemWithId(queue, selectedItem.getId()); queue.add(queue.size()-1, queue.remove(position)); recyclerAdapter.notifyItemMoved(position, queue.size()-1); DBWriter.moveQueueItemToBottom(selectedItem.getId(), true); @@ -506,7 +507,6 @@ public class QueueFragment extends Fragment { layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); - recyclerView.setHasFixedSize(true); registerForContextMenu(recyclerView); itemTouchHelper = new ItemTouchHelper( @@ -550,8 +550,9 @@ public class QueueFragment extends Fragment { final boolean isRead = item.isPlayed(); DBWriter.markItemPlayed(FeedItem.PLAYED, false, item.getId()); DBWriter.removeQueueItem(getActivity(), true, item); - Snackbar snackbar = Snackbar.make - (root, getString(item.hasMedia() ? R.string.marked_as_read_label: R.string.marked_as_read_no_media_label), Snackbar.LENGTH_LONG); + Snackbar snackbar = Snackbar.make(root, getString(item.hasMedia() + ? R.string.marked_as_read_label : R.string.marked_as_read_no_media_label), + Snackbar.LENGTH_LONG); snackbar.setAction(getString(R.string.undo), v -> { DBWriter.addQueueItemAt(getActivity(), item.getId(), position, false); if(!isRead) { @@ -688,46 +689,6 @@ public class QueueFragment extends Fragment { } @Override - public long getItemDownloadedBytes(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - Log.d(TAG, "downloaded bytes: " + downloader.getDownloadRequest().getSoFar()); - return downloader.getDownloadRequest().getSoFar(); - } - } - } - return 0; - } - - @Override - public long getItemDownloadSize(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - Log.d(TAG, "downloaded size: " + downloader.getDownloadRequest().getSize()); - return downloader.getDownloadRequest().getSize(); - } - } - } - return 0; - } - @Override - public int getItemDownloadProgressPercent(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - return downloader.getDownloadRequest().getProgressPercent(); - } - } - } - return 0; - } - - @Override public LongList getQueueIds() { return queue != null ? LongList.of(FeedItemUtil.getIds(queue)) : new LongList(0); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java index 7e8823c27..1bfbd2d78 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -104,11 +104,12 @@ public class RunningDownloadsFragment extends ListFragment { DownloadRequest downloadRequest = downloader.getDownloadRequest(); DownloadRequester.getInstance().cancelDownload(getActivity(), downloadRequest.getSource()); - if(downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA && - UserPreferences.isEnableAutodownload()) { + if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && UserPreferences.isEnableAutodownload()) { FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId()); DBWriter.setFeedItemAutoDownload(media.getItem(), false); - Toast.makeText(getActivity(), R.string.download_canceled_autodownload_enabled_msg, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.download_canceled_autodownload_enabled_msg, + Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getActivity(), R.string.download_canceled_msg, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 6befa7e18..7d284835d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -20,12 +20,12 @@ import androidx.core.view.MenuItemCompat; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.SearchlistAdapter; +import de.danoeh.antennapod.adapter.FeedItemlistAdapter; +import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedComponent; import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.SearchResult; import de.danoeh.antennapod.core.storage.FeedSearcher; import de.danoeh.antennapod.view.EmptyViewHandler; import io.reactivex.Observable; @@ -34,8 +34,10 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.ArrayList; import java.util.List; + import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; /** * Performs a search operation on all feeds or one specific feed and displays the search result. @@ -46,10 +48,9 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL private static final String ARG_QUERY = "query"; private static final String ARG_FEED = "feed"; - private SearchlistAdapter searchAdapter; - private List<SearchResult> searchResults = new ArrayList<>(); + private FeedItemlistAdapter searchAdapter; + private List<FeedComponent> searchResults = new ArrayList<>(); private Disposable disposable; - private ListView listView; private ProgressBar progressBar; private EmptyViewHandler emptyViewHandler; @@ -57,7 +58,9 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL * Create a new SearchFragment that searches all feeds. */ public static SearchFragment newInstance(String query) { - if (query == null) query = ""; + if (query == null) { + query = ""; + } SearchFragment fragment = new SearchFragment(); Bundle args = new Bundle(); args.putString(ARG_QUERY, query); @@ -103,9 +106,9 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label); View layout = inflater.inflate(R.layout.search_fragment, container, false); - listView = layout.findViewById(R.id.listview); + ListView listView = layout.findViewById(R.id.listview); progressBar = layout.findViewById(R.id.progressBar); - searchAdapter = new SearchlistAdapter(getActivity(), itemAccess); + searchAdapter = new FeedItemlistAdapter((MainActivity) getActivity(), itemAccess, true, true); listView.setAdapter(searchAdapter); listView.setOnItemClickListener(this); @@ -125,15 +128,12 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - SearchResult result = (SearchResult) listView.getAdapter().getItem(position); - FeedComponent comp = result.getComponent(); + FeedComponent comp = searchAdapter.getItem(position); if (comp.getClass() == Feed.class) { ((MainActivity) getActivity()).loadFeedFragmentById(comp.getId(), null); - } else { - if (comp.getClass() == FeedItem.class) { - FeedItem item = (FeedItem) comp; - ((MainActivity) getActivity()).loadChildFragment(ItemPagerFragment.newInstance(item.getId())); - } + } else if (comp.getClass() == FeedItem.class) { + FeedItem item = (FeedItem) comp; + ((MainActivity) getActivity()).loadChildFragment(ItemPagerFragment.newInstance(item.getId())); } } @@ -143,7 +143,7 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); final SearchView sv = new SearchView(getActivity()); - sv.setQueryHint(getString(R.string.search_hint)); + sv.setQueryHint(getString(R.string.search_label)); sv.setQuery(getArguments().getString(ARG_QUERY), false); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override @@ -167,7 +167,15 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL search(); } - private void onSearchResults(List<SearchResult> results) { + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + if (searchAdapter != null) { + searchAdapter.notifyDataSetChanged(); + } + } + + private void onSearchResults(List<FeedComponent> results) { progressBar.setVisibility(View.GONE); searchResults = results; searchAdapter.notifyDataSetChanged(); @@ -175,14 +183,14 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL emptyViewHandler.setMessage(getString(R.string.no_results_for_query, query)); } - private final SearchlistAdapter.ItemAccess itemAccess = new SearchlistAdapter.ItemAccess() { + private final FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { @Override public int getCount() { return searchResults.size(); } @Override - public SearchResult getItem(int position) { + public FeedComponent getItem(int position) { if (0 <= position && position < searchResults.size()) { return searchResults.get(position); } else { @@ -204,7 +212,7 @@ public class SearchFragment extends Fragment implements AdapterView.OnItemClickL } @NonNull - private List<SearchResult> performSearch() { + private List<FeedComponent> performSearch() { Bundle args = getArguments(); String query = args.getString(ARG_QUERY); long feed = args.getLong(ARG_FEED); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index af3649ed0..d0f6772ea 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -304,12 +304,12 @@ public class SubscriptionFragment extends Fragment { dialog.createNewDialog().show(); } - @Subscribe + @Subscribe(threadMode = ThreadMode.MAIN) public void onFeedListChanged(FeedListUpdateEvent event) { loadSubscriptions(); } - @Subscribe + @Subscribe(threadMode = ThreadMode.MAIN) public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { loadSubscriptions(); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java index 34ea6d6e3..3059d7ad2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java @@ -16,6 +16,7 @@ import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadStatisticsListAdapter; import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.comparator.CompareCompat; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -23,6 +24,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.Collections; +import java.util.List; /** * Displays the 'download statistics' screen @@ -71,8 +73,8 @@ public class DownloadStatisticsFragment extends Fragment { disposable = Observable.fromCallable(() -> { - DBReader.StatisticsData statisticsData = DBReader.getStatistics(); - Collections.sort(statisticsData.feeds, (item1, item2) -> + List<StatisticsItem> statisticsData = DBReader.getStatistics(); + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.totalDownloadSize, item2.totalDownloadSize)); return statisticsData; }) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java index c6ae8e20c..1e51380ca 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java @@ -74,16 +74,14 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { dialog.show(); return true; }); - findPreference(PREF_GPODNET_SYNC). - setOnPreferenceClickListener(preference -> { + findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> { GpodnetSyncService.sendSyncIntent(getActivity().getApplicationContext()); Toast toast = Toast.makeText(getActivity(), R.string.pref_gpodnet_sync_started, Toast.LENGTH_SHORT); toast.show(); return true; }); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC). - setOnPreferenceClickListener(preference -> { + findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { GpodnetPreferences.setLastSubscriptionSyncTimestamp(0L); GpodnetPreferences.setLastEpisodeActionsSyncTimestamp(0L); GpodnetPreferences.setLastSyncAttempt(false, 0); @@ -94,17 +92,16 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat { toast.show(); return true; }); - findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener( - preference -> { + findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> { GpodnetPreferences.logout(); Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT); toast.show(); updateGpodnetPreferenceScreen(); return true; }); - findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener( - preference -> { - GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(dialog -> updateGpodnetPreferenceScreen()); + findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> { + GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener( + dialog -> updateGpodnetPreferenceScreen()); return true; }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java new file mode 100644 index 000000000..4f0feef47 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -0,0 +1,284 @@ +package de.danoeh.antennapod.fragment.preferences; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceFragmentCompat; +import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.OpmlImportActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.activity.SplashActivity; +import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; +import de.danoeh.antennapod.asynctask.ExportWorker; +import de.danoeh.antennapod.core.export.ExportWriter; +import de.danoeh.antennapod.core.export.html.HtmlWriter; +import de.danoeh.antennapod.core.export.opml.OpmlWriter; +import de.danoeh.antennapod.core.storage.DatabaseExporter; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.List; + +public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { + private static final String TAG = "ImportExPrefFragment"; + private static final String PREF_OPML_EXPORT = "prefOpmlExport"; + private static final String PREF_OPML_IMPORT = "prefOpmlImport"; + private static final String PREF_HTML_EXPORT = "prefHtmlExport"; + private static final String PREF_DATABASE_IMPORT = "prefDatabaseImport"; + private static final String PREF_DATABASE_EXPORT = "prefDatabaseExport"; + private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; + private static final String CONTENT_TYPE_OPML = "text/x-opml"; + private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; + private static final String CONTENT_TYPE_HTML = "text/html"; + private static final int REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH = 1; + private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 2; + private static final int REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH = 3; + private static final int REQUEST_CODE_RESTORE_DATABASE = 4; + private static final int REQUEST_CODE_BACKUP_DATABASE = 5; + private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup.db"; + private Disposable disposable; + private ProgressDialog progressDialog; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_import_export); + setupStorageScreen(); + progressDialog = new ProgressDialog(getContext()); + progressDialog.setIndeterminate(true); + progressDialog.setMessage(getContext().getString(R.string.please_wait)); + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.import_export_pref); + } + + @Override + public void onStop() { + super.onStop(); + if (disposable != null) { + disposable.dispose(); + } + } + + private void setupStorageScreen() { + findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( + preference -> { + openExportPathPicker(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter()); + return true; + } + ); + findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( + preference -> { + openExportPathPicker(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME, + REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter()); + return true; + }); + findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( + preference -> { + try { + Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + return true; + }); + findPreference(PREF_DATABASE_IMPORT).setOnPreferenceClickListener( + preference -> { + importDatabase(); + return true; + }); + findPreference(PREF_DATABASE_EXPORT).setOnPreferenceClickListener( + preference -> { + exportDatabase(); + return true; + }); + } + + private void exportWithWriter(ExportWriter exportWriter, final Uri uri) { + Context context = getActivity(); + progressDialog.show(); + if (uri == null) { + Observable<File> observable = new ExportWorker(exportWriter, getContext()).exportObservable(); + disposable = observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> { + Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), + context.getString(R.string.provider_authority), output); + showExportSuccessDialog(output.toString(), fileUri); + }, this::showExportErrorDialog, progressDialog::dismiss); + } else { + DocumentFileExportWorker worker = new DocumentFileExportWorker(exportWriter, context, uri); + disposable = worker.exportObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(output -> + showExportSuccessDialog(output.getUri().toString(), output.getUri()), + this::showExportErrorDialog, progressDialog::dismiss); + } + } + + private void exportDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/x-sqlite3") + .putExtra(Intent.EXTRA_TITLE, DATABASE_EXPORT_FILENAME); + + startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE); + } else { + File sd = Environment.getExternalStorageDirectory(); + File backupDB = new File(sd, DATABASE_EXPORT_FILENAME); + progressDialog.show(); + disposable = Completable.fromAction(() -> + DatabaseExporter.exportToStream(new FileOutputStream(backupDB), getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + } + + private void importDatabase() { + if (Build.VERSION.SDK_INT >= 19) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + startActivityForResult(intent, REQUEST_CODE_RESTORE_DATABASE); + } else { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + startActivityForResult(Intent.createChooser(intent, + getString(R.string.import_select_file)), REQUEST_CODE_RESTORE_DATABASE); + } + } + + private void showDatabaseImportSuccessDialog() { + AlertDialog.Builder d = new AlertDialog.Builder(getContext()); + d.setMessage(R.string.import_ok); + d.setCancelable(false); + d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { + Intent intent = new Intent(getContext(), SplashActivity.class); + ComponentName cn = intent.getComponent(); + Intent mainIntent = Intent.makeRestartActivityTask(cn); + startActivity(mainIntent); + Runtime.getRuntime().exit(0); + }); + d.show(); + } + + private void showExportSuccessDialog(final String path, final Uri streamUri) { + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_success_title); + alert.setMessage(getContext().getString(R.string.export_success_sum, path)); + alert.setPositiveButton(R.string.send_label, (dialog, which) -> { + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); + sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); + sendIntent.setType("text/plain"); + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + List<ResolveInfo> resInfoList = getContext().getPackageManager() + .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); + }); + alert.create().show(); + } + + private void showExportErrorDialog(final Throwable error) { + progressDialog.dismiss(); + final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + alert.setTitle(R.string.export_error_label); + alert.setMessage(error.getMessage()); + alert.show(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK || data == null) { + return; + } + Uri uri = data.getData(); + + if (requestCode == REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH) { + exportWithWriter(new OpmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH) { + exportWithWriter(new HtmlWriter(), uri); + } else if (requestCode == REQUEST_CODE_RESTORE_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + showDatabaseImportSuccessDialog(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) { + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } else if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { + Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + } + + private void openExportPathPicker(String contentType, String title, int requestCode, ExportWriter writer) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(contentType) + .putExtra(Intent.EXTRA_TITLE, title); + + // Creates an implicit intent to launch a file manager which lets + // the user choose a specific directory to export to. + try { + startActivityForResult(intentPickAction, requestCode); + return; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); + } + } + + // If we are using a SDK lower than API 21 or the implicit intent failed + // fallback to the legacy export process + exportWithWriter(writer, null); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 5fd38d663..da82d4f8c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -103,6 +103,9 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)); config.index(R.xml.preferences_storage) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)); + config.index(R.xml.preferences_import_export) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_storage)) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_import_export)); config.index(R.xml.preferences_autodownload) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)) .addBreadcrumb(R.string.automation) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java index bed767e8e..d25dff743 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java @@ -27,6 +27,7 @@ import de.danoeh.antennapod.adapter.PlaybackStatisticsListAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.comparator.CompareCompat; import io.reactivex.Completable; import io.reactivex.Observable; @@ -35,6 +36,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.Collections; +import java.util.List; /** * Displays the 'playback statistics' screen @@ -180,13 +182,13 @@ public class PlaybackStatisticsFragment extends Fragment { }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - private DBReader.StatisticsData fetchStatistics() { - DBReader.StatisticsData statisticsData = DBReader.getStatistics(); + private List<StatisticsItem> fetchStatistics() { + List<StatisticsItem> statisticsData = DBReader.getStatistics(); if (countAll) { - Collections.sort(statisticsData.feeds, (item1, item2) -> + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.timePlayedCountAll, item2.timePlayedCountAll)); } else { - Collections.sort(statisticsData.feeds, (item1, item2) -> + Collections.sort(statisticsData, (item1, item2) -> CompareCompat.compareLong(item1.timePlayed, item2.timePlayed)); } return statisticsData; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java index 2c1590c47..8a0742b7f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StoragePreferencesFragment.java @@ -1,62 +1,32 @@ package de.danoeh.antennapod.fragment.preferences; import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; -import android.app.ProgressDialog; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import androidx.core.content.FileProvider; -import androidx.documentfile.provider.DocumentFile; +import android.util.Log; import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; import androidx.preference.PreferenceFragmentCompat; -import android.util.Log; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.DirectoryChooserActivity; -import de.danoeh.antennapod.activity.ImportExportActivity; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.asynctask.DocumentFileExportWorker; -import de.danoeh.antennapod.asynctask.ExportWorker; -import de.danoeh.antennapod.core.export.ExportWriter; -import de.danoeh.antennapod.core.export.html.HtmlWriter; -import de.danoeh.antennapod.core.export.opml.OpmlWriter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.ChooseDataFolderDialog; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; import java.io.File; -import java.util.List; public class StoragePreferencesFragment extends PreferenceFragmentCompat { private static final String TAG = "StoragePrefFragment"; - private static final String PREF_OPML_EXPORT = "prefOpmlExport"; - private static final String PREF_OPML_IMPORT = "prefOpmlImport"; - private static final String PREF_HTML_EXPORT = "prefHtmlExport"; - private static final String IMPORT_EXPORT = "importExport"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; + private static final String PREF_IMPORT_EXPORT = "prefImportExport"; private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; - private static final int CHOOSE_OPML_EXPORT_PATH = 1; - private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml"; - private static final String CONTENT_TYPE_OPML = "text/x-opml"; - private static final int CHOOSE_HTML_EXPORT_PATH = 2; - private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html"; - private static final String CONTENT_TYPE_HTML = "text/html"; - private Disposable disposable; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { @@ -76,51 +46,20 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { setDataFolderText(); } - @Override - public void onStop() { - super.onStop(); - if (disposable != null) { - disposable.dispose(); - } - } - private void setupStorageScreen() { final Activity activity = getActivity(); - - findPreference(IMPORT_EXPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, ImportExportActivity.class)); - return true; - } - ); - findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> { - openOpmlExportPathPicker(); - return true; - } - ); - findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( - preference -> { - openHtmlExportPathPicker(); - return true; - }); - findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class)); - return true; - }); findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( preference -> { - if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && - Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { showChooseDataFolderDialog(); } else { int readPermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.READ_EXTERNAL_STORAGE); int writePermission = ActivityCompat.checkSelfPermission( activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (readPermission == PackageManager.PERMISSION_GRANTED && - writePermission == PackageManager.PERMISSION_GRANTED) { + if (readPermission == PackageManager.PERMISSION_GRANTED + && writePermission == PackageManager.PERMISSION_GRANTED) { openDirectoryChooser(); } else { requestPermission(); @@ -129,19 +68,18 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(PREF_CHOOSE_DATA_DIR) - .setOnPreferenceClickListener( - 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; - } - ); + findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( + 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; + } + ); findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( (preference, o) -> { if (o instanceof String) { @@ -158,74 +96,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { return false; } ); - } - - private boolean export(ExportWriter exportWriter) { - return export(exportWriter, null); - } - - private boolean export(ExportWriter exportWriter, final Uri uri) { - Context context = getActivity(); - final ProgressDialog progressDialog = new ProgressDialog(context); - progressDialog.setMessage(context.getString(R.string.exporting_label)); - progressDialog.setIndeterminate(true); - progressDialog.show(); - if (uri == null) { - Observable<File> observable = new ExportWorker(exportWriter).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(), - context.getString(R.string.provider_authority), output); - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri); - }, this::showExportErrorDialog, progressDialog::dismiss); - } else { - Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable(); - disposable = observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(output -> { - showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri()); - }, this::showExportErrorDialog, progressDialog::dismiss); - } - return true; - } - - private void showExportSuccessDialog(final String message, final Uri streamUri) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_success_title); - alert.setMessage(message); - alert.setPositiveButton(R.string.send_label, (dialog, which) -> { - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label)); - sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); - sendIntent.setType("text/plain"); - sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = getContext().getPackageManager() - .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + findPreference(PREF_IMPORT_EXPORT).setOnPreferenceClickListener( + preference -> { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_import_export); + return true; } - } - getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); - }); - alert.create().show(); - } - - private void showExportErrorDialog(final Throwable error) { - final AlertDialog.Builder alert = new AlertDialog.Builder(getContext()) - .setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - alert.setTitle(R.string.export_error_label); - alert.setMessage(error.getMessage()); - alert.show(); + ); } - @SuppressLint("NewApi") public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && - requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + if (resultCode == Activity.RESULT_OK && requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); File path; @@ -255,23 +135,12 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { ab.show(); } } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new OpmlWriter(), uri); - } - - if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) { - Uri uri = data.getData(); - export(new HtmlWriter(), uri); - } } private void setDataFolderText() { File f = UserPreferences.getDataFolder(null); if (f != null) { - findPreference(PREF_CHOOSE_DATA_DIR) - .setSummary(f.getAbsolutePath()); + findPreference(PREF_CHOOSE_DATA_DIR).setSummary(f.getAbsolutePath()); } } @@ -286,50 +155,6 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat { activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); } - private void openOpmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_OPML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_OPML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_OPML_EXPORT_PATH); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - // If we are using a SDK lower than API 21 or the implicit intent failed - // fallback to the legacy export process - export(new OpmlWriter()); - } - - private void openHtmlExportPathPicker() { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(CONTENT_TYPE_HTML) - .putExtra(Intent.EXTRA_TITLE, DEFAULT_HTML_OUTPUT_NAME); - - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, CHOOSE_HTML_EXPORT_PATH); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } - } - - // If we are using a SDK lower than API 21 or the implicit intent failed - // fallback to the legacy export process - export(new HtmlWriter()); - } - private void showChooseDataFolderDialog() { ChooseDataFolderDialog.showDialog( getActivity(), new ChooseDataFolderDialog.RunnableWithString() { diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 4686f0303..e791efe98 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -84,5 +84,8 @@ public class PreferenceUpgrader { UserPreferences.setEnqueueLocation(enqueueLocation); } } + if (oldVersion < 1080100) { + prefs.edit().putString(UserPreferences.PREF_VIDEO_BEHAVIOR, "pip").apply(); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java new file mode 100644 index 000000000..4b3c51cfc --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java @@ -0,0 +1,87 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import androidx.annotation.Nullable; + +public class CircularProgressBar extends View { + private static final float EPSILON = 0.005f; + + private final Paint paintBackground = new Paint(); + private final Paint paintProgress = new Paint(); + private float percentage = 0; + private float targetPercentage = 0; + private Object tag = null; + + public CircularProgressBar(Context context) { + super(context); + setup(); + } + + public CircularProgressBar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + paintBackground.setAntiAlias(true); + paintBackground.setStyle(Paint.Style.STROKE); + + paintProgress.setAntiAlias(true); + paintProgress.setStyle(Paint.Style.STROKE); + paintProgress.setStrokeCap(Paint.Cap.ROUND); + + int[] colorAttrs = new int[] { android.R.attr.textColorPrimary, android.R.attr.textColorSecondary }; + TypedArray a = getContext().obtainStyledAttributes(colorAttrs); + paintProgress.setColor(a.getColor(0, 0xffffffff)); + paintBackground.setColor(a.getColor(1, 0xffffffff)); + a.recycle(); + } + + /** + * Sets the percentage to be displayed. + * @param percentage Number from 0 to 1 + * @param tag When the tag is the same as last time calling setPercentage, the update is animated + */ + public void setPercentage(float percentage, Object tag) { + targetPercentage = percentage; + + if (tag == null || !tag.equals(this.tag)) { + // Do not animate + this.percentage = percentage; + this.tag = tag; + } + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float padding = getHeight() * 0.06f; + paintBackground.setStrokeWidth(getHeight() * 0.02f); + paintProgress.setStrokeWidth(padding); + RectF bounds = new RectF(padding, padding, getWidth() - padding, getHeight() - padding); + canvas.drawArc(bounds, 0, 360, false, paintBackground); + + if (percentage > EPSILON && 1 - percentage > EPSILON) { + canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress); + } + + if (Math.abs(percentage - targetPercentage) > EPSILON) { + float delta = Math.min(0.02f, Math.abs(targetPercentage - percentage)); + percentage += delta * ((targetPercentage - percentage) > 0 ? 1f : -1f); + invalidate(); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java new file mode 100644 index 000000000..60ef820a9 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java @@ -0,0 +1,105 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import androidx.annotation.Nullable; +import androidx.vectordrawable.graphics.drawable.ArgbEvaluator; +import androidx.viewpager.widget.ViewPager; + +public class PagerIndicatorView extends View { + private final Paint paint = new Paint(); + private float position = 0; + private int numPages = 0; + private int disabledPage = -1; + private int circleColor = 0; + private int circleColorHighlight = -1; + + public PagerIndicatorView(Context context) { + super(context); + setup(); + } + + public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + + int[] colorAttrs = new int[] { android.R.attr.textColorSecondary }; + TypedArray a = getContext().obtainStyledAttributes(colorAttrs); + circleColorHighlight = a.getColor(0, 0xffffffff); + circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight); + a.recycle(); + } + + public void setViewPager(ViewPager pager) { + numPages = pager.getAdapter().getCount(); + pager.getAdapter().registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + numPages = pager.getAdapter().getCount(); + invalidate(); + } + }); + pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + PagerIndicatorView.this.position = position + positionOffset; + invalidate(); + } + }); + } + + public void setDisabledPage(int disabledPage) { + this.disabledPage = disabledPage; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + for (int i = 0; i < numPages; i++) { + if ((int) Math.floor(position) == i) { + // This is the current dot + drawCircle(canvas, i, (float) (1 - (position - Math.floor(position)))); + } else if ((int) Math.ceil(position) == i) { + // This is the next dot + drawCircle(canvas, i, (float) (position - Math.floor(position))); + } else { + drawCircle(canvas, i, 0); + } + } + } + + private void drawCircle(Canvas canvas, int position, float frac) { + float circleRadiusSmall = canvas.getHeight() * 0.26f; + float circleRadiusBig = canvas.getHeight() * 0.35f; + float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall); + float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * canvas.getHeight()); + paint.setStrokeWidth(canvas.getHeight() * 0.3f); + + if (position == disabledPage) { + paint.setStyle(Paint.Style.STROKE); + } else { + paint.setStyle(Paint.Style.FILL_AND_STROKE); + } + + paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight)); + canvas.drawCircle(start + (position * 1.5f + 0.75f) * canvas.getHeight(), 0.5f * canvas.getHeight(), + circleRadiusSmall + frac * circleRadiusDelta, paint); + } +}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java index d1b2abf23..ab4920119 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java @@ -39,14 +39,10 @@ public class PieChartView extends AppCompatImageView { } /** - * Set array od names, array of values and array of colors. + * Set of data values to display. */ - public void setData(float[] dataValues) { - drawable.dataValues = dataValues; - drawable.valueSum = 0; - for (float datum : dataValues) { - drawable.valueSum += datum; - } + public void setData(PieChartData data) { + drawable.data = data; } @Override @@ -56,15 +52,49 @@ public class PieChartView extends AppCompatImageView { setMeasuredDimension(width, width / 2); } - private static class PieChartDrawable extends Drawable { - private static final float MIN_DEGREES = 10f; - private static final float PADDING_DEGREES = 3f; - private static final float STROKE_SIZE = 15f; + public static class PieChartData { private static final int[] COLOR_VALUES = new int[]{0xFF3775E6, 0xffe51c23, 0xffff9800, 0xff259b24, 0xff9c27b0, 0xff0099c6, 0xffdd4477, 0xff66aa00, 0xffb82e2e, 0xff316395, 0xff994499, 0xff22aa99, 0xffaaaa11, 0xff6633cc, 0xff0073e6}; - private float[] dataValues; - private float valueSum; + + private final float valueSum; + private final float[] values; + + public PieChartData(float[] values) { + this.values = values; + float valueSum = 0; + for (float datum : values) { + valueSum += datum; + } + this.valueSum = valueSum; + } + + public float getSum() { + return valueSum; + } + + public float getPercentageOfItem(int index) { + if (valueSum == 0) { + return 0; + } + return values[index] / valueSum; + } + + public boolean isLargeEnoughToDisplay(int index) { + return getPercentageOfItem(index) > 0.04; + } + + public int getColorOfItem(int index) { + if (!isLargeEnoughToDisplay(index)) { + return Color.GRAY; + } + return COLOR_VALUES[index % COLOR_VALUES.length]; + } + } + + private static class PieChartDrawable extends Drawable { + private static final float PADDING_DEGREES = 3f; + private PieChartData data; private final Paint paint; private PieChartDrawable() { @@ -73,27 +103,25 @@ public class PieChartView extends AppCompatImageView { paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); - paint.setStrokeWidth(STROKE_SIZE); } @Override public void draw(@NonNull Canvas canvas) { - if (valueSum == 0) { - return; - } - float radius = getBounds().height() - STROKE_SIZE; + final float strokeSize = getBounds().height() / 30f; + paint.setStrokeWidth(strokeSize); + + float radius = getBounds().height() - strokeSize; float center = getBounds().width() / 2.f; - RectF arcBounds = new RectF(center - radius, STROKE_SIZE, center + radius, STROKE_SIZE + radius * 2); + RectF arcBounds = new RectF(center - radius, strokeSize, center + radius, strokeSize + radius * 2); float startAngle = 180; - for (int i = 0; i < dataValues.length; i++) { - float datum = dataValues[i]; - float sweepAngle = (180f - PADDING_DEGREES) * (datum / valueSum); - if (sweepAngle < MIN_DEGREES) { + for (int i = 0; i < data.values.length; i++) { + if (!data.isLargeEnoughToDisplay(i)) { break; } - paint.setColor(COLOR_VALUES[i % COLOR_VALUES.length]); + paint.setColor(data.getColorOfItem(i)); float padding = i == 0 ? PADDING_DEGREES / 2 : PADDING_DEGREES; + float sweepAngle = (180f - PADDING_DEGREES) * data.getPercentageOfItem(i); canvas.drawArc(arcBounds, startAngle + padding, sweepAngle - padding, false, paint); startAngle = startAngle + sweepAngle; } diff --git a/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java new file mode 100644 index 000000000..3ea57eb5e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java @@ -0,0 +1,167 @@ +package de.danoeh.antennapod.view; + +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.core.content.ContextCompat; +import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.Consumer; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.playback.Timeline; + +public class ShownotesWebView extends WebView implements View.OnLongClickListener { + private static final String TAG = "ShownotesWebView"; + + /** + * URL that was selected via long-press. + */ + private String selectedUrl; + private Consumer<Integer> timecodeSelectedListener; + private Runnable pageFinishedListener; + + public ShownotesWebView(Context context) { + super(context); + setup(); + } + + public ShownotesWebView(Context context, AttributeSet attrs) { + super(context, attrs); + setup(); + } + + public ShownotesWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(); + } + + private void setup() { + setBackgroundColor(Color.TRANSPARENT); + if (!NetworkUtils.networkAvailable()) { + getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + // Use cached resources, even if they have expired + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + } + getSettings().setUseWideViewPort(false); + getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + getSettings().setLoadWithOverviewMode(true); + setOnLongClickListener(this); + + setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (Timeline.isTimecodeLink(url) && timecodeSelectedListener != null) { + timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl)); + } else { + IntentUtils.openInBrowser(getContext(), url); + } + return true; + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Log.d(TAG, "Page finished"); + if (pageFinishedListener != null) { + pageFinishedListener.run(); + } + } + }); + } + + @Override + public boolean onLongClick(View v) { + WebView.HitTestResult r = getHitTestResult(); + if (r != null && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { + Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); + selectedUrl = r.getExtra(); + showContextMenu(); + return true; + } + selectedUrl = null; + return false; + } + + public boolean onContextItemSelected(MenuItem item) { + if (selectedUrl == null) { + return false; + } + + switch (item.getItemId()) { + case R.id.open_in_browser_item: + IntentUtils.openInBrowser(getContext(), selectedUrl); + break; + case R.id.share_url_item: + ShareUtils.shareLink(getContext(), selectedUrl); + break; + case R.id.copy_url_item: + ClipData clipData = ClipData.newPlainText(selectedUrl, selectedUrl); + android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(clipData); + Snackbar.make(this, R.string.copied_url_msg, Snackbar.LENGTH_LONG).show(); + break; + case R.id.go_to_position_item: + if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) { + timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl)); + } else { + Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl); + } + break; + default: + selectedUrl = null; + return false; + + } + selectedUrl = null; + return true; + } + + @Override + protected void onCreateContextMenu(ContextMenu menu) { + super.onCreateContextMenu(menu); + if (selectedUrl == null) { + return; + } + + if (Timeline.isTimecodeLink(selectedUrl)) { + menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, R.string.go_to_position_label); + menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedUrl))); + } else { + Uri uri = Uri.parse(selectedUrl); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if (IntentUtils.isCallable(getContext(), intent)) { + menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, R.string.open_in_browser_label); + } + menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, R.string.copy_url_label); + menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, R.string.share_url_label); + menu.setHeaderTitle(selectedUrl); + } + } + + public void setTimecodeSelectedListener(Consumer<Integer> timecodeSelectedListener) { + this.timecodeSelectedListener = timecodeSelectedListener; + } + + public void setPageFinishedListener(Runnable pageFinishedListener) { + this.pageFinishedListener = pageFinishedListener; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java index f82309c4a..dcf1edbe7 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java +++ b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java @@ -1,13 +1,16 @@ package de.danoeh.antennapod.view; import android.content.Context; +import android.content.res.TypedArray; import androidx.appcompat.widget.AppCompatImageView; import android.util.AttributeSet; +import de.danoeh.antennapod.core.R; /** * From http://stackoverflow.com/a/19449488/6839 */ public class SquareImageView extends AppCompatImageView { + private boolean useMinimum = false; public SquareImageView(Context context) { super(context); @@ -15,19 +18,29 @@ public class SquareImageView extends AppCompatImageView { public SquareImageView(Context context, AttributeSet attrs) { super(context, attrs); + loadAttrs(context, attrs); } public SquareImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + loadAttrs(context, attrs); + } + + private void loadAttrs(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.styleable.SquareImageView_useMinimum}); + useMinimum = a.getBoolean(0, false); + a.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int width = getMeasuredWidth(); - //noinspection SuspiciousNameCombination - setMeasuredDimension(width, width); + int size = getMeasuredWidth(); + if (useMinimum) { + size = Math.min(getMeasuredWidth(), getMeasuredHeight()); + } + setMeasuredDimension(size, size); } }
\ 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 deleted file mode 100644 index f4ee092df..000000000 --- a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java +++ /dev/null @@ -1,44 +0,0 @@ -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/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java new file mode 100644 index 000000000..d48db196f --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java @@ -0,0 +1,41 @@ +package de.danoeh.antennapod.view.viewholder; + +import android.content.Context; +import android.os.Build; +import android.text.Layout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; +import com.joanzapata.iconify.widget.IconTextView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.view.CircularProgressBar; + +public class DownloadItemViewHolder extends RecyclerView.ViewHolder { + public final View secondaryActionButton; + public final ImageView secondaryActionIcon; + public final CircularProgressBar secondaryActionProgress; + public final IconTextView icon; + public final TextView title; + public final TextView type; + public final TextView date; + public final TextView reason; + + public DownloadItemViewHolder(Context context, ViewGroup parent) { + super(LayoutInflater.from(context).inflate(R.layout.downloadlog_item, parent, false)); + date = itemView.findViewById(R.id.txtvDate); + type = itemView.findViewById(R.id.txtvType); + icon = itemView.findViewById(R.id.txtvIcon); + reason = itemView.findViewById(R.id.txtvReason); + secondaryActionProgress = itemView.findViewById(R.id.secondaryActionProgress); + secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton); + secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon); + title = itemView.findViewById(R.id.txtvTitle); + if (Build.VERSION.SDK_INT >= 23) { + title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } + itemView.setTag(this); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java new file mode 100644 index 000000000..369574190 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -0,0 +1,213 @@ +package de.danoeh.antennapod.view.viewholder; + +import android.graphics.Color; +import android.os.Build; +import android.text.Layout; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; +import com.joanzapata.iconify.Iconify; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.CoverLoader; +import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; +import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; +import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; +import de.danoeh.antennapod.core.service.download.DownloadRequest; +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.NetworkUtils; +import de.danoeh.antennapod.core.util.ThemeUtils; +import de.danoeh.antennapod.view.CircularProgressBar; + +/** + * Holds the view which shows FeedItems. + */ +public class EpisodeItemViewHolder extends FeedComponentViewHolder + implements QueueRecyclerAdapter.ItemTouchHelperViewHolder { + private static final String TAG = "EpisodeItemViewHolder"; + + private final View container; + public final ImageView dragHandle; + private final TextView placeholder; + private final ImageView cover; + private final TextView title; + private final TextView pubDate; + private final TextView position; + private final TextView duration; + private final TextView size; + public final TextView isNew; + public final ImageView isInQueue; + private final ImageView isVideo; + public final ImageView isFavorite; + private final ProgressBar progressBar; + public final View secondaryActionButton; + public final ImageView secondaryActionIcon; + private final CircularProgressBar secondaryActionProgress; + private final TextView separatorIcons; + public final CardView coverHolder; + + private final MainActivity activity; + private FeedItem item; + + public EpisodeItemViewHolder(MainActivity activity, ViewGroup parent) { + super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)); + this.activity = activity; + container = itemView.findViewById(R.id.container); + dragHandle = itemView.findViewById(R.id.drag_handle); + placeholder = itemView.findViewById(R.id.txtvPlaceholder); + cover = itemView.findViewById(R.id.imgvCover); + title = itemView.findViewById(R.id.txtvTitle); + if (Build.VERSION.SDK_INT >= 23) { + title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } + pubDate = itemView.findViewById(R.id.txtvPubDate); + position = itemView.findViewById(R.id.txtvPosition); + duration = itemView.findViewById(R.id.txtvDuration); + progressBar = itemView.findViewById(R.id.progressBar); + isInQueue = itemView.findViewById(R.id.ivInPlaylist); + isVideo = itemView.findViewById(R.id.ivIsVideo); + isNew = itemView.findViewById(R.id.statusUnread); + isFavorite = itemView.findViewById(R.id.isFavorite); + size = itemView.findViewById(R.id.size); + separatorIcons = itemView.findViewById(R.id.separatorIcons); + secondaryActionProgress = itemView.findViewById(R.id.secondaryActionProgress); + secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton); + secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon); + coverHolder = itemView.findViewById(R.id.coverHolder); + itemView.setTag(this); + } + + @Override + public void onItemSelected() { + itemView.setAlpha(0.5f); + } + + @Override + public void onItemClear() { + itemView.setAlpha(1.0f); + } + + public void bind(FeedItem item) { + this.item = item; + placeholder.setText(item.getFeed().getTitle()); + title.setText(item.getTitle()); + pubDate.setText(DateUtils.formatAbbrev(activity, item.getPubDate())); + isNew.setVisibility(item.isNew() ? View.VISIBLE : View.GONE); + isFavorite.setVisibility(item.isTagged(FeedItem.TAG_FAVORITE) ? View.VISIBLE : View.GONE); + isInQueue.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE); + itemView.setAlpha(item.isPlayed() ? 0.5f : 1.0f); + + ItemActionButton actionButton = ItemActionButton.forItem(item, true, true); + actionButton.configure(secondaryActionButton, secondaryActionIcon, activity); + secondaryActionButton.setFocusable(false); + + if (item.getMedia() != null) { + bind(item.getMedia()); + } else { + secondaryActionProgress.setPercentage(0, item); + } + + if (coverHolder.getVisibility() == View.VISIBLE) { + new CoverLoader(activity) + .withUri(ImageResourceUtils.getImageLocation(item)) + .withFallbackUri(item.getFeed().getImageLocation()) + .withPlaceholderView(placeholder) + .withCoverView(cover) + .load(); + } + } + + private void bind(FeedMedia media) { + isVideo.setVisibility(media.getMediaType() == MediaType.VIDEO ? View.VISIBLE : View.GONE); + duration.setText(Converter.getDurationStringLong(media.getDuration())); + + if (media.isCurrentlyPlaying()) { + container.setBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.currently_playing_background)); + } else { + container.setBackgroundResource(ThemeUtils.getDrawableFromAttr(activity, R.attr.selectableItemBackground)); + } + + if (DownloadRequester.getInstance().isDownloadingFile(media)) { + final DownloadRequest downloadRequest = DownloadRequester.getInstance().getRequestFor(media); + float percent = 0.01f * downloadRequest.getProgressPercent(); + secondaryActionProgress.setPercentage(Math.max(percent, 0.01f), item); + } else if (media.isDownloaded()) { + secondaryActionProgress.setPercentage(1, item); // Do not animate 100% -> 0% + } else { + secondaryActionProgress.setPercentage(0, item); // Animate X% -> 0% + } + + if (media.getDuration() > 0 + && (item.getState() == FeedItem.State.PLAYING || item.getState() == FeedItem.State.IN_PROGRESS)) { + int progress = (int) (100.0 * media.getPosition() / media.getDuration()); + progressBar.setProgress(progress); + position.setText(Converter.getDurationStringLong(media.getPosition())); + duration.setText(Converter.getDurationStringLong(media.getDuration())); + progressBar.setVisibility(View.VISIBLE); + position.setVisibility(View.VISIBLE); + } else { + progressBar.setVisibility(View.GONE); + position.setVisibility(View.GONE); + } + + if (media.getSize() > 0) { + size.setText(Converter.byteToString(media.getSize())); + } else if (NetworkUtils.isEpisodeHeadDownloadAllowed() && !media.checkedOnSizeButUnknown()) { + size.setText("{fa-spinner}"); + Iconify.addIcons(size); + NetworkUtils.getFeedMediaSizeObservable(media).subscribe( + sizeValue -> { + if (sizeValue > 0) { + size.setText(Converter.byteToString(sizeValue)); + } else { + size.setText(""); + } + }, error -> { + size.setText(""); + Log.e(TAG, Log.getStackTraceString(error)); + }); + } else { + size.setText(""); + } + } + + public FeedItem getFeedItem() { + return item; + } + + public boolean isCurrentlyPlayingItem() { + return item.getMedia() != null && item.getMedia().isCurrentlyPlaying(); + } + + public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) { + progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration())); + position.setText(Converter.getDurationStringLong(event.getPosition())); + duration.setText(Converter.getDurationStringLong(event.getDuration())); + } + + /** + * Hides the separator dot between icons and text if there are no icons. + */ + public void hideSeparatorIfNecessary() { + boolean hasIcons = isNew.getVisibility() == View.VISIBLE + || isInQueue.getVisibility() == View.VISIBLE + || isVideo.getVisibility() == View.VISIBLE + || isFavorite.getVisibility() == View.VISIBLE + || isNew.getVisibility() == View.VISIBLE; + separatorIcons.setVisibility(hasIcons ? View.VISIBLE : View.GONE); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java new file mode 100644 index 000000000..f55ea9bc8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java @@ -0,0 +1,15 @@ +package de.danoeh.antennapod.view.viewholder; + +import android.view.View; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Holds the view which shows FeedComponents. + */ +public class FeedComponentViewHolder extends RecyclerView.ViewHolder { + + public FeedComponentViewHolder(@NonNull View itemView) { + super(itemView); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java new file mode 100644 index 000000000..83250bbfa --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java @@ -0,0 +1,62 @@ +package de.danoeh.antennapod.view.viewholder; + +import android.os.Build; +import android.text.Layout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.cardview.widget.CardView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.CoverLoader; +import de.danoeh.antennapod.core.feed.Feed; + +/** + * Holds the view which shows feeds. + */ +public class FeedViewHolder extends FeedComponentViewHolder { + private static final String TAG = "FeedViewHolder"; + + private final TextView placeholder; + private final ImageView cover; + private final TextView title; + public final CardView coverHolder; + + private final MainActivity activity; + private Feed feed; + + public FeedViewHolder(MainActivity activity, ViewGroup parent) { + super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)); + this.activity = activity; + placeholder = itemView.findViewById(R.id.txtvPlaceholder); + cover = itemView.findViewById(R.id.imgvCover); + coverHolder = itemView.findViewById(R.id.coverHolder); + title = itemView.findViewById(R.id.txtvTitle); + if (Build.VERSION.SDK_INT >= 23) { + title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } + + itemView.findViewById(R.id.secondaryActionButton).setVisibility(View.GONE); + itemView.findViewById(R.id.status).setVisibility(View.GONE); + itemView.findViewById(R.id.progress).setVisibility(View.GONE); + itemView.findViewById(R.id.drag_handle).setVisibility(View.GONE); + itemView.setTag(this); + } + + public void bind(Feed feed) { + this.feed = feed; + placeholder.setText(feed.getTitle()); + title.setText(feed.getTitle()); + + if (coverHolder.getVisibility() == View.VISIBLE) { + new CoverLoader(activity) + .withUri(feed.getImageLocation()) + .withPlaceholderView(placeholder) + .withCoverView(cover) + .load(); + } + } + +} diff --git a/app/src/main/play/contact-email.txt b/app/src/main/play/contact-email.txt index d4543c6e0..a35ca7725 100644 --- a/app/src/main/play/contact-email.txt +++ b/app/src/main/play/contact-email.txt @@ -1 +1 @@ -antennapod@googlegroups.com
\ No newline at end of file +info@antennapod.org
\ No newline at end of file diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/00.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/00.png Binary files differnew file mode 100644 index 000000000..9fe34a151 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/00.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/01.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/01.png Binary files differnew file mode 100644 index 000000000..57528fe56 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/01.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/02.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/02.png Binary files differnew file mode 100644 index 000000000..a7b6a35f2 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/02.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/03.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/03.png Binary files differnew file mode 100644 index 000000000..014920d27 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/03.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/04.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/04.png Binary files differnew file mode 100644 index 000000000..88d5c89e2 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/04.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/05.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/05.png Binary files differnew file mode 100644 index 000000000..2708df498 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/05.png diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/1.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/1.png Binary files differdeleted file mode 100644 index f5f7a463b..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/1.png +++ /dev/null diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/2.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/2.png Binary files differdeleted file mode 100644 index eac002341..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/2.png +++ /dev/null diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/3.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/3.png Binary files differdeleted file mode 100644 index f189d11f2..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/3.png +++ /dev/null diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/4.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/4.png Binary files differdeleted file mode 100644 index dcd040008..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/4.png +++ /dev/null diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/5.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/5.png Binary files differdeleted file mode 100644 index 03467f768..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/5.png +++ /dev/null diff --git a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/6.png b/app/src/main/play/listings/de-DE/graphics/phone-screenshots/6.png Binary files differdeleted file mode 100644 index 4d687d88b..000000000 --- a/app/src/main/play/listings/de-DE/graphics/phone-screenshots/6.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/00.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/00.png Binary files differnew file mode 100644 index 000000000..2ab1d595a --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/00.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/01.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/01.png Binary files differnew file mode 100644 index 000000000..e435dfd80 --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/01.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/02.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/02.png Binary files differnew file mode 100644 index 000000000..eba177029 --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/02.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/03.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/03.png Binary files differnew file mode 100644 index 000000000..586b10752 --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/03.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/04.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/04.png Binary files differnew file mode 100644 index 000000000..dd13094c6 --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/04.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/05.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/05.png Binary files differnew file mode 100644 index 000000000..2684f2a8b --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/phone-screenshots/05.png diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png Binary files differdeleted file mode 100644 index f201567a1..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png Binary files differdeleted file mode 100644 index 1c0cd9e8d..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png Binary files differdeleted file mode 100644 index c7812c099..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png Binary files differdeleted file mode 100644 index 8eb6c2bad..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png Binary files differdeleted file mode 100644 index 65fe711a6..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png +++ /dev/null diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png Binary files differdeleted file mode 100644 index 35a1d3b08..000000000 --- a/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png +++ /dev/null diff --git a/app/src/main/play/listings/es-ES/full-description.txt b/app/src/main/play/listings/es-ES/full-description.txt index b98757cee..b15dd1762 100644 --- a/app/src/main/play/listings/es-ES/full-description.txt +++ b/app/src/main/play/listings/es-ES/full-description.txt @@ -1,31 +1,31 @@ -AntennaPod is un gestor y reproductor de podcast que te da acceso instantáneo a millones de podcast gratuitos y de pago, desde podcasters independientes a grandes estaciones como la BBC, NPR y CNN. Agrega, importa y exporta las fuentes de manera sencilla usando el listado de iTunes, archivos OPML o las URL de tipo RSS. Ahorra esfuerzo, batería y datos con los controles de descarga y de borrado de episodios (basado en favoritos y ajustes de tiempo). -Descarga, escucha en stream, o añade la cola episodios y disfrútalos como quieras con velocidad de reproducción ajustable, soporte para capítulos y temporizador de sueño. -Ahorra esuferzo, batería y consumo de datos con los potentes controles de automatización para descarga (especifica a qué horas y desde qué redes Wi-Fi) y borrado de episodios antiguos (basado en tus preferencias). +AntennaPod es un gestor y reproductor de podcasts que te da acceso instantáneo a millones de podcast gratuitos y de pago; desde podcasters independientes a grandes estaciones como la BBC, NPR y CNN. Agrega, importa y exporta las fuentes de manera sencilla usando el listado de iTunes, archivos OPML o las URL de tipo RSS. Ahorra esfuerzo, batería y datos con los controles de descarga y de borrado de episodios (basado en favoritos y ajustes de tiempo). +Descarga, escucha en stream o añade a la cola episodios y disfrútalos como quieras con velocidad de reproducción ajustable, soporte para capítulos y temporizador de sueño. +Ahorra esfuerzo, batería y consumo de datos con los potentes controles de automatización para descarga (especifica la hora y desde qué redes Wi-Fi) y el borrado de episodios antiguos (basado en tus preferencias). Creado por entusiastas del pódcast, AntennaPod es libre en todos los sentidos: código abierto, gratuito y sin publicidad. <b>Importar, organizar y reproducir</b> -• Administra la reproducción desde cualquier parte: control en pantalla de inicio, notificación del sistema y controles de auricular y bluetooth +• Administra la reproducción desde cualquier parte: control en la pantalla de inicio, las notificaciones del sistema y los controles de auriculares y bluetooth • Añade e importa fuentes mediante los directorios de iTunes y gPodder.net, archivos OPML y enlaces RSS o Atom -• Disfruta escuchando a tu manera con velocidad de reproducción ajustable, soporte de capítulos (MP3, VorbisComment y Podlove), recordatorio del punto de reproducción y el temporizador de sueño avanzado (agita para restablecer, bajar el volumen y disminuir la velocidad de reproducción) +• Disfruta escuchando a tu manera, con velocidad de reproducción ajustable, soporte de capítulos (MP3, VorbisComment y Podlove), marcador del punto de reproducción y temporizador de sueño avanzado (agita para restablecer, bajar el volumen y disminuir la velocidad de reproducción) • Accede a fuentes y episodios protegidos con contraseña <b>Seguir, valorar y compartir </b> • Haz un seguimiento de lo mejor de lo mejor marcando episodios como favoritos • Busca ese episodio en el historial o busca por título y notas -• Comparte episodios y fuentes a través de las avanzadas redes sociales y opciones de correo electrónico, los servicios de gPodder.net y la exportación OPML +• Comparte episodios y fuentes a través de las opciones avanzadas de redes sociales y correo electrónico, los servicios de gPodder.net y exportando a OPML <b>Control del sistema</b> -• Controla las descargas automáticas: elige las fuentes, excluye las redes móviles, selecciona redes wifi específicas, o solo cuando el teléfono se esté cargando y establece horarios o intervalos -• Gestiona el almacenamiento configurando la cantidad de episodios en caché, configura borrado inteligente y eligiendo tu ubicación favorita +• Controla las descargas automáticas: elige las fuentes, excluye las redes móviles, selecciona redes WiFi específicas, cuando el teléfono se cargue, establece horarios o intervalos +• Gestiona el almacenamiento configurando la cantidad de episodios en caché, borrado inteligente y eligiendo tu ubicación favorita • Adáptate a tu entorno usando el tema claro u oscuro -• Haz una copia de seguridad de tus suscripciones con la integración de gPodder.net y la exportación OPML +• Haz una copia de seguridad de tus suscripciones con la integración de gPodder.net y exportando a OPML <b>¡Únete a la comunidad AntennaPod!</b> -AntennaPod está en continuo desarrollo por voluntarios. ¡Tú también puedes contribuir, con tu código o con tus comentarios! +AntennaPod es desarrollado por voluntarios. ¡Tú también puedes contribuir, con tu código o con tus comentarios! -GitHub es el sitio que debes visitar para solicitar características nuevas, reportar fallos y contribuir con código: +Visita GitHub para solicitar características nuevas, reportar fallos y contribuir con código: https://www.github.com/AntennaPod/AntennaPod -Para ayudar con las traducciones en Transifex: +Ayuda con las traducciones en Transifex: https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/et/full-description.txt b/app/src/main/play/listings/et/full-description.txt new file mode 100644 index 000000000..446d16ded --- /dev/null +++ b/app/src/main/play/listings/et/full-description.txt @@ -0,0 +1,31 @@ +AntennaPod on taskuhäälingu haldur ja esitaja, millega saad kohese ligipääsu miljonitele tasuta ja tasulistele saadetele nii ise- kui suurtegijatelt nagu BBC, NPR ja CNN. Lisa, impordi ja ekspordi nende voogusid ilma muredeta kasutades iTunes'i taskuhäälingute andmebaasi, OPML fail või lihtsalt RSS URL-e. +Laadi alla, voogedasta või lisa kuulamise järjekorda saateid ning naudi neid täpselt nii, nagu sina tahad, kasutades muudetavat taasesituse kiirust, peatükkide tuge ja unetaimerit. +Säästa pingutuselt, aku kestuselt ja mobiilse andmeside kasutuselt võimekate automatiseerimise vahenditega saadete allalaadimiseks (määra millal, mis sagedusega ja millistest WiFi võrkudes) ning kustutamiseks (vastavalt sinu lemmikutele ja viivituse sätetele). + +Kuna AntennaPod on tehtud taskuhäälingu entusiastide poolt, on AntennaPod vaba selle sõna igas tähenduses: vaba lähtekoodiga, tasuta ja ilma mingi reklaamita. + +<b>Impordi, korrasta ja kuula</b> +• Halda esitust kust tahes: avaekraani vidina abil, süsteemi teate kaudu või kõrvaklappide või bluetoothi nuppude abil +• Lisa ja impordi voogusid iTunesi ja gPodder.net kaustadest, OPML failidest ja RSS või Atom linkide kaudu +• Naudi kuulamist just sinu moodi tänu muudetavale esituskiirusele, peatükkide toele, meeldejäetavale asukohale ning unetaimerile (raputamine, valjuse vähendamine) +• Ligipääs parooliga kaitstud voogudele ja saadetele + +<b>Talleta ja jaga, et avaldada tunnustust</b> +• Talleta parimatest parimaid märkides saateid lemmikuteks +• Leia see üks saade otsides esitatute ajaloost pealkirjade või kirjelduste järgi +• Jaga saateid ja voogusid suurepäraste sotsiaalmeedia ja e-posti valikute abil, gPodder.neti kaudu ja OPML failide abil + +<b>Oma kontrolli</b> +• Võta kontroll automaatse allalaadimise üle enda kätte: vali vood, keela mobiilivõrk, vali õiged WiFi võrgud, ütle, kui telefon peaks allalaadimise ajal laadima või määra millal ja kui tihti saateid alla laadida +• Halda ruumikasutust, määrates mitu saadet varuks hoida, luba nutikas kustutamine ja failide eelistatud asukoha määramine +• Kohanda keskonnaga heleda ja tumeda kujundusega +• Varunda oma tellimused gPodder.net keskkonda ja OPML faili eksportides + +<b>Liitu AntennaPodi kogukonnaga!</b> +AntennaPod on vabatahtlike poolt aktiivselt arendamisel. Ka sina võid anda oma osa, kirjutades koodi või andes tagasisidet! + +GitHub on koht, kus esitada uute võimaluste soove, vearaporteid ja kooditäiendusi: +https://www.github.com/AntennaPod/AntennaPod + +Transifex on koht, kus saab aidata tõlgetega: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/et/short-description.txt b/app/src/main/play/listings/et/short-description.txt new file mode 100644 index 000000000..59ff4ac69 --- /dev/null +++ b/app/src/main/play/listings/et/short-description.txt @@ -0,0 +1 @@ +Lihtne, paindlik ja avatud lähtekoodiga taskuhäälingu haldur ja esitaja
\ No newline at end of file diff --git a/app/src/main/play/listings/et/title.txt b/app/src/main/play/listings/et/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/et/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/fr-FR/full-description.txt b/app/src/main/play/listings/fr-FR/full-description.txt index fe8734be4..7e763023b 100644 --- a/app/src/main/play/listings/fr-FR/full-description.txt +++ b/app/src/main/play/listings/fr-FR/full-description.txt @@ -12,20 +12,20 @@ Conçu par des fans de podcast, AntennaPod est gratuit, open source et sans publ <b>Suivez, partagez et appréciez</b> • Enregistrez les meilleurs épisodes en tant que favoris -• Retrouvez un épisode à partir de l'historique de lecture ou en recherchant les noms et commentaires des épisodes +• Retrouvez un épisode à partir de l'historique de lecture ou en recherchant les titres et commentaires des épisodes • Partagez vos épisodes et flux sur les réseaux sociaux, par email, sur gPodder.net ou en les exportant au format OPML <b>Contrôlez</b> -• Prenez le contrôle en automatisant vos téléchargements : flux spécifiques, restriction de la connexion mobile, réseaux WIFI autorisés, seulement pendant la recharge, fréquence de mise à jour +• Prenez le contrôle en automatisant vos téléchargements : flux spécifiques, restriction de la connexion mobile, réseaux WIFI autorisés, seulement pendant la recharge, fréquence ou heure de mise à jour • Gérez l'espace de stockage en réglant le nombre d'épisodes à garder, quand les supprimer et où les enregistrer -• Choisissez le thème de l'interface +• Adaptez l'interface selon vos préférences avec le thème clair ou sombre • Sauvegardez vos abonnements avec gPodder.net ou des exports OPML <b>Rejoignez la communauté d'AntennaPod !</b> AntennaPod est développé activement par des volontaires. Vous pouvez aussi contribuer avec du code, des traductions ou des commentaires ! -Allez sur GitHub pour demander de nouvelles options, signaler des bugs ou pour contribuer au développement : +Rendez-vous sur GitHub pour demander de nouvelles options, signaler des bugs ou pour contribuer au développement : https://www.github.com/AntennaPod/AntennaPod -Allez sur Transifex pour aider la traduction : +Rendez-vous sur Transifex pour aider la traduction : https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/1.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/1.png Binary files differdeleted file mode 100644 index e32664902..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/1.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/2.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/2.png Binary files differdeleted file mode 100644 index f1119dd11..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/2.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/3.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/3.png Binary files differdeleted file mode 100644 index 17cae455a..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/3.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/4.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/4.png Binary files differdeleted file mode 100644 index 5fa129b40..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/4.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/5.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/5.png Binary files differdeleted file mode 100644 index 2d7572b65..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/5.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/6.png b/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/6.png Binary files differdeleted file mode 100644 index cdf2cac38..000000000 --- a/app/src/main/play/listings/fr-FR/graphics/phone-screenshots/6.png +++ /dev/null diff --git a/app/src/main/play/listings/fr-FR/short-description.txt b/app/src/main/play/listings/fr-FR/short-description.txt index 7143385e1..3214e7933 100644 --- a/app/src/main/play/listings/fr-FR/short-description.txt +++ b/app/src/main/play/listings/fr-FR/short-description.txt @@ -1 +1 @@ -Un lecteur de podcast facile à utiliser et flexible
\ No newline at end of file +Un lecteur de podcast facile et souple à utiliser
\ No newline at end of file diff --git a/app/src/main/play/listings/gl-ES/full-description.txt b/app/src/main/play/listings/gl-ES/full-description.txt index 2acaf65f7..bf9471ad2 100644 --- a/app/src/main/play/listings/gl-ES/full-description.txt +++ b/app/src/main/play/listings/gl-ES/full-description.txt @@ -1,42 +1,31 @@ -AntenaPod é un xestor de podcast e reprodutor que lle da acceso a millóns de podcast tanto gratuítos como de pagamento, desde podcasters independentes a grandes productores como BBC, NPR e CNN. Engada, importe e exporte as súas fontes de xeito doado utilizando a base de datos de iTunes, ficheiros OPML ou URLs RSS. Aforre traballo, enerxía da batería e datos móbiles co sistema automatizado de control das descargas de episodios (indicando horario, intervalos e redes WiFi) e borrando episodios (baseado nos seus favoritos e axustes de retardo).<br> -O máis importante: Descarga, reproduce ou pon en cola os episodios e desfrútaos do xeito en que máis che conveña axustando a velocidade de reprodución, o soporte de capítulos e o apagado programable. - -Escrito por namorados dos podcast, AntennaPod é gratuíto e libre: open source, sen custos, sen publicidade. - -<b>Características:</b><br> -IMPORTAR, ORGANIZAR E REPRODUCIR<br> -• Engada e importe fontes a través dos directorios de iTunes e gPodder.net, ficheiros OPML e RSS ou ligazóns Atom<br> -• Xestione as opcións de reprodución desde calquer lugar: widget na pantalla de inicio, barra de notificacións e tamén desde os controis de auriculares e bluetooth<br> -• Goce escoitando ao seu xeito coa velocidade de reprodución axustable, soporte de capítulos (MP3, VorbisComment e Podlove), recordo da posición de reprodución e apagado configurable (axitar para reiniciar, baixar volume e diminuir velocidade de reprodución)<br> -• Acceso protexido con contrasinal a episodios e fontes<br> -• Benefíciese das fontes paxinadas (www.podlove.org/paged-feeds) - -SIGA, COMPARTA E VALORE <br> -• Garde o melloriño de cada casa marcando episodios como favoritos<br> -• Atope ese episodio especial no historial de reprodución ou buscando (títulos e notas do episodio)<br> -• Comparta episodios e fontes a través das opcións de redes sociais e correro electrónico, os servizos de gPodder.net e exportando a OPML<br> - -CONTROL DO SISTEMA <br> -• Tome control sobre as descargas automáticas: escolla fontes, exclúa redes móbiles, redes WIFI concretas, requerir que o móbil esté a cargar e horarios e intervalos<br> -• Xestione o almacenamento establecendo o volume almacenado de episodios, borrado intelixente (baseándose nos favoritos e situación de reprodución) e selecionando a localización preferida<br> -• Utilice AntennaPod no seu idioma (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH, GL)<br> -• Adápteo a contorna utilizando o decorado claro ou oscuro<br> -• Respalde as súas suscricións coa integración en gPodder.net e exportando a OPML - -<b>Únase a comunidade AntennaPod!</b><br> +AntennaPod é un xestor de podcast e reprodutor que che permite acceder a millóns de contidos gratuítos e de pagamento, desde produtores independentes a grandes empresas de contidos como BBC, NPR ou CNN. Engade, importa e exporta as súas fontes sen complicacións utilizando a base de datos de iTunes, ficheiros OPML ou URL de RSS. +Descarga, difunde ou pon e cola episodios e desfrutaos do xeito que prefiras, con velocidade de reprodución axustable, soporte de capítulos e apagado programable. +De xeito doado, aforrando batería e datos móbiles e con ferramentas potentes para a automatización das descargas (indicando horarios, intervalos e redes WiFi) e borrado de ficheiros (baseado nos favoritos e axustes temporais). + +Feita por entusiastas do podcast, AntennaPod é libre en todos os sentidos da palabra: código aberto, gratuíto e sen anuncios. + +<b>Importar, organizar e reproducir</b> +• Controlar a reprodución desde calquera lugar: widget na pantalla, sistema de notificacións e controis de auriculares e por bluetooth. +• Engade e importa fontes vía directorios de iTunes e gPodder.net, ficheiros OPML e ligazóns RSS ou Atom. +• Desfruta escoitando ao teu xeito, con velocidade axustable, soporte de capítulos, lembra a posición de reprodución e programa a hora de apagado (axita para restablecer, volume máis baixo) +• Acceso protexido con contrasinal para fontes e episodios + +<b>Leva un rexistro, comparte e promociona</b> +• Mantén un rexistro dos mellores episodios marcándoos como favoritos +• Atopa un episodio no histórico de reprodución ou buscando por títulos e notas do episodio +• Comparte episodios e fontes a través da web social, correo electrónico, o servizo gPodder.net e exportándoos como OPML. + +<b>Xestiona o sistema</b> +• Toma o control das descargas automáticas: escolle fontes, exclúe redes móbiles, redes Wifi específicas, que o móbil esté cargando ou establece horarios para a descarga +• Xestiona o almacenaxe establecendo a número de episodios gardados, borrado intelixente e escollendo a localización preferida. +• Adáptase ao teu entorno utilizando os decorados claro ou escuro +• Respalda as túas subscricións con gPodder.net e a exportación OPML + +<b>Participa na comunidade AntennaPod!</b> AntennaPod está baixo continuo desenvolvemento grazas a voluntarios. Vostede tamén pode contribuir, con código ou con comentarios! -En GitHub pode solicitar novas características, informar de erros e contribuir ao código:<br> +Podes solicitar novas características utilizando GitHub, así como informe de fallos ou contribuír ao código: https://www.github.com/AntennaPod/AntennaPod -No noso Google Group pode compartir ideas, momentos favoritos dos podcast e agradecer o traballo dos voluntarios:<br> -https://groups.google.com/forum/#!forum/antennapod - -Ten algunha pregunta ou quere facernos algún comentario? -https://twitter.com/@AntennaPod - -Para axudar na tradución vaia a Transifex:<br> -https://www.transifex.com/antennapod/antennapod - -Mire o noso programa Beta de Probas para ter os últimos avances:<br> -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +As traducións fanse en Transifex: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/hu-HU/full-description.txt b/app/src/main/play/listings/hu-HU/full-description.txt new file mode 100644 index 000000000..708ad5b1e --- /dev/null +++ b/app/src/main/play/listings/hu-HU/full-description.txt @@ -0,0 +1,31 @@ +Az AntennaPod egy podcastkezelő és -lejátszó, amely azonnali hozzáférést nyújt ingyenes és fizetős podcastok millióihoz: a független podcastkészítőktől kezdve a nagy kiadókig, mint a BBC, NPR és a CNN. Adjon hozzá, importáljon és exportáljon csatornákat az iTunes podcast adatbázis, OPML fájlok vagy egyszerű RSS URL-ek segítségével. +Töltsön le, hallgassa élőben vagy állítsa sorba az epizódokat, és elvezze azokat, ahogy szeretné az állítható lejátszási sebességgel, a fejezettámogatással és az alvási időzítővel. +Spóroljon az idejével, az akkumulátorral és a mobiladat-keretével a sokoldalú automatizálási beállításokkal, melyek letöltik az epizódokat (adjon meg időket, intervallumokat és WiFi-hálózatokat), és törlik is azokat (a kedvencei vagy a késleltetési beállításai alapján). + +Podcast-rajongók készítésében, az AntennaPod teljesen szabad, nyílt forráskódú, ingyenes, és hirdetésektől mentes. + +<b>Importálás, rendszerezés és lejátszás</b> +• Lejátszás kezelése bárhonnan: a kezdőképernyőről, fülhallgatóról és bluetoothos vezérlőkről +• Csatornák hozzáadása és importálása iTunesból, gPodder.net könyvtárakból, OPML-fájlokból és RSS/Atom hivatkozásokból +• Hallgasson saját ízlése szerint az állítható lejátszási sebességgel, fejezettámogatással, megjegyzett lejátszási pozícióval és egy nagy tudású alvási időzítővel (rázás az újraindításhoz, hangerő csökkentéshez) +• Jelszóval védett csatornák és epizódok elérése + +<b>Kövesse, ossza meg és élvezze</b> +• Kövesse a legjobbak legjobbját azáltal, hogy kedvencként jelöli meg az epizódokat +• Találja meg azt a bizonyos epizódot a lejátszási előzményekkel vagy kereséssel (cím és adásjegyzetek alapján) +• Osszon meg epizódokat és csatornákat továbbfejlesztett közösségi média és e-mail lehetőségekkel, a gPodder.net szolgáltatásokkal és OPML exportálás segítségével + +<b>Vezérelje a rendszert</b> +• Vegye át az irányítást az automatikus letöltés felett: válasszon csatornákat, mellőzze a mobilhálózatokat, válasszon specifikus WiFi-hálózatokat, követelje meg a telefon töltését és adjon meg időpontokat vagy időszakokat +• Kezelje a tárhelyet a tárolandó epizódok számának megadásával, az okos törléssel és az előnyben részesített hely megadásával +• Alkalmazkodjon a környezethez a világos és sötét téma használatával +• Mentse el a feliratkozásait a gPodder.net integrációval és az OPML exporttal + +<b>Csatlakozzon az AntennaPod közösséghez!</b> +Az AntennaPodot aktívan fejlesztik az önkéntesek. Ön is közreműködhet: kóddal vagy megjegyzésekkel! + +A GitHub a funkciókérések, hibajelentések és a kódbeküldés helye: +https://www.github.com/AntennaPod/AntennaPod + +A Transifexen segíthet a fordításokban: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/hu-HU/short-description.txt b/app/src/main/play/listings/hu-HU/short-description.txt new file mode 100644 index 000000000..0e9612141 --- /dev/null +++ b/app/src/main/play/listings/hu-HU/short-description.txt @@ -0,0 +1 @@ +Könnyen használható, rugalmas és nyílt forráskódú podcastkezelő és -lejátszó
\ No newline at end of file diff --git a/app/src/main/play/listings/hu-HU/title.txt b/app/src/main/play/listings/hu-HU/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/hu-HU/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/it-IT/full-description.txt b/app/src/main/play/listings/it-IT/full-description.txt index 978ae2d46..047a2278b 100644 --- a/app/src/main/play/listings/it-IT/full-description.txt +++ b/app/src/main/play/listings/it-IT/full-description.txt @@ -1,43 +1,31 @@ -AntennaPod è un riproduttore e gestore di podcast che ti da accesso immediato a milioni di podcast gratuiti e a pagamento, dai podcaster indipendenti alle più grandi emittenti come BBC, NPR e CNN. Aggiungi, importa e esporta in modo semplice usando il database di podcast di iTunes, un file OPML o i semplici URL RSS. Risparmia fatica, batteria e dati con il potente controllo automatizzato per il download di episodi (orari specifici, intervalli e reti WiFi) e dell'eliminazione degli episodi (basata sui preferiti e le impostazioni di eliminazione personali).<br> -Ancora più importante: scarica, ascolta in streaming o metti in coda gli episodi e goditeli quando e come vuoi grazie alla velocità di riproduzione personalizzabile, al supporto per i capitoli e al timer di spegnimento. Puoi anche ringraziare i creatori di contenuti grazie all'integrazione con Flattr. - -Creato da amanti dei podcast, AntennaPod è libero in tutti i sensi: open source, gratis, senza pubblicità. - -<b>Funzioni:</b><br> -IMPORTA, ORGANIZZA E RIPRODUCI<br> -• Aggiungi e importa feed via iTunes, gPodder.net, file OPML e link RSS o Atom<br> -• Gestisci la riproduzione da dove vuoi: widget sulla schermata Home, notifiche di sistema e controlli sugli auricolari e via bluetooth. <br> -• Divertiti ad ascoltare nel modo che preferisci grazie alla velocità di riproduzione variabile, il supporto ai capitoli (MP3, VorbisComment e Podlove), la memorizzazione dell'avanzamento di riproduzione e un timer di spegnimento avanzato (scuoti per resettare, volume basso e rallentamento della velocità)<br> -• Accedi a feed e episodi protetti da password<br> -• Sfrutta i vantaggi dei paged feeds (www.podlove.org/paged-feeds) - -TIENI TRACCIA, CONDIVIDI & APPREZZA<br> -• Tieni traccia del meglio del meglio segnando gli episodi come Preferiti<br> -• Trova l'episodio specifico grazie alla cronologia di riproduzione o al sistema di ricerca (titoli e note dell'episodio)<br> -• Condividi gli episodi e i feed tramite le avanzate opzioni social ed email, il servizio gPodder.net e l'esportazione in OPML<br> -• Supporta i creatori di contenuti tramite l'integrazione con Flattr e il flattring automatico - -CONTROLLA IL SISTEMA<br> -• Prendi il controllo grazie ai download automatici: scegli i feed, escludi le reti mobili, seleziona reti WiFi specifiche, richiedi che il telefono sia in carica e imposta tempi e intervalli di aggiornamento<br> -• Gestisci lo spazio impostando l'occupazione massima, la cancellazione intelligente (basata sui Preferiti e sullo stato di riproduzione) e selezionando la posizione che preferisci <br> -• Usa AntennaPod nella tua lingua (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br> -• Adattati all'ambiente in cui ti trovi con i temi Chiaro e Scuro.<br> -• Salva le sottoscrizioni grazie all'integrazione con gPodder.net e l'esportazione OPML - -<b>Entra nella community di AntennaPod!</b><br> +AntennaPod è un riproduttore e gestore di podcast che permette l'accesso immediato a milioni di podcast gratuiti e a pagamento, dai podcaster indipendenti alle più grandi emittenti come BBC, NPR e CNN. Aggiungi, importa e esporta in modo semplice usando il database di podcast di iTunes, un file OPML o i semplici URL RSS. +Scarica, ascolta in streaming o accoda gli episodi e goditeli come preferisci grazie alla velocità di riproduzione modificabile, al supporto ai capitoli e al timer di spegnimento. +Risparmia fatica, carica della batteria e dati con i potenti controlli automatizzati per il download degli episodi (orari specifici, intervalli di tempo e reti WiFi selezionate) e l'eliminazione degli episodi (gestione dei preferiti e i tempi di cancellazione). + +Creato da appassionati di podcast, AntennaPod è libero in tutti i sensi: open source, gratuito e senza pubblicità. + +<b>Importa, organizza e riproduci</b> +• Gestisci la riproduzione ovunque: widget nella schermata home, tendina delle notifiche, cuffie e controlli bluetooth. +• Aggiungi e importa le sottoscrizioni tramite iTunes e gPodder, o anche tramite file OPML, collegamenti RSS o Atom. +• Goditi l'ascolto nel modo che preferisci grazie alla velocità di riproduzione modificabile, il supporto ai capitoli, la memorizzazione dello stato di riproduzione e un timer di spegnimento avanzato (scuoti per riavviare o riduci il volume) +• Accedi ai feed e agli episodi protetti tramite utente e password + +<b>Tieni traccia, condividi & ricerca</b> +• Tieni traccia dei migliori episodi di sempre segnandoli come preferiti +• Trova proprio l'episodio che cercavi grazie alla ricerca nella cronologia, nei titoli e nelle note degli episodi +• Condividi gli episodi e i feed tramite opzioni avanzate di condivisione via social o email, i servizi di gPodder.net o l'esportazione OPML. + +<b>Controlla il sistema</b> +• Gestisci il download automatico: puoi selezionare i feed, escludere le reti mobili, utilizzare reti WiFi specifiche, impostare il download solo se in carica e scaricare in orari o intervalli selezionati. +• Gestisci lo spazio impostando l'occupazione massima degli episodi, la cancellazione intelligente e il percorso in cui scaricare i file. +• Adattati alle tue preferenze grazie ai temi chiaro o scuro. +• Effettua il backup delle sottoscrizioni grazie all'integrazione di gPodder.net e all'esportazione in OPML + +<b>Partecipa alla community di AntennaPod!</b> AntennaPod è sviluppato da volontari. Anche tu puoi contribuire, con il codice o con dei commenti! -GitHub è il posto nel quale fare richieste, segnalare bug e contribuire allo sviluppo:<br> +GitHub è la piattaforma usata per le richieste di funzionalità, le segnalazioni di bug o i contributi al codice: https://www.github.com/AntennaPod/AntennaPod -Il nostro gruppo Google è il luogo ideale dove condividere le idee, stimolare momenti di podcasting e ringraziare tutti i volontari:<br> -https://groups.google.com/forum/#!forum/antennapod - -Hai una domanda o vuoi darci un feedback? -https://twitter.com/@AntennaPod - -Transifex è il posto dove puoi contribuire alla traduzione di AntennaPod:<br> -https://www.transifex.com/antennapod/antennapod - -Dai un'occhiata al nostro programma di Beta Testing per avere accesso in anticipo alle nuove features:<br> -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +Transifex è dove puoi contribuire alla traduzione: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/ja-JP/full-description.txt b/app/src/main/play/listings/ja-JP/full-description.txt index 4dc50f57e..52bb64c46 100644 --- a/app/src/main/play/listings/ja-JP/full-description.txt +++ b/app/src/main/play/listings/ja-JP/full-description.txt @@ -1,42 +1,31 @@ -AntennaPodは、独自のポッドキャスターから、BBC、NPR、CNNなどの大規模な放送まで、数百万の無料や有料ポッドキャストに瞬時にアクセスすることができる、ポッドキャストマネージャーおよびプレーヤーです。フィードは手間のかからないiTunesのPodcastのデータベース、OPMLファイルや簡単なRSSのURLを使用して追加、インポート、エクスポートします。エピソードのダウンロード (時間、間隔およびWiFiネットワークを指定) とエピソードの削除 (お気に入りと遅延設定に基づいて) をするために強力な自動コントロールで、手間、バッテリ消費、モバイルデータ使用量を節約します。<br> -しかし最も重要なこと: エピソードをダウンロード、ストリーム再生、またはキューに入れて、そして再生速度の調整、チャプターのサポート、スリープタイマーで好きなように楽しんでください。 - -ポッドキャスト愛好家が作成した AntennaPod はすべての意味でフリー自由です: オープンソース、コスト不要、広告はありません。 - -<b>すべての機能:</b><br> -インポート、整理、再生<br> -• iTunesや、gPodder.netディレクトリ、OPMLファイル、RSSまたはAtom経由でフィードを追加およびインポートします<br> -• 様々な場所で再生を管理します: ホーム画面ウィジェット、システム通知、イヤホン、Bluetoothコントロール<br> -• 再生速度の調整、チャプターのサポート (MP3、VorbisCommentとPodlove)、再生位置の保存、高度なスリープタイマー (シェイクしてリセット、音量を小さく、再生速度を遅く) で、お好みの聞き方でお楽しみください<br> -• アクセスパスワードで保護されたフィードとエピソード<br> -• ページフィードをご利用ください (www.podlove.org/paged-feeds) - -記録、共有 & 感謝<br> -• エピソードをお気に入りとしてマークして、一番の中の一番を保存してください<br> -• 再生履歴から、または検索 (タイトルとショーノート) して目的のエピソードを見つけてください<br> -• 高度なソーシャルメディアとメールオプション、gPodder.netサービス、OPMLエクスポートからエピソードやフィードを共有してください<br> - -システムのコントロール -• 自動ダウンロードの制御: フィードを選択、モバイルネットワークを除外、特定のWiFiネットワークを選択、電話を充電する必要、時間や間隔を設定<br> -• キャッシュされるエピソードの量の設定、スマート削除 (お気に入りやプレイ状況に基づいて) と、お好みの場所を選択して、ストレージを管理します<br> -• AntennaPod をあなたの言語でご利用ください (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br> -• ライトとダーク テーマを使用して環境に適応します<br> -• gPodder.net統合とOPMLのエクスポートで、購読をバックアップします - -<b>AntennaPod のコミュニティに参加してください!</b><br> +AntennaPod は、独立したポッドキャスターから BBC、NPR、CNN などの大規模な出版社まで、数百万の無料および有料のポッドキャストに即座にアクセスできるポッドキャストマネージャーおよびプレーヤーです。 iTunes ポッドキャストデータベース、OPML ファイル、または簡単な RSS URL を使用して、フィードを簡単に追加、インポート、エクスポートできます。 +エピソードをダウンロード、ストリーミング、またはキューに入れて、調整可能な再生速度、チャプターサポート、スリープタイマーで、お好きな方法でお楽しみください。 +エピソードのダウンロード (時間、間隔、WiFi ネットワークの指定) とエピソードの削除 (お気に入りと遅延設定に基づく) の強力な自動化コントロールにより、労力、バッテリー消費量、モバイルデータ通信使用量を節約できます。 + +ポッドキャスト愛好家によって作られた AntennaPod は、オープンソース、無料、広告なしなど、あらゆる意味でフリーです。 + +<b>インポート、整理、再生</b> +• どこからでも再生を管理: ホームスクリーンウィジェット、システム通知、イヤホン、Bluetooth コントロール +• iTunes および gPodder.net ディレクトリー、OPMLファイル、RSS または Atom リンク経由でフィードを追加およびインポートします +• 調整可能な再生速度、チャプターサポート、記憶した再生位置、高度なスリープタイマー (シェイクしてリセット、音量を下げる) でお好みの方法でお楽しみください +• パスワードで保護されたフィードとエピソードのアクセス + +<b>追跡、共有、感謝</b> +• エピソードをお気に入りとしてマークすることにより、ベストを追跡します +• 再生履歴から、またはタイトルとショーノートを検索して、エピソードを見つけます +• 高度なソーシャルメディア、メールオプション、gPodder.net サービス、OPML エクスポートを介してエピソードとフィードを共有します + +<b>システムをコントロール</b> +• 自動ダウンロードの制御: フィードの選択、モバイル通信ネットワークの除外、特定の WiFi ネットワークの選択、充電が必要な時間または間隔の設定 +• キャッシュされるエピソードの量を設定、スマート削除、お好みの場所を選択してストレージを管理します +• ライトとダークのテーマを使用して環境に適応します +• gPodder.net 統合と OPML エクスポートを使用して購読をバックアップします + +<b>AntennaPod コミュニティに参加しましょう!</b> AntennaPod はボランティアによって活発に開発中です。コードやコメントで、あなたもも貢献することができます! -GitHubは、機能のリクエスト、バグの報告、コードの貢献のための場所です:<br> +GitHub は、機能のリクエスト、バグ報告、およびコードの貢献のための場所です: https://www.github.com/AntennaPod/AntennaPod -私たちのGoogleグループは、あなたのアイデア、お気に入りのポッドキャスティングモーメント、感謝を、すべてのボランティアと共有するための場所です:<br> -https://groups.google.com/forum/#!forum/antennapod - -質問や、私たちへのフィードバックがありませんか? -https://twitter.com/@AntennaPod - -Transifexは翻訳を支援するための場所です:<br> -https://www.transifex.com/antennapod/antennapod - -私たちのベータテストプログラムをチェックして、最新機能を最初に入手してください:<br> -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +Transifex は翻訳を支援する場所です: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/nl-NL/full-description.txt b/app/src/main/play/listings/nl-NL/full-description.txt index 518745f17..851f0b34e 100644 --- a/app/src/main/play/listings/nl-NL/full-description.txt +++ b/app/src/main/play/listings/nl-NL/full-description.txt @@ -1,42 +1,31 @@ -Met AntennaPod speel en beheer je al je podcasts en krijg je directe toegang tot duizenden gratis en betaalde podcasts - van onafhankelijke makers tot grote merken zoals BBC, CNN en NPO. Via de iTunes-databank, OPML-bestanden en simpele RSS-linkjes voeg je deze podcasts gemakkelijk toe. Dankzij simpele maar slimme automatische controle van het downloaden en verwijderen van afleveringen bespaar je de accu, hoef je je favoriete podcast niet meer handmatig te volgen en verbruik je geen onnodige mobiele gegevens.<br> -Maar belangrijker: download, stream of voeg afleveringen toe aan de wachtrij en geniet ervan! Je hebt beschikking over afspeelsnelheden, hoofdstukondersteuning en een slaaptimer. - -Gemaakt door podcast-enthousiastelingen, AntennaPod is vrij in de breedste zin van het woord: vrij van advertenties, open source en gratis. - -<b>Alle features:</b><br> -IMPORTEREN, ORGANISEREN, AFSPELEN<br> -• Voeg feeds toe via iTunes and gPodder.net database, OPML-bestanden and RSS of Atom linkjes<br> -• Beheer het afspelen overal: met een homescreen widget, in je Android meldingen en de knoppen op je koptelefoon en bluetooth-apparaat<br> -• Bepaal zelf hoe je luistert met aanpasbare afspeelsnelheden, ondersteuning van hoofdstukken (MP3, VorbisComment en Podlove), afspeelpositie voor elke aflevering en een handige 'slaap timer' (schudden om te resetten, volume langzaam zachter en herstel naar normale afspeelsnelheid)<br> -• Toegang tot wachtwoord-beveiligde podcasts en afleveringen<br> -• Doe je voordeel met 'paged feeds' (www.podlove.org/paged-feeds) - -DEEL & WAARDEER<br> -• Hou het beste van het beste bij door afleveringen als favoriet te markeren<br> -• Vindt die ene aflevering terug in de afspeelgeschiedenis of door te zoeken (in titels, shownotes en makers)<br> -• Deel podcasts en afleveringen via uitgebreide opties voor sociale media, WhatsApp, email en gPodder.net<br> - -HOUD DE CONTROLE -• Beheer automatische downloads: kies je podcasts, sluit mobiele netwerken uit, selecteer specifieke WiFi-verbindingen, eis dat de telefoon wordt opgeladen and bepaal tijden of intervals<br> -• Controleer geheugengebruik: stel een maximumaantal gedownloade afleveringen in, laat afleveringen automatisch verwijderen (maar sluit je favorieten en niet-afgespeelde afleveringen uit) en selecteer zelf je opslaglocatie<br> -• Gebruik AntennaPod in jouw taal: Nederlands! (Of in het Engels, Zweeds, Frans, Duits, Spaans, enz)<br> -• Voel je een kameleon: kies voor een licht of donker uiterlijk<br> -• Back-up je abonnementen via gPodder.net en geëxporteerde OPML-bestanden - -<b>Doe mee met de AntennaPod gemeenschap!</b><br> +Met AntennaPod speel en beheer je al je podcasts en krijg je directe toegang tot duizenden gratis en betaalde podcasts - van onafhankelijke makers tot grote merken zoals BBC, CNN en NPO. Via de iTunes-database, OPML-bestanden en simpele RSS-linkjes voeg je deze podcasts makkelijk toe. +Download, stream of voeg afleveringen toe aan de wachtrij. Met instelbare afspeelsnelheden, hoofdstukondersteuning en een slaaptimer luister je podcasts op de manier die jij prettig vindt. +Spaar moeite, je batterij en je databundel met slimme automatische controle op het downloaden nieuwe afleveringen. En AntennaPod verwijdert ook je oude afleveringen, afhankelijk van je instellingen. + +AntennaPod is gemaakt door podcast-enthousiastelingen. Het team van vrijwilligers bieden je het gratis aan - vrij van advertenties en open source. + +<b>Importeren, organiseren en afspelen</b> +• Beheer het afspelen op elke manier: met een homescreen widget, in je Android meldingen en via de knoppen op je koptelefoon en bluetooth-apparaat +• Voeg podcasts toe via iTunes- en gPodder.net-databases, OPML-bestanden en RSS- of Atom-links +• Bepaal zelf hoe je luistert met aanpasbare afspeelsnelheden, ondersteuning van hoofdstukken, opslag van afspeelpositie voor elke aflevering en een handige 'slaap timer' (schudden om te resetten, volume langzaam zachter) +• Toegang tot wachtwoord-beveiligde podcasts en afleveringen + +<b>Hou bij, deel en waardeer</b> +• Hou het beste van het beste bij door afleveringen als favoriet te markeren +• Vindt die ene aflevering terug via de afspeelgeschiedenis of door te zoeken in titels en shownotes +• Deel podcasts en afleveringen via uitgebreide opties voor sociale media, email, gPodder.net of een OPML-bestand + +<b>Hou de controle</b> +• Beheer automatische downloads: kies je podcasts, sluit mobiele netwerken uit, selecteer specifieke WiFi-verbindingen, vereis dat de telefoon wordt opgeladen en bepaal tijden of intervals +• Controleer geheugengebruik: stel een maximumaantal gedownloade afleveringen in, laat afleveringen automatisch verwijderen en selecteer zelf je opslaglocatie +• Voel je een kameleon: kies voor een licht of donker uiterlijk +• Back-up je abonnementen via gPodder.net en geëxporteerde OPML-bestanden + +<b>Doe mee met de AntennaPod-gemeenschap!</b> AntennaPod wordt regelmatig geüpdatet door vrijwilligers. En jij kan ook helpen, met code of commentaar! -GitHub is de beste plek voor foutmeldingen, verzoekjes voor nieuwe functies en bijdragen aan de code:<br> +Je kunt op GitHub terecht voor foutmeldingen, verzoekjes voor nieuwe functies en bijdragen aan de code: https://www.github.com/AntennaPod/AntennaPod -Onze Google Group is de beste plek om je ideeën, favoriete podcast-momenten en waardering naar alle vrijwilligers te delen:<br> -https://groups.google.com/forum/#!forum/antennapod - -Heb je een vraag of wil je feedback geven? -https://twitter.com/@AntennaPod - -Transifex is de beste plek om te helpen met vertalen:<br> -https://www.transifex.com/antennapod/antennapod - -Check het Beta Testprogramma om de laatste features als eerst te krijgen:<br> -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +Transifex is de beste plek om te helpen met vertalen: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/pl-PL/full-description.txt b/app/src/main/play/listings/pl-PL/full-description.txt new file mode 100644 index 000000000..8c7236fac --- /dev/null +++ b/app/src/main/play/listings/pl-PL/full-description.txt @@ -0,0 +1,31 @@ +AntennaPod to menedżer i odtwarzacz podcastów, który pozwala na natychmiastowy dostęp do milionów darmowych i płatnych podcastów, od niezależnych twórców po wielkie wydawnictwa takie jak BBC, NPR i CNN. Bezproblemowo dodawaj, importuj i eksportuj ich kanały używając bazy danych podcastów iTunes, plików OPML lub zwykłych adresów RSS. +Pobieraj, streamuj lub dodawaj do kolejki odcinki i korzystaj z nich jak chcesz, przy pomocy konfigurowalnej prędkości odtwarzania, wsparcia rozdziałów i wyłącznika czasowego. +Oszczędź czas, baterię i ilość pobranych danych komórkowych dzięki wielu możliwościom automatyzacji pobierania odcinków (określanie czasu, interwałów i sieci WiFi) i kasowania odcinków (na podstawie twoich ulubionych i ustawień opóźnienia) + +Stworzony przez miłośników podcastów, AntennaPod jest darmowy w każdym znaczeniu tego słowa: otwarty kod, bez opłat, bez reklam. + +<b>Importuj, organizuj i odtwarzaj</b> +• Zarządzaj odtwarzaniem z dowolnego miejsca: widgetu na ekranie głównym, powiadomienia systemowego, pilota na kablu słuchawkowym, urządzeń bluetooth +Dodawaj i importuj kanały z iTunes i gPodder.net, plików OPML oraz z adresów RSS lub Atom +• Ciesz się słuchaniem podcastów po swojemu, dzięki konfigurowalnej prędkości odtwarzania, obsłudze rozdziałów, zapamiętywanej pozycji odtwarzania i zaawansowanemu wyłącznikowi czasowemu (potrząśnij aby zresetować, zmniejszanie głośności) +• Wsparcie dla kanałów i odcinków zabezpieczonych hasłem + +<b>Śledź i dziel się</b> +• Zachowaj najlepsze z najlepszych oznaczając odcinki jako ulubione +• Odnajdź ten jedyny odcinek dzięki historii odtwarzania lub przez wyszukiwanie po tytułach i opisach +• Udostępniaj odcinki i kanały dzięki zaawansowanym opcjom mediów społecznościowych i email, usłudze gPodder.net oraz eksporcie OPML + + <b>Kontroluj</b> +• Kontroluj automatyczne pobieranie: wybierz kanały, oszczędzaj dane komórkowe, wybierz określone sieci WiFi, pobieraj tylko podczas ładowania, ustaw czas dnia lub częstotliwość +• Kontroluj użycie pamięci poprzez ustawienie ilości zachowywanych odcinków, inteligentne kasowanie oraz wybranie domyślnej lokalizacji +• Dostosuj wygląd dzięki motywom: ciemny, jasny, czarny (dla ekranów AMOLED) +• Twórz kopie zapasowe swoich subskrypcji dzięki integracji gPodder.net lub eksportuj je do pliku OPML + +<b>Dołącz do społeczności AntennaPod</b> +AntennaPod jest ciągle rozwijane przez ochotników. Ty też możesz pomóc, kodem lub komentarzem! + +Chcesz zgłosić błąd lub brakuje Ci jakiejś funkcji, a może programujesz? Odwiedź nasz GitHub: +https://www.github.com/AntennaPod/AntennaPod + +Chcesz pomóc tłumaczyć AntennaPod - możesz to zrobić na Transifex: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/pl-PL/short-description.txt b/app/src/main/play/listings/pl-PL/short-description.txt new file mode 100644 index 000000000..5f2cdff77 --- /dev/null +++ b/app/src/main/play/listings/pl-PL/short-description.txt @@ -0,0 +1 @@ +Łatwy w użyciu, konfigurowalny opensource'owy menedżer i odtwarzacz podcastów
\ No newline at end of file diff --git a/app/src/main/play/listings/pl-PL/title.txt b/app/src/main/play/listings/pl-PL/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/pl-PL/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/ru-RU/full-description.txt b/app/src/main/play/listings/ru-RU/full-description.txt new file mode 100644 index 000000000..d3d8f2d8f --- /dev/null +++ b/app/src/main/play/listings/ru-RU/full-description.txt @@ -0,0 +1,31 @@ +AntennaPod — менеджер и проигрыватель подкастов, который обеспечит Вас мгновенным доступом к миллионам бесплатных и платных подкастов, как от независимых подкастеров, так и крупных издательских домов. С лёгкостью добавляйте, импортируйте и экспортируйте их каналы используя каталог подкастов iTunes, файлы OPML или адреса каналов RSS. +Загружайте, транслируйте по сети или добавляйте выпуски в очередь и наслаждайтесь ими так, как вам нравится, с регулируемой скоростью воспроизведения, поддержкой оглавления и таймером сна. +Экономьте время, заряд батареи и мобильный трафик при помощи мощных средств автоматизации загрузки выпусков (фильтрация, указание времени и интервалов, а также сетей WiFi) и их удаления (избранные и настройки хранения). + +Созданное поклонниками подкастов, AntennaPod — бесплатное и свободное приложение без рекламы и платежей. + +<b>Импортируйте, систематизируйте и слушайте</b> +• Всевозможное управление воспроизведением: виджетом, системным уведомлением и кнопками проводных и беспроводных гарнитур +• Добавление и импорт каналов через каталоги iTunes и gPodder.net, файлы OPML и ссылки на каналы в RSS или Atom +• Приятное Вам прослушивание с регулировкой скорости воспроизведения, оглавлениями, запоминанием места воспроизведения и продвинутым таймером сна (со сбросом при встряхивании, снижением громкости) +• Доступ к каналам и выпускам защищённым паролем + +<b>Отслеживайте, делитесь и благодарите</b> +• Отслеживайте лучших из лучших, помещая выпуски в избранное +• Поиск того самого выпуска в истории воспроизведения или по заголовкам и примечаниям +• Разнообразие способов поделиться выпусками и каналами через социальные сети и e-mail, услуги gPodder.net и экспорт в OPML + +<b>Управляйте системой</b> +• Управление автоматической загрузкой: выбор отдельных каналов, запрет на использование мобильных сетей, выбор определённых точек доступа WiFi, только во время зарядки телефона и в заданное время или интервалы +• Управление хранением путём задания количества выпусков в кэше, автоудаление и выбор предпочтительного размещения +• Приспосабливается к Вашему окружению посредством светлого или тёмного оформления +• Резервирование Ваших подписок путём интеграции с gPodder.net и экпорта в OPML + +<b>Присоединяйтесь к сообществу AntennaPod!</b> +AntennaPod постоянно развивается силами добровольцев. Вы тоже можете сделать свой вклад при помощи кода или комментария! + +Посещайте GitHub для запроса новых возможностей, уведомления об ошибках и внесения кода: +https://www.github.com/AntennaPod/AntennaPod + +Помогайте с переводом приложения на Transifex: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/ru-RU/short-description.txt b/app/src/main/play/listings/ru-RU/short-description.txt new file mode 100644 index 000000000..10923eafb --- /dev/null +++ b/app/src/main/play/listings/ru-RU/short-description.txt @@ -0,0 +1 @@ +Удобный и гибкий менеджер и проигрыватель подкастов с открытым исходным кодом
\ No newline at end of file diff --git a/app/src/main/play/listings/ru-RU/title.txt b/app/src/main/play/listings/ru-RU/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/ru-RU/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/sv-SE/full-description.txt b/app/src/main/play/listings/sv-SE/full-description.txt index d46fdafdc..5965f45bc 100644 --- a/app/src/main/play/listings/sv-SE/full-description.txt +++ b/app/src/main/play/listings/sv-SE/full-description.txt @@ -1,42 +1,31 @@ -AntennaPod är en podcasthanterare och spelare som ger dig omedelbar tillgång till miljoner av gratis och betalda podcasts, från oberoende podcastare till stora publiceringshus så som BBC, NPR och CNN. Lägg till, importera och exportera enkelt deras flöden med iTunes podcastdatabas, OPML-filer eller vanliga RSS URL:er. Spara möda, batterikraft och mobildata med kraftfulla automatiseringskontroller för nedladdning (välj tider, intervall och WiFi-nätverk) och borttagning av episoder (baserat på dina favoriter och fördröjningsinställningar). -Men viktigast av allt: Ladda ner, strömma eller köa episoder och avnjut dem på det sätt du gillar med justerbar uppspelningshastighet, kapitelstöd och insomningstimer. +Antennapod är en podcasthanterare och spelare som ger dig omedelbar tillgång till millioner av gratis och betalda podcasts, allt ifrån oberoende podcasters till publiceringsjättar som BBC, NPR och CNN. Lägg till, importera och exportera enkelt deras flöden med hjälp av Itunes-podcastdatabas, OPML filer eller enkla RSS URL:er. +Ladda ner, strömma eller köa episoder och avnjut dem på det sätt du gillar med justerbar uppspelningshastighet, kapitelstöd och sömntimer. +Spara tid, batteri och mobildata med kraftfulla automatiseringskontroller för nedladdning av episoder (specifera tider, intervall och Wi-Fi nätverk) och borttagning av episoder (baserat på dina favoriseringar och fördröjningsinställningar). Gjord av podcastenthusiaster, AntennaPod är fri i alla ordets bemärkelser: öppen källkod, inga kostnader, ingen reklam. -<b>Alla funktioner:</b><br> -IMPORTERA, ORGANISERA OCH SPELA<br> -• Lägg till och importera flöden via iTunes och gPodder.net, OPML filer och RSS eller Atom länkar<br> -• Hantera uppspelningen från vartsomhelst: hemskärmswidget, aviseringsfältet och hörlurs/bluetoth-kontroller<br> -• Njut av att lyssna på ditt sätt med justerbar uppspelningshastighet, kapitelstöd (MP4, VorbisComment och Podlove), ihågkommen uppspelningsposition och en avancerad sömntimer (skaka för återställaning, sänk volymen och sänk hastigheten)<br> -• Kom åt lösenordsskyddade flöden och episoder<br> -• Dra nytta av siduppdelade flöden (www.podlove.ord/paged-feeds) +<b>Importera, organisera och spela</b> +• Hantera uppspelningen från vartsomhelst: hemskärmswidget, aviseringsfältet och hörlurs/bluetoth-kontroller +• Lägg till och importera flöden via iTunes och gPodder.net, OPML filer och RSS eller Atom länkar +• Njut av att lyssna på ditt sätt med justerbar uppspelningshastighet, kapitelstöd (MP3, VorbisComment och Podlove), ihågkommen uppspelningsposition och en avancerad sömntimer (skaka för återställaning, sänk volymen och sänk hastigheten) +• Kom åt lösenordsskyddade flöden och episoder -SPÅRA, DELA & UPPSKATTA<br> -• Håll ordning på de bästa av de bästa med favoritmarkering av episoder<br> -• Hitta just den där episoden i uppspelningshistoriken eller genom sökning (titel och shownotes)<br> -• Dela episoder och flöden med avancerade vald för social media och och email, tjänsten gPodder.net och via OPML export<br> +<b>Håll reda på, dela & uppskatta</b> +• Håll ordning på de bästa av de bästa med favoritmarkering av episoder +• Hitta just den där episoden i uppspelningshistoriken eller genom sökning (titel och shownotes) +• Dela episoder och flöden med avancerade vald för social media och och email, tjänsten gPodder.net och via OPML export -KONTROLLERA SYSTEMET<br> -• Ta kontroll över automatisk nedladdning: välj flöden, exkludera mobilnätverk, välj specifika WiFi nätverk, kräv att telefonen är inkopplad för laddning och sätt tider eller intervall för körning<br> -• Hantera lagringsutrymme genom att välja antalet cachade episoder, smart borttagning (baserat på dina favoriter och uppspelningsstatus) och välj den lagringsplats du föredrar<br> -• Använd AntennaPod på ditt språk (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br> -• Anpassa till din omgivning med det ljusa och mörka temat<br> -• Ta backup av dina prenumerationer med integreringen av gPodder.net och OPML exportering +<b>Kontroller systemet</b> +• Ta kontroll över automatisk nedladdning: välj flöden, exkludera mobilnätverk, välj specifika Wi-Fi nätverk, kräv att telefonen är inkopplad för laddning och sätt tider eller intervall för körning +• Hantera lagring med inställningar för antalet episoder i cachen, smart borttagning (med hänsyn till dina favoriter och uppspelningsstatus) och välj din föredragna plats +• Anpassa till din omgivning med det ljusa och mörka temat +• Ta backup av dina prenumerationer med integreringen av gPodder.net och OPML exportering -<b>Gå med i AntennaPods gemenskap!</b><br> +<b>Gå med i AntennaPods gemenskap!</b> AntennaPod är under aktiv utveckling av volontärer. Du kan också bidra, med kod eller kommentarer! -GitHub är platsen att gå till för att be om funktioner, skapa buggrapporter eller bidra med kod:<br> +GitHub är platsen att gå till för att be om funktioner, skapa buggrapporter eller bidra med kod: https://www.github.com/AntennaPod/AntennaPod -Vår Google Group är platsen för att dela idéer, dina favoritögonblick med podcasting och din uppskattning till volontärerna:<br> -https://groups.google.com/forum/#!forum/antennapod - -Har du frågor eller vill ge feedback? -https://twitter.com/@AntennaPod - -Transifex är platsen att gå till för att hjälpa till med översättningen:<br> -https://www.transifex.com/antennapod/antennapod - -Kolla in vårat Beta Testing program för att få de senaste funktionerna först:<br> -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +Transifex är platsen att gå till för att hjälpa till med översättningen: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/zh-CN/full-description.txt b/app/src/main/play/listings/zh-CN/full-description.txt index a39b43eee..993e7e18c 100644 --- a/app/src/main/play/listings/zh-CN/full-description.txt +++ b/app/src/main/play/listings/zh-CN/full-description.txt @@ -1,42 +1,31 @@ -AntennaPod 是一款播客管理器和播放器,可让您即时收听数百万免费和付费的播客,从独立播客到大型出版商如BBC,NPR和CNN;使用 iTunes 播客数据库,OPML文件或简单的RSS URL轻松添加,导入和导出 feeds ;通过强大的自动化控制功能节省工作量,电池电量和移动数据使用情况,以便下载剧集(指定时间,间隔和 WiFi 网络)和删除剧集(根据您的收藏和延迟设置)。 -但最重要的是:下载,串流或安排节目并用可调节回放速度,章节支持和睡眠定时器以您喜欢的方式享受它们。 - -AntennaPod由播客爱好者开发,在任意方面上都是自由的:开源,免费,无广告。 - -<b>所有功能:</b><br> -导入、整理与播放<br> -• 通过 iTunes、gPodder.net、OPML 文件、RSS 或 Atom 链接添加和导入订阅 -•;从任意地点管理回放:主屏widget,系统通知,耳塞和蓝牙控制装置 -•;借助可调节回放速度,章节支持(Mp3、Vorbis注释和Podlove),记忆的回放位置和一个高级的睡眠定时器 (摇动设备来重置、调低音量并减慢回放)以您自己的方式享受收听播客节目 -#8226; 访问受密码保护的 feeds 和剧集 -• 充分利用分页 feeds (www.podlove.org/paged-feeds) - -保持更新,分享&欣赏 -&#8226; 通过将剧集标记为收藏来追踪最佳的剧集 -• 通过回放历史或搜索(标题和节目笔记)找到您需要的那期节目 -• 通过高级社交媒体和电邮选项,gPodder.net服务和导出OPML文件来分享节目和源 - -一切尽在掌控<br> -• 控制自动下载:选择源,排除移动网络,选择特定无线网络,要求手机充电并设定次数或间隔 -• 通过设定缓存节目的数量管理存储,智能删除(基于您的喜好和播放状态)并选择您偏好的存储位置 -• 使用对应您的语言的 AntennaPod (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br> -&#8226; 使用浅色和深色主题以适应您的环境 -&#8226; 使用 gPodder.net 集成功能和 OPML 导出以备份您的订阅 - -<b>加入 AntennaPod 社区!</b><br> +AntennaPod是一个播客管理器和播放器,可以让你即时访问数以百万计的免费和付费播客,从独立播主到大型新闻机构出版社,如BBC、NPR和CNN。使用iTunes podcast数据库、OPML文件或简单的RSS地址轻而易举地添加、导入和导出它们的提要(Feed)。 +下载,在线播放或对音频进行排队,并借助可调节的回放速度、章节支持及睡眠定时器以您喜爱的方式享用它们。 +通过针对音频下载(指定时间、间隔和WiFi网络)和音频删除(根据您的喜好和延迟设置)的强大自动控制功能,节省精力、电池电量和移动数据使用量。 + +由播客爱好者制作的AntennaPod在任何意义上都是免费的:开源、无成本、无广告。 + +<b>导入、组织和播放</b> +•从任何地方管理播放:主屏幕小部件、系统通知、耳塞和蓝牙控制 +•通过iTunes和gPodder.net目录、OPML文件和RSS或Atom链接添加和导入提要 +•通过可调整的回放速度、章节支持、记忆回放位置及高级睡眠定时器(晃动手机重置、调低音量)以您的方式享受收听 +•访问有密码保护的提要和音频 + +<b>保持跟踪、分享和欣赏</b> +•把剧集标记为最爱 +•通过回放历史或通过搜索标题和shownotes找到某期节目 +•通过高级社交媒体和电子邮件选项、gPodder.net服务和导出OPML分享剧集和提要 + +<b>控制系统</b> +•控制自动下载:选择订阅源、排除移动网络、选择特定的WiFi网络、要求手机充电,设置次数或间隔 +•通过设置缓存的剧集数量、智能删除和选择您的首选位置来管理存储 +•使用浅色和深色主题适应你的环境 +• 使用gPodder.net集成和OPML导出备份订阅 + +<b>加入AntennaPod社区</b> 志愿者正在积极开发 AntennaPod 。您也可以通过代码或评论做出贡献! -GitHub 是申请增加功能,报告错误和贡献代码的地方: +前往Github请求功能、报告错误和贡献代码 https://www.github.com/AntennaPod/AntennaPod -对于所有志愿者来说,我们的谷歌社群是分享你的创意,收藏的播客瞬间和表达感激的地方: -https://groups.google.com/forum/#!forum/antennapod - -抱有疑问或者想要向我们提供反馈? -https://twitter.com/@AntennaPod - -Transifex 是与翻译者帮助项目的网站: -https://www.transifex.com/antennapod/antennapod - -查看我们的测试程序以获得最新的功能列表: -https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
\ No newline at end of file +要帮忙翻译请前往Transifex +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/release-notes/en-US/default.txt b/app/src/main/play/release-notes/en-US/default.txt index 20f9bf802..667338c61 100644 --- a/app/src/main/play/release-notes/en-US/default.txt +++ b/app/src/main/play/release-notes/en-US/default.txt @@ -1,7 +1,3 @@ -- Added per-feed playback speed setting (by @spacecowboy) -- Support sorting in Podcast screen (by @orionlee) -- Option to show stream button rather than download in lists (by @dsmith47) -- Option to replace Episode cover with Podcast cover (by @xgouchet) -- Transparent widget (by @M-arcel) -- User interface tweaks (by @ByteHamster) -- Tons of bug fixes and improvements +- Enabled picture-in-picture for video podcasts by default (by @ByteHamster) +- Fixed podcast discovery not showing local trends (by @tonytamsf) +- Various bug fixes and improvements (by @ByteHamster) diff --git a/app/src/main/res/layout/all_episodes_fragment.xml b/app/src/main/res/layout/all_episodes_fragment.xml index 784e7a1c8..2ccfe2494 100644 --- a/app/src/main/res/layout/all_episodes_fragment.xml +++ b/app/src/main/res/layout/all_episodes_fragment.xml @@ -34,7 +34,7 @@ app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" tools:itemCount="13" - tools:listitem="@layout/new_episodes_listitem" /> + tools:listitem="@layout/feeditemlist_item" /> <ProgressBar android:id="@+id/progLoading" diff --git a/app/src/main/res/layout/cover_fragment.xml b/app/src/main/res/layout/cover_fragment.xml index 8e0ec3679..4292344fd 100644 --- a/app/src/main/res/layout/cover_fragment.xml +++ b/app/src/main/res/layout/cover_fragment.xml @@ -1,44 +1,49 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:padding="8dp"> + android:padding="8dp" + android:gravity="center"> + + <de.danoeh.antennapod.view.SquareImageView + android:id="@+id/imgvCover" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:contentDescription="@string/cover_label" + android:scaleType="fitCenter" + android:layout_marginLeft="32dp" + android:layout_marginRight="32dp" + android:transitionName="coverTransition" + tools:src="@android:drawable/sym_def_app_icon" + android:foreground="?attr/selectableItemBackgroundBorderless" + squareImageView:useMinimum="true" /> <TextView android:id="@+id/txtvPodcastTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="0.25" android:ellipsize="end" android:gravity="center" android:maxLines="2" + android:layout_marginTop="16dp" android:textColor="?android:attr/textColorSecondary" android:textIsSelectable="true" tools:text="Podcast" /> - <ImageView - android:id="@+id/imgvCover" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="0.5" - android:contentDescription="@string/cover_label" - android:scaleType="fitCenter" - android:transitionName="coverTransition" - tools:src="@android:drawable/sym_def_app_icon" - android:foreground="?attr/selectableItemBackground" /> - <TextView android:id="@+id/txtvEpisodeTitle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="0.25" android:ellipsize="end" android:gravity="center" android:maxLines="2" android:textColor="?android:attr/textColorPrimary" android:textIsSelectable="true" + android:layout_marginBottom="8dp" tools:text="Episode" /> </LinearLayout> diff --git a/app/src/main/res/layout/downloaded_episodeslist_item.xml b/app/src/main/res/layout/downloaded_episodeslist_item.xml deleted file mode 100644 index 3f8065466..000000000 --- a/app/src/main/res/layout/downloaded_episodeslist_item.xml +++ /dev/null @@ -1,102 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - tools:background="@android:color/darker_gray"> - - <ImageView - android:id="@+id/imgvImage" - android:layout_width="@dimen/thumbnail_length_downloaded_item" - android:layout_height="@dimen/thumbnail_length_downloaded_item" - android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:contentDescription="@string/cover_label" - android:scaleType="centerCrop" - tools:src="@drawable/ic_antenna" - tools:background="@android:color/holo_green_dark"/> - - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding" - android:layout_marginStart="@dimen/listitem_threeline_textleftpadding" - android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" - android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_weight="1" - android:orientation="vertical" - tools:background="@android:color/holo_red_dark"> - - <TextView - android:id="@+id/txtvTitle" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="4dp" - tools:text="Downloaded episode title" - tools:background="@android:color/holo_green_dark"/> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:background="@android:color/holo_red_dark" > - - <TextView - android:id="@+id/txtvSize" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - tools:text="23 MB" - tools:background="@android:color/holo_green_dark"/> - - <View - android:layout_width="0dip" - android:layout_height="1dip" - android:layout_weight="1" /> - - <ImageView - android:id="@+id/imgvInPlaylist" - android:layout_width="@dimen/enc_icons_size" - android:layout_height="@dimen/enc_icons_size" - android:contentDescription="@string/in_queue_label" - android:src="?attr/stat_playlist" - android:visibility="visible" - tools:src="@drawable/ic_list_white_24dp" - tools:background="@android:color/holo_red_light"/> - - <TextView - android:id="@+id/txtvPublished" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - tools:text="Jan 23" - tools:background="@android:color/holo_green_dark"/> - - </LinearLayout> - </LinearLayout> - - <include layout="@layout/vertical_list_divider"/> - - <ImageButton - android:id="@+id/butSecondaryAction" - android:layout_width="@dimen/listview_secondary_button_width" - android:layout_height="match_parent" - android:background="?attr/selectableItemBackground" - android:clickable="false" - android:contentDescription="@string/delete_episode_label" - android:focusable="false" - android:focusableInTouchMode="false" - android:src="?attr/content_discard" - tools:src="@drawable/ic_delete_white_24dp" - tools:background="@android:color/holo_green_dark" /> - -</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/downloadlist_item.xml b/app/src/main/res/layout/downloadlist_item.xml index 668ec817a..7a4c2fede 100644 --- a/app/src/main/res/layout/downloadlist_item.xml +++ b/app/src/main/res/layout/downloadlist_item.xml @@ -1,90 +1,39 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - tools:background="@android:color/darker_gray"> - - <LinearLayout - android:layout_width="0dp" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false" + android:descendantFocusability="blocksDescendants"> - <TextView - android:id="@+id/txtvTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:ellipsize="end" - android:lines="1" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - tools:text="Download item title" - tools:background="@android:color/holo_green_dark" /> - - <ProgressBar - android:id="@+id/progProgress" - style="?android:attr/progressBarStyleHorizontal" - android:layout_width="match_parent" - android:layout_height="16dp" - android:layout_marginBottom="4dp" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="4dp" - tools:background="@android:color/holo_blue_light" /> - - <RelativeLayout - android:layout_width="match_parent" + <LinearLayout + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"> - - <TextView - android:id="@+id/txtvDownloaded" - android:layout_width="wrap_content" + android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" + android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_weight="1" + android:orientation="vertical"> + <TextView + android:id="@+id/txtvTitle" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:ellipsize="end" - android:lines="1" - android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/text_size_small" - tools:text="21 MB / 42 MB" - tools:background="@android:color/holo_green_dark" /> - - <TextView - android:id="@+id/txtvPercent" + tools:text="@sample/episodes.json/data/title" + android:ellipsize="end"/> + <TextView + android:id="@+id/txtvStatus" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:ellipsize="end" - android:lines="1" - android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/text_size_small" - tools:text="50%" - tools:background="@android:color/holo_green_dark" /> - </RelativeLayout> - + tools:text="Media file · 10MB / 20MB"/> </LinearLayout> - - <include layout="@layout/vertical_list_divider"/> - - <ImageButton - android:id="@+id/butSecondaryAction" - android:layout_width="@dimen/listview_secondary_button_width" - android:layout_height="match_parent" - android:background="?attr/selectableItemBackground" - android:clickable="false" - android:contentDescription="@string/cancel_download_label" - android:focusable="false" - android:focusableInTouchMode="false" - android:src="?attr/navigation_cancel" - tools:src="@drawable/ic_cancel_white_24dp" - tools:background="@android:color/holo_green_dark" /> + <include layout="@layout/secondary_action"/> </LinearLayout> diff --git a/app/src/main/res/layout/downloadlog_item.xml b/app/src/main/res/layout/downloadlog_item.xml index 505102ea4..c8cbf15f6 100644 --- a/app/src/main/res/layout/downloadlog_item.xml +++ b/app/src/main/res/layout/downloadlog_item.xml @@ -1,97 +1,90 @@ <?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:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:paddingBottom="8dp" - android:descendantFocusability="blocksDescendants" - tools:background="@android:color/darker_gray"> +<LinearLayout + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false" + android:descendantFocusability="blocksDescendants"> <com.joanzapata.iconify.widget.IconTextView - android:id="@+id/txtvIcon" - android:layout_width="48sp" - android:layout_height="48sp" - android:layout_alignParentTop="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:textSize="48sp" - android:gravity="center" /> + android:id="@+id/txtvIcon" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding" + android:layout_marginStart="@dimen/listitem_threeline_textleftpadding" + android:textSize="40dp" + android:gravity="center" + tools:text="X"/> - <com.joanzapata.iconify.widget.IconButton - android:id="@+id/btnRetry" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/txtvIcon" - android:layout_alignLeft="@id/txtvIcon" - android:layout_alignStart="@id/txtvIcon" - android:layout_alignRight="@id/txtvIcon" - android:layout_alignEnd="@id/txtvIcon" - android:layout_marginTop="8dp" - android:text="{fa-repeat}" - tools:text="↻" /> + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" + android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_weight="1" + android:orientation="vertical"> - <TextView - android:id="@+id/txtvType" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:layout_marginBottom="8dp" - tools:text="Media file" - tools:background="@android:color/holo_green_dark" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/status" + android:orientation="horizontal" + android:gravity="center_vertical"> - <TextView - android:id="@+id/txtvTitle" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_toRightOf="@id/txtvIcon" - android:layout_toEndOf="@id/txtvIcon" - android:layout_toLeftOf="@id/txtvType" - android:layout_toStartOf="@id/txtvType" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:layout_marginBottom="8dp" - android:minLines="1" - android:maxLines="2" - tools:text="Download item title" - tools:background="@android:color/holo_blue_light" /> + <TextView + android:id="@+id/txtvType" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="Media file"/> + <TextView + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:layout_marginLeft="4dp" + android:layout_marginStart="4dp" + android:text="·" + tools:background="@android:color/holo_blue_light"/> + <TextView + android:id="@+id/txtvDate" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="January 23"/> - <TextView - android:id="@+id/txtvDate" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/txtvIcon" - android:layout_toEndOf="@id/txtvIcon" - android:layout_below="@id/txtvTitle" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:layout_marginBottom="8dp" - tools:text="January 23" - tools:background="@android:color/holo_green_dark" /> + </LinearLayout> + <TextView + android:id="@+id/txtvTitle" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:text="@sample/episodes.json/data/title" + android:ellipsize="end" + tools:background="@android:color/holo_blue_light"/> - <TextView - android:id="@+id/txtvReason" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/txtvDate" - android:layout_toRightOf="@id/txtvIcon" - android:layout_toEndOf="@id/txtvIcon" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:textColor="?android:attr/textColorTertiary" - android:textSize="@dimen/text_size_micro" - tools:text="@string/design_time_downloaded_log_failure_reason" - tools:background="@android:color/holo_green_dark" /> + <TextView + android:id="@+id/txtvReason" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textColor="?android:attr/textColorSecondary" + tools:text="@string/design_time_downloaded_log_failure_reason"/> + + </LinearLayout> + + <include layout="@layout/secondary_action"/> -</RelativeLayout>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/external_player_fragment.xml b/app/src/main/res/layout/external_player_fragment.xml index dc890807c..3e2efe47e 100644 --- a/app/src/main/res/layout/external_player_fragment.xml +++ b/app/src/main/res/layout/external_player_fragment.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/fragmentLayout" android:layout_width="match_parent" android:layout_height="@dimen/external_player_height" @@ -44,7 +44,9 @@ android:layout_centerVertical="true" android:contentDescription="@string/pause_label" android:background="?attr/selectableItemBackground" - android:src="?attr/av_play_big" + app:srcCompat="?attr/av_play" + android:scaleType="fitCenter" + android:padding="8dp" tools:src="@drawable/ic_play_arrow_white_36dp"/> <TextView diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml index 3352fdf19..beabd20b6 100644 --- a/app/src/main/res/layout/feeditem_fragment.xml +++ b/app/src/main/res/layout/feeditem_fragment.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/content_root" android:layout_width="match_parent" @@ -11,86 +10,87 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:background="?attr/colorPrimary" android:gravity="center_horizontal" android:orientation="vertical" - tools:background="@android:color/darker_gray"> + android:paddingLeft="16dp" + android:paddingRight="16dp"> - <RelativeLayout + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" android:layout_marginTop="16dp" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp"> + android:orientation="horizontal"> <ImageView android:id="@+id/imgvCover" - android:layout_alignParentTop="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_width="50dp" - android:layout_height="50dp" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" - android:layout_marginBottom="16dp" + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:layout_gravity="center_vertical" android:contentDescription="@string/cover_label" - android:gravity="center_vertical" - android:foreground="?attr/selectableItemBackground" - tools:src="@drawable/ic_antenna" - tools:background="@android:color/holo_green_dark" /> - - <TextView - android:id="@+id/txtvPodcast" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@id/imgvCover" - android:layout_toRightOf="@id/imgvCover" - android:layout_toEndOf="@id/imgvCover" android:foreground="?attr/selectableItemBackground" - 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:layout_toEndOf="@id/imgvCover" - android:textSize="16sp" - android:textColor="?android:attr/textColorPrimary" - android:ellipsize="end" - android:maxLines="5" - tools:text="Episode title" - tools:background="@android:color/holo_green_dark" /> - - <TextView - android:id="@+id/txtvDuration" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/imgvCover" - android:layout_toEndOf="@id/imgvCover" - android:layout_below="@id/txtvTitle" - tools:text="00:42:23" - tools:background="@android:color/holo_green_dark"/> - - <TextView - android:id="@+id/txtvPublished" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_below="@id/txtvTitle" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - tools:text="Jan 23" - tools:background="@android:color/holo_green_dark" /> - - </RelativeLayout> + tools:src="@tools:sample/avatars" /> + + <LinearLayout + android:layout_width="match_parent" + android:orientation="vertical" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/txtvPodcast" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:foreground="?attr/selectableItemBackground" + 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:textSize="16sp" + android:textColor="?android:attr/textColorPrimary" + android:ellipsize="end" + android:maxLines="5" + tools:text="@sample/episodes.json/data/title" + tools:background="@android:color/holo_green_dark" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/txtvDuration" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="@sample/episodes.json/data/duration" + tools:background="@android:color/holo_green_dark" /> + + <TextView + android:id="@+id/separatorIcons" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="4dp" + android:layout_marginStart="4dp" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:text="·" + tools:background="@android:color/holo_blue_light" /> + + <TextView + android:id="@+id/txtvPublished" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="Jan 23" + tools:background="@android:color/holo_green_dark" /> + </LinearLayout> + </LinearLayout> + </LinearLayout> <ProgressBar android:id="@+id/progbarDownload" @@ -98,67 +98,80 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" android:visibility="gone" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="16dp" - android:layout_marginStart="16dp" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" android:orientation="horizontal" + android:gravity="center_vertical" tools:background="@android:color/holo_blue_bright"> - <View + <LinearLayout android:layout_width="0dp" - android:layout_height="1dp" - android:layout_weight="1" /> - - <Button + android:layout_height="wrap_content" + android:layout_weight="1" android:id="@+id/butAction1" - style="?attr/buttonBarButtonStyle" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:ellipsize="end" - android:drawablePadding="8dp" - android:textColor="?android:attr/textColorPrimary" - tools:text="Button 1" - tools:background="@android:color/holo_red_light" /> - - <View + android:orientation="horizontal" + android:background="?android:attr/selectableItemBackground" + android:gravity="center"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:id="@+id/butAction1Icon" + android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + tools:src="@drawable/ic_settings_grey600_24dp" /> + + <TextView + android:textAppearance="@style/TextAppearance.AppCompat.Button" + android:id="@+id/butAction1Text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + tools:text="Button 1" /> + </LinearLayout> + + <LinearLayout android:layout_width="0dp" - android:layout_height="1dp" - android:layout_weight="1" /> - - <Button + android:layout_height="wrap_content" + android:layout_weight="1" android:id="@+id/butAction2" - style="?attr/buttonBarButtonStyle" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:drawablePadding="8dp" - android:ellipsize="end" - android:textColor="?android:attr/textColorPrimary" - tools:text="Button 2" - tools:background="@android:color/holo_orange_dark" /> - - <View - android:layout_width="0dp" - android:layout_height="1dp" - android:layout_weight="1" /> - + android:orientation="horizontal" + android:background="?android:attr/selectableItemBackground" + android:gravity="center"> + + <ImageView + android:layout_width="24dp" + android:layout_height="24dp" + android:id="@+id/butAction2Icon" + android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + tools:src="@drawable/ic_settings_grey600_24dp" /> + + <TextView + android:textAppearance="@style/TextAppearance.AppCompat.Button" + android:id="@+id/butAction2Text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + tools:text="Button 2" /> + </LinearLayout> </LinearLayout> <View android:layout_width="match_parent" - android:layout_height="2dp" - android:background="@color/light_gray"/> + android:layout_height="1dp" + android:background="?android:attr/dividerVertical"/> </LinearLayout> - <WebView + <de.danoeh.antennapod.view.ShownotesWebView android:id="@+id/webvDescription" android:layout_width="match_parent" android:layout_below="@id/header" diff --git a/app/src/main/res/layout/feeditemlist_item.xml b/app/src/main/res/layout/feeditemlist_item.xml index adf0748eb..36e0f4108 100644 --- a/app/src/main/res/layout/feeditemlist_item.xml +++ b/app/src/main/res/layout/feeditemlist_item.xml @@ -1,124 +1,207 @@ <?xml version="1.0" encoding="utf-8"?> - <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - tools:background="@android:color/darker_gray"> - - <RelativeLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" - android:layout_weight="1" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - tools:background="@android:color/holo_orange_dark"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false" + android:paddingLeft="12dp" + android:paddingStart="12dp"> - <TextView - android:id="@+id/statusUnread" - style="@style/AntennaPod.TextView.UnreadIndicator" + <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_alignParentTop="true" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - tools:text="NEW" - tools:background="@android:color/white" /> + android:layout_height="match_parent" + android:minWidth="4dp"> + <ImageView + android:id="@+id/drag_handle" + android:layout_width="24dp" + android:layout_height="match_parent" + android:contentDescription="@string/drag_handle_content_description" + android:scaleType="center" + android:src="?attr/dragview_background" + android:paddingEnd="8dp" + android:paddingRight="8dp" + tools:src="@drawable/ic_drag_vertical_grey600_48dp" + tools:background="@android:color/holo_green_dark"/> - <TextView - android:id="@+id/txtvItemname" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" + </LinearLayout> + + <androidx.cardview.widget.CardView + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_marginRight="@dimen/listitem_threeline_textleftpadding" + android:layout_marginEnd="@dimen/listitem_threeline_textleftpadding" + android:id="@+id/coverHolder" + app:cardCornerRadius="4dp" + app:cardElevation="0dp"> + + <RelativeLayout + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item"> + + <TextView + android:id="@+id/txtvPlaceholder" + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:layout_centerVertical="true" + android:gravity="center" + android:background="@color/light_gray" + android:maxLines="3" + android:padding="2dp" + android:ellipsize="end"/> + <ImageView + android:id="@+id/imgvCover" + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:layout_centerVertical="true" + android:contentDescription="@string/cover_label" + tools:src="@tools:sample/avatars"/> + + </RelativeLayout> + </androidx.cardview.widget.CardView> + + <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:layout_marginBottom="4dp" - android:layout_toLeftOf="@id/statusUnread" - android:layout_toStartOf="@id/statusUnread" - tools:text="Episode title" - tools:background="@android:color/holo_green_dark" /> + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" + android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_weight="1" + tools:background="@android:color/holo_red_dark" + android:orientation="vertical"> - <TextView - android:id="@+id/txtvLenSize" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_below="@id/txtvItemname" - tools:text="00:42:23" - tools:background="@android:color/holo_green_dark" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/status" + android:orientation="horizontal" + android:gravity="center_vertical"> - <ImageView - android:id="@+id/imgvInPlaylist" - android:layout_width="@dimen/enc_icons_size" - android:layout_height="@dimen/enc_icons_size" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_below="@id/txtvItemname" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" - android:contentDescription="@string/in_queue_label" - android:src="?attr/stat_playlist" - android:visibility="visible" - tools:src="@drawable/ic_list_white_24dp" - tools:background="@android:color/holo_red_light" /> + <TextView + android:text="@string/new_episodes_label" + style="@style/AntennaPod.TextView.UnreadIndicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/statusUnread" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + tools:text="@sample/episodes.json/data/status_label"/> - <ImageView - android:id="@+id/imgvType" - android:layout_width="@dimen/enc_icons_size" - android:layout_height="@dimen/enc_icons_size" - android:layout_below="@id/txtvItemname" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" - android:layout_toLeftOf="@id/imgvInPlaylist" - android:layout_toStartOf="@id/imgvInPlaylist" - tools:ignore="ContentDescription" - tools:src="@drawable/ic_hearing_white_18dp" - tools:background="@android:color/holo_red_light" /> + <ImageView + android:layout_width="14sp" + android:layout_height="14sp" + app:srcCompat="?attr/type_video" + tools:srcCompat="@drawable/ic_videocam_grey600_24dp" + android:id="@+id/ivIsVideo"/> + + <ImageView + android:layout_width="14sp" + android:layout_height="14sp" + app:srcCompat="?attr/ic_unfav" + tools:srcCompat="@drawable/ic_star_grey600_24dp" + android:id="@+id/isFavorite"/> + <ImageView + android:layout_width="14sp" + android:layout_height="14sp" + app:srcCompat="?attr/stat_playlist" + tools:srcCompat="@drawable/ic_list_grey600_24dp" + android:id="@+id/ivInPlaylist"/> + + <TextView + android:id="@+id/separatorIcons" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="4dp" + android:layout_marginStart="4dp" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:text="·" + tools:background="@android:color/holo_blue_light"/> + + <TextView + android:id="@+id/txtvPubDate" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + tools:text="@sample/episodes.json/data/published_at"/> + + <TextView + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:text="·" + tools:background="@android:color/holo_blue_light"/> + + <TextView + android:id="@+id/size" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_marginRight="4dp" + android:layout_marginEnd="4dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="10 MB"/> + + </LinearLayout> <TextView - android:id="@+id/txtvPublished" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/txtvItemname" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" - android:layout_toLeftOf="@id/imgvType" - android:layout_toStartOf="@id/imgvType" - tools:text="Jan 23" - tools:background="@android:color/holo_green_dark" /> - - <ProgressBar - android:id="@+id/pbar_episode_progress" - style="?android:attr/progressBarStyleHorizontal" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_alignBottom="@id/txtvPublished" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - android:layout_toStartOf="@id/txtvPublished" - android:layout_toLeftOf="@id/txtvPublished" - android:layout_toEndOf="@id/txtvLenSize" - android:layout_toRightOf="@id/txtvLenSize" - android:layoutDirection="ltr" - android:indeterminate="false" - android:max="100" - android:progress="42" - tools:background="@android:color/holo_blue_light" /> - - </RelativeLayout> - - <include layout="@layout/vertical_list_divider"/> + android:id="@+id/txtvTitle" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:text="@sample/episodes.json/data/title" + android:ellipsize="end" + tools:background="@android:color/holo_blue_light"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/progress" + android:orientation="horizontal" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/txtvPosition" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="0dp" + tools:text="00:42:23" + tools:background="@android:color/holo_blue_light"/> + + <ProgressBar + android:id="@+id/progressBar" + style="?attr/progressBarTheme" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="4dp" + android:max="100" + android:layout_margin="4dp" + tools:background="@android:color/holo_blue_light"/> + + <TextView + android:id="@+id/txtvDuration" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="0dp" + tools:text="@sample/episodes.json/data/duration" + tools:background="@android:color/holo_blue_light"/> + + </LinearLayout> + + </LinearLayout> <include layout="@layout/secondary_action"/> diff --git a/app/src/main/res/layout/import_export_activity.xml b/app/src/main/res/layout/import_export_activity.xml deleted file mode 100644 index 97ff34e41..000000000 --- a/app/src/main/res/layout/import_export_activity.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:id="@+id/import_export_layout" - android:padding="8dp" - android:clipToPadding="false"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/import_export_warning" - android:gravity="center_horizontal"/> - - <Button - android:text="@string/label_export" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/button_export" - android:layout_marginTop="24dp"/> - - <Button - android:text="@string/label_import" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/button_import"/> - </LinearLayout> -</ScrollView> diff --git a/app/src/main/res/layout/mediaplayerinfo_activity.xml b/app/src/main/res/layout/mediaplayerinfo_activity.xml index 61a832043..d434c3f13 100644 --- a/app/src/main/res/layout/mediaplayerinfo_activity.xml +++ b/app/src/main/res/layout/mediaplayerinfo_activity.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.drawerlayout.widget.DrawerLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawer_layout" @@ -26,80 +25,79 @@ android:minHeight="?attr/actionBarSize" tools:background="@android:color/darker_gray"/> - <com.viewpagerindicator.CirclePageIndicator + <de.danoeh.antennapod.view.PagerIndicatorView android:id="@+id/page_indicator" - android:layout_height="wrap_content" - android:layout_width="match_parent" + android:layout_height="8dp" + android:layout_width="40dp" android:layout_marginTop="-12dp" - android:layout_marginBottom="4dp" - android:background="@android:color/transparent" - app:fillColor="?android:attr/textColorSecondary" - app:strokeColor="?android:attr/textColorSecondary" - app:radius="4dp" /> + android:paddingBottom="4dp" + android:layout_gravity="center" + android:background="@android:color/transparent" /> </com.google.android.material.appbar.AppBarLayout> + <androidx.viewpager.widget.ViewPager + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_above="@id/playtime_layout" + android:layout_below="@id/appBar" + android:foreground="?android:windowContentOverlay" + tools:background="@android:color/holo_orange_light" + android:layout_marginBottom="12dp" /> + + <SeekBar + android:id="@+id/sbPosition" + android:layout_width="match_parent" + android:layout_height="24dp" + android:max="500" + tools:progress="100" + android:layout_above="@id/playtime_layout" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layoutDirection="ltr" + tools:background="@android:color/holo_green_dark" /> + <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/playtime_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" android:layout_alignParentBottom="true" - android:background="?attr/overlay_drawable" android:layoutDirection="ltr" android:orientation="vertical"> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="@dimen/scrubber_vertical_padding" - android:paddingBottom="@dimen/scrubber_vertical_padding"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:layout_marginBottom="4dp"> <TextView - android:id="@+id/txtvPosition" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:text="@string/position_default_label" - android:textColor="?android:attr/textColorSecondary" - android:textSize="@dimen/text_size_micro" - tools:background="@android:color/holo_green_dark"/> + android:id="@+id/txtvPosition" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:text="@string/position_default_label" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_micro" + tools:background="@android:color/holo_green_dark" /> <TextView - android:id="@+id/txtvLength" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" - android:text="@string/position_default_label" - android:textColor="?android:attr/textColorSecondary" - android:textSize="@dimen/text_size_micro" - tools:background="@android:color/holo_green_dark"/> - - <SeekBar - android:id="@+id/sbPosition" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:layout_marginRight="8dp" - android:layout_marginEnd="8dp" - android:layout_toLeftOf="@id/txtvLength" - android:layout_toStartOf="@id/txtvLength" - android:layout_toRightOf="@id/txtvPosition" - android:layout_toEndOf="@id/txtvPosition" - android:max="500" - tools:background="@android:color/holo_green_dark"/> + android:id="@+id/txtvLength" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:text="@string/position_default_label" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_micro" + tools:background="@android:color/holo_green_dark" /> </RelativeLayout> @@ -107,26 +105,36 @@ android:id="@+id/player_control" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="4dp" - android:paddingBottom="8dp" - android:background="?attr/overlay_background" + android:layout_marginBottom="24dp" tools:background="@android:color/holo_purple"> <ImageButton android:id="@+id/butPlay" - android:layout_width="@dimen/audioplayer_playercontrols_length" - android:layout_height="@dimen/audioplayer_playercontrols_length" + android:layout_width="@dimen/audioplayer_playercontrols_length_big" + android:layout_height="@dimen/audioplayer_playercontrols_length_big" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" + android:padding="8dp" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/pause_label" + app:srcCompat="?attr/av_play" + android:scaleType="fitCenter" + tools:srcCompat="@drawable/ic_av_play_white_24dp" + tools:background="@android:color/holo_green_dark" /> + + <de.danoeh.antennapod.view.CircularProgressBar + android:layout_width="@dimen/audioplayer_playercontrols_length_big" + android:layout_height="@dimen/audioplayer_playercontrols_length_big" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_marginRight="16dp" android:layout_marginEnd="16dp" android:layout_centerHorizontal="true" - android:background="?attr/selectableItemBackground" - android:contentDescription="@string/pause_label" - android:src="?attr/av_play" - android:scaleType="fitCenter" - tools:src="@drawable/ic_play_arrow_white_24dp" - tools:background="@android:color/holo_green_dark" /> + android:layout_centerVertical="true" /> <ImageButton android:id="@+id/butRev" @@ -134,13 +142,14 @@ android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_toLeftOf="@id/butPlay" android:layout_toStartOf="@id/butPlay" - android:layout_marginLeft="16dp" - android:layout_marginStart="16dp" - android:background="?attr/selectableItemBackground" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/rewind_label" - android:src="?attr/av_rew_big" + app:srcCompat="?attr/av_rewind" android:scaleType="fitCenter" - tools:src="@drawable/ic_fast_rewind_white_36dp" + tools:srcCompat="@drawable/ic_av_fast_rewind_white_48dp" tools:background="@android:color/holo_blue_dark" /> <TextView @@ -152,12 +161,11 @@ android:layout_alignStart="@id/butRev" android:layout_alignRight="@id/butRev" android:layout_alignEnd="@id/butRev" - android:layout_marginTop="-8dp" android:gravity="center" android:text="30" android:textSize="12sp" android:textColor="?android:attr/textColorSecondary" - android:clickable="false"/> + android:clickable="false" /> <de.danoeh.antennapod.view.PlaybackSpeedIndicatorView android:id="@+id/butPlaybackSpeed" @@ -165,9 +173,10 @@ android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_toLeftOf="@id/butRev" android:layout_toStartOf="@id/butRev" - android:background="?attr/selectableItemBackground" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/set_playback_speed_label" - tools:src="@drawable/ic_playback_speed_white_48dp" + tools:srcCompat="@drawable/ic_playback_speed_white_48dp" tools:visibility="gone" tools:background="@android:color/holo_green_dark" /> @@ -180,12 +189,11 @@ android:layout_alignStart="@id/butPlaybackSpeed" android:layout_alignRight="@id/butPlaybackSpeed" android:layout_alignEnd="@id/butPlaybackSpeed" - android:layout_marginTop="-8dp" android:gravity="center" android:text="1.00" android:textSize="12sp" android:textColor="?android:attr/textColorSecondary" - android:clickable="false"/> + android:clickable="false" /> <ImageButton android:id="@+id/butCastDisconnect" @@ -193,7 +201,8 @@ android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_toLeftOf="@id/butRev" android:layout_toStartOf="@id/butRev" - android:background="?attr/selectableItemBackground" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/cast_disconnect_label" android:src="?attr/ic_cast_disconnect" android:scaleType="fitCenter" @@ -208,13 +217,14 @@ android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_toRightOf="@id/butPlay" android:layout_toEndOf="@id/butPlay" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" - android:background="?attr/selectableItemBackground" + android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/fast_forward_label" - android:src="?attr/av_ff_big" + app:srcCompat="?attr/av_fast_forward" android:scaleType="fitCenter" - tools:src="@drawable/ic_fast_forward_white_36dp" + tools:srcCompat="@drawable/ic_av_fast_forward_white_48dp" tools:background="@android:color/holo_blue_dark" /> <TextView @@ -226,12 +236,11 @@ android:layout_alignStart="@id/butFF" android:layout_alignRight="@id/butFF" android:layout_alignEnd="@id/butFF" - android:layout_marginTop="-8dp" android:gravity="center" android:text="30" android:textSize="12sp" android:textColor="?android:attr/textColorSecondary" - android:clickable="false"/> + android:clickable="false" /> <ImageButton android:id="@+id/butSkip" @@ -239,33 +248,17 @@ android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_toRightOf="@id/butFF" android:layout_toEndOf="@id/butFF" - android:background="?attr/selectableItemBackground" + android:layout_centerVertical="true" + android:background="?attr/selectableItemBackgroundBorderless" android:scaleType="fitCenter" - android:src="?attr/av_skip_big" + app:srcCompat="?attr/av_skip" android:contentDescription="@string/skip_episode_label" - tools:src="@drawable/ic_skip_white_36dp" + tools:srcCompat="@drawable/ic_av_skip_white_48dp" tools:background="@android:color/holo_green_dark" /> </RelativeLayout> </LinearLayout> - <androidx.viewpager.widget.ViewPager - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/pager" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_above="@id/playtime_layout" - android:layout_below="@id/appBar" - android:foreground="?android:windowContentOverlay" - tools:background="@android:color/holo_orange_light" /> - - <View - android:id="@+id/shadow" - android:layout_width="match_parent" - android:layout_height="4dp" - android:layout_below="@id/appBar" - android:background="@drawable/shadow" /> - </RelativeLayout> <include layout="@layout/nav_list" /> diff --git a/app/src/main/res/layout/nav_listitem.xml b/app/src/main/res/layout/nav_listitem.xml index 1332b5263..d63252b58 100644 --- a/app/src/main/res/layout/nav_listitem.xml +++ b/app/src/main/res/layout/nav_listitem.xml @@ -24,7 +24,7 @@ android:layout_marginStart="@dimen/listitem_icon_leftpadding" android:layout_marginTop="4dp" android:layout_marginBottom="4dp" - tools:src="@drawable/ic_new_releases_white_24dp" + tools:src="@drawable/ic_file_download_grey600_24dp" tools:background="@android:color/holo_green_dark"/> diff --git a/app/src/main/res/layout/new_episodes_listitem.xml b/app/src/main/res/layout/new_episodes_listitem.xml deleted file mode 100644 index 150d692e7..000000000 --- a/app/src/main/res/layout/new_episodes_listitem.xml +++ /dev/null @@ -1,147 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<FrameLayout - android:id="@+id/container" - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - -<LinearLayout - android:id="@+id/content" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" - android:orientation="horizontal" - android:gravity="center_vertical"> - - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <TextView - android:id="@+id/txtvPlaceholder" - android:layout_width="@dimen/thumbnail_length_itemlist" - android:layout_height="@dimen/thumbnail_length_itemlist" - android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:background="@color/light_gray" - android:ellipsize="end" - android:maxLines="3" - android:gravity="center"/> - - <ImageView - android:id="@+id/imgvCover" - android:layout_height="64dp" - android:layout_width="64dp" - android:layout_alignLeft="@id/txtvPlaceholder" - android:layout_alignStart="@id/txtvPlaceholder" - android:layout_alignTop="@id/txtvPlaceholder" - android:layout_alignRight="@id/txtvPlaceholder" - android:layout_alignEnd="@id/txtvPlaceholder" - android:layout_alignBottom="@id/txtvPlaceholder" - android:contentDescription="@string/cover_label" - tools:src="@tools:sample/avatars" /> - - </RelativeLayout> - - <RelativeLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding" - android:layout_marginStart="@dimen/listitem_threeline_textleftpadding" - android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" - android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:layout_weight="1"> - - - <TextView - android:id="@+id/statusUnread" - style="@style/AntennaPod.TextView.UnreadIndicator" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_alignParentTop="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - tools:text="@sample/episodes.json/data/status_label"/> - - <TextView - android:id="@+id/txtvTitle" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:layout_toLeftOf="@id/statusUnread" - android:layout_toStartOf="@id/statusUnread" - tools:text="@sample/episodes.json/data/title" /> - - <RelativeLayout - android:id="@+id/bottom_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_below="@id/txtvTitle" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true"> - - <TextView - android:id="@+id/txtvDuration" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - tools:text="@sample/episodes.json/data/duration" /> - - <ImageView - android:id="@+id/imgvInPlaylist" - android:layout_width="@dimen/enc_icons_size" - android:layout_height="@dimen/enc_icons_size" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:contentDescription="@string/in_queue_label" - android:src="?attr/stat_playlist" - tools:src="@sample/inplaylist" /> - - <TextView - android:id="@+id/txtvPublished" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_toLeftOf="@id/imgvInPlaylist" - android:layout_toStartOf="@id/imgvInPlaylist" - android:ellipsize="end" - tools:text="@sample/episodes.json/data/published_at" /> - - <ProgressBar - android:id="@+id/pbar_progress" - style="?attr/progressBarTheme" - android:layout_width="match_parent" - android:layout_height="4dp" - android:layout_below="@id/txtvDuration" - android:max="100" /> - - </RelativeLayout> - </RelativeLayout> - - <include layout="@layout/vertical_list_divider"/> - - <include layout="@layout/secondary_action" /> - -</LinearLayout> - -</FrameLayout> diff --git a/app/src/main/res/layout/opml_import.xml b/app/src/main/res/layout/opml_import.xml deleted file mode 100644 index 9b2036228..000000000 --- a/app/src/main/res/layout/opml_import.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:paddingBottom="8dp" - tools:background="@android:color/darker_gray"> - - <TextView - android:id="@+id/txtvHeadingExplanation" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/AntennaPod.TextView.Heading" - android:text="@string/txtvfeedurl_label"/> - - <TextView - android:id="@+id/txtvExplanation" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/opml_import_explanation_1" - android:textSize="@dimen/text_size_medium" - android:layout_marginTop="4dp" - tools:background="@android:color/holo_green_dark" /> - - <Button - android:id="@+id/butChooseFileFromFilesystem" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="8dp" - android:text="@string/choose_file_from_filesystem" /> - - <View - android:id="@+id/divider" - android:layout_width="fill_parent" - android:layout_height="1dp" - android:layout_margin="16dp" - android:background="?android:attr/listDivider"/> - - <TextView - android:id="@+id/txtvHeadingExplanationOpenWith" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/AntennaPod.TextView.Heading" - android:text="@string/txtvfeedurl_label"/> - - <TextView - android:id="@+id/txtvExplanationOpenWith" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/opml_import_explanation_3" - android:textSize="@dimen/text_size_medium" - android:layout_marginTop="4dp" - tools:background="@android:color/holo_green_dark" /> - - </LinearLayout> -</ScrollView> diff --git a/app/src/main/res/layout/opml_selection.xml b/app/src/main/res/layout/opml_selection.xml index f8f37b5ac..e018ffde3 100644 --- a/app/src/main/res/layout/opml_selection.xml +++ b/app/src/main/res/layout/opml_selection.xml @@ -37,6 +37,7 @@ android:layout_toLeftOf="@id/horizontal_divider" android:layout_toStartOf="@id/horizontal_divider" android:background="?android:attr/selectableItemBackground" + android:textColor="?android:attr/textColorPrimary" android:text="@string/cancel_label" /> <Button @@ -50,6 +51,7 @@ android:layout_toRightOf="@id/horizontal_divider" android:layout_toEndOf="@id/horizontal_divider" android:background="?android:attr/selectableItemBackground" + android:textColor="?android:attr/textColorPrimary" android:text="@string/confirm_label" /> </RelativeLayout> diff --git a/app/src/main/res/layout/queue_listitem.xml b/app/src/main/res/layout/queue_listitem.xml deleted file mode 100644 index 1dcc34bce..000000000 --- a/app/src/main/res/layout/queue_listitem.xml +++ /dev/null @@ -1,153 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<FrameLayout - android:id="@+id/container" - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - -<LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" - android:orientation="horizontal" - android:gravity="center_vertical" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - tools:background="@android:color/darker_gray" > - - <ImageView - android:id="@+id/drag_handle" - android:layout_width="104dp" - android:layout_height="64dp" - android:layout_marginLeft="-16dp" - android:layout_marginStart="-16dp" - android:layout_marginRight="-72dp" - android:layout_marginEnd="-72dp" - android:contentDescription="@string/drag_handle_content_description" - android:scaleType="fitXY" - android:src="?attr/dragview_background" - tools:src="@drawable/ic_drag_vertical_grey600_48dp" - tools:background="@android:color/holo_green_dark" /> - - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp"> - <TextView - android:id="@+id/txtvPlaceholder" - android:layout_width="@dimen/thumbnail_length_queue_item" - android:layout_height="@dimen/thumbnail_length_queue_item" - android:layout_centerVertical="true" - android:gravity="center" - android:background="@color/light_gray" - android:maxLines="3" - android:ellipsize="end"/> - <ImageView - android:id="@+id/imgvCover" - android:layout_width="@dimen/thumbnail_length_queue_item" - android:layout_height="@dimen/thumbnail_length_queue_item" - android:layout_centerVertical="true" - android:contentDescription="@string/cover_label" - tools:src="@drawable/ic_antenna" - tools:background="@android:color/holo_green_dark"/> - </RelativeLayout> - - <RelativeLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding" - android:layout_marginStart="@dimen/listitem_threeline_textleftpadding" - android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" - android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:layout_weight="1" - tools:background="@android:color/holo_red_dark"> - - <!-- order is important, pubDate first! --> - <TextView - android:id="@+id/txtvPubDate" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:lines="2" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_alignParentTop="true" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - android:gravity="end|top" - android:text="Feb\n12" - tools:background="@android:color/holo_blue_light" /> - - <TextView - android:id="@+id/txtvTitle" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toLeftOf="@id/txtvPubDate" - android:layout_toStartOf="@id/txtvPubDate" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:text="Queue item title" - android:ellipsize="end" - tools:background="@android:color/holo_blue_light" /> - - <RelativeLayout - android:id="@+id/bottom_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_below="@id/txtvTitle" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true"> - - <TextView - android:id="@+id/txtvProgressLeft" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_marginBottom="0dp" - android:text="00:42:23" - tools:background="@android:color/holo_blue_light"/> - - <TextView - android:id="@+id/txtvProgressRight" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:layout_marginBottom="0dp" - tools:text="Jan 23" - tools:background="@android:color/holo_green_dark" /> - - <ProgressBar - android:id="@+id/progressBar" - style="?attr/progressBarTheme" - android:layout_width="match_parent" - android:layout_height="4dp" - android:layout_below="@id/txtvProgressLeft" - android:layoutDirection="ltr" - android:max="100" - tools:background="@android:color/holo_blue_light" /> - - - </RelativeLayout> - </RelativeLayout> - - <include layout="@layout/vertical_list_divider"/> - - <include layout="@layout/secondary_action"/> - -</LinearLayout> - -</FrameLayout> diff --git a/app/src/main/res/layout/search_fragment.xml b/app/src/main/res/layout/search_fragment.xml index 6f455a056..489c2d392 100644 --- a/app/src/main/res/layout/search_fragment.xml +++ b/app/src/main/res/layout/search_fragment.xml @@ -13,8 +13,5 @@ <ListView android:id="@+id/listview" android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipToPadding="false" - android:paddingLeft="@dimen/list_vertical_padding" - android:paddingRight="@dimen/list_vertical_padding" /> + android:layout_height="match_parent" /> </FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/secondary_action.xml b/app/src/main/res/layout/secondary_action.xml index 1f4d9e4e6..73ca174a6 100644 --- a/app/src/main/res/layout/secondary_action.xml +++ b/app/src/main/res/layout/secondary_action.xml @@ -1,12 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> -<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/butSecondaryAction" - android:layout_width="@dimen/listview_secondary_button_width" - android:layout_height="match_parent" - android:background="?attr/selectableItemBackground" - android:clickable="false" - android:focusable="false" - android:focusableInTouchMode="false" - tools:ignore="ContentDescription" - tools:src="@sample/secondaryaction" /> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginRight="12dp" + android:layout_marginEnd="12dp" + android:id="@+id/secondaryActionButton" + android:background="?selectableItemBackgroundBorderless" + android:clickable="true" + android:focusable="false" + android:focusableInTouchMode="false" > + + <ImageView + android:id="@+id/secondaryActionIcon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + tools:ignore="ContentDescription" + tools:src="@sample/secondaryaction"/> + + <de.danoeh.antennapod.view.CircularProgressBar + android:id="@+id/secondaryActionProgress" + android:layout_width="40dp" + android:layout_gravity="center" + android:layout_height="40dp"/> +</FrameLayout> diff --git a/app/src/main/res/layout/simplechapter_item.xml b/app/src/main/res/layout/simplechapter_item.xml index 0d02eac1a..54c607d71 100644 --- a/app/src/main/res/layout/simplechapter_item.xml +++ b/app/src/main/res/layout/simplechapter_item.xml @@ -1,86 +1,73 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - tools:background="@android:color/darker_gray"> - - <TextView - android:id="@+id/txtvStart" - style="@style/AntennaPod.TextView.ListItemSecondaryTitle" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="center_vertical" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" android:gravity="center_vertical" - tools:text="Start" - tools:background="@android:color/holo_green_dark" /> + android:baselineAligned="false" + android:descendantFocusability="blocksDescendants"> + + <ImageView + android:id="@+id/imgvCover" + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:contentDescription="@string/cover_label" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + tools:src="@tools:sample/avatars"/> <LinearLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginStart="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginEnd="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:layout_weight="1" - android:gravity="center_vertical" - android:orientation="vertical" - tools:background="@android:color/holo_red_dark"> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginRight="@dimen/listitem_threeline_textrightpadding" + android:layout_marginEnd="@dimen/listitem_threeline_textrightpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_weight="1" + android:orientation="vertical"> <TextView - android:id="@+id/txtvTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="4dp" - android:ellipsize="end" - android:maxLines="2" - android:textColor="?android:attr/textColorPrimary" - android:textSize="16sp" - tools:text="Chapter title" - tools:background="@android:color/holo_green_dark" /> + android:id="@+id/txtvStart" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="00:00:00"/> <TextView - android:id="@+id/txtvLink" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="false" - android:focusableInTouchMode="false" - android:maxLines="1" - android:visibility="gone" - tools:visibility="visible" - tools:text="Link" - tools:background="@android:color/holo_green_dark" /> + android:id="@+id/txtvTitle" + style="@style/AntennaPod.TextView.ListItemPrimaryTitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:text="@sample/episodes.json/data/title" + android:ellipsize="end"/> + + <TextView + android:id="@+id/txtvLink" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" + android:focusable="false" + android:focusableInTouchMode="false" + android:visibility="gone" + android:background="?attr/selectableItemBackground" + tools:visibility="visible" + tools:text="https://example.com"/> <TextView android:id="@+id/txtvDuration" android:layout_width="wrap_content" android:layout_height="wrap_content" + style="@style/AntennaPod.TextView.ListItemSecondaryTitle" android:focusable="false" android:focusableInTouchMode="false" - android:maxLines="1" - tools:text="Duration" - tools:background="@android:color/holo_green_dark" /> - + tools:text="Duration: 00:00:00"/> </LinearLayout> - <include layout="@layout/vertical_list_divider" /> - - <ImageButton xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/butPlayChapter" - android:layout_width="@dimen/listview_secondary_button_width" - android:layout_height="match_parent" - android:background="?attr/selectableItemBackground" - android:clickable="false" - android:contentDescription="@string/chapters_label" - android:focusable="false" - android:focusableInTouchMode="false" - android:src="?attr/av_play" - tools:src="@drawable/ic_play_arrow_white_36dp" - tools:background="@android:color/holo_green_dark" /> + <include layout="@layout/secondary_action"/> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/app/src/main/res/layout/statistics_listitem.xml b/app/src/main/res/layout/statistics_listitem.xml index 8e0a7bf81..aaa0f666c 100644 --- a/app/src/main/res/layout/statistics_listitem.xml +++ b/app/src/main/res/layout/statistics_listitem.xml @@ -36,22 +36,36 @@ android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_toRightOf="@id/imgvCover" + android:layout_toEndOf="@id/imgvCover" android:layout_alignTop="@id/imgvCover" android:layout_alignWithParentIfMissing="true" tools:text="Feed title"/> <TextView - android:id="@+id/txtvValue" + android:id="@+id/chip" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:lines="1" - android:textColor="?android:attr/textColorTertiary" - android:textSize="14sp" + android:textSize="13sp" android:layout_toEndOf="@+id/imgvCover" android:layout_toRightOf="@+id/imgvCover" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_below="@+id/txtvTitle" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:text="⬤" + tools:ignore="HardcodedText"/> + + <TextView + android:id="@+id/txtvValue" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:lines="1" + android:textColor="?android:attr/textColorTertiary" + android:textSize="14sp" + android:layout_toEndOf="@+id/chip" + android:layout_toRightOf="@+id/chip" + android:layout_below="@+id/txtvTitle" tools:text="23 hours"/> </RelativeLayout> diff --git a/app/src/main/res/layout/statistics_listitem_total.xml b/app/src/main/res/layout/statistics_listitem_total.xml index f24f13398..2efe37bfe 100644 --- a/app/src/main/res/layout/statistics_listitem_total.xml +++ b/app/src/main/res/layout/statistics_listitem_total.xml @@ -7,10 +7,13 @@ <de.danoeh.antennapod.view.PieChartView android:id="@+id/pie_chart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> + android:maxWidth="800dp" + android:minWidth="460dp" /> <TextView android:layout_width="match_parent" diff --git a/app/src/main/res/layout/subscription_item.xml b/app/src/main/res/layout/subscription_item.xml index 502fa8672..d065c5cf1 100644 --- a/app/src/main/res/layout/subscription_item.xml +++ b/app/src/main/res/layout/subscription_item.xml @@ -1,24 +1,28 @@ <?xml version='1.0' encoding='utf-8'?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> + android:layout_height="match_parent"> <de.danoeh.antennapod.view.SquareImageView android:id="@+id/imgvCover" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="centerCrop" - tools:src="@mipmap/ic_launcher_round"> - </de.danoeh.antennapod.view.SquareImageView> + tools:src="@mipmap/ic_launcher_round" /> <com.joanzapata.iconify.widget.IconTextView android:id="@+id/txtvTitle" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/light_gray" + android:layout_alignLeft="@+id/imgvCover" + android:layout_alignRight="@+id/imgvCover" + android:layout_alignStart="@+id/imgvCover" + android:layout_alignEnd="@+id/imgvCover" + android:layout_alignTop="@+id/imgvCover" + android:layout_alignBottom="@+id/imgvCover" android:ellipsize="end" android:gravity="center" tools:text="@string/app_name" /> @@ -27,11 +31,12 @@ android:id="@+id/triangleCountView" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_gravity="right|top" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true" app:backgroundColor="@color/antennapod_blue" app:corner="rightTop" app:primaryText="Test" app:primaryTextColor="@color/white" - app:primaryTextSize="12sp"> - </jp.shts.android.library.TriangleLabelView> -</FrameLayout> + app:primaryTextSize="12sp" /> +</RelativeLayout> diff --git a/app/src/main/res/layout/time_dialog.xml b/app/src/main/res/layout/time_dialog.xml index b3742c20c..8ada0d6cf 100644 --- a/app/src/main/res/layout/time_dialog.xml +++ b/app/src/main/res/layout/time_dialog.xml @@ -1,67 +1,95 @@ <?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="wrap_content" - android:orientation="vertical" - android:gravity="center" - android:padding="16dp"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:padding="16dp"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" > - - <EditText - android:id="@+id/etxtTime" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_margin="8dp" - android:ems="2" - android:hint="@string/enter_time_here_label" - android:inputType="number" - android:maxLength="2" > - - <requestFocus /> - </EditText> - - <Spinner - android:id="@+id/spTimeUnit" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp" /> + android:id="@+id/timeSetup" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <EditText + android:id="@+id/etxtTime" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_margin="8dp" + android:ems="2" + android:inputType="number" + android:maxLength="3"/> + + <Spinner + android:id="@+id/spTimeUnit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp"/> + </LinearLayout> + + <Button + android:text="@string/set_sleeptimer_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/setSleeptimerButton"/> </LinearLayout> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/timeDisplay" + android:orientation="vertical" + android:visibility="gone"> <TextView - android:layout_width="wrap_content" + android:text="00:00:00" + android:layout_gravity="center" + android:textSize="32sp" + android:textColor="?android:attr/textColorPrimary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/time"/> + + <Button + android:text="@string/disable_sleeptimer_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/disableSleeptimerButton"/> + + </LinearLayout> + + + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/timer_about_to_expire_label" - android:textSize="16sp" /> + android:orientation="vertical" + android:layout_marginTop="8dp"> <CheckBox - android:id="@+id/cbShakeToReset" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/shake_to_reset_label" /> + android:id="@+id/cbShakeToReset" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/shake_to_reset_label"/> <CheckBox - android:id="@+id/cbVibrate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/timer_vibration_label" /> + android:id="@+id/cbVibrate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/timer_vibration_label"/> <CheckBox - android:id="@+id/chAutoEnable" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/auto_enable_label" /> + android:id="@+id/chAutoEnable" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/auto_enable_label"/> </LinearLayout> diff --git a/app/src/main/res/layout/videoplayer_activity.xml b/app/src/main/res/layout/videoplayer_activity.xml index b26ef304c..42484ea1b 100644 --- a/app/src/main/res/layout/videoplayer_activity.xml +++ b/app/src/main/res/layout/videoplayer_activity.xml @@ -27,6 +27,8 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:layoutDirection="ltr" + android:background="@android:color/transparent" + android:padding="16dp" android:orientation="horizontal"> <ImageButton @@ -34,7 +36,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" - android:background="@android:color/transparent" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/rewind_label" app:srcCompat="@drawable/ic_av_fast_rewind_white_80dp" /> @@ -43,7 +45,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" - android:background="@android:color/transparent" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/pause_label" app:srcCompat="@drawable/ic_av_pause_white_80dp" /> @@ -52,7 +54,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" - android:background="@android:color/transparent" + android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/fast_forward_label" app:srcCompat="@drawable/ic_av_fast_forward_white_80dp" /> diff --git a/app/src/main/res/xml/feed_settings.xml b/app/src/main/res/xml/feed_settings.xml index 978f66299..08e7572af 100644 --- a/app/src/main/res/xml/feed_settings.xml +++ b/app/src/main/res/xml/feed_settings.xml @@ -28,6 +28,15 @@ android:summary="@string/feed_auto_download_global" android:key="autoDelete"/> + <ListPreference + android:entries="@array/spnVolumeReductionItems" + android:entryValues="@array/spnVolumeReductionValues" + android:icon="?attr/ic_volume_adaption" + android:summary="@string/feed_volume_reduction_summary" + android:title="@string/feed_volume_reduction" + android:defaultValue="off" + android:key="volumeReduction"/> + <PreferenceCategory android:title="@string/auto_download_settings_label"> <SwitchPreference android:key="autoDownload" diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6e734f789..1ca3a8088 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -19,7 +19,7 @@ android:key="prefScreenPlayback" android:title="@string/playback_pref" android:summary="@string/playback_pref_sum" - android:icon="?attr/av_play" /> + android:icon="?attr/ic_settings_playback" /> <Preference android:key="prefScreenNetwork" diff --git a/app/src/main/res/xml/preferences_import_export.xml b/app/src/main/res/xml/preferences_import_export.xml new file mode 100644 index 000000000..12e27236d --- /dev/null +++ b/app/src/main/res/xml/preferences_import_export.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch"> + + <PreferenceCategory android:title="@string/database"> + <Preference + android:key="prefDatabaseExport" + search:keywords="@string/import_export_search_keywords" + android:title="@string/database_export_label" + android:summary="@string/database_export_summary"/> + <Preference + android:key="prefDatabaseImport" + search:keywords="@string/import_export_search_keywords" + android:title="@string/database_import_label" + android:summary="@string/database_import_summary"/> + </PreferenceCategory> + + <PreferenceCategory android:title="@string/opml"> + <Preference + android:key="prefOpmlExport" + android:title="@string/opml_export_label" + android:summary="@string/opml_export_summary"/> + <Preference + android:key="prefOpmlImport" + android:title="@string/opml_import_label" + android:summary="@string/opml_import_summary"/> + </PreferenceCategory> + + <PreferenceCategory android:title="@string/html"> + <Preference + android:key="prefHtmlExport" + android:title="@string/html_export_label" + android:summary="@string/html_export_summary"/> + </PreferenceCategory> +</PreferenceScreen> diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml index 7a7535dda..32bf383d7 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -35,7 +35,7 @@ android:summary="@string/pref_resumeAfterCall_sum" android:title="@string/pref_resumeAfterCall_title"/> <ListPreference - android:defaultValue="stop" + android:defaultValue="pip" android:entries="@array/video_background_behavior_options" android:entryValues="@array/video_background_behavior_values" android:key="prefVideoBehavior" diff --git a/app/src/main/res/xml/preferences_storage.xml b/app/src/main/res/xml/preferences_storage.xml index 9f394ad12..18e2abb31 100644 --- a/app/src/main/res/xml/preferences_storage.xml +++ b/app/src/main/res/xml/preferences_storage.xml @@ -31,20 +31,9 @@ android:key="prefDeleteRemovesFromQueue" android:summary="@string/pref_delete_removes_from_queue_sum" android:title="@string/pref_delete_removes_from_queue_title"/> - - <PreferenceCategory android:title="@string/import_export_pref"> - <Preference - android:key="prefOpmlExport" - android:title="@string/opml_export_label"/> - <Preference - android:key="prefOpmlImport" - android:title="@string/opml_import_label"/> - <Preference - android:key="prefHtmlExport" - android:title="@string/html_export_label"/> - <Preference - android:key="importExport" - search:keywords="@string/import_export_search_keywords" - android:title="@string/import_export"/> - </PreferenceCategory> + <Preference + android:title="@string/import_export_pref" + android:summary="@string/import_export_summary" + android:key="prefImportExport" + search:ignore="true"/> </PreferenceScreen> diff --git a/app/src/main/res/xml/searchable.xml b/app/src/main/res/xml/searchable.xml index ee73aca8d..0861ecdae 100644 --- a/app/src/main/res/xml/searchable.xml +++ b/app/src/main/res/xml/searchable.xml @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" - android:hint="@string/search_hint" + android:hint="@string/search_label" android:label="@string/app_name"/>
\ No newline at end of file diff --git a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java index 912571a7e..e392a50c6 100644 --- a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -43,8 +43,8 @@ public abstract class CastEnabledActivity extends AppCompatActivity return; } - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()). - registerOnSharedPreferenceChangeListener(this); + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) + .registerOnSharedPreferenceChangeListener(this); castConsumer = new DefaultCastConsumer() { @Override |