1
.gitignore
vendored
@ -42,3 +42,4 @@ libs
|
||||
*.DS_Store
|
||||
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
|
||||
gradle.properties
|
||||
*.keystore
|
||||
|
6
.gitmodules
vendored
@ -1,6 +0,0 @@
|
||||
[submodule "submodules/dslv"]
|
||||
path = submodules/dslv
|
||||
url = git://github.com/danieloeh/drag-sort-listview.git
|
||||
[submodule "app/dslv"]
|
||||
path = app/dslv
|
||||
url = https://github.com/danieloeh/drag-sort-listview.git
|
12
CHANGELOG.md
@ -1,5 +1,15 @@
|
||||
Change Log
|
||||
==========
|
||||
Version 1.1
|
||||
-----------
|
||||
* iTunes podcast integration
|
||||
* Swipe to remove items from the queue
|
||||
* Set the number of parallel downloads
|
||||
* Fix for gpodder.net on old devices
|
||||
* Fixed date problems for some feeds
|
||||
* Display improvements
|
||||
* Usability improvements
|
||||
* Several other bugfixes
|
||||
|
||||
Version 1.0
|
||||
-----------
|
||||
@ -278,4 +288,4 @@ Version 0.8.1
|
||||
------------
|
||||
|
||||
* Added support for SimpleChapters
|
||||
* OPML import
|
||||
* OPML import
|
||||
|
@ -10,6 +10,11 @@ hzulla
|
||||
andrewgaul
|
||||
peschmae0
|
||||
TomHennen
|
||||
mfietz
|
||||
volhol
|
||||
eerden
|
||||
twiceyuan
|
||||
rharriso
|
||||
|
||||
Translations:
|
||||
|
||||
|
@ -11,7 +11,6 @@ dependencies {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile project('dslv:library')
|
||||
compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
|
||||
compile 'org.jsoup:jsoup:1.7.3'
|
||||
compile 'com.squareup.picasso:picasso:2.4.0'
|
||||
@ -19,6 +18,7 @@ dependencies {
|
||||
compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
|
||||
compile 'com.squareup.okio:okio:1.2.0'
|
||||
compile project(':core')
|
||||
compile project(':library:drag-sort-listview')
|
||||
}
|
||||
|
||||
android {
|
||||
|
1
app/dslv
@ -1 +0,0 @@
|
||||
Subproject commit 80011c50e444e1c7d5e13b57bdb127b524a1ff92
|
@ -69,3 +69,13 @@
|
||||
-keep class org.apache.commons.** { *; }
|
||||
|
||||
-dontskipnonpubliclibraryclassmembers
|
||||
|
||||
# disable logging
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static boolean isLoggable(java.lang.String, int);
|
||||
public static *** v(...);
|
||||
public static *** i(...);
|
||||
public static *** w(...);
|
||||
public static *** d(...);
|
||||
public static *** e(...);
|
||||
}
|
@ -1 +0,0 @@
|
||||
include ':app:dslv:library'
|
@ -2,7 +2,12 @@ package de.test.antennapod.service.download;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.FeedFile;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
@ -10,9 +15,6 @@ import de.danoeh.antennapod.core.service.download.HttpDownloader;
|
||||
import de.danoeh.antennapod.core.util.DownloadError;
|
||||
import de.test.antennapod.util.service.download.HTTPBin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpDownloaderTest extends InstrumentationTestCase {
|
||||
private static final String TAG = "HttpDownloaderTest";
|
||||
private static final String DOWNLOAD_DIR = "testdownloads";
|
||||
@ -41,6 +43,7 @@ public class HttpDownloaderTest extends InstrumentationTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
UserPreferences.createInstance(getInstrumentation().getTargetContext());
|
||||
destDir = getInstrumentation().getTargetContext().getExternalFilesDir(DOWNLOAD_DIR);
|
||||
assertNotNull(destDir);
|
||||
assertTrue(destDir.exists());
|
||||
@ -90,7 +93,7 @@ public class HttpDownloaderTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
public void testGzip() {
|
||||
download("http://httpbin.org/gzip", "testGzip", true);
|
||||
download(HTTPBin.BASE_URL + "/gzip/100", "testGzip", true);
|
||||
}
|
||||
|
||||
public void test404() {
|
||||
|
@ -3,12 +3,13 @@ package de.test.antennapod.ui;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.robotium.solo.Solo;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivityGingerbread;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.danoeh.antennapod.preferences.PreferenceController;
|
||||
@ -49,10 +50,14 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void openNavDrawer() {
|
||||
solo.clickOnScreen(50, 50);
|
||||
}
|
||||
|
||||
public void testAddFeed() throws Exception {
|
||||
uiTestUtils.addHostedFeedData();
|
||||
final Feed feed = uiTestUtils.hostedFeeds.get(0);
|
||||
solo.setNavigationDrawer(Solo.OPENED);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.add_feed_label));
|
||||
solo.enterText(0, feed.getDownload_url());
|
||||
solo.clickOnButton(0);
|
||||
@ -65,39 +70,43 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
|
||||
|
||||
public void testClickNavDrawer() throws Exception {
|
||||
uiTestUtils.addLocalFeedData(false);
|
||||
final View home = solo.getView(UITestUtils.HOME_VIEW);
|
||||
|
||||
// all episodes
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.new_episodes_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.all_episodes_label), getActionbarTitle());
|
||||
assertEquals(solo.getString(R.string.new_episodes_label), getActionbarTitle());
|
||||
|
||||
// queue
|
||||
solo.clickOnView(home);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.queue_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.queue_label), getActionbarTitle());
|
||||
|
||||
// downloads
|
||||
solo.clickOnView(home);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.downloads_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.downloads_label), getActionbarTitle());
|
||||
|
||||
// playback history
|
||||
solo.clickOnView(home);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.playback_history_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.playback_history_label), getActionbarTitle());
|
||||
|
||||
// add podcast
|
||||
solo.clickOnView(home);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.add_feed_label));
|
||||
solo.waitForView(R.id.txtvFeedurl);
|
||||
assertEquals(solo.getString(R.string.add_feed_label), getActionbarTitle());
|
||||
|
||||
// podcasts
|
||||
ListView list = (ListView)solo.getView(R.id.nav_list);
|
||||
for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) {
|
||||
Feed f = uiTestUtils.hostedFeeds.get(i);
|
||||
solo.clickOnView(home);
|
||||
solo.clickOnScreen(50, 50); // open nav drawer
|
||||
solo.scrollListToLine(list, i);
|
||||
solo.clickOnText(f.getTitle());
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals("", getActionbarTitle());
|
||||
@ -109,7 +118,7 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
|
||||
}
|
||||
|
||||
public void testGoToPreferences() {
|
||||
solo.setNavigationDrawer(Solo.CLOSED);
|
||||
openNavDrawer();
|
||||
solo.clickOnMenuItem(solo.getString(R.string.settings_label));
|
||||
solo.waitForActivity(PreferenceController.getPreferenceActivity());
|
||||
}
|
||||
|
@ -5,7 +5,11 @@ import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.robotium.solo.Solo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.AudioplayerActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -16,8 +20,6 @@ import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity
|
||||
*/
|
||||
@ -40,6 +42,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_UNPAUSE_ON_HEADSET_RECONNECT, false).commit();
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false).commit();
|
||||
@ -59,6 +62,10 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void openNavDrawer() {
|
||||
solo.clickOnScreen(50, 50);
|
||||
}
|
||||
|
||||
private void setContinuousPlaybackPreference(boolean value) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit();
|
||||
@ -71,7 +78,9 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
|
||||
private void startLocalPlayback() {
|
||||
assertTrue(solo.waitForActivity(MainActivity.class));
|
||||
solo.setNavigationDrawer(Solo.CLOSED);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.new_episodes_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
solo.clickOnView(solo.getView(R.id.butSecondaryAction));
|
||||
assertTrue(solo.waitForActivity(AudioplayerActivity.class));
|
||||
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
|
||||
@ -79,10 +88,10 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
|
||||
private void startLocalPlaybackFromQueue() {
|
||||
assertTrue(solo.waitForActivity(MainActivity.class));
|
||||
solo.clickOnView(solo.getView(UITestUtils.HOME_VIEW));
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.queue_label));
|
||||
assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
|
||||
solo.clickOnImageButton(0);
|
||||
solo.clickOnImageButton(1);
|
||||
assertTrue(solo.waitForActivity(AudioplayerActivity.class));
|
||||
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
|
||||
}
|
||||
@ -108,7 +117,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
setContinuousPlaybackPreference(false);
|
||||
uiTestUtils.addLocalFeedData(true);
|
||||
List<FeedItem> queue = DBReader.getQueue(getInstrumentation().getTargetContext());
|
||||
FeedItem second = queue.get(1);
|
||||
FeedItem second = queue.get(0);
|
||||
|
||||
startLocalPlaybackFromQueue();
|
||||
assertTrue(solo.waitForText(second.getTitle()));
|
||||
@ -147,4 +156,6 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
|
||||
public void testReplayEpisodeContinuousPlaybackOff() throws Exception {
|
||||
replayEpisodeCheck(false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,12 +5,8 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.*;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.util.service.download.HTTPBin;
|
||||
import de.test.antennapod.util.syndication.feedgenerator.RSS2Generator;
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -23,6 +19,16 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedImage;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.util.service.download.HTTPBin;
|
||||
import de.test.antennapod.util.syndication.feedgenerator.RSS2Generator;
|
||||
|
||||
/**
|
||||
* Utility methods for UI tests.
|
||||
* Starts a web server that hosts feeds, episodes and images.
|
||||
@ -174,7 +180,8 @@ public class UITestUtils {
|
||||
feed.setDownloaded(true);
|
||||
if (feed.getImage() != null) {
|
||||
FeedImage image = feed.getImage();
|
||||
image.setFile_url(image.getDownload_url());
|
||||
int fileId = Integer.parseInt(StringUtils.substringAfter(image.getDownload_url(), "files/"));
|
||||
image.setFile_url(server.accessFile(fileId).getAbsolutePath());
|
||||
image.setDownloaded(true);
|
||||
}
|
||||
if (downloadEpisodes) {
|
||||
|
@ -2,15 +2,28 @@ package de.test.antennapod.util.service.download;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLConnection;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
|
||||
/**
|
||||
* Http server for testing purposes
|
||||
* <p/>
|
||||
@ -264,7 +277,7 @@ public class HTTPBin extends NanoHTTPD {
|
||||
|
||||
private Response getGzippedResponse(int size) throws IOException {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -272,14 +285,15 @@ public class HTTPBin extends NanoHTTPD {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
random.nextBytes(buffer);
|
||||
|
||||
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream compressed = new ByteArrayOutputStream(buffer.length);
|
||||
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(compressed);
|
||||
gzipOutputStream.write(buffer);
|
||||
gzipOutputStream.close();
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(compressed.toByteArray());
|
||||
Response response = new Response(Response.Status.OK, MIME_PLAIN, inputStream);
|
||||
response.addHeader("Content-encoding", "gzip");
|
||||
response.addHeader("Content-length", String.valueOf(compressed.size()));
|
||||
response.addHeader("Content-Encoding", "gzip");
|
||||
response.addHeader("Content-Length", String.valueOf(compressed.size()));
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.danoeh.antennapod"
|
||||
android:versionCode="44"
|
||||
android:versionName="1.0">
|
||||
android:versionCode="49"
|
||||
android:versionName="1.1">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
@ -41,7 +41,7 @@
|
||||
<div id="header" align="center">
|
||||
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
|
||||
|
||||
<p>AntennaPod, Version 1.0</p>
|
||||
<p>AntennaPod, Version 1.1</p>
|
||||
|
||||
<p>Copyright © 2014 Daniel Oeh</p>
|
||||
|
||||
|
@ -138,7 +138,7 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
Feed f = new Feed(selectedDownloadUrl, new Date(), feed.getTitle());
|
||||
Feed f = new Feed(selectedDownloadUrl, new Date(0), feed.getTitle());
|
||||
f.setPreferences(feed.getPreferences());
|
||||
DefaultOnlineFeedViewActivity.this.feed = f;
|
||||
|
||||
|
@ -66,8 +66,8 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
|
||||
public static final String SAVE_TITLE = "title";
|
||||
|
||||
|
||||
public static final int POS_NEW = 0,
|
||||
POS_QUEUE = 1,
|
||||
public static final int POS_QUEUE = 0,
|
||||
POS_NEW = 1,
|
||||
POS_DOWNLOADS = 2,
|
||||
POS_HISTORY = 3,
|
||||
POS_ADD = 4;
|
||||
|
@ -13,9 +13,21 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
@ -31,16 +43,7 @@ import de.danoeh.antennapod.core.util.FileNameGenerator;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.URLChecker;
|
||||
import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
|
||||
/**
|
||||
* Downloads a feed from a feed URL and parses it. Subclasses can display the
|
||||
@ -181,7 +184,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Starting feed download");
|
||||
url = URLChecker.prepareURL(url);
|
||||
feed = new Feed(url, new Date());
|
||||
feed = new Feed(url, new Date(0));
|
||||
if (username != null && password != null) {
|
||||
feed.setPreferences(new FeedPreferences(0, false, username, password));
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
@ -10,23 +12,31 @@ import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.LangUtils;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Lets the user start the OPML-import process from a path
|
||||
*/
|
||||
public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
|
||||
|
||||
private static final String TAG = "OpmlImportFromPathActivity";
|
||||
private TextView txtvPath;
|
||||
private Button butStart;
|
||||
private String importPath;
|
||||
|
||||
private static final int CHOOSE_OPML_FILE = 1;
|
||||
|
||||
private Intent intentPickAction;
|
||||
private Intent intentGetContentAction;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -36,47 +46,74 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setContentView(R.layout.opml_import);
|
||||
|
||||
txtvPath = (TextView) findViewById(R.id.txtvPath);
|
||||
butStart = (Button) findViewById(R.id.butStartImport);
|
||||
final TextView txtvHeaderExplanation1 = (TextView) findViewById(R.id.txtvHeadingExplanation1);
|
||||
final TextView txtvExplanation1 = (TextView) findViewById(R.id.txtvExplanation1);
|
||||
final TextView txtvHeaderExplanation2 = (TextView) findViewById(R.id.txtvHeadingExplanation2);
|
||||
final TextView txtvExplanation2 = (TextView) findViewById(R.id.txtvExplanation2);
|
||||
final TextView txtvHeaderExplanation3 = (TextView) findViewById(R.id.txtvHeadingExplanation3);
|
||||
|
||||
butStart.setOnClickListener(new OnClickListener() {
|
||||
Button butChooseFilesystem = (Button) findViewById(R.id.butChooseFileFromFilesystem);
|
||||
butChooseFilesystem.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
checkFolderForFiles();
|
||||
chooseFileFromFilesystem();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Button butChooseExternal = (Button) findViewById(R.id.butChooseFileFromExternal);
|
||||
butChooseExternal.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
chooseFileFromExternal();
|
||||
}
|
||||
});
|
||||
|
||||
int nextOption = 1;
|
||||
intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
intentPickAction.setData(Uri.parse("file://"));
|
||||
List<ResolveInfo> intentActivities = getPackageManager()
|
||||
.queryIntentActivities(intentPickAction, CHOOSE_OPML_FILE);
|
||||
if(intentActivities.size() == 0) {
|
||||
intentPickAction.setData(null);
|
||||
intentActivities = getPackageManager()
|
||||
.queryIntentActivities(intentPickAction, CHOOSE_OPML_FILE);
|
||||
if(intentActivities.size() == 0) {
|
||||
txtvHeaderExplanation1.setVisibility(View.GONE);
|
||||
txtvExplanation1.setVisibility(View.GONE);
|
||||
findViewById(R.id.divider1).setVisibility(View.GONE);
|
||||
butChooseFilesystem.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
if(txtvExplanation1.getVisibility() == View.VISIBLE) {
|
||||
txtvHeaderExplanation1.setText("Option " + nextOption);
|
||||
nextOption++;
|
||||
}
|
||||
|
||||
intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentGetContentAction.setType("*/*");
|
||||
intentActivities = getPackageManager()
|
||||
.queryIntentActivities(intentGetContentAction, CHOOSE_OPML_FILE);
|
||||
if(intentActivities.size() == 0) {
|
||||
txtvHeaderExplanation2.setVisibility(View.GONE);
|
||||
txtvExplanation2.setVisibility(View.GONE);
|
||||
findViewById(R.id.divider2).setVisibility(View.GONE);
|
||||
butChooseExternal.setVisibility(View.GONE);
|
||||
} else {
|
||||
txtvHeaderExplanation2.setText("Option " + nextOption);
|
||||
nextOption++;
|
||||
}
|
||||
|
||||
txtvHeaderExplanation3.setText("Option " + nextOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
StorageUtils.checkStorageAvailability(this);
|
||||
setImportPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the importPath variable and makes txtvPath display the import
|
||||
* directory.
|
||||
*/
|
||||
private void setImportPath() {
|
||||
File importDir = UserPreferences.getDataFolder(this, UserPreferences.IMPORT_DIR);
|
||||
boolean success = true;
|
||||
if (!importDir.exists()) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Import directory doesn't exist. Creating...");
|
||||
success = importDir.mkdir();
|
||||
if (!success) {
|
||||
Log.e(TAG, "Could not create directory");
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
txtvPath.setText(importDir.toString());
|
||||
importPath = importDir.toString();
|
||||
} else {
|
||||
txtvPath.setText(R.string.opml_directory_error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
@ -95,32 +132,6 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the contents of the import directory and decides what to do. If
|
||||
* more than one file is in the directory, a dialog will be created to let
|
||||
* the user choose which item to import
|
||||
*/
|
||||
private void checkFolderForFiles() {
|
||||
File dir = new File(importPath);
|
||||
if (dir.isDirectory()) {
|
||||
File[] fileList = dir.listFiles();
|
||||
if (fileList.length == 1) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Found one file, choosing that one.");
|
||||
startImport(fileList[0]);
|
||||
} else if (fileList.length > 1) {
|
||||
Log.w(TAG, "Import directory contains more than one file.");
|
||||
askForFile(dir);
|
||||
} else {
|
||||
Log.e(TAG, "Import directory is empty");
|
||||
Toast toast = Toast
|
||||
.makeText(this, R.string.opml_import_error_dir_empty,
|
||||
Toast.LENGTH_LONG);
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startImport(File file) {
|
||||
Reader mReader = null;
|
||||
try {
|
||||
@ -134,38 +145,36 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the user to choose from a list of files in a directory and returns
|
||||
* his choice.
|
||||
/*
|
||||
* Creates an implicit intent to launch a file manager which lets
|
||||
* the user choose a specific OPML-file to import from.
|
||||
*/
|
||||
private void askForFile(File dir) {
|
||||
final File[] fileList = dir.listFiles();
|
||||
String[] fileNames = dir.list();
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
|
||||
dialog.setTitle(R.string.choose_file_to_import_label);
|
||||
dialog.setNeutralButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Dialog was cancelled");
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialog.setItems(fileNames, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "File at index " + which + " was chosen");
|
||||
dialog.dismiss();
|
||||
startImport(fileList[which]);
|
||||
}
|
||||
});
|
||||
dialog.create().show();
|
||||
private void chooseFileFromFilesystem() {
|
||||
try {
|
||||
startActivityForResult(intentPickAction, CHOOSE_OPML_FILE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "No activity found. Should never happen...");
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
String filename = data.getData().getPath();
|
||||
startImport(new File(filename));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class ActionButtonUtils {
|
||||
} else {
|
||||
// item is not being downloaded
|
||||
butSecondary.setVisibility(View.VISIBLE);
|
||||
if (media.isPlaying()) {
|
||||
if (media.isCurrentlyPlaying()) {
|
||||
butSecondary.setImageDrawable(drawables.getDrawable(3));
|
||||
} else {
|
||||
butSecondary
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
@ -9,6 +10,7 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
@ -46,7 +48,15 @@ public class DefaultActionButtonCallback implements ActionButtonCallback {
|
||||
DownloadRequester.getInstance().cancelDownload(context, media);
|
||||
Toast.makeText(context, R.string.download_cancelled_msg, Toast.LENGTH_SHORT).show();
|
||||
} else { // media is downloaded
|
||||
DBTasks.playMedia(context, media, true, true, false);
|
||||
if (item.hasMedia() && item.getMedia().isCurrentlyPlaying()) {
|
||||
context.sendBroadcast(new Intent(PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE));
|
||||
}
|
||||
else if (item.hasMedia() && item.getMedia().isCurrentlyPaused()) {
|
||||
context.sendBroadcast(new Intent(PlaybackService.ACTION_RESUME_PLAY_CURRENT_EPISODE));
|
||||
}
|
||||
else {
|
||||
DBTasks.playMedia(context, media, false, true, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!item.isRead()) {
|
||||
|
@ -9,6 +9,7 @@ import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
|
@ -25,7 +25,7 @@ public class NavListAdapter extends BaseAdapter {
|
||||
public static final int VIEW_TYPE_SECTION_DIVIDER = 1;
|
||||
public static final int VIEW_TYPE_SUBSCRIPTION = 2;
|
||||
|
||||
public static final int[] NAV_TITLES = {R.string.all_episodes_label, R.string.queue_label, R.string.downloads_label, R.string.playback_history_label, R.string.add_feed_label};
|
||||
public static final int[] NAV_TITLES = {R.string.queue_label, R.string.new_episodes_label, R.string.downloads_label, R.string.playback_history_label, R.string.add_feed_label};
|
||||
|
||||
private final Drawable[] drawables;
|
||||
|
||||
@ -38,7 +38,7 @@ public class NavListAdapter extends BaseAdapter {
|
||||
this.itemAccess = itemAccess;
|
||||
this.context = context;
|
||||
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.ic_new, R.attr.stat_playlist,
|
||||
TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.stat_playlist, R.attr.ic_new,
|
||||
R.attr.av_download, R.attr.ic_history, R.attr.content_new});
|
||||
drawables = new Drawable[]{ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2),
|
||||
ta.getDrawable(3), ta.getDrawable(4)};
|
||||
@ -132,7 +132,7 @@ public class NavListAdapter extends BaseAdapter {
|
||||
} else {
|
||||
holder.count.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (NAV_TITLES[position] == R.string.all_episodes_label) {
|
||||
} else if (NAV_TITLES[position] == R.string.new_episodes_label) {
|
||||
int unreadItems = itemAccess.getNumberOfUnreadItems();
|
||||
if (unreadItems > 0) {
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -11,9 +12,11 @@ import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nineoldandroids.view.ViewHelper;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
@ -139,6 +142,13 @@ public class NewEpisodesListAdapter extends BaseAdapter {
|
||||
.fit()
|
||||
.into(holder.imageView);
|
||||
|
||||
if (item.isRead()) {
|
||||
// grey it out
|
||||
ViewHelper.setAlpha(convertView, .2f);
|
||||
} else {
|
||||
ViewHelper.setAlpha(convertView, 1.0f);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -13,9 +14,11 @@ import android.widget.TextView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
|
||||
/**
|
||||
* List adapter for the queue.
|
||||
@ -64,12 +67,16 @@ public class QueueListAdapter extends BaseAdapter {
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.queue_listitem,
|
||||
parent, false);
|
||||
holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage);
|
||||
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
|
||||
holder.pubDate = (TextView) convertView.findViewById(R.id.txtvPubDate);
|
||||
holder.progressLeft = (TextView) convertView.findViewById(R.id.txtvProgressLeft);
|
||||
holder.progressRight = (TextView) convertView
|
||||
.findViewById(R.id.txtvProgressRight);
|
||||
holder.butSecondary = (ImageButton) convertView
|
||||
.findViewById(R.id.butSecondaryAction);
|
||||
holder.position = (TextView) convertView.findViewById(R.id.txtvPosition);
|
||||
holder.progress = (ProgressBar) convertView
|
||||
.findViewById(R.id.pbar_download_progress);
|
||||
.findViewById(R.id.progressBar);
|
||||
holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
@ -77,19 +84,39 @@ public class QueueListAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
holder.title.setText(item.getTitle());
|
||||
|
||||
AdapterUtils.updateEpisodePlaybackProgress(item, context.getResources(), holder.position, holder.progress);
|
||||
|
||||
FeedMedia media = item.getMedia();
|
||||
|
||||
|
||||
holder.title.setText(item.getTitle());
|
||||
String pubDate = DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL);
|
||||
holder.pubDate.setText(pubDate.replace(" ", "\n"));
|
||||
|
||||
if (media != null) {
|
||||
final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media);
|
||||
|
||||
if (!media.isDownloaded()) {
|
||||
if (isDownloadingMedia) {
|
||||
// item is being downloaded
|
||||
holder.progress.setVisibility(View.VISIBLE);
|
||||
holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
|
||||
FeedItem.State state = item.getState();
|
||||
if (isDownloadingMedia) {
|
||||
holder.progressLeft.setText(Converter.byteToString(itemAccess.getItemDownloadedBytes(item)));
|
||||
if(itemAccess.getItemDownloadSize(item) > 0) {
|
||||
holder.progressRight.setText(Converter.byteToString(itemAccess.getItemDownloadSize(item)));
|
||||
} else {
|
||||
holder.progressRight.setText(Converter.byteToString(media.getSize()));
|
||||
}
|
||||
holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
|
||||
holder.progress.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());
|
||||
holder.progress.setProgress(progress);
|
||||
holder.progress.setVisibility(View.VISIBLE);
|
||||
holder.progressLeft.setText(Converter
|
||||
.getDurationStringLong(media.getPosition()));
|
||||
holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
}
|
||||
} else {
|
||||
holder.progressLeft.setText(Converter.byteToString(media.getSize()));
|
||||
holder.progressRight.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
holder.progress.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,18 +143,20 @@ public class QueueListAdapter extends BaseAdapter {
|
||||
|
||||
|
||||
static class Holder {
|
||||
TextView title;
|
||||
ImageView imageView;
|
||||
TextView position;
|
||||
TextView title;
|
||||
TextView pubDate;
|
||||
TextView progressLeft;
|
||||
TextView progressRight;
|
||||
ProgressBar progress;
|
||||
ImageButton butSecondary;
|
||||
}
|
||||
|
||||
public interface ItemAccess {
|
||||
int getCount();
|
||||
|
||||
FeedItem getItem(int position);
|
||||
|
||||
int getCount();
|
||||
long getItemDownloadedBytes(FeedItem item);
|
||||
long getItemDownloadSize(FeedItem item);
|
||||
int getItemDownloadProgressPercent(FeedItem item);
|
||||
}
|
||||
}
|
||||
|
@ -39,16 +39,15 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> {
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
convertView = inflater.inflate(R.layout.gpodnet_podcast_listitem, parent, false);
|
||||
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
|
||||
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
|
||||
|
||||
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
|
||||
holder.subscribers = (TextView) convertView.findViewById(R.id.txtvSubscribers);
|
||||
holder.url = (TextView) convertView.findViewById(R.id.txtvUrl);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (Holder) convertView.getTag();
|
||||
}
|
||||
|
||||
holder.title.setText(podcast.getTitle());
|
||||
|
||||
if (StringUtils.isNotBlank(podcast.getLogoUrl())) {
|
||||
Picasso.with(convertView.getContext())
|
||||
.load(podcast.getLogoUrl())
|
||||
@ -56,11 +55,17 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> {
|
||||
.into(holder.image);
|
||||
}
|
||||
|
||||
holder.title.setText(podcast.getTitle());
|
||||
holder.subscribers.setText(String.valueOf(podcast.getSubscribers()));
|
||||
holder.url.setText(podcast.getUrl());
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
TextView title;
|
||||
ImageView image;
|
||||
TextView title;
|
||||
TextView subscribers;
|
||||
TextView url;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
package de.danoeh.antennapod.adapter.gpodnet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
|
||||
|
||||
/**
|
||||
* Adapter for displaying a list of GPodnetPodcast-Objects.
|
||||
*/
|
||||
public class TagListAdapter extends ArrayAdapter<GpodnetTag> {
|
||||
|
||||
public TagListAdapter(Context context, int resource, List<GpodnetTag> objects) {
|
||||
super(context, resource, objects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
Holder holder;
|
||||
|
||||
GpodnetTag tag = getItem(position);
|
||||
|
||||
// Inflate Layout
|
||||
if (convertView == null) {
|
||||
holder = new Holder();
|
||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
convertView = inflater.inflate(R.layout.gpodnet_tag_listitem, parent, false);
|
||||
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
|
||||
holder.usage = (TextView) convertView.findViewById(R.id.txtvUsage);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (Holder) convertView.getTag();
|
||||
}
|
||||
|
||||
holder.title.setText(tag.getTitle());
|
||||
holder.usage.setText(String.valueOf(tag.getUsage()));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
TextView title;
|
||||
TextView usage;
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package de.danoeh.antennapod.adapter.itunes;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
|
||||
public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||
/**
|
||||
* Related Context
|
||||
*/
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* List holding the podcasts found in the search
|
||||
*/
|
||||
private final List<Podcast> data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context Related context
|
||||
* @param objects Search result
|
||||
*/
|
||||
public ItunesAdapter(Context context, List<Podcast> objects) {
|
||||
super(context, 0, objects);
|
||||
this.data = objects;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given ImageView with the image in the given Podcast's imageUrl
|
||||
*/
|
||||
class FetchImageTask extends AsyncTask<Void,Void,Bitmap>{
|
||||
/**
|
||||
* Current podcast
|
||||
*/
|
||||
private final Podcast podcast;
|
||||
|
||||
/**
|
||||
* ImageView to be updated
|
||||
*/
|
||||
private final ImageView imageView;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param podcast Podcast that has the image
|
||||
* @param imageView UI image to be updated
|
||||
*/
|
||||
FetchImageTask(Podcast podcast, ImageView imageView){
|
||||
this.podcast = podcast;
|
||||
this.imageView = imageView;
|
||||
}
|
||||
|
||||
//Get the image from the url
|
||||
@Override
|
||||
protected Bitmap doInBackground(Void... params) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(podcast.imageUrl);
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
return BitmapFactory.decodeStream(response.getEntity().getContent());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Set the background image for the podcast
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap img) {
|
||||
super.onPostExecute(img);
|
||||
if(img!=null) {
|
||||
imageView.setImageBitmap(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
//Current podcast
|
||||
Podcast podcast = data.get(position);
|
||||
|
||||
//ViewHolder
|
||||
PodcastViewHolder viewHolder;
|
||||
|
||||
//Resulting view
|
||||
View view;
|
||||
|
||||
//Handle view holder stuff
|
||||
if(convertView == null) {
|
||||
view = ((MainActivity) context).getLayoutInflater()
|
||||
.inflate(R.layout.itunes_podcast_listitem, parent, false);
|
||||
viewHolder = new PodcastViewHolder(view);
|
||||
view.setTag(viewHolder);
|
||||
} else {
|
||||
view = convertView;
|
||||
viewHolder = (PodcastViewHolder) view.getTag();
|
||||
}
|
||||
|
||||
//Set the title
|
||||
viewHolder.titleView.setText(podcast.title);
|
||||
|
||||
//Update the empty imageView with the image from the feed
|
||||
new FetchImageTask(podcast,viewHolder.coverView).execute();
|
||||
|
||||
//Feed the grid view
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* View holder object for the GridView
|
||||
*/
|
||||
class PodcastViewHolder {
|
||||
|
||||
/**
|
||||
* ImageView holding the Podcast image
|
||||
*/
|
||||
public final ImageView coverView;
|
||||
|
||||
/**
|
||||
* TextView holding the Podcast title
|
||||
*/
|
||||
public final TextView titleView;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param view GridView cell
|
||||
*/
|
||||
PodcastViewHolder(View view){
|
||||
coverView = (ImageView) view.findViewById(R.id.imgvCover);
|
||||
titleView = (TextView) view.findViewById(R.id.txtvTitle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual podcast on the iTunes Store.
|
||||
*/
|
||||
public static class Podcast { //TODO: Move this out eventually. Possibly to core.itunes.model
|
||||
|
||||
/**
|
||||
* The name of the podcast
|
||||
*/
|
||||
public final String title;
|
||||
|
||||
/**
|
||||
* URL of the podcast image
|
||||
*/
|
||||
public final String imageUrl;
|
||||
/**
|
||||
* URL of the podcast feed
|
||||
*/
|
||||
public final String feedUrl;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param json object holding the podcast information
|
||||
* @throws JSONException
|
||||
*/
|
||||
public Podcast(JSONObject json) throws JSONException {
|
||||
title = json.getString("collectionName");
|
||||
imageUrl = json.getString("artworkUrl100");
|
||||
feedUrl = json.getString("feedUrl");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,16 +4,17 @@ import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import de.danoeh.antennapod.activity.OpmlImportHolder;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.opml.OpmlElement;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
/** Queues items for download in the background. */
|
||||
public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
|
||||
private Context context;
|
||||
@ -46,7 +47,7 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
|
||||
for (int idx = 0; idx < selection.length; idx++) {
|
||||
OpmlElement element = OpmlImportHolder.getReadElements().get(
|
||||
selection[idx]);
|
||||
Feed feed = new Feed(element.getXmlUrl(), new Date(),
|
||||
Feed feed = new Feed(element.getXmlUrl(), new Date(0),
|
||||
element.getText());
|
||||
try {
|
||||
requester.downloadFeed(context.getApplicationContext(), feed);
|
||||
|
@ -8,6 +8,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -41,10 +42,18 @@ public class AddFeedFragment extends Fragment {
|
||||
Button butBrowserGpoddernet = (Button) root.findViewById(R.id.butBrowseGpoddernet);
|
||||
Button butOpmlImport = (Button) root.findViewById(R.id.butOpmlImport);
|
||||
Button butConfirm = (Button) root.findViewById(R.id.butConfirm);
|
||||
Button butSearchITunes = (Button) root.findViewById(R.id.butSearchItunes);
|
||||
|
||||
final MainActivity activity = (MainActivity) getActivity();
|
||||
activity.getMainActivtyActionBar().setTitle(R.string.add_feed_label);
|
||||
|
||||
butSearchITunes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.loadChildFragment(new ItunesSearchFragment());
|
||||
}
|
||||
});
|
||||
|
||||
butBrowserGpoddernet.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -53,7 +62,6 @@ public class AddFeedFragment extends Fragment {
|
||||
});
|
||||
|
||||
butOpmlImport.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(getActivity(),
|
||||
|
@ -1,25 +1,35 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.DownloadLogAdapter;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
|
||||
import java.util.List;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
||||
|
||||
/**
|
||||
* Shows the download log
|
||||
*/
|
||||
public class DownloadLogFragment extends ListFragment {
|
||||
|
||||
private static final String TAG = "DownloadLogFragment";
|
||||
|
||||
private List<DownloadStatus> downloadLog;
|
||||
private DownloadLogAdapter adapter;
|
||||
|
||||
@ -29,6 +39,7 @@ public class DownloadLogFragment extends ListFragment {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setHasOptionsMenu(true);
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
startItemLoader();
|
||||
}
|
||||
@ -63,7 +74,7 @@ public class DownloadLogFragment extends ListFragment {
|
||||
}
|
||||
setListShown(true);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() {
|
||||
@ -105,6 +116,41 @@ public class DownloadLogFragment extends ListFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
|
||||
MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label);
|
||||
MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
|
||||
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard});
|
||||
clearHistory.setIcon(drawables.getDrawable(0));
|
||||
drawables.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (itemsLoaded && !MenuItemUtils.isActivityDrawerOpen((NavDrawerActivity) getActivity())) {
|
||||
menu.findItem(R.id.clear_history_item).setVisible(downloadLog != null && !downloadLog.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (!super.onOptionsItemSelected(item)) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.clear_history_item:
|
||||
DBWriter.clearDownloadLog(getActivity());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ItemLoader extends AsyncTask<Void, Void, List<DownloadStatus>> {
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,7 @@ import android.support.v4.util.Pair;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -49,6 +50,7 @@ import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
@ -91,6 +93,8 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
|
||||
private View header;
|
||||
private WebView webvDescription;
|
||||
private TextView txtvTitle;
|
||||
private TextView txtvDuration;
|
||||
private TextView txtvPublished;
|
||||
private ImageView imgvCover;
|
||||
private ProgressBar progbarDownload;
|
||||
private ProgressBar progbarLoading;
|
||||
@ -166,6 +170,8 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
|
||||
header = inflater.inflate(R.layout.feeditem_fragment_header, toolbar, false);
|
||||
root = (ViewGroup) layout.findViewById(R.id.content_root);
|
||||
txtvTitle = (TextView) header.findViewById(R.id.txtvTitle);
|
||||
txtvDuration = (TextView) header.findViewById(R.id.txtvDuration);
|
||||
txtvPublished = (TextView) header.findViewById(R.id.txtvPublished);
|
||||
if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448
|
||||
txtvTitle.setEllipsize(TextUtils.TruncateAt.END);
|
||||
}
|
||||
@ -313,6 +319,8 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
|
||||
|
||||
private void updateAppearance() {
|
||||
txtvTitle.setText(item.getTitle());
|
||||
txtvPublished.setText(DateUtils.formatDateTime(getActivity(), item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL));
|
||||
|
||||
Picasso.with(getActivity()).load(item.getImageUri())
|
||||
.fit()
|
||||
.into(imgvCover);
|
||||
@ -348,7 +356,10 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba
|
||||
}
|
||||
|
||||
drawables.recycle();
|
||||
} else {
|
||||
} else {if(media.getDuration() > 0) {
|
||||
txtvDuration.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
}
|
||||
|
||||
boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media);
|
||||
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.av_play,
|
||||
R.attr.av_download, R.attr.action_stream, R.attr.content_discard, R.attr.navigation_cancel});
|
||||
|
@ -66,7 +66,8 @@ public class ItemlistFragment extends ListFragment {
|
||||
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
|
||||
| EventDistributor.DOWNLOAD_QUEUED
|
||||
| EventDistributor.QUEUE_UPDATE
|
||||
| EventDistributor.UNREAD_ITEMS_UPDATE;
|
||||
| EventDistributor.UNREAD_ITEMS_UPDATE
|
||||
| EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
|
||||
public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem";
|
||||
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
|
||||
|
@ -0,0 +1,193 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
|
||||
|
||||
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.*;
|
||||
|
||||
//Searches iTunes store for given string and displays results in a list
|
||||
public class ItunesSearchFragment extends Fragment {
|
||||
final String TAG = "ItunesSearchFragment";
|
||||
/**
|
||||
* Search input field
|
||||
*/
|
||||
private SearchView searchView;
|
||||
|
||||
/**
|
||||
* Adapter responsible with the search results
|
||||
*/
|
||||
private ItunesAdapter adapter;
|
||||
|
||||
/**
|
||||
* List of podcasts retreived from the search
|
||||
*/
|
||||
private List<Podcast> searchResults;
|
||||
|
||||
/**
|
||||
* Replace adapter data with provided search results from SearchTask.
|
||||
* @param result List of Podcast objects containing search results
|
||||
*/
|
||||
void updateData(List<Podcast> result) {
|
||||
this.searchResults = result;
|
||||
adapter.clear();
|
||||
|
||||
//ArrayAdapter.addAll() requires minsdk > 10
|
||||
for(Podcast p: result) {
|
||||
adapter.add(p);
|
||||
}
|
||||
|
||||
adapter.notifyDataSetInvalidated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ItunesSearchFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
adapter = new ItunesAdapter(getActivity(), new ArrayList<Podcast>());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View view = inflater.inflate(R.layout.fragment_itunes_search, container, false);
|
||||
GridView gridView = (GridView) view.findViewById(R.id.gridView);
|
||||
gridView.setAdapter(adapter);
|
||||
|
||||
//Show information about the podcast when the list item is clicked
|
||||
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
DefaultOnlineFeedViewActivity.class);
|
||||
|
||||
//Tell the OnlineFeedViewActivity where to go
|
||||
String url = searchResults.get(position).feedUrl;
|
||||
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url);
|
||||
|
||||
intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, "iTunes");
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
//Configure search input view to be expanded by default with a visible submit button
|
||||
searchView = (SearchView) view.findViewById(R.id.itunes_search_view);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setIconified(false);
|
||||
searchView.setSubmitButtonEnabled(true);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
//This prevents onQueryTextSubmit() from being called twice when keyboard is used
|
||||
//to submit the query.
|
||||
searchView.clearFocus();
|
||||
new SearchTask(s).execute();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the iTunes store for podcasts using the given query
|
||||
*/
|
||||
class SearchTask extends AsyncTask<Void,Void,Void> {
|
||||
/**
|
||||
* Incomplete iTunes API search URL
|
||||
*/
|
||||
final String apiUrl = "https://itunes.apple.com/search?media=podcast&term=%s";
|
||||
|
||||
/**
|
||||
* Search terms
|
||||
*/
|
||||
final String query;
|
||||
|
||||
/**
|
||||
* Search result
|
||||
*/
|
||||
final List<Podcast> taskData = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param query Search string
|
||||
*/
|
||||
public SearchTask(String query){
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
//Get the podcast data
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
|
||||
//Spaces in the query need to be replaced with '+' character.
|
||||
String formattedUrl = String.format(apiUrl, query).replace(' ', '+');
|
||||
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(formattedUrl);
|
||||
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
String resultString = EntityUtils.toString(response.getEntity());
|
||||
JSONObject result = new JSONObject(resultString);
|
||||
JSONArray j = result.getJSONArray("results");
|
||||
|
||||
for (int i = 0; i < j.length(); i++){
|
||||
JSONObject podcastJson = j.getJSONObject(i);
|
||||
Podcast podcast = new Podcast(podcastJson);
|
||||
taskData.add(podcast);
|
||||
}
|
||||
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Save the data and update the list
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
updateData(taskData);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,12 +2,15 @@ package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@ -29,6 +32,7 @@ import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
|
||||
import de.danoeh.antennapod.adapter.NewEpisodesListAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
@ -41,6 +45,8 @@ import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
|
||||
import de.danoeh.antennapod.core.util.gui.UndoBarController;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
||||
|
||||
@ -52,18 +58,22 @@ public class NewEpisodesFragment extends Fragment {
|
||||
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
|
||||
EventDistributor.DOWNLOAD_QUEUED |
|
||||
EventDistributor.QUEUE_UPDATE |
|
||||
EventDistributor.UNREAD_ITEMS_UPDATE;
|
||||
EventDistributor.UNREAD_ITEMS_UPDATE |
|
||||
EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
|
||||
private static final int RECENT_EPISODES_LIMIT = 150;
|
||||
private static final String PREF_NAME = "PrefNewEpisodesFragment";
|
||||
private static final String PREF_EPISODE_FILTER_BOOL = "newEpisodeFilterEnabled";
|
||||
|
||||
private static final String PREF_KEY_LIST_TOP = "list_top";
|
||||
private static final String PREF_KEY_LIST_SELECTION = "list_selection";
|
||||
|
||||
private DragSortListView listView;
|
||||
private NewEpisodesListAdapter listAdapter;
|
||||
private TextView txtvEmpty;
|
||||
private ProgressBar progLoading;
|
||||
|
||||
private UndoBarController undoBarController;
|
||||
|
||||
private List<FeedItem> unreadItems;
|
||||
private List<FeedItem> recentItems;
|
||||
private QueueAccess queueAccess;
|
||||
@ -108,6 +118,12 @@ public class NewEpisodesFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
@ -127,10 +143,35 @@ public class NewEpisodesFragment extends Fragment {
|
||||
resetViewState();
|
||||
}
|
||||
|
||||
private void saveScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
View v = listView.getChildAt(0);
|
||||
int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
|
||||
editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
|
||||
editor.putInt(PREF_KEY_LIST_TOP, top);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void restoreScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
|
||||
int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
|
||||
if(listSelection > 0 || top > 0) {
|
||||
listView.setSelectionFromTop(listSelection, top);
|
||||
// restore once, then forget
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_KEY_LIST_SELECTION, 0);
|
||||
editor.putInt(PREF_KEY_LIST_TOP, 0);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetViewState() {
|
||||
listAdapter = null;
|
||||
activity.set(null);
|
||||
viewsCreated = false;
|
||||
undoBarController = null;
|
||||
if (downloadObserver != null) {
|
||||
downloadObserver.onPause();
|
||||
}
|
||||
@ -190,8 +231,19 @@ public class NewEpisodesFragment extends Fragment {
|
||||
}
|
||||
return true;
|
||||
case R.id.mark_all_read_item:
|
||||
DBWriter.markAllItemsRead(getActivity());
|
||||
Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
|
||||
ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.mark_all_read_label,
|
||||
R.string.mark_all_read_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(
|
||||
DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.markAllItemsRead(getActivity());
|
||||
Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
conDialog.createNewDialog().show();
|
||||
return true;
|
||||
case R.id.episode_filter_item:
|
||||
boolean newVal = !item.isChecked();
|
||||
@ -210,7 +262,7 @@ public class NewEpisodesFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.all_episodes_label);
|
||||
((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.new_episodes_label);
|
||||
|
||||
View root = inflater.inflate(R.layout.new_episodes_fragment, container, false);
|
||||
|
||||
@ -229,6 +281,33 @@ public class NewEpisodesFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
listView.setRemoveListener(new DragSortListView.RemoveListener() {
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
Log.d(TAG, "remove("+which+")");
|
||||
stopItemLoader();
|
||||
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
|
||||
DBWriter.markItemRead(getActivity(), item.getId(), true);
|
||||
undoBarController.showUndoBar(false,
|
||||
getString(R.string.marked_as_read_label), new FeedItemUndoToken(item,
|
||||
which)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
undoBarController = new UndoBarController(root.findViewById(R.id.undobar), new UndoBarController.UndoListener() {
|
||||
@Override
|
||||
public void onUndo(Parcelable token) {
|
||||
// Perform the undo
|
||||
FeedItemUndoToken undoToken = (FeedItemUndoToken) token;
|
||||
if (token != null) {
|
||||
long itemId = undoToken.getFeedItemId();
|
||||
int position = undoToken.getPosition();
|
||||
DBWriter.markItemRead(getActivity(), itemId, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final int secondColor = (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) ? R.color.swipe_refresh_secondary_color_dark : R.color.swipe_refresh_secondary_color_light;
|
||||
|
||||
if (!itemsLoaded) {
|
||||
@ -254,6 +333,7 @@ public class NewEpisodesFragment extends Fragment {
|
||||
downloadObserver.onResume();
|
||||
}
|
||||
listAdapter.notifyDataSetChanged();
|
||||
restoreScrollPosition();
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
updateShowOnlyEpisodesListViewState();
|
||||
}
|
||||
@ -332,7 +412,7 @@ public class NewEpisodesFragment extends Fragment {
|
||||
|
||||
private void updateShowOnlyEpisodes() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
showOnlyNewEpisodes = prefs.getBoolean(PREF_EPISODE_FILTER_BOOL, false);
|
||||
showOnlyNewEpisodes = prefs.getBoolean(PREF_EPISODE_FILTER_BOOL, true);
|
||||
}
|
||||
|
||||
private void setShowOnlyNewEpisodes(boolean newVal) {
|
||||
|
@ -34,6 +34,8 @@ import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
||||
|
||||
public class PlaybackHistoryFragment extends ListFragment {
|
||||
private static final String TAG = "PlaybackHistoryFragment";
|
||||
private static final int EVENTS = EventDistributor.PLAYBACK_HISTORY_UPDATE |
|
||||
EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
|
||||
private List<FeedItem> playbackHistory;
|
||||
private QueueAccess queue;
|
||||
@ -167,7 +169,7 @@ public class PlaybackHistoryFragment extends ListFragment {
|
||||
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
if ((arg & EventDistributor.PLAYBACK_HISTORY_UPDATE) != 0) {
|
||||
if ((arg & EVENTS) != 0) {
|
||||
startItemLoader();
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.util.Log;
|
||||
@ -30,6 +32,7 @@ import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
|
||||
import de.danoeh.antennapod.adapter.QueueListAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
@ -41,6 +44,8 @@ import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.QueueSorter;
|
||||
import de.danoeh.antennapod.core.util.gui.FeedItemUndoToken;
|
||||
import de.danoeh.antennapod.core.util.gui.UndoBarController;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
||||
|
||||
@ -51,13 +56,16 @@ public class QueueFragment extends Fragment {
|
||||
private static final String TAG = "QueueFragment";
|
||||
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
|
||||
EventDistributor.DOWNLOAD_QUEUED |
|
||||
EventDistributor.QUEUE_UPDATE;
|
||||
EventDistributor.QUEUE_UPDATE |
|
||||
EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
|
||||
private DragSortListView listView;
|
||||
private QueueListAdapter listAdapter;
|
||||
private TextView txtvEmpty;
|
||||
private ProgressBar progLoading;
|
||||
|
||||
private UndoBarController undoBarController;
|
||||
|
||||
private List<FeedItem> queue;
|
||||
private List<Downloader> downloaderList;
|
||||
|
||||
@ -65,6 +73,10 @@ public class QueueFragment extends Fragment {
|
||||
private boolean viewsCreated = false;
|
||||
private boolean isUpdatingFeeds = false;
|
||||
|
||||
private static final String PREFS = "QueueFragment";
|
||||
private static final String PREF_KEY_LIST_TOP = "list_top";
|
||||
private static final String PREF_KEY_LIST_SELECTION = "list_selection";
|
||||
|
||||
private AtomicReference<Activity> activity = new AtomicReference<Activity>();
|
||||
|
||||
private DownloadObserver downloadObserver = null;
|
||||
@ -102,6 +114,12 @@ public class QueueFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
@ -115,10 +133,35 @@ public class QueueFragment extends Fragment {
|
||||
this.activity.set((MainActivity) activity);
|
||||
}
|
||||
|
||||
private void saveScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
View v = listView.getChildAt(0);
|
||||
int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
|
||||
editor.putInt(PREF_KEY_LIST_SELECTION, listView.getFirstVisiblePosition());
|
||||
editor.putInt(PREF_KEY_LIST_TOP, top);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void restoreScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
int listSelection = prefs.getInt(PREF_KEY_LIST_SELECTION, 0);
|
||||
int top = prefs.getInt(PREF_KEY_LIST_TOP, 0);
|
||||
if(listSelection > 0 || top > 0) {
|
||||
listView.setSelectionFromTop(listSelection, top);
|
||||
// restore once, then forget
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_KEY_LIST_SELECTION, 0);
|
||||
editor.putInt(PREF_KEY_LIST_TOP, 0);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetViewState() {
|
||||
unregisterForContextMenu(listView);
|
||||
listAdapter = null;
|
||||
activity.set(null);
|
||||
undoBarController = null;
|
||||
viewsCreated = false;
|
||||
blockDownloadObserverUpdate = false;
|
||||
if (downloadObserver != null) {
|
||||
@ -175,6 +218,21 @@ public class QueueFragment extends Fragment {
|
||||
DBTasks.refreshAllFeeds(getActivity(), feeds);
|
||||
}
|
||||
return true;
|
||||
case R.id.clear_queue:
|
||||
// make sure the user really wants to clear the queue
|
||||
ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.clear_queue_label,
|
||||
R.string.clear_queue_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(
|
||||
DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.clearQueue(getActivity());
|
||||
}
|
||||
};
|
||||
conDialog.createNewDialog().show();
|
||||
return true;
|
||||
case R.id.queue_sort_alpha_asc:
|
||||
QueueSorter.sort(getActivity(), QueueSorter.Rule.ALPHA_ASC, true);
|
||||
return true;
|
||||
@ -285,9 +343,31 @@ public class QueueFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
Log.d(TAG, "remove("+which+")");
|
||||
stopItemLoader();
|
||||
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
|
||||
DBWriter.removeQueueItem(getActivity(), item.getId(), true);
|
||||
undoBarController.showUndoBar(false,
|
||||
getString(R.string.removed_from_queue), new FeedItemUndoToken(item,
|
||||
which)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
undoBarController = new UndoBarController(root.findViewById(R.id.undobar), new UndoBarController.UndoListener() {
|
||||
@Override
|
||||
public void onUndo(Parcelable token) {
|
||||
// Perform the undo
|
||||
FeedItemUndoToken undoToken = (FeedItemUndoToken) token;
|
||||
if (token != null) {
|
||||
long itemId = undoToken.getFeedItemId();
|
||||
int position = undoToken.getPosition();
|
||||
DBWriter.addQueueItemAt(getActivity(), itemId, position, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
registerForContextMenu(listView);
|
||||
|
||||
if (!itemsLoaded) {
|
||||
@ -313,6 +393,8 @@ public class QueueFragment extends Fragment {
|
||||
}
|
||||
listAdapter.notifyDataSetChanged();
|
||||
|
||||
restoreScrollPosition();
|
||||
|
||||
// we need to refresh the options menu because it sometimes
|
||||
// needs data that may have just been loaded.
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
@ -346,6 +428,33 @@ public class QueueFragment extends Fragment {
|
||||
return (itemsLoaded) ? queue.get(position) : null;
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
@ -24,11 +24,11 @@ public class TagFragment extends PodcastListFragment {
|
||||
|
||||
private GpodnetTag tag;
|
||||
|
||||
public static TagFragment newInstance(String tagName) {
|
||||
Validate.notNull(tagName);
|
||||
public static TagFragment newInstance(GpodnetTag tag) {
|
||||
Validate.notNull(tag);
|
||||
TagFragment fragment = new TagFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("tag", tagName);
|
||||
args.putParcelable("tag", tag);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
@ -38,14 +38,14 @@ public class TagFragment extends PodcastListFragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
Validate.isTrue(args != null && args.getString("tag") != null, "args invalid");
|
||||
tag = new GpodnetTag(args.getString("tag"));
|
||||
Validate.isTrue(args != null && args.getParcelable("tag") != null, "args invalid");
|
||||
tag = args.getParcelable("tag");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getName());
|
||||
((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -10,14 +10,13 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
|
||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetTag;
|
||||
@ -67,15 +66,21 @@ public class TagListFragment extends ListFragment {
|
||||
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String selectedTag = (String) getListAdapter().getItem(position);
|
||||
GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position);
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
activity.loadChildFragment(TagFragment.newInstance(selectedTag));
|
||||
activity.loadChildFragment(TagFragment.newInstance(tag));
|
||||
}
|
||||
});
|
||||
|
||||
startLoadTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(R.string.add_feed_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@ -121,11 +126,7 @@ public class TagListFragment extends ListFragment {
|
||||
final Context context = getActivity();
|
||||
if (context != null) {
|
||||
if (gpodnetTags != null) {
|
||||
List<String> tagNames = new ArrayList<String>();
|
||||
for (GpodnetTag tag : gpodnetTags) {
|
||||
tagNames.add(tag.getName());
|
||||
}
|
||||
setListAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, tagNames));
|
||||
setListAdapter(new TagListAdapter(context, android.R.layout.simple_list_item_1, gpodnetTags));
|
||||
} else if (exception != null) {
|
||||
TextView txtvError = new TextView(getActivity());
|
||||
txtvError.setText(exception.getMessage());
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.menuhandler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
@ -10,6 +11,7 @@ import android.view.MenuItem;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
@ -51,8 +53,8 @@ public class FeedMenuHandler {
|
||||
*
|
||||
* @throws DownloadRequestException
|
||||
*/
|
||||
public static boolean onOptionsItemClicked(Context context, MenuItem item,
|
||||
Feed selectedFeed) throws DownloadRequestException {
|
||||
public static boolean onOptionsItemClicked(final Context context, final MenuItem item,
|
||||
final Feed selectedFeed) throws DownloadRequestException {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh_item:
|
||||
DBTasks.refreshFeed(context, selectedFeed);
|
||||
@ -61,7 +63,18 @@ public class FeedMenuHandler {
|
||||
DBTasks.refreshCompleteFeed(context, selectedFeed);
|
||||
break;
|
||||
case R.id.mark_all_read_item:
|
||||
DBWriter.markFeedRead(context, selectedFeed.getId());
|
||||
ConfirmationDialog conDialog = new ConfirmationDialog(context,
|
||||
R.string.mark_all_read_label,
|
||||
R.string.mark_all_read_feed_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(
|
||||
DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.markFeedRead(context, selectedFeed.getId());
|
||||
}
|
||||
};
|
||||
conDialog.createNewDialog().show();
|
||||
break;
|
||||
case R.id.visit_website_item:
|
||||
Uri uri = Uri.parse(selectedFeed.getLink());
|
||||
|
@ -14,7 +14,7 @@ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuIte
|
||||
|
||||
public static MenuItem addSearchItem(Menu menu, SearchView searchView) {
|
||||
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
|
||||
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
|
||||
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
|
||||
MenuItemCompat.setActionView(item, searchView);
|
||||
return item;
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package de.danoeh.antennapod.preferences;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
public class CustomEditTextPreference extends EditTextPreference {
|
||||
|
||||
public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public CustomEditTextPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CustomEditTextPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
builder.setInverseBackgroundForced(true);
|
||||
getEditText().setTextColor(getContext().getResources().getColor(R.color.black));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -9,10 +9,14 @@ import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
@ -59,8 +63,6 @@ public class PreferenceController {
|
||||
public static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout";
|
||||
public static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname";
|
||||
public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
|
||||
private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
|
||||
|
||||
|
||||
private final PreferenceUI ui;
|
||||
|
||||
@ -216,6 +218,52 @@ public class PreferenceController {
|
||||
}
|
||||
}
|
||||
);
|
||||
ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)
|
||||
.setOnPreferenceChangeListener(
|
||||
new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
if (o instanceof String) {
|
||||
try {
|
||||
int value = Integer.valueOf((String) o);
|
||||
if (1 <= value && value <= 50) {
|
||||
setParallelDownloadsText(value);
|
||||
return true;
|
||||
}
|
||||
} catch(NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
);
|
||||
// validate and set correct value: number of downloads between 1 and 50 (inclusive)
|
||||
final EditText ev = ((EditTextPreference)ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText();
|
||||
ev.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if(s.length() > 0) {
|
||||
try {
|
||||
int value = Integer.valueOf(s.toString());
|
||||
if (value <= 0) {
|
||||
ev.setText("1");
|
||||
} else if (value > 50) {
|
||||
ev.setText("50");
|
||||
}
|
||||
} catch(NumberFormatException e) {
|
||||
ev.setText("6");
|
||||
}
|
||||
ev.setSelection(ev.getText().length());
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
|
||||
.setOnPreferenceChangeListener(
|
||||
new Preference.OnPreferenceChangeListener() {
|
||||
@ -302,6 +350,7 @@ public class PreferenceController {
|
||||
|
||||
public void onResume() {
|
||||
checkItemVisibility();
|
||||
setParallelDownloadsText(UserPreferences.getParallelDownloads());
|
||||
setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
|
||||
setDataFolderText();
|
||||
updateGpodnetPreferenceScreen();
|
||||
@ -381,6 +430,15 @@ public class PreferenceController {
|
||||
.setEnabled(UserPreferences.isEnableAutodownload());
|
||||
}
|
||||
|
||||
|
||||
private void setParallelDownloadsText(int downloads) {
|
||||
final Resources res = ui.getActivity().getResources();
|
||||
|
||||
String s = Integer.toString(downloads)
|
||||
+ res.getString(R.string.parallel_downloads_suffix);
|
||||
ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s);
|
||||
}
|
||||
|
||||
private void setEpisodeCacheSizeText(int cacheSize) {
|
||||
final Resources res = ui.getActivity().getResources();
|
||||
|
||||
|
@ -13,16 +13,19 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
|
||||
// modified from http://developer.android.com/training/monitoring-device-state/battery-monitoring.html
|
||||
// and ConnectivityActionReceiver.java
|
||||
// Updated based on http://stackoverflow.com/questions/20833241/android-charge-intent-has-no-extra-data
|
||||
// Since the intent doesn't have the EXTRA_STATUS like the android.com article says it does
|
||||
// (though it used to)
|
||||
public class PowerConnectionReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "PowerConnectionReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||||
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
status == BatteryManager.BATTERY_STATUS_FULL;
|
||||
final String action = intent.getAction();
|
||||
|
||||
if (isCharging) {
|
||||
Log.d(TAG, "charging intent: " + action);
|
||||
|
||||
if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
|
||||
Log.d(TAG, "charging, starting auto-download");
|
||||
// we're plugged in, this is a great time to auto-download if everything else is
|
||||
// right. So, even if the user allows auto-dl on battery, let's still start
|
||||
|
@ -5,15 +5,17 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Receives intents from AntennaPod Single Purpose apps
|
||||
@ -34,7 +36,7 @@ public class SPAReceiver extends BroadcastReceiver{
|
||||
if (feedUrls != null) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Received feeds list: " + Arrays.toString(feedUrls));
|
||||
for (String url : feedUrls) {
|
||||
Feed f = new Feed(url, new Date());
|
||||
Feed f = new Feed(url, new Date(0));
|
||||
try {
|
||||
DownloadRequester.getInstance().downloadFeed(context, f);
|
||||
} catch (DownloadRequestException e) {
|
||||
|
@ -1,100 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:scrollbars="vertical">
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
<TextView
|
||||
android:id="@+id/txtvFeedurl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvFeedurl"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_margin="16dp"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
<EditText
|
||||
android:id="@+id/etxtFeedurl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:hint="@string/etxtFeedurlHint"
|
||||
android:inputType="textUri"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etxtFeedurl"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvFeedurl"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/etxtFeedurlHint"
|
||||
android:inputType="textUri"/>
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/confirm_label"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/etxtFeedurl"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/confirm_label"/>
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastDirectories"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butConfirm"
|
||||
android:layout_margin="8dp"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/podcastdirectories_label"/>
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastDirectories"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/divider1"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/podcastdirectories_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastDirectoriesDescr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/podcastdirectories_descr"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:layout_below="@id/txtvPodcastDirectories"
|
||||
android:layout_margin="8dp"/>
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastDirectoriesDescr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/podcastdirectories_descr"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butBrowseGpoddernet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvPodcastDirectoriesDescr"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/browse_gpoddernet_label"/>
|
||||
<Button
|
||||
android:id="@+id/butBrowseGpoddernet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/browse_gpoddernet_label"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butSearchItunes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/search_itunes_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvOpmlImport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butBrowseGpoddernet"
|
||||
android:layout_margin="8dp"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/opml_import_label"/>
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvOpmlImportExpl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvOpmlImport"
|
||||
android:layout_margin="8dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/opml_import_txtv_button_lable"/>
|
||||
<TextView
|
||||
android:id="@+id/txtvOpmlImport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/opml_import_label"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butOpmlImport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvOpmlImportExpl"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/opml_import_label"/>
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
<TextView
|
||||
android:id="@+id/txtvOpmlImportExpl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="@string/opml_import_txtv_button_lable"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<Button
|
||||
android:id="@+id/butOpmlImport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/opml_import_label"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
@ -13,7 +13,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:scaleType="centerInside"
|
||||
tools:src="@android:drawable/sym_def_app_icon" />
|
||||
|
||||
</RelativeLayout>
|
@ -16,7 +16,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp">
|
||||
android:paddingBottom="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
@ -59,6 +59,29 @@
|
||||
android:maxLines="5"
|
||||
tools:text="Podcast 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_below="@id/txtvTitle"
|
||||
android:layout_marginLeft="16dp"
|
||||
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_toLeftOf="@id/butMoreActions"
|
||||
android:layout_marginRight="8dp"
|
||||
tools:text="Jan 23"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
android:layout_below="@+id/txtvTitle"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ProgressBar
|
||||
|
26
app/src/main/res/layout/fragment_itunes_search.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<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="match_parent"
|
||||
tools:context="de.danoeh.antennapod.activity.ITunesSearchActivity">
|
||||
<android.support.v7.widget.SearchView
|
||||
android:id="@+id/itunes_search_view"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
/>
|
||||
<GridView
|
||||
android:id="@+id/gridView"
|
||||
android:layout_below="@id/itunes_search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:columnWidth="200dp"
|
||||
android:gravity="center"
|
||||
android:horizontalSpacing="8dp"
|
||||
android:numColumns="auto_fit"
|
||||
android:paddingBottom="@dimen/list_vertical_padding"
|
||||
android:paddingTop="@dimen/list_vertical_padding"
|
||||
android:stretchMode="columnWidth"
|
||||
android:verticalSpacing="8dp"
|
||||
tools:listitem="@layout/gpodnet_podcast_listitem" />
|
||||
</RelativeLayout>
|
@ -23,16 +23,60 @@
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/subscribers_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/txtvTitle"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgFeed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="-4dp"
|
||||
android:src="?attr/feed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvSubscribers"
|
||||
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
tools:text="150"
|
||||
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_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginBottom="@dimen/list_vertical_padding"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:maxLines="1"
|
||||
tools:text="Podcast title"
|
||||
android:layout_toLeftOf="@id/subscribers_container"
|
||||
android:layout_alignTop="@id/imgvCover"
|
||||
android:lines="1"
|
||||
tools:text="Title"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvUrl"
|
||||
style="android:style/TextAppearance.Small"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
tools:text="http://www.example.com/feed"
|
||||
tools:background="@android:color/holo_green_dark"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
34
app/src/main/res/layout/gpodnet_tag_listitem.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?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:gravity="center_vertical"
|
||||
tools:background="@android:color/darker_gray">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:lines="1"
|
||||
tools:text="Tag Title"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvUsage"
|
||||
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
tools:text="301"
|
||||
tools:background="@android:color/holo_green_dark"/>
|
||||
|
||||
</RelativeLayout>
|
38
app/src/main/res/layout/itunes_podcast_listitem.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?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="@dimen/listitem_threeline_height"
|
||||
tools:background="@android:color/darker_gray">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_height="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/cover_label"
|
||||
android:cropToPadding="true"
|
||||
android:scaleType="fitXY"
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:maxLines="1"
|
||||
tools:text="Podcast title"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
</RelativeLayout>
|
@ -16,7 +16,8 @@
|
||||
android:paddingBottom="@dimen/list_vertical_padding"
|
||||
android:clipToPadding="false"
|
||||
dslv:collapsed_height="2dp"
|
||||
dslv:drag_enabled="false"
|
||||
dslv:drag_enabled="true"
|
||||
dslv:drag_handle_id="@id/drag_handle"
|
||||
dslv:drag_scroll_start="0.33"
|
||||
dslv:float_alpha="0.6"
|
||||
dslv:max_drag_scroll_speed="0.5"
|
||||
@ -49,4 +50,18 @@
|
||||
tools:layout_height="64dp"
|
||||
tools:background="@android:color/holo_red_light"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/undobar"
|
||||
style="@style/UndoBar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/undobar_message"
|
||||
style="@style/UndoBarMessage"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/undobar_button"
|
||||
style="@style/UndoBarButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
@ -1,32 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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/txtvHeadingExplanation1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/opml_import_explanation"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPath"
|
||||
android:id="@+id/txtvExplanation1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
tools:text="Path"
|
||||
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/butStartImport"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/butChooseFileFromFilesystem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/start_import_label" />
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/choose_file_from_filesystem" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvHeadingExplanation2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvExplanation2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/opml_import_explanation_2"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/butChooseFileFromExternal"
|
||||
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_external_application" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?android:attr/listDivider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvHeadingExplanation3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvExplanation3"
|
||||
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>
|
@ -20,7 +20,8 @@
|
||||
dslv:float_alpha="0.6"
|
||||
dslv:float_background_color="?attr/dragview_float_background"
|
||||
dslv:max_drag_scroll_speed="0.5"
|
||||
dslv:remove_enabled="false"
|
||||
dslv:remove_enabled="true"
|
||||
dslv:remove_mode="flingRemove"
|
||||
dslv:slide_shuffle_speed="0.3"
|
||||
dslv:sort_enabled="true"
|
||||
dslv:track_drag_sort="true"
|
||||
@ -42,4 +43,18 @@
|
||||
android:indeterminateOnly="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/undobar"
|
||||
style="@style/UndoBar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/undobar_message"
|
||||
style="@style/UndoBarMessage"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/undobar_button"
|
||||
style="@style/UndoBarButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
@ -9,11 +9,12 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="24dp"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="-64dp"
|
||||
android:contentDescription="@string/drag_handle_content_description"
|
||||
android:scaleType="center"
|
||||
android:scaleType="fitXY"
|
||||
android:src="?attr/dragview_background"
|
||||
tools:src="@drawable/ic_drag_handle"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
@ -32,7 +33,7 @@
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginLeft="@dimen/listitem_threeline_textleftpadding"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_textrightpadding"
|
||||
@ -40,46 +41,72 @@
|
||||
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_alignParentTop="true"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:gravity="right|bottom"
|
||||
android:text="Feb\n12"
|
||||
tools:background="@android:color/holo_blue_light" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@id/txtvPubDate"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="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_alignParentRight="true"
|
||||
android:layout_marginTop="16dp">
|
||||
android:layout_alignParentRight="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPosition"
|
||||
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_marginBottom="0dp"
|
||||
android:text="00:42:23"
|
||||
tools:background="@android:color/holo_blue_light" />
|
||||
tools:background="@android:color/holo_blue_light"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbar_download_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
<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_marginLeft="8dp"
|
||||
android:layout_toRightOf="@id/txtvPosition"
|
||||
android:layout_marginBottom="0dp"
|
||||
tools:text="Jan 23"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvProgressLeft"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:max="100"
|
||||
tools:background="@android:color/holo_blue_light" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:icon="?attr/navigation_refresh"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/refresh_label"
|
||||
custom:showAsAction="ifRoom">
|
||||
custom:showAsAction="always">
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/refresh_complete_item"
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:id="@+id/refresh_item"
|
||||
android:title="@string/refresh_label"
|
||||
android:menuCategory="container"
|
||||
custom:showAsAction="ifRoom"
|
||||
custom:showAsAction="always"
|
||||
android:icon="?attr/navigation_refresh"/>
|
||||
|
||||
<item
|
||||
|
@ -7,9 +7,16 @@
|
||||
android:id="@+id/refresh_item"
|
||||
android:title="@string/refresh_label"
|
||||
android:menuCategory="container"
|
||||
custom:showAsAction="ifRoom"
|
||||
custom:showAsAction="always"
|
||||
android:icon="?attr/navigation_refresh"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_queue"
|
||||
android:title="Clear Queue"
|
||||
android:menuCategory="container"
|
||||
custom:showAsAction="collapseActionView"
|
||||
android:icon="?attr/navigation_accept"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/queue_sort"
|
||||
android:title="@string/sort">
|
||||
|
@ -22,6 +22,17 @@
|
||||
android:summary="@string/pref_persistNotify_sum"
|
||||
android:title="@string/pref_persistNotify_title"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/queue_label">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:enabled="true"
|
||||
android:key="prefQueueAddToFront"
|
||||
android:summary="@string/pref_queueAddToFront_sum"
|
||||
android:title="@string/pref_queueAddToFront_title"/>
|
||||
/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/playback_pref">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
@ -77,13 +88,17 @@
|
||||
android:key="prefAutoUpdateIntervall"
|
||||
android:summary="@string/pref_autoUpdateIntervall_sum"
|
||||
android:title="@string/pref_autoUpdateIntervall_title"/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:enabled="true"
|
||||
android:key="prefMobileUpdate"
|
||||
android:summary="@string/pref_mobileUpdate_sum"
|
||||
android:title="@string/pref_mobileUpdate_title"/>
|
||||
<de.danoeh.antennapod.preferences.CustomEditTextPreference
|
||||
android:defaultValue="6"
|
||||
android:inputType="number"
|
||||
android:key="prefParallelDownloads"
|
||||
android:title="@string/pref_parallel_downloads_title"/>
|
||||
<ListPreference
|
||||
android:defaultValue="20"
|
||||
android:entries="@array/episode_cache_size_entries"
|
||||
@ -111,6 +126,7 @@
|
||||
</PreferenceScreen>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/services_label">
|
||||
<PreferenceScreen
|
||||
android:key="prefFlattrSettings"
|
||||
|
@ -44,4 +44,5 @@ dependencies {
|
||||
compile 'com.squareup.okhttp:okhttp:2.2.0'
|
||||
compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
|
||||
compile 'com.squareup.okio:okio:1.2.0'
|
||||
}
|
||||
compile 'com.nineoldandroids:library:2.4.0'
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ public class EventDistributor extends Observable {
|
||||
public static final int PLAYBACK_HISTORY_UPDATE = 16;
|
||||
public static final int DOWNLOAD_QUEUED = 32;
|
||||
public static final int DOWNLOAD_HANDLED = 64;
|
||||
public static final int PLAYER_STATUS_UPDATE = 128;
|
||||
|
||||
private Handler handler;
|
||||
private AbstractQueue<Integer> events;
|
||||
@ -124,6 +125,10 @@ public class EventDistributor extends Observable {
|
||||
addEvent(DOWNLOAD_HANDLED);
|
||||
}
|
||||
|
||||
public void sendPlayerStatusUpdateBroadcast() {
|
||||
addEvent(PLAYER_STATUS_UPDATE);
|
||||
}
|
||||
|
||||
public static abstract class EventListener implements Observer {
|
||||
|
||||
@Override
|
||||
|
@ -2,10 +2,10 @@ package de.danoeh.antennapod.core.feed;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import de.danoeh.antennapod.core.asynctask.PicassoImageResource;
|
||||
|
||||
|
||||
public class FeedImage extends FeedFile implements PicassoImageResource {
|
||||
public static final int FEEDFILETYPE_FEEDIMAGE = 1;
|
||||
@ -65,6 +65,8 @@ public class FeedImage extends FeedFile implements PicassoImageResource {
|
||||
public Uri getImageUri() {
|
||||
if (file_url != null && downloaded) {
|
||||
return Uri.fromFile(new File(file_url));
|
||||
} else if(download_url != null) {
|
||||
return Uri.parse(download_url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -135,8 +135,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
|
||||
if (other.media != null) {
|
||||
if (media == null) {
|
||||
setMedia(other.media);
|
||||
} else if (media.compareWithOther(other)) {
|
||||
media.updateFromOther(other);
|
||||
} else if (media.compareWithOther(other.media)) {
|
||||
media.updateFromOther(other.media);
|
||||
}
|
||||
}
|
||||
if (other.paymentLink != null) {
|
||||
|
@ -127,6 +127,25 @@ public class FeedMedia extends FeedFile implements Playable {
|
||||
&& PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads playback preferences to determine whether this FeedMedia object is
|
||||
* currently being played and the current player status is playing.
|
||||
*/
|
||||
public boolean isCurrentlyPlaying() {
|
||||
return isPlaying() &&
|
||||
((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PLAYING));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads playback preferences to determine whether this FeedMedia object is
|
||||
* currently being played and the current player status is paused.
|
||||
*/
|
||||
public boolean isCurrentlyPaused() {
|
||||
return isPlaying() &&
|
||||
((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PAUSED));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTypeAsInt() {
|
||||
return FEEDFILETYPE_FEEDMEDIA;
|
||||
|
@ -1,5 +1,8 @@
|
||||
package de.danoeh.antennapod.core.gpoddernet;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.okhttp.Credentials;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
@ -18,16 +21,27 @@ import org.json.JSONObject;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStore;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
|
||||
@ -43,6 +57,8 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
*/
|
||||
public class GpodnetService {
|
||||
|
||||
private static final String TAG = "GpodnetService";
|
||||
|
||||
private static final String BASE_SCHEME = "https";
|
||||
|
||||
public static final String DEFAULT_BASE_HOST = "gpodder.net";
|
||||
@ -56,9 +72,84 @@ public class GpodnetService {
|
||||
|
||||
public GpodnetService() {
|
||||
httpClient = AntennapodHttpClient.getHttpClient();
|
||||
if (Build.VERSION.SDK_INT <= 10) {
|
||||
Log.d(TAG, "Use custom SSL factory");
|
||||
SSLSocketFactory factory = getCustomSslSocketFactory();
|
||||
httpClient.setSslSocketFactory(factory);
|
||||
}
|
||||
BASE_HOST = GpodnetPreferences.getHostname();
|
||||
}
|
||||
|
||||
private synchronized static SSLSocketFactory getCustomSslSocketFactory() {
|
||||
try {
|
||||
TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
defaultTrustManagerFactory.init((KeyStore) null); // use system keystore
|
||||
final X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0];
|
||||
TrustManager[] customTrustManagers = new TrustManager[]{new X509TrustManager() {
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
// chain may out of order - construct data structures to walk from server certificate to root certificate
|
||||
Map<Principal, X509Certificate> certificates = new HashMap<Principal, X509Certificate>(chain.length - 1);
|
||||
X509Certificate subject = null;
|
||||
for (X509Certificate cert : chain) {
|
||||
cert.checkValidity();
|
||||
if (cert.getSubjectDN().toString().startsWith("CN=" + DEFAULT_BASE_HOST)) {
|
||||
subject = cert;
|
||||
} else {
|
||||
certificates.put(cert.getSubjectDN(), cert);
|
||||
}
|
||||
}
|
||||
if (subject == null) {
|
||||
throw new CertificateException("Chain does not contain a certificate for " + DEFAULT_BASE_HOST);
|
||||
}
|
||||
// follow chain to root CA
|
||||
while (certificates.get(subject.getIssuerDN()) != null) {
|
||||
subject.checkValidity();
|
||||
X509Certificate issuer = certificates.get(subject.getIssuerDN());
|
||||
try {
|
||||
subject.verify(issuer.getPublicKey());
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "failed: " + issuer.getSubjectDN() + " -> " + subject.getSubjectDN());
|
||||
throw new CertificateException("Could not verify certificate");
|
||||
}
|
||||
subject = issuer;
|
||||
}
|
||||
X500Principal rootAuthority = subject.getIssuerX500Principal();
|
||||
boolean accepted = false;
|
||||
for (X509Certificate cert :
|
||||
defaultTrustManager.getAcceptedIssuers()) {
|
||||
if (cert.getSubjectX500Principal().equals(rootAuthority)) {
|
||||
try {
|
||||
subject.verify(cert.getPublicKey());
|
||||
accepted = true;
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "failed: " + cert.getSubjectDN() + " -> " + subject.getSubjectDN());
|
||||
throw new CertificateException("Could not verify root certificate");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (accepted == false) {
|
||||
throw new CertificateException("Could not verify root certificate");
|
||||
}
|
||||
}
|
||||
}};
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, customTrustManagers, null);
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [count] most used tags.
|
||||
*/
|
||||
@ -81,9 +172,10 @@ public class GpodnetService {
|
||||
jsonTagList.length());
|
||||
for (int i = 0; i < jsonTagList.length(); i++) {
|
||||
JSONObject jObj = jsonTagList.getJSONObject(i);
|
||||
String name = jObj.getString("tag");
|
||||
String title = jObj.getString("title");
|
||||
String tag = jObj.getString("tag");
|
||||
int usage = jObj.getInt("usage");
|
||||
tagList.add(new GpodnetTag(name, usage));
|
||||
tagList.add(new GpodnetTag(title, tag, usage));
|
||||
}
|
||||
return tagList;
|
||||
} catch (JSONException e) {
|
||||
@ -103,7 +195,7 @@ public class GpodnetService {
|
||||
|
||||
try {
|
||||
URL url = new URI(BASE_SCHEME, BASE_HOST, String.format(
|
||||
"/api/2/tag/%s/%d.json", tag.getName(), count), null).toURL();
|
||||
"/api/2/tag/%s/%d.json", tag.getTag(), count), null).toURL();
|
||||
Request.Builder request = new Request.Builder().url(url);
|
||||
String response = executeRequest(request);
|
||||
|
||||
|
@ -1,46 +1,60 @@
|
||||
package de.danoeh.antennapod.core.gpoddernet.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.Comparator;
|
||||
public class GpodnetTag implements Parcelable {
|
||||
|
||||
public class GpodnetTag {
|
||||
private final String title;
|
||||
private final String tag;
|
||||
private final int usage;
|
||||
|
||||
private String name;
|
||||
private int usage;
|
||||
public GpodnetTag(String title, String tag, int usage) {
|
||||
Validate.notNull(title);
|
||||
Validate.notNull(tag);
|
||||
|
||||
public GpodnetTag(String name, int usage) {
|
||||
Validate.notNull(name);
|
||||
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.tag = tag;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public GpodnetTag(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
public static GpodnetTag createFromParcel(Parcel in) {
|
||||
final String title = in.readString();
|
||||
final String tag = in.readString();
|
||||
final int usage = in.readInt();
|
||||
return new GpodnetTag(title, tag, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GpodnetTag [name=" + name + ", usage=" + usage + "]";
|
||||
return "GpodnetTag [title="+title+", tag=" + tag + ", usage=" + usage + "]";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public int getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public static class UsageComparator implements Comparator<GpodnetTag> {
|
||||
|
||||
@Override
|
||||
public int compare(GpodnetTag o1, GpodnetTag o2) {
|
||||
return o1.usage - o2.usage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(title);
|
||||
dest.writeString(tag);
|
||||
dest.writeInt(usage);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.util.Log;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
|
||||
/**
|
||||
* Provides access to preferences set by the playback service. A private
|
||||
@ -43,14 +44,27 @@ public class PlaybackPreferences implements
|
||||
/** True if last played media was a video. */
|
||||
public static final String PREF_CURRENT_EPISODE_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo";
|
||||
|
||||
/** The current player status as int. */
|
||||
public static final String PREF_CURRENT_PLAYER_STATUS = "de.danoeh.antennapod.preferences.currentPlayerStatus";
|
||||
|
||||
/** Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. */
|
||||
public static final long NO_MEDIA_PLAYING = -1;
|
||||
|
||||
/** Value of PREF_CURRENT_PLAYER_STATUS if media player status is playing. */
|
||||
public static final int PLAYER_STATUS_PLAYING = 1;
|
||||
|
||||
/** Value of PREF_CURRENT_PLAYER_STATUS if media player status is paused. */
|
||||
public static final int PLAYER_STATUS_PAUSED = 2;
|
||||
|
||||
/** Value of PREF_CURRENT_PLAYER_STATUS if media player status is neither playing nor paused. */
|
||||
public static final int PLAYER_STATUS_OTHER = 3;
|
||||
|
||||
private long currentlyPlayingFeedId;
|
||||
private long currentlyPlayingFeedMediaId;
|
||||
private long currentlyPlayingMedia;
|
||||
private boolean currentEpisodeIsStream;
|
||||
private boolean currentEpisodeIsVideo;
|
||||
private int currentPlayerStatus;
|
||||
|
||||
private static PlaybackPreferences instance;
|
||||
private Context context;
|
||||
@ -87,6 +101,8 @@ public class PlaybackPreferences implements
|
||||
NO_MEDIA_PLAYING);
|
||||
currentEpisodeIsStream = sp.getBoolean(PREF_CURRENT_EPISODE_IS_STREAM, true);
|
||||
currentEpisodeIsVideo = sp.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false);
|
||||
currentPlayerStatus = sp.getInt(PREF_CURRENT_PLAYER_STATUS,
|
||||
PLAYER_STATUS_OTHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,6 +125,11 @@ public class PlaybackPreferences implements
|
||||
currentlyPlayingFeedMediaId = sp.getLong(
|
||||
PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING);
|
||||
}
|
||||
else if (key.equals(PREF_CURRENT_PLAYER_STATUS)) {
|
||||
currentPlayerStatus = sp.getInt(PREF_CURRENT_PLAYER_STATUS,
|
||||
PLAYER_STATUS_OTHER);
|
||||
EventDistributor.getInstance().sendPlayerStatusUpdateBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
private static void instanceAvailable() {
|
||||
@ -143,4 +164,10 @@ public class PlaybackPreferences implements
|
||||
return instance.currentEpisodeIsVideo;
|
||||
}
|
||||
|
||||
public static int getCurrentPlayerStatus() {
|
||||
instanceAvailable();
|
||||
return instance.currentPlayerStatus;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.core.ApplicationCallbacks;
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
@ -42,6 +41,7 @@ public class UserPreferences implements
|
||||
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
|
||||
public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly";
|
||||
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
|
||||
public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
|
||||
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
|
||||
public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes";
|
||||
public static final String PREF_AUTO_DELETE = "prefAutoDelete";
|
||||
@ -60,6 +60,7 @@ public class UserPreferences implements
|
||||
private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs";
|
||||
private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
|
||||
private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
|
||||
public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
|
||||
|
||||
// TODO: Make this value configurable
|
||||
private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f;
|
||||
@ -85,6 +86,7 @@ public class UserPreferences implements
|
||||
private boolean enableAutodownloadWifiFilter;
|
||||
private boolean enableAutodownloadOnBattery;
|
||||
private String[] autodownloadSelectedNetworks;
|
||||
private int parallelDownloads;
|
||||
private int episodeCacheSize;
|
||||
private String playbackSpeed;
|
||||
private String[] playbackSpeedArray;
|
||||
@ -143,6 +145,7 @@ public class UserPreferences implements
|
||||
PREF_ENABLE_AUTODL_WIFI_FILTER, false);
|
||||
autodownloadSelectedNetworks = StringUtils.split(
|
||||
sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
|
||||
parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
|
||||
episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
|
||||
PREF_EPISODE_CACHE_SIZE, "20"));
|
||||
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
|
||||
@ -313,6 +316,11 @@ public class UserPreferences implements
|
||||
return instance.autodownloadSelectedNetworks;
|
||||
}
|
||||
|
||||
public static int getParallelDownloads() {
|
||||
instanceAvailable();
|
||||
return instance.parallelDownloads;
|
||||
}
|
||||
|
||||
public static int getEpisodeCacheSizeUnlimited() {
|
||||
return EPISODE_CACHE_SIZE_UNLIMITED;
|
||||
}
|
||||
@ -398,6 +406,8 @@ public class UserPreferences implements
|
||||
} else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) {
|
||||
autodownloadSelectedNetworks = StringUtils.split(
|
||||
sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ',');
|
||||
} else if(key.equals(PREF_PARALLEL_DOWNLOADS)) {
|
||||
parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6"));
|
||||
} else if (key.equals(PREF_EPISODE_CACHE_SIZE)) {
|
||||
episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
|
||||
PREF_EPISODE_CACHE_SIZE, "20"));
|
||||
|
@ -140,7 +140,7 @@ public class GpodnetSyncService extends Service {
|
||||
private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
|
||||
for (String downloadUrl : changes.getAdded()) {
|
||||
if (!localSubscriptions.contains(downloadUrl)) {
|
||||
Feed feed = new Feed(downloadUrl, new Date());
|
||||
Feed feed = new Feed(downloadUrl, new Date(0));
|
||||
DownloadRequester.getInstance().downloadFeed(this, feed);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ import android.os.Parcelable;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.FeedFile;
|
||||
import de.danoeh.antennapod.core.util.URLChecker;
|
||||
|
||||
public class DownloadRequest implements Parcelable {
|
||||
|
||||
private final String destination;
|
||||
@ -13,6 +16,7 @@ public class DownloadRequest implements Parcelable {
|
||||
private final String title;
|
||||
private String username;
|
||||
private String password;
|
||||
private long ifModifiedSince;
|
||||
private boolean deleteOnFailure;
|
||||
private final long feedfileId;
|
||||
private final int feedfileType;
|
||||
@ -45,12 +49,26 @@ public class DownloadRequest implements Parcelable {
|
||||
this(destination, source, title, feedfileId, feedfileType, null, null, true, null);
|
||||
}
|
||||
|
||||
public DownloadRequest(Builder builder) {
|
||||
this.destination = builder.destination;
|
||||
this.source = builder.source;
|
||||
this.title = builder.title;
|
||||
this.feedfileId = builder.feedfileId;
|
||||
this.feedfileType = builder.feedfileType;
|
||||
this.username = builder.username;
|
||||
this.password = builder.password;
|
||||
this.ifModifiedSince = builder.ifModifiedSince;
|
||||
this.deleteOnFailure = builder.deleteOnFailure;
|
||||
this.arguments = (builder.arguments != null) ? builder.arguments : new Bundle();
|
||||
}
|
||||
|
||||
private DownloadRequest(Parcel in) {
|
||||
destination = in.readString();
|
||||
source = in.readString();
|
||||
title = in.readString();
|
||||
feedfileId = in.readLong();
|
||||
feedfileType = in.readInt();
|
||||
ifModifiedSince = in.readLong();
|
||||
deleteOnFailure = (in.readByte() > 0);
|
||||
arguments = in.readBundle();
|
||||
if (in.dataAvail() > 0) {
|
||||
@ -77,6 +95,7 @@ public class DownloadRequest implements Parcelable {
|
||||
dest.writeString(title);
|
||||
dest.writeLong(feedfileId);
|
||||
dest.writeInt(feedfileType);
|
||||
dest.writeLong(ifModifiedSince);
|
||||
dest.writeByte((deleteOnFailure) ? (byte) 1 : 0);
|
||||
dest.writeBundle(arguments);
|
||||
if (username != null) {
|
||||
@ -105,6 +124,7 @@ public class DownloadRequest implements Parcelable {
|
||||
|
||||
DownloadRequest that = (DownloadRequest) o;
|
||||
|
||||
if (ifModifiedSince != that.ifModifiedSince) return false;
|
||||
if (deleteOnFailure != that.deleteOnFailure) return false;
|
||||
if (feedfileId != that.feedfileId) return false;
|
||||
if (feedfileType != that.feedfileType) return false;
|
||||
@ -131,6 +151,7 @@ public class DownloadRequest implements Parcelable {
|
||||
result = 31 * result + (title != null ? title.hashCode() : 0);
|
||||
result = 31 * result + (username != null ? username.hashCode() : 0);
|
||||
result = 31 * result + (password != null ? password.hashCode() : 0);
|
||||
result = 31 * result + (int)ifModifiedSince;
|
||||
result = 31 * result + (deleteOnFailure ? 1 : 0);
|
||||
result = 31 * result + (int) (feedfileId ^ (feedfileId >>> 32));
|
||||
result = 31 * result + feedfileType;
|
||||
@ -210,6 +231,15 @@ public class DownloadRequest implements Parcelable {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public DownloadRequest setIfModifiedSince(long time) {
|
||||
this.ifModifiedSince = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getIfModifiedSince() {
|
||||
return this.ifModifiedSince;
|
||||
}
|
||||
|
||||
public boolean isDeleteOnFailure() {
|
||||
return deleteOnFailure;
|
||||
}
|
||||
@ -217,4 +247,54 @@ public class DownloadRequest implements Parcelable {
|
||||
public Bundle getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String destination;
|
||||
private String source;
|
||||
private String title;
|
||||
private String username;
|
||||
private String password;
|
||||
private long ifModifiedSince;
|
||||
private boolean deleteOnFailure = false;
|
||||
private long feedfileId;
|
||||
private int feedfileType;
|
||||
private Bundle arguments;
|
||||
|
||||
public Builder(String destination, FeedFile item) {
|
||||
this.destination = destination;
|
||||
this.source = URLChecker.prepareURL(item.getDownload_url());
|
||||
this.title = item.getHumanReadableIdentifier();
|
||||
this.feedfileId = item.getId();
|
||||
this.feedfileType = item.getTypeAsInt();
|
||||
}
|
||||
|
||||
public Builder deleteOnFailure(boolean deleteOnFailure) {
|
||||
this.deleteOnFailure = deleteOnFailure;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder ifModifiedSince(long time) {
|
||||
this.ifModifiedSince = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAuthentication(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DownloadRequest build() {
|
||||
Validate.notNull(destination);
|
||||
Validate.notNull(source);
|
||||
Validate.notNull(title);
|
||||
return new DownloadRequest(this);
|
||||
}
|
||||
|
||||
public Builder withArguments(Bundle args) {
|
||||
this.arguments = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.R;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
@ -61,6 +60,7 @@ import de.danoeh.antennapod.core.feed.FeedImage;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
@ -172,12 +172,11 @@ public class DownloadService extends Service {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started");
|
||||
Log.d(TAG, "downloadCompletionThread was started");
|
||||
while (!isInterrupted()) {
|
||||
try {
|
||||
Downloader downloader = downloadExecutor.take().get();
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Received 'Download Complete' - message.");
|
||||
Log.d(TAG, "Received 'Download Complete' - message.");
|
||||
removeDownload(downloader);
|
||||
DownloadStatus status = downloader.getResult();
|
||||
boolean successful = status.isSuccessful();
|
||||
@ -213,13 +212,13 @@ public class DownloadService extends Service {
|
||||
queryDownloadsAsync();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted");
|
||||
Log.d(TAG, "DownloadCompletionThread was interrupted");
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
numberOfDownloads.decrementAndGet();
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread");
|
||||
Log.d(TAG, "End of downloadCompletionThread");
|
||||
}
|
||||
};
|
||||
|
||||
@ -236,8 +235,7 @@ public class DownloadService extends Service {
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Service started");
|
||||
Log.d(TAG, "Service started");
|
||||
isRunning = true;
|
||||
handler = new Handler();
|
||||
newMediaFiles = Collections.synchronizedList(new ArrayList<Long>());
|
||||
@ -258,8 +256,9 @@ public class DownloadService extends Service {
|
||||
return t;
|
||||
}
|
||||
});
|
||||
Log.d(TAG, "parallel downloads: " + UserPreferences.getParallelDownloads());
|
||||
downloadExecutor = new ExecutorCompletionService<Downloader>(
|
||||
Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
|
||||
Executors.newFixedThreadPool(UserPreferences.getParallelDownloads(),
|
||||
new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
@ -304,8 +303,7 @@ public class DownloadService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Service shutting down");
|
||||
Log.d(TAG, "Service shutting down");
|
||||
isRunning = false;
|
||||
|
||||
if (ClientConfig.downloadServiceCallbacks.shouldCreateReport()) {
|
||||
@ -346,8 +344,7 @@ public class DownloadService extends Service {
|
||||
.setLargeIcon(icon)
|
||||
.setSmallIcon(R.drawable.stat_notify_sync);
|
||||
}
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Notification set up");
|
||||
Log.d(TAG, "Notification set up");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -427,8 +424,7 @@ public class DownloadService extends Service {
|
||||
String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
|
||||
Validate.notNull(url, "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Cancelling download with url " + url);
|
||||
Log.d(TAG, "Cancelling download with url " + url);
|
||||
Downloader d = getDownloader(url);
|
||||
if (d != null) {
|
||||
d.cancel();
|
||||
@ -439,8 +435,7 @@ public class DownloadService extends Service {
|
||||
} else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
|
||||
for (Downloader d : downloads) {
|
||||
d.cancel();
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Cancelled all downloads");
|
||||
Log.d(TAG, "Cancelled all downloads");
|
||||
}
|
||||
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
|
||||
|
||||
@ -451,8 +446,7 @@ public class DownloadService extends Service {
|
||||
};
|
||||
|
||||
private void onDownloadQueued(Intent intent) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Received enqueue request");
|
||||
Log.d(TAG, "Received enqueue request");
|
||||
DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException(
|
||||
@ -462,7 +456,12 @@ public class DownloadService extends Service {
|
||||
Downloader downloader = getDownloader(request);
|
||||
if (downloader != null) {
|
||||
numberOfDownloads.incrementAndGet();
|
||||
downloads.add(downloader);
|
||||
// smaller rss feeds before bigger media files
|
||||
if(request.getFeedfileId() == Feed.FEEDFILETYPE_FEED) {
|
||||
downloads.add(0, downloader);
|
||||
} else {
|
||||
downloads.add(downloader);
|
||||
}
|
||||
downloadExecutor.submit(downloader);
|
||||
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
|
||||
}
|
||||
@ -490,12 +489,10 @@ public class DownloadService extends Service {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Removing downloader: "
|
||||
+ d.getDownloadRequest().getSource());
|
||||
Log.d(TAG, "Removing downloader: "
|
||||
+ d.getDownloadRequest().getSource());
|
||||
boolean rc = downloads.remove(d);
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Result of downloads.remove: " + rc);
|
||||
Log.d(TAG, "Result of downloads.remove: " + rc);
|
||||
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
|
||||
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
|
||||
}
|
||||
@ -544,8 +541,7 @@ public class DownloadService extends Service {
|
||||
}
|
||||
|
||||
if (createReport) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Creating report");
|
||||
Log.d(TAG, "Creating report");
|
||||
// create notification object
|
||||
Notification notification = new NotificationCompat.Builder(this)
|
||||
.setTicker(
|
||||
@ -569,8 +565,7 @@ public class DownloadService extends Service {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(REPORT_ID, notification);
|
||||
} else {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "No report is created");
|
||||
Log.d(TAG, "No report is created");
|
||||
}
|
||||
reportQueue.clear();
|
||||
}
|
||||
@ -592,13 +587,10 @@ public class DownloadService extends Service {
|
||||
* Check if there's something else to download, otherwise stop
|
||||
*/
|
||||
void queryDownloads() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, numberOfDownloads.get() + " downloads left");
|
||||
}
|
||||
Log.d(TAG, numberOfDownloads.get() + " downloads left");
|
||||
|
||||
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
|
||||
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
|
||||
stopSelf();
|
||||
} else {
|
||||
setupNotificationUpdater();
|
||||
@ -634,8 +626,7 @@ public class DownloadService extends Service {
|
||||
* Is called whenever a Feed is downloaded
|
||||
*/
|
||||
private void handleCompletedFeedDownload(DownloadRequest request) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Handling completed Feed Download");
|
||||
Log.d(TAG, "Handling completed Feed Download");
|
||||
feedSyncThread.submitCompletedDownload(request);
|
||||
|
||||
}
|
||||
@ -644,8 +635,7 @@ public class DownloadService extends Service {
|
||||
* Is called whenever a Feed-Image is downloaded
|
||||
*/
|
||||
private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Handling completed Image Download");
|
||||
Log.d(TAG, "Handling completed Image Download");
|
||||
syncExecutor.execute(new ImageHandlerThread(status, request));
|
||||
}
|
||||
|
||||
@ -653,13 +643,12 @@ public class DownloadService extends Service {
|
||||
* Is called whenever a FeedMedia is downloaded.
|
||||
*/
|
||||
private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Handling completed FeedMedia Download");
|
||||
Log.d(TAG, "Handling completed FeedMedia Download");
|
||||
syncExecutor.execute(new MediaHandlerThread(status, request));
|
||||
}
|
||||
|
||||
private void handleFailedDownload(DownloadStatus status, DownloadRequest request) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Handling failed download");
|
||||
Log.d(TAG, "Handling failed download");
|
||||
syncExecutor.execute(new FailedDownloadHandler(status, request));
|
||||
}
|
||||
|
||||
@ -709,12 +698,10 @@ public class DownloadService extends Service {
|
||||
long currentTime = startTime;
|
||||
while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) {
|
||||
try {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms");
|
||||
Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms");
|
||||
sleep(startTime + WAIT_TIMEOUT - currentTime);
|
||||
} catch (InterruptedException e) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "interrupted while waiting for more downloads");
|
||||
Log.d(TAG, "interrupted while waiting for more downloads");
|
||||
tasks += pollCompletedDownloads();
|
||||
} finally {
|
||||
currentTime = System.currentTimeMillis();
|
||||
@ -762,7 +749,7 @@ public class DownloadService extends Service {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Bundling " + results.size() + " feeds");
|
||||
Log.d(TAG, "Bundling " + results.size() + " feeds");
|
||||
|
||||
for (Pair<DownloadRequest, FeedHandlerResult> result : results) {
|
||||
removeDuplicateImages(result.second.feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet.
|
||||
@ -789,8 +776,7 @@ public class DownloadService extends Service {
|
||||
// Download Feed Image if provided and not downloaded
|
||||
if (savedFeed.getImage() != null
|
||||
&& savedFeed.getImage().isDownloaded() == false) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Feed has image; Downloading....");
|
||||
Log.d(TAG, "Feed has image; Downloading....");
|
||||
savedFeed.getImage().setOwner(savedFeed);
|
||||
final Feed savedFeedRef = savedFeed;
|
||||
try {
|
||||
@ -856,7 +842,7 @@ public class DownloadService extends Service {
|
||||
}
|
||||
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Shutting down");
|
||||
Log.d(TAG, "Shutting down");
|
||||
|
||||
}
|
||||
|
||||
@ -902,8 +888,7 @@ public class DownloadService extends Service {
|
||||
FeedHandlerResult result = null;
|
||||
try {
|
||||
result = feedHandler.parseFeed(feed);
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, feed.getTitle() + " parsed");
|
||||
Log.d(TAG, feed.getTitle() + " parsed");
|
||||
if (checkFeedData(feed) == false) {
|
||||
throw new InvalidFeedException();
|
||||
}
|
||||
@ -1008,13 +993,13 @@ public class DownloadService extends Service {
|
||||
*/
|
||||
private void cleanup(Feed feed) {
|
||||
if (feed.getFile_url() != null) {
|
||||
if (new File(feed.getFile_url()).delete())
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Successfully deleted cache file.");
|
||||
else
|
||||
Log.e(TAG, "Failed to delete cache file.");
|
||||
if (new File(feed.getFile_url()).delete()) {
|
||||
Log.d(TAG, "Successfully deleted cache file.");
|
||||
} else {
|
||||
Log.e(TAG, "Failed to delete cache file.");
|
||||
}
|
||||
feed.setFile_url(null);
|
||||
} else if (BuildConfig.DEBUG) {
|
||||
} else {
|
||||
Log.d(TAG, "Didn't delete cache file: File url is not set.");
|
||||
}
|
||||
}
|
||||
@ -1056,7 +1041,7 @@ public class DownloadService extends Service {
|
||||
@Override
|
||||
public void run() {
|
||||
if (request.isDeleteOnFailure()) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
|
||||
Log.d(TAG, "Ignoring failed download, deleteOnFailure=true");
|
||||
} else {
|
||||
File dest = new File(request.getDestination());
|
||||
if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
@ -1144,8 +1129,7 @@ public class DownloadService extends Service {
|
||||
mmr.setDataSource(media.getFile_url());
|
||||
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
media.setDuration(Integer.parseInt(durationStr));
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Duration of file is " + media.getDuration());
|
||||
Log.d(TAG, "Duration of file is " + media.getDuration());
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
} catch (RuntimeException e) {
|
||||
@ -1191,8 +1175,7 @@ public class DownloadService extends Service {
|
||||
* Schedules the notification updater task if it hasn't been scheduled yet.
|
||||
*/
|
||||
private void setupNotificationUpdater() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Setting up notification updater");
|
||||
Log.d(TAG, "Setting up notification updater");
|
||||
if (notificationUpdater == null) {
|
||||
notificationUpdater = new NotificationUpdater();
|
||||
notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
|
||||
|
@ -7,6 +7,7 @@ import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.ResponseBody;
|
||||
import com.squareup.okhttp.internal.http.HttpDate;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -21,6 +22,7 @@ import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
@ -64,6 +66,15 @@ public class HttpDownloader extends Downloader {
|
||||
final URI uri = URIUtil.getURIFromRequestUrl(request.getSource());
|
||||
Request.Builder httpReq = new Request.Builder().url(uri.toURL())
|
||||
.header("User-Agent", ClientConfig.USER_AGENT);
|
||||
if(request.getIfModifiedSince() > 0) {
|
||||
long threeDaysAgo = System.currentTimeMillis() - 1000*60*60*24*3;
|
||||
if(request.getIfModifiedSince() > threeDaysAgo) {
|
||||
Date date = new Date(request.getIfModifiedSince());
|
||||
String httpDate = HttpDate.format(date);
|
||||
Log.d(TAG, "addHeader(\"If-Modified-Since\", \"" + httpDate + "\")");
|
||||
httpReq.addHeader("If-Modified-Since", httpDate);
|
||||
}
|
||||
}
|
||||
|
||||
// add authentication information
|
||||
String userInfo = uri.getUserInfo();
|
||||
@ -83,7 +94,7 @@ public class HttpDownloader extends Downloader {
|
||||
request.setSoFar(destination.length());
|
||||
httpReq.addHeader("Range",
|
||||
"bytes=" + request.getSoFar() + "-");
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Adding range header: " + request.getSoFar());
|
||||
Log.d(TAG, "Adding range header: " + request.getSoFar());
|
||||
}
|
||||
|
||||
Response response = httpClient.newCall(httpReq.build()).execute();
|
||||
@ -96,6 +107,12 @@ public class HttpDownloader extends Downloader {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Response code is " + response.code());
|
||||
|
||||
if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled");
|
||||
onCancelled();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.isSuccessful() || response.body() == null) {
|
||||
final DownloadError error;
|
||||
final String details;
|
||||
|
@ -100,6 +100,18 @@ public class PlaybackService extends Service {
|
||||
*/
|
||||
public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.skipCurrentEpisode";
|
||||
|
||||
/**
|
||||
* If the PlaybackService receives this action, it will pause playback.
|
||||
*/
|
||||
public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode";
|
||||
|
||||
|
||||
/**
|
||||
* If the PlaybackService receives this action, it will resume playback.
|
||||
*/
|
||||
public static final String ACTION_RESUME_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.resumePlayCurrentEpisode";
|
||||
|
||||
|
||||
/**
|
||||
* Used in NOTIFICATION_TYPE_RELOAD.
|
||||
*/
|
||||
@ -216,6 +228,10 @@ public class PlaybackService extends Service {
|
||||
AudioManager.ACTION_AUDIO_BECOMING_NOISY));
|
||||
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
|
||||
ACTION_SKIP_CURRENT_EPISODE));
|
||||
registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter(
|
||||
ACTION_PAUSE_PLAY_CURRENT_EPISODE));
|
||||
registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter(
|
||||
ACTION_RESUME_PLAY_CURRENT_EPISODE));
|
||||
remoteControlClient = setupRemoteControlClient();
|
||||
taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback);
|
||||
mediaPlayer = new PlaybackServiceMediaPlayer(this, mediaPlayerCallback);
|
||||
@ -427,6 +443,7 @@ public class PlaybackService extends Service {
|
||||
// remove notifcation on pause
|
||||
stopForeground(true);
|
||||
}
|
||||
writePlayerStatusPlaybackPreferences();
|
||||
|
||||
break;
|
||||
|
||||
@ -443,9 +460,11 @@ public class PlaybackService extends Service {
|
||||
|
||||
taskManager.startPositionSaver();
|
||||
taskManager.startWidgetUpdater();
|
||||
writePlayerStatusPlaybackPreferences();
|
||||
setupNotification(newInfo);
|
||||
started = true;
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
writePlaybackPreferencesNoMediaPlaying();
|
||||
break;
|
||||
@ -634,9 +653,26 @@ public class PlaybackService extends Service {
|
||||
editor.putLong(
|
||||
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID,
|
||||
PlaybackPreferences.NO_MEDIA_PLAYING);
|
||||
editor.putInt(
|
||||
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS,
|
||||
PlaybackPreferences.PLAYER_STATUS_OTHER);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private int getCurrentPlayerStatusAsInt(PlayerStatus playerStatus) {
|
||||
int playerStatusAsInt;
|
||||
switch (playerStatus) {
|
||||
case PLAYING:
|
||||
playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PLAYING;
|
||||
break;
|
||||
case PAUSED:
|
||||
playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PAUSED;
|
||||
break;
|
||||
default:
|
||||
playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_OTHER;
|
||||
}
|
||||
return playerStatusAsInt;
|
||||
}
|
||||
|
||||
private void writePlaybackPreferences() {
|
||||
if (BuildConfig.DEBUG)
|
||||
@ -647,6 +683,7 @@ public class PlaybackService extends Service {
|
||||
PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
|
||||
MediaType mediaType = mediaPlayer.getCurrentMediaType();
|
||||
boolean stream = mediaPlayer.isStreaming();
|
||||
int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus);
|
||||
|
||||
if (info.playable != null) {
|
||||
editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA,
|
||||
@ -683,6 +720,23 @@ public class PlaybackService extends Service {
|
||||
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID,
|
||||
PlaybackPreferences.NO_MEDIA_PLAYING);
|
||||
}
|
||||
editor.putInt(
|
||||
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
|
||||
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void writePlayerStatusPlaybackPreferences() {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Writing player status playback preferences");
|
||||
|
||||
SharedPreferences.Editor editor = PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext()).edit();
|
||||
PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
|
||||
int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus);
|
||||
|
||||
editor.putInt(
|
||||
PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus);
|
||||
|
||||
editor.commit();
|
||||
}
|
||||
@ -1101,6 +1155,28 @@ public class PlaybackService extends Service {
|
||||
}
|
||||
};
|
||||
|
||||
private BroadcastReceiver pauseResumeCurrentEpisodeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (StringUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent");
|
||||
mediaPlayer.resume();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private BroadcastReceiver pausePlayCurrentEpisodeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (StringUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent");
|
||||
mediaPlayer.pause(false, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static MediaType getCurrentMediaType() {
|
||||
return currentMediaType;
|
||||
}
|
||||
|
@ -169,7 +169,8 @@ public class PlaybackServiceMediaPlayer {
|
||||
|
||||
|
||||
if (media != null) {
|
||||
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
|
||||
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())
|
||||
&& playerStatus == PlayerStatus.PLAYING) {
|
||||
// episode is already playing -> ignore method call
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
|
||||
@ -179,6 +180,10 @@ public class PlaybackServiceMediaPlayer {
|
||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
|
||||
mediaPlayer.stop();
|
||||
}
|
||||
// set temporarily to pause in order to update list with current position
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +298,8 @@ public final class DBTasks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a specific Feed.
|
||||
* Refresh a specific Feed. The refresh may get canceled if the feed does not seem to be modified
|
||||
* and the last update was only few days ago.
|
||||
*
|
||||
* @param context Used for requesting the download.
|
||||
* @param feed The Feed object.
|
||||
@ -311,9 +312,9 @@ public final class DBTasks {
|
||||
private static void refreshFeed(Context context, Feed feed, boolean loadAllPages) throws DownloadRequestException {
|
||||
Feed f;
|
||||
if (feed.getPreferences() == null) {
|
||||
f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle());
|
||||
f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle());
|
||||
} else {
|
||||
f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle(),
|
||||
f = new Feed(feed.getDownload_url(), feed.getLastUpdate(), feed.getTitle(),
|
||||
feed.getPreferences().getUsername(), feed.getPreferences().getPassword());
|
||||
}
|
||||
f.setId(feed.getId());
|
||||
|
@ -7,7 +7,6 @@ import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.shredzone.flattr4j.model.Flattr;
|
||||
|
||||
import java.io.File;
|
||||
@ -35,6 +34,7 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||
@ -238,6 +238,26 @@ public class DBWriter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entire download log.
|
||||
*
|
||||
* @param context A context that is used for opening a database connection.
|
||||
*/
|
||||
public static Future<?> clearDownloadLog(final Context context) {
|
||||
return dbExec.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PodDBAdapter adapter = new PodDBAdapter(context);
|
||||
adapter.open();
|
||||
adapter.clearDownloadLog();
|
||||
adapter.close();
|
||||
EventDistributor.getInstance()
|
||||
.sendDownloadLogUpdateBroadcast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
|
||||
* its playback completion date is set to a non-null value. This method will set the playback completion date to the
|
||||
@ -386,7 +406,16 @@ public class DBWriter {
|
||||
context, itemIds[i]);
|
||||
|
||||
if (item != null) {
|
||||
queue.add(item);
|
||||
// add item to either front ot back of queue
|
||||
boolean addToFront = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(UserPreferences.PREF_QUEUE_ADD_TO_FRONT, false);
|
||||
|
||||
if(addToFront){
|
||||
queue.add(0, item);
|
||||
}else{
|
||||
queue.add(item);
|
||||
}
|
||||
|
||||
queueModified = true;
|
||||
if (!item.isRead()) {
|
||||
item.setRead(true);
|
||||
|
@ -91,57 +91,53 @@ public class DownloadRequester {
|
||||
}
|
||||
|
||||
private void download(Context context, FeedFile item, FeedFile container, File dest,
|
||||
boolean overwriteIfExists, String username, String password, boolean deleteOnFailure, Bundle arguments) {
|
||||
boolean overwriteIfExists, String username, String password,
|
||||
long ifModifiedSince, boolean deleteOnFailure, Bundle arguments) {
|
||||
final boolean partiallyDownloadedFileExists = item.getFile_url() != null;
|
||||
if (!isDownloadingFile(item)) {
|
||||
if (!isFilenameAvailable(dest.toString()) || (!partiallyDownloadedFileExists && dest.exists())) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Filename already used.");
|
||||
if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
|
||||
boolean result = dest.delete();
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Deleting file. Result: " + result);
|
||||
} else {
|
||||
// find different name
|
||||
File newDest = null;
|
||||
for (int i = 1; i < Integer.MAX_VALUE; i++) {
|
||||
String newName = FilenameUtils.getBaseName(dest
|
||||
.getName())
|
||||
+ "-"
|
||||
+ i
|
||||
+ FilenameUtils.EXTENSION_SEPARATOR
|
||||
+ FilenameUtils.getExtension(dest.getName());
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Testing filename " + newName);
|
||||
newDest = new File(dest.getParent(), newName);
|
||||
if (!newDest.exists()
|
||||
&& isFilenameAvailable(newDest.toString())) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "File doesn't exist yet. Using "
|
||||
+ newName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newDest != null) {
|
||||
dest = newDest;
|
||||
if (isDownloadingFile(item)) {
|
||||
Log.e(TAG, "URL " + item.getDownload_url()
|
||||
+ " is already being downloaded");
|
||||
return;
|
||||
}
|
||||
if (!isFilenameAvailable(dest.toString()) || (!partiallyDownloadedFileExists && dest.exists())) {
|
||||
Log.d(TAG, "Filename already used.");
|
||||
if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
|
||||
boolean result = dest.delete();
|
||||
Log.d(TAG, "Deleting file. Result: " + result);
|
||||
} else {
|
||||
// find different name
|
||||
File newDest = null;
|
||||
for (int i = 1; i < Integer.MAX_VALUE; i++) {
|
||||
String newName = FilenameUtils.getBaseName(dest
|
||||
.getName())
|
||||
+ "-"
|
||||
+ i
|
||||
+ FilenameUtils.EXTENSION_SEPARATOR
|
||||
+ FilenameUtils.getExtension(dest.getName());
|
||||
Log.d(TAG, "Testing filename " + newName);
|
||||
newDest = new File(dest.getParent(), newName);
|
||||
if (!newDest.exists()
|
||||
&& isFilenameAvailable(newDest.toString())) {
|
||||
Log.d(TAG, "File doesn't exist yet. Using " + newName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newDest != null) {
|
||||
dest = newDest;
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Requesting download of url " + item.getDownload_url());
|
||||
String baseUrl = (container != null) ? container.getDownload_url() : null;
|
||||
item.setDownload_url(URLChecker.prepareURL(item.getDownload_url(), baseUrl));
|
||||
|
||||
DownloadRequest request = new DownloadRequest(dest.toString(),
|
||||
URLChecker.prepareURL(item.getDownload_url()), item.getHumanReadableIdentifier(),
|
||||
item.getId(), item.getTypeAsInt(), username, password, deleteOnFailure, arguments);
|
||||
|
||||
download(context, request);
|
||||
} else {
|
||||
Log.e(TAG, "URL " + item.getDownload_url()
|
||||
+ " is already being downloaded");
|
||||
}
|
||||
Log.d(TAG, "Requesting download of url " + item.getDownload_url());
|
||||
String baseUrl = (container != null) ? container.getDownload_url() : null;
|
||||
item.setDownload_url(URLChecker.prepareURL(item.getDownload_url(), baseUrl));
|
||||
|
||||
DownloadRequest.Builder builder = new DownloadRequest.Builder(dest.toString(), item)
|
||||
.withAuthentication(username, password)
|
||||
.ifModifiedSince(ifModifiedSince)
|
||||
.deleteOnFailure(deleteOnFailure)
|
||||
.withArguments(arguments);
|
||||
DownloadRequest request = builder.build();
|
||||
download(context, request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,18 +159,26 @@ public class DownloadRequester {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a feed
|
||||
*
|
||||
* @param context The application's environment.
|
||||
* @param feed Feed to download
|
||||
* @param loadAllPages Set to true to download all pages
|
||||
*/
|
||||
public synchronized void downloadFeed(Context context, Feed feed, boolean loadAllPages)
|
||||
throws DownloadRequestException {
|
||||
if (feedFileValid(feed)) {
|
||||
String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null;
|
||||
String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null;
|
||||
long ifModifiedSince = feed.getLastUpdate().getTime();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
|
||||
args.putBoolean(REQUEST_ARG_LOAD_ALL_PAGES, loadAllPages);
|
||||
|
||||
download(context, feed, null, new File(getFeedfilePath(context),
|
||||
getFeedfileName(feed)), true, username, password, true, args);
|
||||
getFeedfileName(feed)), true, username, password, ifModifiedSince, true, args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +191,7 @@ public class DownloadRequester {
|
||||
if (feedFileValid(image)) {
|
||||
FeedFile container = (image.getOwner() instanceof FeedFile) ? (FeedFile) image.getOwner() : null;
|
||||
download(context, image, container, new File(getImagefilePath(context),
|
||||
getImagefileName(image)), false, null, null, false, null);
|
||||
getImagefileName(image)), false, null, null, 0, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +217,7 @@ public class DownloadRequester {
|
||||
getMediafilename(feedmedia));
|
||||
}
|
||||
download(context, feedmedia, feed,
|
||||
dest, false, username, password, false, null);
|
||||
dest, false, username, password, 0, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -889,6 +889,10 @@ public class PodDBAdapter {
|
||||
db.update(TABLE_NAME_FEED_MEDIA, values, null, null);
|
||||
}
|
||||
|
||||
public void clearDownloadLog() {
|
||||
db.delete(TABLE_NAME_DOWNLOAD_LOG, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Feeds from the Feed Table.
|
||||
*
|
||||
|
@ -1,14 +1,23 @@
|
||||
package de.danoeh.antennapod.core.syndication.handler;
|
||||
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.*;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import de.danoeh.antennapod.core.BuildConfig;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSContent;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSDublinCore;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSITunes;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSRSS20;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSSimpleChapters;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.Namespace;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.SyndElement;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.atom.NSAtom;
|
||||
|
||||
/** Superclass for all SAX Handlers which process Syndication formats */
|
||||
public class SyndHandler extends DefaultHandler {
|
||||
private static final String TAG = "SyndHandler";
|
||||
@ -100,7 +109,12 @@ public class SyndHandler extends DefaultHandler {
|
||||
state.namespaces.put(uri, new NSMedia());
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Recognized media namespace");
|
||||
}
|
||||
} else if (uri.equals(NSDublinCore.NSURI)
|
||||
&& prefix.equals(NSDublinCore.NSTAG)) {
|
||||
state.namespaces.put(uri, new NSDublinCore());
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, "Recognized DublinCore namespace");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
package de.danoeh.antennapod.core.syndication.namespace;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
|
||||
import de.danoeh.antennapod.core.syndication.util.SyndDateUtils;
|
||||
|
||||
public class NSDublinCore extends Namespace {
|
||||
private static final String TAG = "NSDublinCore";
|
||||
public static final String NSTAG = "dc";
|
||||
public static final String NSURI = "http://purl.org/dc/elements/1.1/";
|
||||
|
||||
private static final String ITEM = "item";
|
||||
private static final String DATE = "date";
|
||||
|
||||
@Override
|
||||
public SyndElement handleElementStart(String localName, HandlerState state,
|
||||
Attributes attributes) {
|
||||
return new SyndElement(localName, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleElementEnd(String localName, HandlerState state) {
|
||||
if(state.getTagstack().size() >= 2
|
||||
&& state.getContentBuf() != null) {
|
||||
String content = state.getContentBuf().toString();
|
||||
SyndElement topElement = state.getTagstack().peek();
|
||||
String top = topElement.getName();
|
||||
SyndElement secondElement = state.getSecondTag();
|
||||
String second = secondElement.getName();
|
||||
if (top.equals(DATE) && second.equals(ITEM)) {
|
||||
state.getCurrentItem().setPubDate(
|
||||
SyndDateUtils.parseISO8601Date(content));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,8 @@ public class SyndDateUtils {
|
||||
*/
|
||||
public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
|
||||
public static final String ISO8601_SHORT = "yyyy-MM-dd";
|
||||
|
||||
private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
protected SimpleDateFormat initialValue() {
|
||||
@ -44,6 +46,14 @@ public class SyndDateUtils {
|
||||
|
||||
};
|
||||
|
||||
private static ThreadLocal<SimpleDateFormat> ISO8601ShortFormatter = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
protected SimpleDateFormat initialValue() {
|
||||
return new SimpleDateFormat(ISO8601_SHORT, Locale.US);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public static Date parseRFC822Date(String date) {
|
||||
Date result = null;
|
||||
if (date.contains("PDT")) {
|
||||
@ -123,6 +133,23 @@ public class SyndDateUtils {
|
||||
|
||||
}
|
||||
|
||||
public static Date parseISO8601Date(String date) {
|
||||
if(date.length() > ISO8601_SHORT.length()) {
|
||||
return parseRFC3339Date(date);
|
||||
}
|
||||
Date result = null;
|
||||
if(date.length() == "YYYYMMDD".length()) {
|
||||
date = date.substring(0, 4) + "-" + date.substring(4, 6) + "-" + date.substring(6,8);
|
||||
}
|
||||
SimpleDateFormat format = ISO8601ShortFormatter.get();
|
||||
try {
|
||||
result = format.parse(date);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string of the form [HH:]MM:SS[.mmm] and converts it to
|
||||
* milliseconds.
|
||||
|
@ -0,0 +1,121 @@
|
||||
package de.danoeh.antennapod.core.util.gui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nineoldandroids.animation.Animator;
|
||||
import com.nineoldandroids.animation.AnimatorListenerAdapter;
|
||||
import com.nineoldandroids.view.ViewHelper;
|
||||
import com.nineoldandroids.view.ViewPropertyAnimator;
|
||||
|
||||
import de.danoeh.antennapod.core.R;
|
||||
|
||||
import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
|
||||
|
||||
public class UndoBarController {
|
||||
private View mBarView;
|
||||
private TextView mMessageView;
|
||||
private ViewPropertyAnimator mBarAnimator;
|
||||
private Handler mHideHandler = new Handler();
|
||||
|
||||
private UndoListener mUndoListener;
|
||||
|
||||
// State objects
|
||||
private Parcelable mUndoToken;
|
||||
private CharSequence mUndoMessage;
|
||||
|
||||
public interface UndoListener {
|
||||
void onUndo(Parcelable token);
|
||||
}
|
||||
|
||||
public UndoBarController(View undoBarView, UndoListener undoListener) {
|
||||
mBarView = undoBarView;
|
||||
mBarAnimator = animate(mBarView);
|
||||
mUndoListener = undoListener;
|
||||
|
||||
mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message);
|
||||
mBarView.findViewById(R.id.undobar_button)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
hideUndoBar(false);
|
||||
mUndoListener.onUndo(mUndoToken);
|
||||
}
|
||||
});
|
||||
|
||||
hideUndoBar(true);
|
||||
}
|
||||
|
||||
public void showUndoBar(boolean immediate, CharSequence message, Parcelable undoToken) {
|
||||
mUndoToken = undoToken;
|
||||
mUndoMessage = message;
|
||||
mMessageView.setText(mUndoMessage);
|
||||
|
||||
mHideHandler.removeCallbacks(mHideRunnable);
|
||||
mHideHandler.postDelayed(mHideRunnable,
|
||||
mBarView.getResources().getInteger(R.integer.undobar_hide_delay));
|
||||
|
||||
mBarView.setVisibility(View.VISIBLE);
|
||||
if (immediate) {
|
||||
ViewHelper.setAlpha(mBarView, 1);
|
||||
} else {
|
||||
mBarAnimator.cancel();
|
||||
mBarAnimator
|
||||
.alpha(1)
|
||||
.setDuration(
|
||||
mBarView.getResources()
|
||||
.getInteger(android.R.integer.config_shortAnimTime))
|
||||
.setListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideUndoBar(boolean immediate) {
|
||||
mHideHandler.removeCallbacks(mHideRunnable);
|
||||
if (immediate) {
|
||||
mBarView.setVisibility(View.GONE);
|
||||
ViewHelper.setAlpha(mBarView, 0);
|
||||
mUndoMessage = null;
|
||||
} else {
|
||||
mBarAnimator.cancel();
|
||||
mBarAnimator
|
||||
.alpha(0)
|
||||
.setDuration(mBarView.getResources()
|
||||
.getInteger(android.R.integer.config_shortAnimTime))
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mBarView.setVisibility(View.GONE);
|
||||
mUndoMessage = null;
|
||||
mUndoToken = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putCharSequence("undo_message", mUndoMessage);
|
||||
outState.putParcelable("undo_token", mUndoToken);
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
mUndoMessage = savedInstanceState.getCharSequence("undo_message");
|
||||
mUndoToken = savedInstanceState.getParcelable("undo_token");
|
||||
|
||||
if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) {
|
||||
showUndoBar(true, mUndoMessage, mUndoToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable mHideRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
hideUndoBar(false);
|
||||
}
|
||||
};
|
||||
}
|
BIN
core/src/main/res/drawable-hdpi/ic_drag_handle.9.png
Normal file
After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 220 B |
BIN
core/src/main/res/drawable-hdpi/ic_drag_handle_dark.9.png
Normal file
After Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 204 B |
BIN
core/src/main/res/drawable-hdpi/ic_feed_grey600_24dp.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
core/src/main/res/drawable-hdpi/ic_feed_white_24dp.png
Executable file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/src/main/res/drawable-mdpi/ic_drag_handle.9.png
Normal file
After Width: | Height: | Size: 217 B |
Before Width: | Height: | Size: 175 B |
BIN
core/src/main/res/drawable-mdpi/ic_drag_handle_dark.9.png
Normal file
After Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 159 B |
BIN
core/src/main/res/drawable-mdpi/ic_feed_grey600_24dp.png
Executable file
After Width: | Height: | Size: 1018 B |
BIN
core/src/main/res/drawable-mdpi/ic_feed_white_24dp.png
Executable file
After Width: | Height: | Size: 875 B |
BIN
core/src/main/res/drawable-xhdpi/ic_drag_handle.9.png
Normal file
After Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 234 B |
BIN
core/src/main/res/drawable-xhdpi/ic_drag_handle_dark.9.png
Normal file
After Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 216 B |
BIN
core/src/main/res/drawable-xhdpi/ic_feed_grey600_24dp.png
Executable file
After Width: | Height: | Size: 2.2 KiB |