Merge pull request #735 from AntennaPod/version_1.1

Version 1.1
This commit is contained in:
Tom Hennen 2015-04-12 18:11:00 -04:00
commit 6a1a9afa6b
148 changed files with 7285 additions and 601 deletions

1
.gitignore vendored
View File

@ -42,3 +42,4 @@ libs
*.DS_Store
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
gradle.properties
*.keystore

6
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -10,6 +10,11 @@ hzulla
andrewgaul
peschmae0
TomHennen
mfietz
volhol
eerden
twiceyuan
rharriso
Translations:

View File

@ -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 +0,0 @@
Subproject commit 80011c50e444e1c7d5e13b57bdb127b524a1ff92

View File

@ -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(...);
}

View File

@ -1 +0,0 @@
include ':app:dslv:library'

View File

@ -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() {

View File

@ -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());
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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"/>

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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));
}

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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()) {

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -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);

View File

@ -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(),

View File

@ -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

View File

@ -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});

View File

@ -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";

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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();
}

View File

@ -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) {

View File

@ -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

View File

@ -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());

View File

@ -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());

View File

@ -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;
}

View File

@ -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));
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -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) {

View File

@ -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>

View File

@ -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>

View File

@ -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

View 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>

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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

View File

@ -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">

View File

@ -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"

View File

@ -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'
}

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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"));

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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.
*

View File

@ -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");
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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.

View File

@ -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);
}
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Some files were not shown because too many files have changed in this diff Show More