Merge branch 'master' of github.com:rharriso/AntennaPod

This commit is contained in:
Ross Harrison 2015-02-08 11:49:44 -06:00
commit 67aca21858
79 changed files with 2519 additions and 1565 deletions

View File

@ -9,6 +9,7 @@ wseemann
hzulla
andrewgaul
peschmae0
TomHennen
Translations:

View File

@ -4,12 +4,10 @@ repositories {
mavenCentral()
}
dependencies {
compile 'com.android.support:support-v4:21.0.2'
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'org.apache.commons:commons-lang3:3.3.2'
compile('org.shredzone.flattr4j:flattr4j-core:2.11') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
compile('org.shredzone.flattr4j:flattr4j-core:2.12') {
exclude group: 'org.json', module: 'json'
}
compile 'commons-io:commons-io:2.4'
@ -17,15 +15,15 @@ dependencies {
compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
compile 'org.jsoup:jsoup:1.7.3'
compile 'com.squareup.picasso:picasso:2.4.0'
compile 'com.squareup.okhttp:okhttp:2.1.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.1.0'
compile 'com.squareup.okio:okio:1.0.1'
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 project(':core')
}
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
buildToolsVersion "21.1.2"
defaultConfig {
minSdkVersion 10

View File

@ -41,6 +41,7 @@ public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity>
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();
}

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="43"
android:versionName="0.9.9.6">
android:versionCode="44"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@ -304,6 +304,13 @@
</intent-filter>
</receiver>
<receiver android:name=".receiver.PowerConnectionReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>
<receiver android:name=".receiver.SPAReceiver">
<intent-filter>
<action android:name="de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE"/>

View File

@ -41,7 +41,7 @@
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
<p>AntennaPod, Version 0.9.9.6</p>
<p>AntennaPod, Version 1.0</p>
<p>Copyright © 2014 Daniel Oeh</p>

View File

@ -11,6 +11,7 @@ import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@ -21,7 +22,6 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.TextView;
@ -49,6 +49,7 @@ import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import de.danoeh.antennapod.preferences.PreferenceController;
/**
* Activity for playing audio files.
@ -59,6 +60,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
private static final int POS_NONE = -1;
final String TAG = "AudioplayerActivity";
private static final String PREFS = "AudioPlayerActivityPreferences";
@ -68,6 +70,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
private DrawerLayout drawerLayout;
private NavListAdapter navAdapter;
private ListView navList;
private View navDrawer;
private ActionBarDrawerToggle drawerToggle;
private Fragment[] detachedFragments;
@ -78,6 +81,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
private Fragment currentlyShownFragment;
private int currentlyShownPosition = -1;
private int lastShownPosition = POS_NONE;
/**
* Used if onResume was called without loadMediaInfo.
*/
@ -85,8 +89,8 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
private TextView txtvTitle;
private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
private ImageButton butNavChaptersShownotes;
private ImageButton butShowCover;
private void resetFragmentView() {
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
@ -138,10 +142,14 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
}
@Override
protected void chooseTheme() {
setTheme(UserPreferences.getNoTitleTheme());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowTitleEnabled(false);
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
}
@ -320,24 +328,32 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
chapterFragment = new ListFragment() {
@Override
public void onListItemClick(ListView l, View v,
int position, long id) {
super.onListItemClick(l, v, position, id);
Chapter chapter = (Chapter) this
.getListAdapter().getItem(position);
controller.seekToChapter(chapter);
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// add padding
final ListView lv = getListView();
lv.setClipToPadding(false);
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding);
lv.setPadding(0, vertPadding, 0, vertPadding);
}
};
chapterFragment.setListAdapter(new ChapterListAdapter(
AudioplayerActivity.this, 0, media
.getChapters(), media
.getChapters(), media, new ChapterListAdapter.Callback() {
@Override
public void onPlayChapterButtonClicked(int position) {
Chapter chapter = (Chapter)
chapterFragment.getListAdapter().getItem(position);
controller.seekToChapter(chapter);
}
}
));
}
currentlyShownFragment = chapterFragment;
break;
}
if (currentlyShownFragment != null) {
lastShownPosition = currentlyShownPosition;
currentlyShownPosition = pos;
if (detachedFragments[pos] != null) {
if (BuildConfig.DEBUG)
@ -355,78 +371,70 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
}
}
/**
* Switches to the fragment that was displayed before the current one or the description fragment
* if no fragment was previously displayed.
*/
public void switchToLastFragment() {
if (lastShownPosition != POS_NONE) {
switchToFragment(lastShownPosition);
} else {
switchToFragment(POS_DESCR);
}
}
private void updateNavButtonDrawable() {
final int[] buttonTexts = new int[]{R.string.show_shownotes_label,
R.string.show_chapters_label, R.string.show_cover_label};
R.string.show_chapters_label};
final TypedArray drawables = obtainStyledAttributes(new int[]{
R.attr.navigation_shownotes, R.attr.navigation_chapters});
final Playable media = controller.getMedia();
if (butNavLeft != null && butNavRight != null && media != null) {
butNavRight.setTag(R.id.imageloader_key, null);
butNavLeft.setTag(R.id.imageloader_key, null);
if (butNavChaptersShownotes != null && butShowCover != null && media != null) {
butNavChaptersShownotes.setTag(R.id.imageloader_key, null);
setNavButtonVisibility();
switch (currentlyShownPosition) {
case POS_COVER:
butNavLeft.setScaleType(ScaleType.CENTER);
butNavLeft.setImageDrawable(drawables.getDrawable(0));
butNavLeft.setContentDescription(getString(buttonTexts[0]));
butNavRight.setImageDrawable(drawables.getDrawable(1));
butNavRight.setContentDescription(getString(buttonTexts[1]));
butShowCover.setVisibility(View.GONE);
if (lastShownPosition == POS_CHAPTERS) {
butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(1));
butNavChaptersShownotes.setContentDescription(getString(buttonTexts[1]));
} else {
butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(0));
butNavChaptersShownotes.setContentDescription(getString(buttonTexts[0]));
}
break;
case POS_DESCR:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
@Override
public void run() {
Picasso.with(AudioplayerActivity.this)
.load(media.getImageUri())
.fit()
.into(butNavLeft);
}
});
butNavLeft.setContentDescription(getString(buttonTexts[2]));
butNavRight.setImageDrawable(drawables.getDrawable(1));
butNavRight.setContentDescription(getString(buttonTexts[1]));
butShowCover.setVisibility(View.VISIBLE);
butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(1));
butNavChaptersShownotes.setContentDescription(getString(buttonTexts[1]));
break;
case POS_CHAPTERS:
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
butNavLeft.post(new Runnable() {
@Override
public void run() {
Picasso.with(AudioplayerActivity.this)
.load(media.getImageUri())
.fit()
.into(butNavLeft);
}
});
butNavLeft.setContentDescription(getString(buttonTexts[2]));
butNavRight.setImageDrawable(drawables.getDrawable(0));
butNavRight.setContentDescription(getString(buttonTexts[0]));
butShowCover.setVisibility(View.VISIBLE);
butNavChaptersShownotes.setImageDrawable(drawables.getDrawable(0));
butNavChaptersShownotes.setContentDescription(getString(buttonTexts[0]));
break;
}
}
drawables.recycle();
}
@Override
protected void setupGUI() {
super.setupGUI();
resetFragmentView();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navList = (ListView) findViewById(R.id.nav_list);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
navDrawer = findViewById(R.id.nav_layout);
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavChaptersShownotes = (ImageButton) findViewById(R.id.butNavChaptersShownotes);
butShowCover = (ImageButton) findViewById(R.id.butCover);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
CharSequence currentTitle = getSupportActionBar().getTitle();
@ -463,38 +471,39 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
intent.putExtra(MainActivity.EXTRA_NAV_INDEX, relPos);
startActivity(intent);
}
drawerLayout.closeDrawer(navList);
drawerLayout.closeDrawer(navDrawer);
}
});
drawerToggle.syncState();
butNavLeft.setOnClickListener(new OnClickListener() {
findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (currentlyShownFragment == null
|| currentlyShownPosition == POS_DESCR) {
switchToFragment(POS_COVER);
} else if (currentlyShownPosition == POS_COVER) {
switchToFragment(POS_DESCR);
} else if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_COVER);
}
drawerLayout.closeDrawer(navDrawer);
startActivity(new Intent(AudioplayerActivity.this, PreferenceController.getPreferenceActivity()));
}
});
butNavRight.setOnClickListener(new OnClickListener() {
butNavChaptersShownotes.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (currentlyShownPosition == POS_CHAPTERS) {
switchToFragment(POS_DESCR);
} else {
} else if (currentlyShownPosition == POS_DESCR) {
switchToFragment(POS_CHAPTERS);
} else if (currentlyShownPosition == POS_COVER) {
switchToLastFragment();
}
}
});
butShowCover.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switchToFragment(POS_COVER);
}
});
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
@Override
@ -539,6 +548,22 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
});
}
private void setNavButtonVisibility() {
if (butNavChaptersShownotes != null) {
if (controller != null) {
Playable media = controller.getMedia();
if (media != null) {
if (media.getChapters() != null || currentlyShownPosition == POS_COVER) {
butNavChaptersShownotes.setVisibility(View.VISIBLE);
return;
}
}
}
butNavChaptersShownotes.setVisibility(View.GONE);
}
}
@Override
protected void onPlaybackSpeedChange() {
super.onPlaybackSpeedChange();
@ -567,12 +592,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
return false;
}
txtvTitle.setText(media.getEpisodeTitle());
if (media.getChapters() != null) {
butNavRight.setVisibility(View.VISIBLE);
} else {
butNavRight.setVisibility(View.INVISIBLE);
}
getSupportActionBar().setTitle("");
Picasso.with(this)
.load(media.getImageUri())
.fit()
.into(butShowCover);
setNavButtonVisibility();
if (currentlyShownPosition == -1) {
if (!restoreFromPreferences()) {
@ -632,7 +658,7 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc
@Override
public boolean isDrawerOpen() {
return drawerLayout != null && navList != null && drawerLayout.isDrawerOpen(navList);
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
}
@Override

View File

@ -14,9 +14,8 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
@ -62,15 +61,22 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
public static final String EXTRA_NAV_TYPE = "nav_type";
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
public static final String SAVE_BACKSTACK_COUNT = "backstackCount";
public static final String SAVE_SELECTED_NAV_INDEX = "selectedNavIndex";
public static final String SAVE_TITLE = "title";
public static final int POS_NEW = 0,
POS_QUEUE = 1,
POS_DOWNLOADS = 2,
POS_HISTORY = 3,
POS_ADD = 4;
private Toolbar toolbar;
private ExternalPlayerFragment externalPlayerFragment;
private DrawerLayout drawerLayout;
private View navDrawer;
private ListView navList;
private NavListAdapter navAdapter;
@ -82,17 +88,22 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
setTheme(UserPreferences.getNoTitleTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
setContentView(R.layout.main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setElevation(3.0f);
drawerTitle = currentTitle = getTitle();
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navList = (ListView) findViewById(R.id.nav_list);
navDrawer = findViewById(R.id.nav_layout);
Log.i(TAG, "");
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
@Override
public void onDrawerOpened(View drawerView) {
@ -111,8 +122,21 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
};
if (savedInstanceState != null) {
int backstackCount = savedInstanceState.getInt(SAVE_BACKSTACK_COUNT, 0);
drawerToggle.setDrawerIndicatorEnabled(backstackCount == 0);
}
drawerLayout.setDrawerListener(drawerToggle);
FragmentManager fm = getSupportFragmentManager();
final FragmentManager fm = getSupportFragmentManager();
fm.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0);
}
});
FragmentTransaction transaction = fm.beginTransaction();
@ -120,7 +144,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
if (mainFragment != null) {
transaction.replace(R.id.main_view, mainFragment);
} else {
loadFragment(NavListAdapter.VIEW_TYPE_NAV, POS_NEW, null);
loadFragment(NavListAdapter.VIEW_TYPE_NAV, POS_QUEUE, null);
}
externalPlayerFragment = new ExternalPlayerFragment();
@ -134,6 +158,14 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
navList.setAdapter(navAdapter);
navList.setOnItemClickListener(navListClickListener);
findViewById(R.id.nav_settings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.closeDrawer(navDrawer);
startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity()));
}
});
checkFirstLaunch();
}
@ -143,7 +175,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
drawerLayout.openDrawer(navList);
drawerLayout.openDrawer(navDrawer);
}
}, 1500);
@ -158,7 +190,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
}
public boolean isDrawerOpen() {
return drawerLayout != null && navList != null && drawerLayout.isDrawerOpen(navList);
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
}
public List<Feed> getFeeds() {
@ -241,6 +273,14 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
.commit();
}
public void dismissChildFragment() {
getSupportFragmentManager().popBackStack();
}
public Toolbar getToolbar() {
return toolbar;
}
private AdapterView.OnItemClickListener navListClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@ -251,7 +291,7 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
selectedNavListIndex = position;
navAdapter.notifyDataSetChanged();
}
drawerLayout.closeDrawer(navList);
drawerLayout.closeDrawer(navDrawer);
}
};
@ -260,11 +300,11 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
if (savedInstanceState != null) {
currentTitle = savedInstanceState.getString("title");
if (!drawerLayout.isDrawerOpen(navList)) {
currentTitle = savedInstanceState.getString(SAVE_TITLE);
if (!drawerLayout.isDrawerOpen(navDrawer)) {
getSupportActionBar().setTitle(currentTitle);
}
selectedNavListIndex = savedInstanceState.getInt("selectedNavIndex");
selectedNavListIndex = savedInstanceState.getInt(SAVE_SELECTED_NAV_INDEX);
}
}
@ -277,8 +317,9 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("title", getSupportActionBar().getTitle().toString());
outState.putInt("selectedNavIndex", selectedNavListIndex);
outState.putString(SAVE_TITLE, getSupportActionBar().getTitle().toString());
outState.putInt(SAVE_SELECTED_NAV_INDEX, selectedNavListIndex);
outState.putInt(SAVE_BACKSTACK_COUNT, getSupportFragmentManager().getBackStackEntryCount());
}
@ -312,29 +353,16 @@ public class MainActivity extends ActionBarActivity implements NavDrawerActivity
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch (item.getItemId()) {
case R.id.show_preferences:
startActivity(new Intent(this, PreferenceController.getPreferenceActivity()));
return true;
default:
return super.onOptionsItemSelected(item);
} else if (item.getItemId() == android.R.id.home) {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
dismissChildFragment();
}
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
private DBReader.NavDrawerData navDrawerData;
private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;

View File

@ -174,7 +174,6 @@ public abstract class MediaplayerActivity extends ActionBarActivity
orientation = getResources().getConfiguration().orientation;
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override

View File

@ -72,15 +72,6 @@ public class PreferenceActivity extends ActionBarActivity {
preferenceController.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
// The default back button behavior has to be overwritten because changing the theme clears the back stack
Intent destIntent = new Intent(this, MainActivity.class);
destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destIntent);
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@ -91,9 +82,6 @@ public class PreferenceActivity extends ActionBarActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent destIntent = new Intent(this, MainActivity.class);
destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destIntent);
finish();
return true;
default:

View File

@ -84,13 +84,4 @@ public class PreferenceActivityGingerbread extends android.preference.Preference
);
return false;
}
@Override
public void onBackPressed() {
// The default back button behavior has to be overwritten because changing the theme clears the back stack
Intent destIntent = new Intent(this, MainActivity.class);
destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destIntent);
finish();
}
}

View File

@ -111,6 +111,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
@Override
protected void setupGUI() {
super.setupGUI();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
videoview = (AspectRatioVideoView) findViewById(R.id.videoview);
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);

View File

@ -14,6 +14,7 @@ import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Chapter;
@ -31,16 +32,18 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
private Playable media;
private int defaultTextColor;
private final Callback callback;
public ChapterListAdapter(Context context, int textViewResourceId,
List<Chapter> objects, Playable media) {
List<Chapter> objects, Playable media, Callback callback) {
super(context, textViewResourceId, objects);
this.chapters = objects;
this.media = media;
this.callback = callback;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
public View getView(final int position, View convertView, ViewGroup parent) {
Holder holder;
Chapter sc = getItem(position);
@ -56,6 +59,7 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
defaultTextColor = holder.title.getTextColors().getDefaultColor();
holder.start = (TextView) convertView.findViewById(R.id.txtvStart);
holder.link = (TextView) convertView.findViewById(R.id.txtvLink);
holder.butPlayChapter = (ImageButton) convertView.findViewById(R.id.butPlayChapter);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
@ -122,6 +126,14 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
}
});
holder.butPlayChapter.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callback != null) {
callback.onPlayChapterButtonClicked(position);
}
}
});
Chapter current = ChapterUtils.getCurrentChapter(media);
if (current != null) {
if (current == sc) {
@ -144,6 +156,7 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
TextView title;
TextView start;
TextView link;
ImageButton butPlayChapter;
}
@Override
@ -177,4 +190,8 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
return super.getItem(position);
}
public static interface Callback {
public void onPlayChapterButtonClicked(int position);
}
}

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.config;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.core.ClientConfig;
/**
@ -8,12 +9,13 @@ import de.danoeh.antennapod.core.ClientConfig;
public class ClientConfigurator {
static {
ClientConfig.USER_AGENT = "AntennaPod/0.9.9.6";
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl();
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
ClientConfig.storageCallbacks = new StorageCallbacksImpl();
ClientConfig.flattrCallbacks = new FlattrCallbacksImpl();
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
}
}

View File

@ -0,0 +1,20 @@
package de.danoeh.antennapod.config;
import de.danoeh.antennapod.core.DBTasksCallbacks;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
public class DBTasksCallbacksImpl implements DBTasksCallbacks {
@Override
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
return new APDownloadAlgorithm();
}
@Override
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
return new APCleanupAlgorithm();
}
}

View File

@ -13,7 +13,7 @@ public class StorageCallbacksImpl implements StorageCallbacks {
@Override
public int getDatabaseVersion() {
return 13;
return 14;
}
@Override
@ -105,9 +105,24 @@ public class StorageCallbacksImpl implements StorageCallbacks {
}
if (oldVersion <= 12) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0");
+ " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_NEXT_PAGE_LINK + " TEXT");
}
if (oldVersion <= 13) {
// remove duplicate rows in "Chapters" table that were created because of a bug.
db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
"(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
PodDBAdapter.KEY_ID,
PodDBAdapter.KEY_ID,
PodDBAdapter.KEY_ID,
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
PodDBAdapter.KEY_TITLE,
PodDBAdapter.KEY_START,
PodDBAdapter.KEY_FEEDITEM,
PodDBAdapter.KEY_LINK,
PodDBAdapter.KEY_CHAPTER_TYPE));
}
}
}

View File

@ -1,445 +0,0 @@
package de.danoeh.antennapod.dialog;
import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.PopupMenu;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.Validate;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.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.QueueAccess;
import de.danoeh.antennapod.core.util.ShownotesProvider;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
/**
* Shows information about a specific FeedItem and provides actions like playing, downloading, etc.
*/
public class FeedItemDialog extends Dialog {
private static final String TAG = "FeedItemDialog";
private FeedItem item;
private QueueAccess queue;
private ViewGroup contentContainer;
private View header;
private TextView txtvTitle;
private WebView webvDescription;
private ImageButton butAction1;
private ImageButton butAction2;
private ImageButton butMore;
private PopupMenu popupMenu;
public static FeedItemDialog newInstance(Context context, FeedItemDialogSavedInstance savedInstance) {
Validate.notNull(savedInstance);
FeedItemDialog dialog = newInstance(context, savedInstance.item, savedInstance.queueAccess);
if (savedInstance.isShowing) {
dialog.show();
}
return dialog;
}
public static FeedItemDialog newInstance(Context context, FeedItem item, QueueAccess queue) {
if (useDarkThemeWorkAround()) {
return new FeedItemDialog(context, R.style.Theme_AntennaPod_Dark, item, queue);
} else {
return new FeedItemDialog(context, item, queue);
}
}
public FeedItemDialog(Context context, int theme, FeedItem item, QueueAccess queue) {
super(context, theme);
Validate.notNull(item);
Validate.notNull(queue);
this.item = item;
this.queue = queue;
}
private FeedItemDialog(Context context, FeedItem item, QueueAccess queue) {
this(context, 0, item, queue);
}
/**
* Returns true if the dialog should use a dark theme. This has to be done on Gingerbread devices
* because dialogs are only available in a dark theme.
*/
private static boolean useDarkThemeWorkAround() {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1
&& UserPreferences.getTheme() != R.style.Theme_AntennaPod_Dark;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.feeditem_dialog);
contentContainer = (ViewGroup) findViewById(R.id.contentContainer);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
header = findViewById(R.id.header);
webvDescription = (WebView) findViewById(R.id.webview);
butAction1 = (ImageButton) findViewById(R.id.butAction1);
butAction2 = (ImageButton) findViewById(R.id.butAction2);
butMore = (ImageButton) findViewById(R.id.butMoreActions);
popupMenu = new PopupMenu(getContext(), butMore);
webvDescription.setWebViewClient(new WebViewClient());
if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448
txtvTitle.setEllipsize(TextUtils.TruncateAt.END);
}
txtvTitle.setText(item.getTitle());
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
if (Build.VERSION.SDK_INT >= 11
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webvDescription.setBackgroundColor(getContext().getResources().getColor(
R.color.black));
}
webvDescription.getSettings().setUseWideViewPort(false);
webvDescription.getSettings().setLayoutAlgorithm(
WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
webvDescription.getSettings().setLoadWithOverviewMode(true);
webvDescription.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
return false;
}
return true;
}
});
loadDescriptionWebview(item);
butAction1.setOnClickListener(new View.OnClickListener() {
DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getContext());
@Override
public void onClick(View v) {
actionButtonCallback.onActionButtonPressed(item);
FeedMedia media = item.getMedia();
if (media != null && media.isDownloaded()) {
// playback was started, dialog should close itself
dismiss();
}
}
}
);
butAction2.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
if (item.hasMedia()) {
FeedMedia media = item.getMedia();
if (!media.isDownloaded()) {
DBTasks.playMedia(getContext(), media, true, true, true);
dismiss();
} else {
DBWriter.deleteFeedMediaOfItem(getContext(), media.getId());
}
} else if (item.getLink() != null) {
Uri uri = Uri.parse(item.getLink());
getContext().startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
}
}
);
butMore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popupMenu.getMenu().clear();
popupMenu.inflate(R.menu.feeditem_dialog);
if (item.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue);
} else {
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue,
R.id.mark_read_item, R.id.visit_website_item);
}
popupMenu.show();
}
}
);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
try {
return FeedItemMenuHandler.onMenuItemClicked(getContext(), menuItem.getItemId(), item);
} catch (DownloadRequestException e) {
e.printStackTrace();
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
return true;
}
}
}
);
updateMenuAppearance();
}
@Override
public void dismiss() {
super.dismiss();
if (contentContainer != null && webvDescription != null) {
contentContainer.removeAllViews();
webvDescription.destroy();
}
}
private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
MenuItem item = popupMenu.getMenu().findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
public void updateMenuAppearance() {
if (item == null || queue == null) {
Log.w(TAG, "UpdateMenuAppearance called while item or queue was null");
return;
}
FeedMedia media = item.getMedia();
if (media == null) {
TypedArray drawables = getContext().obtainStyledAttributes(new int[]{R.attr.navigation_accept,
R.attr.location_web_site});
if (!item.isRead()) {
butAction1.setImageDrawable(drawables.getDrawable(0));
butAction1.setContentDescription(getContext().getString(R.string.mark_read_label));
butAction1.setVisibility(View.VISIBLE);
} else {
butAction1.setVisibility(View.INVISIBLE);
}
if (item.getLink() != null) {
butAction2.setImageDrawable(drawables.getDrawable(1));
butAction2.setContentDescription(getContext().getString(R.string.visit_website_label));
} else {
butAction2.setEnabled(false);
}
drawables.recycle();
} else {
boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media);
TypedArray drawables = getContext().obtainStyledAttributes(new int[]{R.attr.av_play,
R.attr.av_download, R.attr.action_stream, R.attr.content_discard, R.attr.navigation_cancel});
if (!media.isDownloaded()) {
butAction2.setImageDrawable(drawables.getDrawable(2));
butAction2.setContentDescription(getContext().getString(R.string.stream_label));
} else {
butAction2.setImageDrawable(drawables.getDrawable(3));
butAction2.setContentDescription(getContext().getString(R.string.remove_episode_lable));
}
if (isDownloading) {
butAction1.setImageDrawable(drawables.getDrawable(4));
butAction1.setContentDescription(getContext().getString(R.string.cancel_download_label));
} else if (media.isDownloaded()) {
butAction1.setImageDrawable(drawables.getDrawable(0));
butAction1.setContentDescription(getContext().getString(R.string.play_label));
} else {
butAction1.setImageDrawable(drawables.getDrawable(1));
butAction1.setContentDescription(getContext().getString(R.string.download_label));
}
drawables.recycle();
}
}
private void loadDescriptionWebview(final ShownotesProvider shownotesProvider) {
AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
String data;
private String applyWebviewStyle(String textColor, String data) {
final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> @font-face { font-family: 'Roboto-Light'; src: url('file:///android_asset/Roboto-Light.ttf'); } * { color: %s; font-family: roboto-Light; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
final int pageMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 8, getContext().getResources()
.getDisplayMetrics()
);
return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
pageMargin, pageMargin, pageMargin, data);
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// /webvDescription.loadData(url, "text/html", "utf-8");
if (FeedItemDialog.this.isShowing() && webvDescription != null) {
webvDescription.loadDataWithBaseURL(null, data, "text/html",
"utf-8", "about:blank");
if (BuildConfig.DEBUG)
Log.d(TAG, "Webview loaded");
}
}
@Override
protected Void doInBackground(Void... params) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Loading Webview");
try {
Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
final String shownotes = shownotesLoadTask.call();
data = StringEscapeUtils.unescapeHtml4(shownotes);
TypedArray res = getContext()
.getTheme()
.obtainStyledAttributes(
new int[]{android.R.attr.textColorPrimary});
int colorResource;
if (useDarkThemeWorkAround()) {
colorResource = getContext().getResources().getColor(R.color.black);
} else {
colorResource = res.getColor(0, 0);
}
String colorString = String.format("#%06X",
0xFFFFFF & colorResource);
Log.i(TAG, "text color: " + colorString);
res.recycle();
data = applyWebviewStyle(colorString, data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
};
loadTask.execute();
}
/**
* Convenience method that calls setQueue() and setItemFromCollection() with
* the given arguments.
*
* @return true if one of the calls to setItemFromCollection returned true,
* false otherwise.
*/
public boolean updateContent(QueueAccess queue, List<FeedItem>... collections) {
setQueue(queue);
boolean setItemFromCollectionResult = false;
if (collections != null) {
for (List<FeedItem> list : collections) {
setItemFromCollectionResult |= setItemFromCollection(list);
}
}
if (isShowing()) {
updateMenuAppearance();
}
return setItemFromCollectionResult;
}
public void setItem(FeedItem item) {
Validate.notNull(item);
this.item = item;
}
/**
* Finds the FeedItem of this dialog in a collection and updates its state from that
* collection.
*
* @return true if the FeedItem was found, false otherwise.
*/
public boolean setItemFromCollection(Collection<FeedItem> items) {
for (FeedItem item : items) {
if (item.getId() == this.item.getId()) {
setItem(item);
return true;
}
}
return false;
}
public void setQueue(QueueAccess queue) {
Validate.notNull(queue);
this.queue = queue;
}
public FeedItem getItem() {
return item;
}
public QueueAccess getQueue() {
return queue;
}
public FeedItemDialogSavedInstance save() {
return new FeedItemDialogSavedInstance(item, queue, isShowing());
}
/**
* Used to save the FeedItemDialog's state across configuration changes
*/
public static class FeedItemDialogSavedInstance {
final FeedItem item;
final QueueAccess queueAccess;
final boolean isShowing;
private FeedItemDialogSavedInstance(FeedItem item, QueueAccess queueAccess, boolean isShowing) {
this.item = item;
this.queueAccess = queueAccess;
this.isShowing = isShowing;
}
}
}

View File

@ -8,17 +8,17 @@ import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DownloadedEpisodesListAdapter;
import de.danoeh.antennapod.dialog.FeedItemDialog;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.QueueAccess;
import java.util.List;
/**
* Displays all running downloads and provides a button to delete them
*/
@ -36,8 +36,6 @@ public class CompletedDownloadsFragment extends ListFragment {
private boolean viewCreated = false;
private boolean itemsLoaded = false;
private FeedItemDialog feedItemDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -69,7 +67,6 @@ public class CompletedDownloadsFragment extends ListFragment {
super.onDestroyView();
listAdapter = null;
viewCreated = false;
feedItemDialog = null;
stopItemLoader();
}
@ -102,8 +99,7 @@ public class CompletedDownloadsFragment extends ListFragment {
super.onListItemClick(l, v, position, id);
FeedItem item = listAdapter.getItem(position - l.getHeaderViewsCount());
if (item != null) {
feedItemDialog = FeedItemDialog.newInstance(getActivity(), item, queue);
feedItemDialog.show();
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
@ -115,12 +111,6 @@ public class CompletedDownloadsFragment extends ListFragment {
}
setListShown(true);
listAdapter.notifyDataSetChanged();
if (feedItemDialog != null) {
boolean res = feedItemDialog.updateContent(queue, items);
if (!res && feedItemDialog.isShowing()) {
feedItemDialog.dismiss();
}
}
}
private DownloadedEpisodesListAdapter.ItemAccess itemAccess = new DownloadedEpisodesListAdapter.ItemAccess() {
@ -143,11 +133,7 @@ public class CompletedDownloadsFragment extends ListFragment {
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EventDistributor.DOWNLOAD_QUEUED) != 0) {
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateMenuAppearance();
}
} else if ((arg & EVENTS) != 0) {
if ((arg & EVENTS) != 0) {
startItemLoader();
}
}

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.fragment;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -13,6 +14,7 @@ import com.squareup.picasso.Picasso;
import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity;
import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment;
import de.danoeh.antennapod.core.util.playback.Playable;
@ -57,6 +59,15 @@ public class CoverFragment extends Fragment implements
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.cover_fragment, container, false);
imgvCover = (ImageView) root.findViewById(R.id.imgvCover);
imgvCover.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Activity activity = getActivity();
if (activity != null && activity instanceof AudioplayerActivity) {
((AudioplayerActivity)activity).switchToLastFragment();
}
}
});
viewCreated = true;
return root;
}

View File

@ -1,19 +1,16 @@
package de.danoeh.antennapod.fragment;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
/**
* Shows the CompletedDownloadsFragment and the RunningDownloadsFragment
@ -27,7 +24,6 @@ public class DownloadsFragment extends Fragment {
public static final int POS_LOG = 2;
private ViewPager pager;
private MainActivity activity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -36,42 +32,6 @@ public class DownloadsFragment extends Fragment {
pager = (ViewPager) root.findViewById(R.id.pager);
DownloadsPagerAdapter pagerAdapter = new DownloadsPagerAdapter(getChildFragmentManager(), getResources());
pager.setAdapter(pagerAdapter);
final ActionBar actionBar = activity.getMainActivtyActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.TabListener tabListener = new ActionBar.TabListener() {
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
pager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
};
actionBar.removeAllTabs();
actionBar.addTab(actionBar.newTab()
.setText(R.string.downloads_running_label)
.setTabListener(tabListener));
actionBar.addTab(actionBar.newTab()
.setText(R.string.downloads_completed_label)
.setTabListener(tabListener));
actionBar.addTab(actionBar.newTab()
.setText(R.string.downloads_log_label)
.setTabListener(tabListener));
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
actionBar.setSelectedNavigationItem(position);
}
});
return root;
}
@ -84,24 +44,8 @@ public class DownloadsFragment extends Fragment {
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.activity = (MainActivity) activity;
}
@Override
public void onDetach() {
super.onDetach();
activity.getMainActivtyActionBar().removeAllTabs();
activity.getMainActivtyActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
public class DownloadsPagerAdapter extends FragmentPagerAdapter {
Resources resources;
public DownloadsPagerAdapter(FragmentManager fm, Resources resources) {

View File

@ -12,7 +12,6 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;

View File

@ -0,0 +1,442 @@
package de.danoeh.antennapod.fragment;
import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.util.Pair;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.squareup.picasso.Picasso;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.core.asynctask.DBTaskLoader;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
/**
* Displays information about a FeedItem and actions.
*/
public class ItemFragment extends Fragment implements LoaderManager.LoaderCallbacks<Pair<FeedItem, QueueAccess>> {
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED |
EventDistributor.DOWNLOAD_QUEUED |
EventDistributor.QUEUE_UPDATE |
EventDistributor.UNREAD_ITEMS_UPDATE;
private static final String ARG_FEEDITEM = "feeditem";
/**
* Creates a new instance of an ItemFragment
*
* @param feeditem The ID of the FeedItem that should be displayed.
* @return The ItemFragment instance
*/
public static ItemFragment newInstance(long feeditem) {
ItemFragment fragment = new ItemFragment();
Bundle args = new Bundle();
args.putLong(ARG_FEEDITEM, feeditem);
fragment.setArguments(args);
return fragment;
}
private boolean itemsLoaded = false;
private long itemID;
private FeedItem item;
private QueueAccess queue;
private String webviewData;
private DownloadObserver downloadObserver;
private List<Downloader> downloaderList;
private ViewGroup root;
private View header;
private WebView webvDescription;
private TextView txtvTitle;
private ImageView imgvCover;
private ProgressBar progbarDownload;
private ProgressBar progbarLoading;
private Button butAction1;
private Button butAction2;
private ImageButton butMore;
private PopupMenu popupMenu;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(false);
itemID = getArguments().getLong(ARG_FEEDITEM, -1);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
Toolbar toolbar = ((MainActivity) getActivity()).getToolbar();
toolbar.addView(header);
}
@Override
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
if (downloadObserver != null) {
downloadObserver.setActivity(getActivity());
downloadObserver.onResume();
}
if (itemsLoaded) {
onFragmentLoaded();
}
}
@Override
public void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(contentUpdate);
}
private void resetViewState() {
if (downloadObserver != null) {
downloadObserver.onPause();
}
Toolbar toolbar = ((MainActivity) getActivity()).getToolbar();
toolbar.removeView(header);
}
@Override
public void onDestroyView() {
super.onDestroyView();
resetViewState();
if (webvDescription != null && root != null) {
root.removeView(webvDescription);
webvDescription.destroy();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
((MainActivity) getActivity()).getSupportActionBar().setTitle("");
Toolbar toolbar = ((MainActivity) getActivity()).getToolbar();
View layout = inflater.inflate(R.layout.feeditem_fragment, container, false);
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);
if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448
txtvTitle.setEllipsize(TextUtils.TruncateAt.END);
}
webvDescription = (WebView) layout.findViewById(R.id.webvDescription);
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
if (Build.VERSION.SDK_INT >= 11
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webvDescription.setBackgroundColor(getResources().getColor(
R.color.black));
}
webvDescription.getSettings().setUseWideViewPort(false);
webvDescription.getSettings().setLayoutAlgorithm(
WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
webvDescription.getSettings().setLoadWithOverviewMode(true);
webvDescription.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
return true;
}
return true;
}
});
imgvCover = (ImageView) header.findViewById(R.id.imgvCover);
progbarDownload = (ProgressBar) header.findViewById(R.id.progbarDownload);
progbarLoading = (ProgressBar) layout.findViewById(R.id.progbarLoading);
butAction1 = (Button) header.findViewById(R.id.butAction1);
butAction2 = (Button) header.findViewById(R.id.butAction2);
butMore = (ImageButton) header.findViewById(R.id.butMoreActions);
popupMenu = new PopupMenu(getActivity(), butMore);
butAction1.setOnClickListener(new View.OnClickListener() {
DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getActivity());
@Override
public void onClick(View v) {
if (item == null) {
return;
}
actionButtonCallback.onActionButtonPressed(item);
FeedMedia media = item.getMedia();
if (media != null && media.isDownloaded()) {
// playback was started, dialog should close itself
((MainActivity) getActivity()).dismissChildFragment();
}
}
}
);
butAction2.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
if (item == null) {
return;
}
if (item.hasMedia()) {
FeedMedia media = item.getMedia();
if (!media.isDownloaded()) {
DBTasks.playMedia(getActivity(), media, true, true, true);
((MainActivity) getActivity()).dismissChildFragment();
} else {
DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId());
}
} else if (item.getLink() != null) {
Uri uri = Uri.parse(item.getLink());
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
}
}
);
butMore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (item == null) {
return;
}
popupMenu.getMenu().clear();
popupMenu.inflate(R.menu.feeditem_dialog);
if (item.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue);
} else {
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue,
R.id.mark_read_item, R.id.visit_website_item);
}
popupMenu.show();
}
}
);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
try {
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item);
} catch (DownloadRequestException e) {
e.printStackTrace();
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
return true;
}
}
}
);
return layout;
}
private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
MenuItem item = popupMenu.getMenu().findItem(id);
if (item != null) {
item.setVisible(visible);
}
}
};
private void onFragmentLoaded() {
progbarLoading.setVisibility(View.GONE);
if (webviewData != null) {
webvDescription.loadDataWithBaseURL(null, webviewData, "text/html",
"utf-8", "about:blank");
}
updateAppearance();
downloadObserver = new DownloadObserver(getActivity(), new Handler(), downloadObserverCallback);
downloadObserver.onResume();
}
private void updateAppearance() {
txtvTitle.setText(item.getTitle());
Picasso.with(getActivity()).load(item.getImageUri())
.fit()
.into(imgvCover);
progbarDownload.setVisibility(View.GONE);
if (item.hasMedia() && downloaderList != null) {
for (Downloader downloader : downloaderList) {
if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
&& downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) {
progbarDownload.setVisibility(View.VISIBLE);
progbarDownload.setProgress(downloader.getDownloadRequest().getProgressPercent());
}
}
}
FeedMedia media = item.getMedia();
if (media == null) {
TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.navigation_accept,
R.attr.location_web_site});
if (!item.isRead()) {
butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(0), null, null, null);
butAction1.setText(getActivity().getString(R.string.mark_read_label));
butAction1.setVisibility(View.VISIBLE);
} else {
butAction1.setVisibility(View.INVISIBLE);
}
if (item.getLink() != null) {
butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(1), null, null, null);
butAction2.setText(getActivity().getString(R.string.visit_website_label));
} else {
butAction2.setEnabled(false);
}
drawables.recycle();
} else {
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});
if (!media.isDownloaded()) {
butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(2), null, null, null);
butAction2.setText(getActivity().getString(R.string.stream_label));
} else {
butAction2.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(3), null, null, null);
butAction2.setText(getActivity().getString(R.string.remove_episode_lable));
}
if (isDownloading) {
butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(4), null, null, null);
butAction1.setText(getActivity().getString(R.string.cancel_download_label));
} else if (media.isDownloaded()) {
butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(0), null, null, null);
butAction1.setText(getActivity().getString(R.string.play_label));
} else {
butAction1.setCompoundDrawablesWithIntrinsicBounds(drawables.getDrawable(1), null, null, null);
butAction1.setText(getActivity().getString(R.string.download_label));
}
drawables.recycle();
}
}
@Override
public Loader<Pair<FeedItem, QueueAccess>> onCreateLoader(int id, Bundle args) {
return new DBTaskLoader<Pair<FeedItem, QueueAccess>>(getActivity()) {
@Override
public Pair<FeedItem, QueueAccess> loadInBackground() {
FeedItem data1 = DBReader.getFeedItem(getContext(), itemID);
if (data1 != null) {
Timeline t = new Timeline(getActivity(), data1);
webviewData = t.processShownotes(false);
}
QueueAccess data2 = QueueAccess.IDListAccess(DBReader.getQueueIDList(getContext()));
return Pair.create(data1, data2);
}
};
}
@Override
public void onLoadFinished(Loader<Pair<FeedItem, QueueAccess>> loader, Pair<FeedItem, QueueAccess> data) {
if (data != null) {
item = data.first;
queue = data.second;
if (!itemsLoaded) {
itemsLoaded = true;
onFragmentLoaded();
} else {
updateAppearance();
}
}
}
@Override
public void onLoaderReset(Loader<Pair<FeedItem, QueueAccess>> loader) {
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EVENTS) != 0) {
getLoaderManager().restartLoader(0, null, ItemFragment.this);
}
}
};
private final DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
@Override
public void onContentChanged() {
if (itemsLoaded && getActivity() != null) {
updateAppearance();
}
}
@Override
public void onDownloadDataAvailable(List<Downloader> downloaderList) {
ItemFragment.this.downloaderList = downloaderList;
if (itemsLoaded && getActivity() != null) {
updateAppearance();
}
}
};
}

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ListFragment;
@ -18,6 +19,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
@ -35,6 +37,7 @@ import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.FeedItemlistAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.core.asynctask.FeedRemover;
import de.danoeh.antennapod.core.asynctask.PicassoProvider;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.EventDistributor;
@ -49,7 +52,6 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
import de.danoeh.antennapod.dialog.FeedItemDialog;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
@ -81,9 +83,6 @@ public class ItemlistFragment extends ListFragment {
private DownloadObserver downloadObserver;
private List<Downloader> downloaderList;
private FeedItemDialog feedItemDialog;
private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance;
private MoreContentListFooterUtil listFooter;
private boolean isUpdatingFeed;
@ -156,13 +155,10 @@ public class ItemlistFragment extends ListFragment {
private void resetViewState() {
adapter = null;
viewsCreated = false;
listFooter = null;
if (downloadObserver != null) {
downloadObserver.onPause();
}
if (feedItemDialog != null) {
feedItemDialogSavedInstance = feedItemDialog.save();
}
feedItemDialog = null;
}
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() {
@ -258,6 +254,15 @@ public class ItemlistFragment extends ListFragment {
}
@Override
public void setListAdapter(ListAdapter adapter) {
// This workaround prevents the ListFragment from setting a list adapter when its state is restored.
// This is only necessary on API 10 because addFooterView throws an internal exception in this case.
if (Build.VERSION.SDK_INT > 10 || insideOnFragmentLoaded) {
super.setListAdapter(adapter);
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -272,8 +277,9 @@ public class ItemlistFragment extends ListFragment {
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
FeedItem selection = adapter.getItem(position - l.getHeaderViewsCount());
feedItemDialog = FeedItemDialog.newInstance(getActivity(), selection, queue);
feedItemDialog.show();
if (selection != null) {
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(selection.getId()));
}
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@ -303,9 +309,12 @@ public class ItemlistFragment extends ListFragment {
}
private boolean insideOnFragmentLoaded = false;
private void onFragmentLoaded() {
insideOnFragmentLoaded = true;
if (adapter == null) {
getListView().setAdapter(null);
setListAdapter(null);
setupHeaderView();
setupFooterView();
adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(getActivity()), false);
@ -316,17 +325,14 @@ public class ItemlistFragment extends ListFragment {
setListShown(true);
adapter.notifyDataSetChanged();
if (feedItemDialog != null) {
feedItemDialog.updateContent(queue, feed.getItems());
} else if (feedItemDialogSavedInstance != null) {
feedItemDialog = FeedItemDialog.newInstance(getActivity(), feedItemDialogSavedInstance);
}
getActivity().supportInvalidateOptionsMenu();
if (feed != null && feed.getNextPageLink() == null && listFooter != null) {
getListView().removeFooterView(listFooter.getRoot());
}
insideOnFragmentLoaded = false;
}
private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
@ -335,9 +341,6 @@ public class ItemlistFragment extends ListFragment {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateMenuAppearance();
}
}
@Override
@ -362,12 +365,21 @@ public class ItemlistFragment extends ListFragment {
TextView txtvTitle = (TextView) header.findViewById(R.id.txtvTitle);
TextView txtvAuthor = (TextView) header.findViewById(R.id.txtvAuthor);
ImageView imgvBackground = (ImageView) header.findViewById(R.id.imgvBackground);
ImageView imgvCover = (ImageView) header.findViewById(R.id.imgvCover);
ImageButton butShowInfo = (ImageButton) header.findViewById(R.id.butShowInfo);
txtvTitle.setText(feed.getTitle());
txtvAuthor.setText(feed.getAuthor());
Picasso.with(getActivity())
.load(feed.getImageUri())
.placeholder(R.color.image_readability_tint)
.error(R.color.image_readability_tint)
.transform(PicassoProvider.blurTransformation)
.resize(PicassoProvider.BLUR_IMAGE_SIZE, PicassoProvider.BLUR_IMAGE_SIZE)
.into(imgvBackground);
Picasso.with(getActivity())
.load(feed.getImageUri())
.fit()

View File

@ -7,21 +7,28 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.*;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.NewEpisodesListAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.dialog.FeedItemDialog;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@ -37,9 +44,6 @@ import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Shows unread or recently published episodes
*/
@ -73,9 +77,6 @@ public class NewEpisodesFragment extends Fragment {
private DownloadObserver downloadObserver = null;
private FeedItemDialog feedItemDialog;
private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance;
private boolean isUpdatingFeeds;
@Override
@ -133,10 +134,6 @@ public class NewEpisodesFragment extends Fragment {
if (downloadObserver != null) {
downloadObserver.onPause();
}
if (feedItemDialog != null) {
feedItemDialogSavedInstance = feedItemDialog.save();
}
feedItemDialog = null;
}
@ -226,8 +223,7 @@ public class NewEpisodesFragment extends Fragment {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount());
if (item != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, queueAccess);
feedItemDialog.show();
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
@ -257,11 +253,6 @@ public class NewEpisodesFragment extends Fragment {
downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback);
downloadObserver.onResume();
}
if (feedItemDialog != null) {
feedItemDialog.updateContent(queueAccess, unreadItems, recentItems);
} else if (feedItemDialogSavedInstance != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance);
}
listAdapter.notifyDataSetChanged();
getActivity().supportInvalidateOptionsMenu();
updateShowOnlyEpisodesListViewState();
@ -273,9 +264,6 @@ public class NewEpisodesFragment extends Fragment {
if (listAdapter != null) {
listAdapter.notifyDataSetChanged();
}
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateMenuAppearance();
}
}
@Override

View File

@ -13,11 +13,15 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.DefaultActionButtonCallback;
import de.danoeh.antennapod.adapter.FeedItemlistAdapter;
import de.danoeh.antennapod.core.asynctask.DownloadObserver;
import de.danoeh.antennapod.dialog.FeedItemDialog;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
@ -28,9 +32,6 @@ import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class PlaybackHistoryFragment extends ListFragment {
private static final String TAG = "PlaybackHistoryFragment";
@ -46,9 +47,6 @@ public class PlaybackHistoryFragment extends ListFragment {
private DownloadObserver downloadObserver;
private List<Downloader> downloaderList;
private FeedItemDialog feedItemDialog;
private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -103,10 +101,6 @@ public class PlaybackHistoryFragment extends ListFragment {
if (downloadObserver != null) {
downloadObserver.onPause();
}
if (feedItemDialog != null) {
feedItemDialogSavedInstance = feedItemDialog.save();
}
feedItemDialog = null;
}
@Override
@ -130,8 +124,7 @@ public class PlaybackHistoryFragment extends ListFragment {
super.onListItemClick(l, v, position, id);
FeedItem item = adapter.getItem(position - l.getHeaderViewsCount());
if (item != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, queue);
feedItemDialog.show();
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
@ -158,7 +151,7 @@ public class PlaybackHistoryFragment extends ListFragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!super.onOptionsItemSelected(item)) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.clear_history_item:
DBWriter.clearPlaybackHistory(getActivity());
return true;
@ -190,11 +183,6 @@ public class PlaybackHistoryFragment extends ListFragment {
}
setListShown(true);
adapter.notifyDataSetChanged();
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateContent(queue, playbackHistory);
} else if (feedItemDialogSavedInstance != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance);
}
getActivity().supportInvalidateOptionsMenu();
}
@ -204,9 +192,6 @@ public class PlaybackHistoryFragment extends ListFragment {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateMenuAppearance();
}
}
@Override

View File

@ -29,14 +29,12 @@ 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.dialog.FeedItemDialog;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
@ -64,9 +62,6 @@ public class QueueFragment extends Fragment {
private DownloadObserver downloadObserver = null;
private FeedItemDialog feedItemDialog;
private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance;
/**
* Download observer updates won't result in an upate of the list adapter if this is true.
*/
@ -122,10 +117,6 @@ public class QueueFragment extends Fragment {
if (downloadObserver != null) {
downloadObserver.onPause();
}
if (feedItemDialog != null) {
feedItemDialogSavedInstance = feedItemDialog.save();
}
feedItemDialog = null;
}
@Override
@ -215,8 +206,7 @@ public class QueueFragment extends Fragment {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
FeedItem item = (FeedItem) listAdapter.getItem(position - listView.getHeaderViewsCount());
if (item != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, QueueAccess.ItemListAccess(queue));
feedItemDialog.show();
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
});
@ -268,11 +258,6 @@ public class QueueFragment extends Fragment {
downloadObserver.onResume();
}
listAdapter.notifyDataSetChanged();
if (feedItemDialog != null) {
feedItemDialog.updateContent(QueueAccess.ItemListAccess(queue), queue);
} else if (feedItemDialogSavedInstance != null) {
feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance);
}
}
private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() {
@ -281,9 +266,6 @@ public class QueueFragment extends Fragment {
if (listAdapter != null && !blockDownloadObserverUpdate) {
listAdapter.notifyDataSetChanged();
}
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.updateMenuAppearance();
}
}
@Override

View File

@ -12,19 +12,23 @@ 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.activity.MainActivity;
import de.danoeh.antennapod.adapter.SearchlistAdapter;
import de.danoeh.antennapod.dialog.FeedItemDialog;
import de.danoeh.antennapod.core.feed.*;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedComponent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.SearchResult;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
import java.util.List;
/**
* Performs a search operation on all feeds or one specific feed and displays the search result.
*/
@ -42,9 +46,6 @@ public class SearchFragment extends ListFragment {
private QueueAccess queue;
private FeedItemDialog feedItemDialog;
private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance;
/**
* Create a new SearchFragment that searches all feeds.
*/
@ -99,10 +100,6 @@ public class SearchFragment extends ListFragment {
super.onDestroyView();
searchAdapter = null;
viewCreated = false;
if (feedItemDialog != null) {
feedItemDialogSavedInstance = feedItemDialog.save();
}
feedItemDialog = null;
}
@Override
@ -128,11 +125,11 @@ public class SearchFragment extends ListFragment {
SearchResult result = (SearchResult) l.getAdapter().getItem(position);
FeedComponent comp = result.getComponent();
if (comp.getClass() == Feed.class) {
((MainActivity)getActivity()).loadFeedFragment(comp.getId());
((MainActivity) getActivity()).loadFeedFragment(comp.getId());
} else {
if (comp.getClass() == FeedItem.class) {
feedItemDialog = FeedItemDialog.newInstance(getActivity(), (FeedItem) comp, queue);
feedItemDialog.show();
FeedItem item = (FeedItem) comp;
((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(item.getId()));
}
}
}
@ -167,9 +164,6 @@ public class SearchFragment extends ListFragment {
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & (EventDistributor.DOWNLOAD_QUEUED)) != 0 && feedItemDialog != null) {
feedItemDialog.updateMenuAppearance();
}
if ((arg & (EventDistributor.UNREAD_ITEMS_UPDATE
| EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.QUEUE_UPDATE)) != 0) {
@ -185,18 +179,6 @@ public class SearchFragment extends ListFragment {
}
searchAdapter.notifyDataSetChanged();
setListShown(true);
if (feedItemDialog != null && feedItemDialog.isShowing()) {
feedItemDialog.setQueue(queue);
for (SearchResult result : searchResults) {
FeedComponent comp = result.getComponent();
if (comp.getClass() == FeedItem.class && ((FeedItem) comp).getId() == feedItemDialog.getItem().getId()) {
feedItemDialog.setItem((FeedItem) comp);
}
}
feedItemDialog.updateMenuAppearance();
} else if (feedItemDialogSavedInstance != null) {
feedItemDialog = FeedItemDialog.newInstance(getActivity(), feedItemDialogSavedInstance);
}
}
private final SearchlistAdapter.ItemAccess itemAccess = new SearchlistAdapter.ItemAccess() {

View File

@ -1,85 +1,32 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
/**
* Main navigation hub for gpodder.net podcast directory
*/
public class GpodnetMainFragment extends Fragment {
private ViewPager pager;
private MainActivity activity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.pager_fragment, container, false);
pager = (ViewPager) root.findViewById(R.id.pager);
ViewPager pager = (ViewPager) root.findViewById(R.id.pager);
GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(getChildFragmentManager(), getResources());
pager.setAdapter(pagerAdapter);
final ActionBar actionBar = activity.getMainActivtyActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.TabListener tabListener = new ActionBar.TabListener() {
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
pager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
};
actionBar.removeAllTabs();
actionBar.addTab(actionBar.newTab()
.setText(R.string.gpodnet_taglist_header)
.setTabListener(tabListener));
actionBar.addTab(actionBar.newTab()
.setText(R.string.gpodnet_toplist_header)
.setTabListener(tabListener));
actionBar.setTitle(R.string.gpodnet_main_label);
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
actionBar.setSelectedNavigationItem(position);
}
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
activity.getMainActivtyActionBar().removeAllTabs();
activity.getMainActivtyActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.activity = (MainActivity) activity;
}
public class GpodnetPagerAdapter extends FragmentPagerAdapter {

View File

@ -10,7 +10,7 @@ import de.danoeh.antennapod.core.R;
/**
* Utilities for menu items
*/
public class MenuItemUtils {
public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuItemUtils {
public static MenuItem addSearchItem(Menu menu, SearchView searchView) {
MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label);
@ -28,29 +28,4 @@ public class MenuItemUtils {
public static boolean isActivityDrawerOpen(NavDrawerActivity activity) {
return activity != null && activity.isDrawerOpen();
}
/**
* Changes the appearance of a MenuItem depending on whether the given UpdateRefreshMenuItemChecker
* is refreshing or not. If it returns true, the menu item will be replaced by an indeterminate progress
* bar, otherwise nothing will happen.
*
* @param menu The menu that the MenuItem belongs to
* @param resId The id of the MenuItem
* @param checker Is used for checking whether to show the progress indicator or not.
* @return The returned value of the UpdateRefreshMenuItemChecker's isRefreshing() method.
*/
public static boolean updateRefreshMenuItem(Menu menu, int resId, UpdateRefreshMenuItemChecker checker) {
// expand actionview if feeds are being downloaded, collapse otherwise
if (checker.isRefreshing()) {
MenuItem refreshItem = menu.findItem(resId);
MenuItemCompat.setActionView(refreshItem, de.danoeh.antennapod.R.layout.refresh_action_view);
return true;
} else {
return false;
}
}
public static interface UpdateRefreshMenuItemChecker {
public boolean isRefreshing();
}
}

View File

@ -24,6 +24,7 @@ import de.danoeh.antennapod.BuildConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AboutActivity;
import de.danoeh.antennapod.activity.DirectoryChooserActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.activity.PreferenceActivityGingerbread;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
@ -176,7 +177,7 @@ public class PreferenceController {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
Intent i = activity.getIntent();
Intent i = new Intent(activity, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
activity.finish();
@ -192,6 +193,7 @@ public class PreferenceController {
if (newValue instanceof Boolean) {
ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled((Boolean) newValue);
setSelectedNetworksEnabled((Boolean) newValue && UserPreferences.isEnableAutodownloadWifiFilter());
ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled((Boolean) newValue);
}
return true;
}
@ -373,6 +375,8 @@ public class PreferenceController {
setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
&& UserPreferences.isEnableAutodownloadWifiFilter());
ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY)
.setEnabled(UserPreferences.isEnableAutodownload());
}
private void setEpisodeCacheSizeText(int cacheSize) {

View File

@ -0,0 +1,45 @@
package de.danoeh.antennapod.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.util.Log;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DownloadRequester;
// modified from http://developer.android.com/training/monitoring-device-state/battery-monitoring.html
// and ConnectivityActionReceiver.java
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;
if (isCharging) {
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
// downloading now. They shouldn't mind.
// autodownloadUndownloadedItems will make sure we're on the right wifi networks,
// etc... so we don't have to worry about it.
DBTasks.autodownloadUndownloadedItems(context);
} else {
// if we're not supposed to be auto-downloading when we're not charging, stop it
if (!UserPreferences.isEnableAutodownloadOnBattery()) {
Log.d(TAG, "not charging anymore, canceling auto-download");
DownloadRequester.getInstance().cancelAllDownloads(context);
} else {
Log.d(TAG, "not charging anymore, but the user allows auto-download " +
"when on battery so we'll keep going");
}
}
}
}

View File

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/contentView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5">
</FrameLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:background="?attr/non_transparent_background"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/navBar"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_alignParentTop="true">
<ImageButton
android:id="@+id/butNavLeft"
android:contentDescription="@string/show_shownotes_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="?attr/selectableItemBackground"
android:padding="4dp"/>
<ImageButton
android:id="@+id/butNavRight"
android:contentDescription="@string/show_chapters_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:padding="4dp"/>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/butNavRight"
android:layout_toRightOf="@id/butNavLeft"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"
android:textStyle="bold"/>
<TextView
android:id="@+id/txtvFeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/txtvTitle"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/butNavRight"
android:layout_toRightOf="@id/butNavLeft"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="1"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small"/>
</RelativeLayout>
<View
android:id="@+id/navBarDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/navBar"
android:background="@color/bright_blue"/>
<RelativeLayout
android:id="@+id/player_control"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:background="?attr/overlay_background">
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_pause"/>
<ImageButton
android:id="@+id/butRev"
android:contentDescription="@string/rewind_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_rew_big"/>
<ImageButton
android:id="@+id/butFF"
android:contentDescription="@string/fast_forward_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_ff_big"/>
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/playtime_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/player_control"
android:layout_alignParentLeft="true"
android:background="?attr/overlay_drawable">
<TextView
android:id="@+id/txtvPosition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro"/>
<TextView
android:id="@+id/txtvLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro"/>
<SeekBar
android:id="@+id/sbPosition"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:layout_toLeftOf="@id/txtvLength"
android:layout_toRightOf="@id/txtvPosition"
android:max="500"/>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
<include layout="@layout/nav_list"/>
</android.support.v4.widget.DrawerLayout>

View File

@ -1,173 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/non_transparent_background"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/navBar"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_alignParentTop="true">
<ImageButton
android:id="@+id/butNavLeft"
android:contentDescription="@string/show_shownotes_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="?attr/selectableItemBackground"
android:padding="4dp"/>
<ImageButton
android:id="@+id/butNavRight"
android:contentDescription="@string/show_chapters_label"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:padding="4dp"/>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/butNavRight"
android:layout_toRightOf="@id/butNavLeft"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-light"
/>
</RelativeLayout>
<View
android:id="@+id/navBarDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/navBar"
android:background="@color/bright_blue"/>
<RelativeLayout
android:id="@+id/player_control"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:background="?attr/overlay_background">
<ImageButton
android:id="@+id/butPlay"
android:contentDescription="@string/pause_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_pause"/>
<ImageButton
android:id="@+id/butRev"
android:contentDescription="@string/rewind_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_rew_big"/>
<ImageButton
android:id="@+id/butFF"
android:contentDescription="@string/fast_forward_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_ff_big"/>
<Button
android:id="@+id/butPlaybackSpeed"
android:contentDescription="@string/set_playback_speed_label"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/playtime_layout"
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/player_control"
android:layout_alignParentLeft="true"
android:background="?attr/overlay_drawable">
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize">
<TextView
android:id="@+id/txtvPosition"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<TextView
android:id="@+id/txtvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="left"
android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<ImageButton
android:id="@+id/butCover"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/show_cover_label"
android:gravity="right" />
</LinearLayout>
</android.support.v7.widget.Toolbar>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/non_transparent_background"
android:foreground="?android:windowContentOverlay"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/player_control"
android:layout_width="match_parent"
android:layout_height="@dimen/audioplayer_playercontrols_length"
android:layout_alignParentBottom="true"
android:background="?attr/overlay_background">
<ImageButton
android:id="@+id/butPlay"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/pause_label"
android:src="?attr/av_pause" />
<ImageButton
android:id="@+id/butRev"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/rewind_label"
android:src="?attr/av_rew_big" />
<ImageButton
android:id="@+id/butFF"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/fast_forward_label"
android:src="?attr/av_ff_big" />
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/set_playback_speed_label"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone" />
<ImageButton
android:id="@+id/butNavChaptersShownotes"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butRev"
android:background="?attr/selectableItemBackground"
android:scaleType="centerInside" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/playtime_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/player_control"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:fontFamily="sans-serif-light"
android:textSize="@dimen/text_size_micro"/>
android:background="?attr/overlay_drawable">
<TextView
android:id="@+id/txtvLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
<TextView
android:id="@+id/txtvPosition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" />
<TextView
android:id="@+id/txtvLength"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" />
<SeekBar
android:id="@+id/sbPosition"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:layout_toLeftOf="@id/txtvLength"
android:layout_toRightOf="@id/txtvPosition"
android:max="500" />
</RelativeLayout>
<FrameLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_above="@id/playtime_layout"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary"
android:fontFamily="sans-serif-light"
android:textSize="@dimen/text_size_micro"/>
android:foreground="?android:windowContentOverlay" />
<SeekBar
android:id="@+id/sbPosition"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:layout_toLeftOf="@id/txtvLength"
android:layout_toRightOf="@id/txtvPosition"
android:max="500"/>
</RelativeLayout>
<FrameLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_above="@id/playtime_layout"
android:layout_below="@id/navBarDivider">
</FrameLayout>
</RelativeLayout>
</LinearLayout>
<include layout="@layout/nav_list"/>
<include layout="@layout/nav_list" />
</android.support.v4.widget.DrawerLayout>

View File

@ -11,9 +11,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
android:scaleType="centerInside" />
android:scaleType="centerCrop" />
</RelativeLayout>

View File

@ -1,73 +0,0 @@
<?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:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtvTitle"
style="@style/AntennaPod.Dialog.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
android:ellipsize="none"
android:maxLines="5" />
<View
android:id="@+id/title_divider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_below="@id/txtvTitle"
android:background="@color/bright_blue" />
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_divider"
android:orientation="horizontal">
<ImageButton
android:id="@+id/butAction1"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/butAction2"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/butMoreActions"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/butAction_label"
android:src="?attr/ic_action_overflow" />
</LinearLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_below="@id/header"
android:background="@color/bright_blue" />
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:layout_below="@id/divider" />
</RelativeLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webvDescription"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:windowContentOverlay" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progbarLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?attr/colorPrimary"
android:gravity="center_horizontal"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/imgvCover"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp"
android:contentDescription="@string/cover_label"
android:gravity="center_vertical" />
<ImageButton
android:id="@+id/butMoreActions"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/butAction_label"
android:paddingTop="4dp"
android:src="?attr/ic_action_overflow" />
<TextView
android:id="@+id/txtvTitle"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:layout_toLeftOf="@id/butMoreActions"
android:layout_toRightOf="@id/imgvCover"
android:maxLines="5" />
</RelativeLayout>
<ProgressBar
android:id="@+id/progbarDownload"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginRight="8dp"
android:orientation="horizontal">
<Button
android:id="@+id/butAction1"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:paddingTop="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
<Button
android:id="@+id/butAction2"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:paddingTop="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
</LinearLayout>
</LinearLayout>

View File

@ -6,6 +6,12 @@
android:layout_height="@dimen/feeditemlist_header_height"
tools:context="de.danoeh.antennapod.activity.MainActivity">
<ImageView
android:id="@+id/imgvBackground"
style="@style/BigBlurryBackground"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/imgvCover"
android:layout_width="@dimen/thumbnail_length_onlinefeedview"
@ -29,7 +35,7 @@
android:layout_marginTop="8dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/show_info_label"
android:src="?attr/action_about" />
android:src="@drawable/ic_info_white_24dp" />
<TextView
android:id="@+id/txtvTitle"
@ -43,7 +49,10 @@
android:layout_toLeftOf="@id/butShowInfo"
android:layout_toRightOf="@id/imgvCover"
android:ellipsize="end"
android:maxLines="2" />
android:maxLines="2"
android:shadowColor="@color/black"
android:shadowRadius="3"
android:textColor="@color/white" />
<TextView
android:id="@+id/txtvAuthor"
@ -56,7 +65,9 @@
android:layout_toRightOf="@id/imgvCover"
android:ellipsize="end"
android:lines="1"
android:textColor="?android:attr/textColorSecondary"
android:shadowColor="@color/black"
android:shadowRadius="3"
android:textColor="@color/white"
android:textSize="@dimen/text_size_small" />

View File

@ -1,31 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/playerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
android:layout_alignParentBottom="true" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"/>
<FrameLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_alignParentTop="true"
android:layout_above="@id/playerFragment"/>
android:layout_above="@id/playerFragment"
android:layout_below="@id/toolbar"
android:foreground="?android:windowContentOverlay" />
</RelativeLayout>
<include layout="@layout/nav_list"/>
<include layout="@layout/nav_list" />
</android.support.v4.widget.DrawerLayout>

View File

@ -1,14 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_list"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_layout"
android:layout_width="@dimen/drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/nav_drawer_background"
android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:paddingBottom="@dimen/list_vertical_padding"
android:paddingTop="@dimen/list_vertical_padding"
android:scrollbarStyle="outsideOverlay" />
android:orientation="vertical">
<ListView
android:id="@+id/nav_list"
android:layout_width="@dimen/drawer_width"
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:paddingBottom="@dimen/list_vertical_padding"
android:paddingTop="@dimen/list_vertical_padding"
android:scrollbarStyle="outsideOverlay" />
<View
android:layout_width="@dimen/drawer_width"
android:layout_height="1dp"
android:layout_centerVertical="true"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/nav_settings"
android:layout_width="@dimen/drawer_width"
android:layout_height="@dimen/listitem_iconwithtext_height"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/settings_label"
android:orientation="horizontal">
<ImageView
android:id="@+id/imgvCover"
android:layout_width="@dimen/thumbnail_length_navlist"
android:layout_height="@dimen/thumbnail_length_navlist"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginBottom="8dp"
android:layout_marginLeft="@dimen/listitem_icon_leftpadding"
android:layout_marginTop="8dp"
android:adjustViewBounds="true"
android:contentDescription="@string/cover_label"
android:cropToPadding="true"
android:padding="8dp"
android:scaleType="centerCrop"
android:src="?attr/ic_settings" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_margin="16dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/settings_label"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_navdrawer" />
</LinearLayout>
</LinearLayout>

View File

@ -10,5 +10,5 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_centerVertical="true"
android:background="@color/gray" />
android:background="?android:attr/listDivider" />
</RelativeLayout>

View File

@ -1,12 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent">
<android.support.v4.view.PagerTabStrip
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
</LinearLayout>

View File

@ -1,43 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="12dp"
android:paddingTop="12dp">
android:layout_height="@dimen/listitem_threeline_height"
android:orientation="horizontal">
<TextView
android:id="@+id/txtvStart"
style="@style/AntennaPod.TextView.ListItemSecondaryTitle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small"/>
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
android:gravity="center_vertical" />
<TextView
android:id="@+id/txtvTitle"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:padding="8dp"
android:layout_toLeftOf="@id/txtvStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small"/>
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/txtvLink"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/txtvTitle"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/txtvStart"
<TextView
android:id="@+id/txtvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<TextView
android:id="@+id/txtvLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
android:maxLines="1"
android:visibility="gone" />
</LinearLayout>
<include layout="@layout/vertical_list_divider" />
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/butPlayChapter"
android:layout_width="@dimen/listview_secondary_button_width"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:clickable="false"
android:contentDescription="@string/chapters_label"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
android:maxLines="2" />
android:src="?attr/av_play" />
</RelativeLayout>
</LinearLayout>

View File

@ -5,19 +5,19 @@
<item
android:id="@+id/download_item"
android:icon="?attr/av_download"
custom:showAsAction="ifRoom|collapseActionView"
custom:showAsAction="collapseActionView"
android:title="@string/download_label">
</item>
<item
android:id="@+id/stream_item"
android:icon="?attr/action_stream"
custom:showAsAction="ifRoom|collapseActionView"
custom:showAsAction="collapseActionView"
android:title="@string/stream_label">
</item>
<item
android:id="@+id/play_item"
android:icon="?attr/av_play"
custom:showAsAction="ifRoom|collapseActionView"
custom:showAsAction="collapseActionView"
android:title="@string/play_label">
</item>
<item
@ -65,7 +65,7 @@
<item
android:id="@+id/visit_website_item"
android:icon="?attr/location_web_site"
custom:showAsAction="ifRoom|collapseActionView"
custom:showAsAction="collapseActionView"
android:title="@string/visit_website_label">
</item>
<item

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/show_preferences"
android:title="@string/settings_label"
android:menuCategory="system"
custom:showAsAction="collapseActionView"/>
</menu>

View File

@ -21,7 +21,7 @@
<item
android:id="@+id/visit_website_item"
android:icon="?attr/location_web_site"
custom:showAsAction="ifRoom|collapseActionView"
custom:showAsAction="collapseActionView"
android:title="@string/visit_website_label"
android:visible="false">
</item>

View File

@ -40,6 +40,13 @@
android:key="prefPauseOnHeadsetDisconnect"
android:summary="@string/pref_pauseOnHeadsetDisconnect_sum"
android:title="@string/pref_pauseOnHeadsetDisconnect_title"/>
<CheckBoxPreference
android:defaultValue="true"
android:enabled="true"
android:dependency="prefPauseOnHeadsetDisconnect"
android:key="prefUnpauseOnHeadsetReconnect"
android:summary="@string/pref_unpauseOnHeadsetReconnect_sum"
android:title="@string/pref_unpauseOnHeadsetReconnect_title"/>
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
@ -96,6 +103,11 @@
android:key="prefEnableAutoDl"
android:title="@string/pref_automatic_download_title"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="prefEnableAutoDownloadOnBattery"
android:title="@string/pref_automatic_download_on_battery_title"
android:summary="@string/pref_automatic_download_on_battery_sum"
android:defaultValue="true"/>
<CheckBoxPreference
android:key="prefEnableAutoDownloadWifiFilter"
android:title="@string/pref_autodl_wifi_filter_title"

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0-rc4'
classpath 'com.android.tools.build:gradle:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
buildToolsVersion "21.1.2"
defaultConfig {
minSdkVersion 10
@ -31,19 +31,17 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.android.support:support-v4:21.0.2'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:support-v4:21.0.3'
compile 'org.apache.commons:commons-lang3:3.3.2'
compile ('org.shredzone.flattr4j:flattr4j-core:2.11') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
compile ('org.shredzone.flattr4j:flattr4j-core:2.12') {
exclude group: 'org.json', module: 'json'
}
compile 'commons-io:commons-io:2.4'
compile 'com.jayway.android.robotium:robotium-solo:5.2.1'
compile 'org.jsoup:jsoup:1.7.3'
compile 'com.squareup.picasso:picasso:2.4.0'
compile 'com.squareup.okhttp:okhttp:2.1.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.1.0'
compile 'com.squareup.okio:okio:1.0.1'
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'
}

View File

@ -22,4 +22,6 @@ public class ClientConfig {
public static FlattrCallbacks flattrCallbacks;
public static StorageCallbacks storageCallbacks;
public static DBTasksCallbacks dbTasksCallbacks;
}

View File

@ -0,0 +1,20 @@
package de.danoeh.antennapod.core;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
/**
* Callbacks for the DBTasks class of the storage module.
*/
public interface DBTasksCallbacks {
/**
* Returns the client's implementation of the AutomaticDownloadAlgorithm interface.
*/
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm();
/**
* Returns the client's implementation of the EpisodeCacheCleanupAlgorithm interface.
*/
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm();
}

View File

@ -0,0 +1,29 @@
package de.danoeh.antennapod.core.asynctask;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
/**
* Subclass of AsyncTaskLoader that is made for loading data with one of the DB*-classes.
* This class will provide a useful default implementation that would otherwise always be necessary when interacting
* with the DB*-classes with an AsyncTaskLoader.
*/
public abstract class DBTaskLoader<D> extends AsyncTaskLoader<D> {
public DBTaskLoader(Context context) {
super(context);
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
protected void onStartLoading() {
super.onStartLoading();
// according to https://code.google.com/p/android/issues/detail?id=14944, this has to be called manually
forceLoad();
}
}

View File

@ -14,6 +14,7 @@ import com.squareup.picasso.OkHttpDownloader;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Request;
import com.squareup.picasso.RequestHandler;
import com.squareup.picasso.Transformation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -209,4 +210,254 @@ public class PicassoProvider {
options.inJustDecodeBounds = false;
}
}
public static final int BLUR_RADIUS = 1;
public static final int BLUR_IMAGE_SIZE = 100;
public static final String BLUR_KEY = "blur";
public static final Transformation blurTransformation = new Transformation() {
@Override
public Bitmap transform(Bitmap source) {
Bitmap result = fastblur(source, BLUR_RADIUS);
source.recycle();
return result;
}
@Override
public String key() {
return BLUR_KEY;
}
};
public static Bitmap fastblur(Bitmap sentBitmap, int radius) {
// Stack Blur v1.0 from
// http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//
// Java Author: Mario Klingemann <mario at quasimondo.com>
// http://incubator.quasimondo.com
// created Feburary 29, 2004
// Android port : Yahel Bouaziz <yahel at kayenko.com>
// http://www.kayenko.com
// ported april 5th, 2012
// This is a compromise between Gaussian Blur and Box blur
// It creates much better looking blurs than Box Blur, but is
// 7x faster than my Gaussian Blur implementation.
//
// I called it Stack Blur because this describes best how this
// filter works internally: it creates a kind of moving stack
// of colors whilst scanning through the image. Thereby it
// just has to add one new block of color to the right side
// of the stack and remove the leftmost color. The remaining
// colors on the topmost layer of the stack are either added on
// or reduced by one, depending on if they are on the right or
// on the left side of the stack.
//
// If you are using this algorithm in your code please add
// the following line:
//
// Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
}

View File

@ -143,7 +143,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
paymentLink = other.paymentLink;
}
if (other.chapters != null) {
if (chapters == null) {
if (!hasChapters) {
chapters = other.chapters;
}
}

View File

@ -0,0 +1,38 @@
package de.danoeh.antennapod.core.menuhandler;
import android.support.v4.view.MenuItemCompat;
import android.view.Menu;
import android.view.MenuItem;
import de.danoeh.antennapod.core.R;
/**
* Utilities for menu items
*/
public class MenuItemUtils {
/**
* Changes the appearance of a MenuItem depending on whether the given UpdateRefreshMenuItemChecker
* is refreshing or not. If it returns true, the menu item will be replaced by an indeterminate progress
* bar, otherwise nothing will happen.
*
* @param menu The menu that the MenuItem belongs to
* @param resId The id of the MenuItem
* @param checker Is used for checking whether to show the progress indicator or not.
* @return The returned value of the UpdateRefreshMenuItemChecker's isRefreshing() method.
*/
public static boolean updateRefreshMenuItem(Menu menu, int resId, UpdateRefreshMenuItemChecker checker) {
// expand actionview if feeds are being downloaded, collapse otherwise
if (checker.isRefreshing()) {
MenuItem refreshItem = menu.findItem(resId);
MenuItemCompat.setActionView(refreshItem, R.layout.refresh_action_view);
return true;
} else {
return false;
}
}
public static interface UpdateRefreshMenuItemChecker {
public boolean isRefreshing();
}
}

View File

@ -38,6 +38,7 @@ public class UserPreferences implements
private static final String TAG = "UserPreferences";
public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly";
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
@ -50,6 +51,7 @@ public class UserPreferences implements
public static final String PREF_DATA_FOLDER = "prefDataFolder";
public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
@ -69,6 +71,7 @@ public class UserPreferences implements
// Preferences
private boolean pauseOnHeadsetDisconnect;
private boolean unpauseOnHeadsetReconnect;
private boolean followQueue;
private boolean downloadMediaOnWifiOnly;
private long updateInterval;
@ -80,6 +83,7 @@ public class UserPreferences implements
private int theme;
private boolean enableAutodownload;
private boolean enableAutodownloadWifiFilter;
private boolean enableAutodownloadOnBattery;
private String[] autodownloadSelectedNetworks;
private int episodeCacheSize;
private String playbackSpeed;
@ -121,6 +125,8 @@ public class UserPreferences implements
R.integer.episode_cache_size_unlimited);
pauseOnHeadsetDisconnect = sp.getBoolean(
PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
unpauseOnHeadsetReconnect = sp.getBoolean(
PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false);
downloadMediaOnWifiOnly = sp.getBoolean(
PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true);
@ -140,6 +146,7 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
PREF_PLAYBACK_SPEED_ARRAY, null));
@ -221,6 +228,11 @@ public class UserPreferences implements
return instance.pauseOnHeadsetDisconnect;
}
public static boolean isUnpauseOnHeadsetReconnect() {
instanceAvailable();
return instance.unpauseOnHeadsetReconnect;
}
public static boolean isFollowQueue() {
instanceAvailable();
return instance.followQueue;
@ -282,6 +294,15 @@ public class UserPreferences implements
return instance.theme;
}
public static int getNoTitleTheme() {
int theme = getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.style.Theme_AntennaPod_Dark_NoTitle;
} else {
return R.style.Theme_AntennaPod_Light_NoTitle;
}
}
public static boolean isEnableAutodownloadWifiFilter() {
instanceAvailable();
return instance.enableAutodownloadWifiFilter;
@ -326,6 +347,11 @@ public class UserPreferences implements
return instance.enableAutodownload;
}
public static boolean isEnableAutodownloadOnBattery() {
instanceAvailable();
return instance.enableAutodownloadOnBattery;
}
public static boolean shouldPauseForFocusLoss() {
instanceAvailable();
return instance.pauseForFocusLoss;
@ -377,6 +403,8 @@ public class UserPreferences implements
PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
} else if (key.equals(PREF_ENABLE_AUTODL_ON_BATTERY)) {
enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
} else if (key.equals(PREF_PLAYBACK_SPEED)) {
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
} else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
@ -388,6 +416,8 @@ public class UserPreferences implements
seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30"));
} else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) {
pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
} else if (key.equals(PREF_UNPAUSE_ON_HEADSET_RECONNECT)) {
unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
} else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) {
autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD,
PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT);

View File

@ -144,6 +144,10 @@ public class PlaybackService extends Service {
* Is true if service has received a valid start command.
*/
public static boolean started = false;
/**
* Is true if the service was running, but paused due to headphone disconnect
*/
public static boolean transientPause = false;
private static final int NOTIFICATION_ID = 1;
@ -206,6 +210,8 @@ public class PlaybackService extends Service {
Intent.ACTION_HEADSET_PLUG));
registerReceiver(shutdownReceiver, new IntentFilter(
ACTION_SHUTDOWN_PLAYBACK_SERVICE));
registerReceiver(bluetoothStateUpdated, new IntentFilter(
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
registerReceiver(audioBecomingNoisy, new IntentFilter(
AudioManager.ACTION_AUDIO_BECOMING_NOISY));
registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
@ -228,6 +234,7 @@ public class PlaybackService extends Service {
unregisterReceiver(headsetDisconnected);
unregisterReceiver(shutdownReceiver);
unregisterReceiver(bluetoothStateUpdated);
unregisterReceiver(audioBecomingNoisy);
unregisterReceiver(skipCurrentEpisodeReceiver);
mediaPlayer.shutdown();
@ -284,7 +291,6 @@ public class PlaybackService extends Service {
private void handleKeycode(int keycode) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
switch (keycode) {
@ -315,12 +321,14 @@ public class PlaybackService extends Service {
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
if (status == PlayerStatus.PLAYING) {
if (UserPreferences.isPersistNotify()) {
mediaPlayer.pause(false, true);
} else {
mediaPlayer.pause(true, true);
}
mediaPlayer.pause(false, true);
}
if (UserPreferences.isPersistNotify()) {
mediaPlayer.pause(false, true);
} else {
mediaPlayer.pause(true, true);
}
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
@ -333,7 +341,9 @@ public class PlaybackService extends Service {
case KeyEvent.KEYCODE_MEDIA_STOP:
if (status == PlayerStatus.PLAYING) {
mediaPlayer.pause(true, true);
started = false;
}
stopForeground(true); // gets rid of persistent notification
break;
default:
@ -411,10 +421,13 @@ public class PlaybackService extends Service {
taskManager.cancelWidgetUpdater();
if (UserPreferences.isPersistNotify() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// do not remove notification on pause based on user pref and whether android version supports expanded notifications
} else {
// Change [Play] button to [Pause]
setupNotification(newInfo);
} else if (!UserPreferences.isPersistNotify()) {
// remove notifcation on pause
stopForeground(true);
}
break;
case STOPPED:
@ -431,6 +444,7 @@ public class PlaybackService extends Service {
taskManager.startPositionSaver();
taskManager.startWidgetUpdater();
setupNotification(newInfo);
started = true;
break;
case ERROR:
writePlaybackPreferencesNoMediaPlaying();
@ -734,8 +748,9 @@ public class PlaybackService extends Service {
PlaybackServiceMediaPlayer.PSMPInfo newInfo = mediaPlayer.getPSMPInfo();
final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
if (!isCancelled() && info.playerStatus == PlayerStatus.PLAYING
&& info.playable != null) {
if (!isCancelled() &&
started &&
info.playable != null) {
String contentText = info.playable.getFeedTitle();
String contentTitle = info.playable.getEpisodeTitle();
Notification notification = null;
@ -775,16 +790,30 @@ public class PlaybackService extends Service {
.setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(smallIcon)
.setPriority(UserPreferences.getNotifyPriority()) // set notification priority
.addAction(android.R.drawable.ic_media_play, //play action
getString(R.string.play_label),
playButtonPendingIntent)
.addAction(android.R.drawable.ic_media_pause, //pause action
getString(R.string.pause_label),
pauseButtonPendingIntent)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, // stop action
getString(R.string.stop_label),
stopButtonPendingIntent);
.setPriority(UserPreferences.getNotifyPriority()); // set notification priority
if (newInfo.playerStatus == PlayerStatus.PLAYING) {
notificationBuilder.addAction(android.R.drawable.ic_media_pause, //pause action
getString(R.string.pause_label),
pauseButtonPendingIntent);
} else {
notificationBuilder.addAction(android.R.drawable.ic_media_play, //play action
getString(R.string.play_label),
playButtonPendingIntent);
}
if (UserPreferences.isPersistNotify()) {
notificationBuilder.addAction(android.R.drawable.ic_menu_close_clear_cancel, // stop action
getString(R.string.stop_label),
stopButtonPendingIntent);
}
if (Build.VERSION.SDK_INT >= 21) {
notificationBuilder.setStyle(new Notification.MediaStyle()
.setMediaSession((android.media.session.MediaSession.Token) mediaPlayer.getSessionToken().getToken())
.setShowActionsInCompactView(0))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setColor(Notification.COLOR_DEFAULT);
}
notification = notificationBuilder.build();
} else {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
@ -793,11 +822,9 @@ public class PlaybackService extends Service {
.setContentText(contentText).setOngoing(true)
.setContentIntent(pIntent).setLargeIcon(icon)
.setSmallIcon(smallIcon);
notification = notificationBuilder.getNotification();
}
if (newInfo.playerStatus == PlayerStatus.PLAYING) {
startForeground(NOTIFICATION_ID, notification);
notification = notificationBuilder.build();
}
startForeground(NOTIFICATION_ID, notification);
if (BuildConfig.DEBUG)
Log.d(TAG, "Notification set up");
}
@ -966,6 +993,7 @@ public class PlaybackService extends Service {
private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
private static final String TAG = "headsetDisconnected";
private static final int UNPLUGGED = 0;
private static final int PLUGGED = 1;
@Override
public void onReceive(Context context, Intent intent) {
@ -978,6 +1006,10 @@ public class PlaybackService extends Service {
if (BuildConfig.DEBUG)
Log.d(TAG, "Headset was unplugged during playback.");
pauseIfPauseOnDisconnect();
} else if (state == PLUGGED) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Headset was plugged in during playback.");
unpauseIfPauseOnDisconnect();
}
} else {
Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent");
@ -986,6 +1018,21 @@ public class PlaybackService extends Service {
}
};
private BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (StringUtils.equals(intent.getAction(), AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
int prevState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Received bluetooth connection intent");
unpauseIfPauseOnDisconnect();
}
}
}
};
private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
@Override
@ -1003,6 +1050,9 @@ public class PlaybackService extends Service {
*/
private void pauseIfPauseOnDisconnect() {
if (UserPreferences.isPauseOnHeadsetDisconnect()) {
if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) {
transientPause = true;
}
if (UserPreferences.isPersistNotify()) {
mediaPlayer.pause(false, true);
} else {
@ -1011,6 +1061,15 @@ public class PlaybackService extends Service {
}
}
private void unpauseIfPauseOnDisconnect() {
if (transientPause) {
transientPause = false;
if (UserPreferences.isPauseOnHeadsetDisconnect() && UserPreferences.isUnpauseOnHeadsetReconnect()) {
mediaPlayer.resume();
}
}
}
private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
@Override

View File

@ -6,6 +6,9 @@ import android.media.AudioManager;
import android.media.RemoteControlClient;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
@ -48,6 +51,10 @@ public class PlaybackServiceMediaPlayer {
private volatile PlayerStatus statusBeforeSeeking;
private volatile IPlayer mediaPlayer;
private volatile Playable media;
/**
* Only used for Lollipop notifications.
*/
private final MediaSessionCompat mediaSession;
private volatile boolean stream;
private volatile MediaType mediaType;
@ -89,6 +96,10 @@ public class PlaybackServiceMediaPlayer {
}
);
mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setCallback(sessionCallback);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaPlayer = null;
statusBeforeSeeking = null;
pausedBecauseOfTransientAudiofocusLoss = false;
@ -181,6 +192,7 @@ public class PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.INITIALIZING, media);
try {
media.loadMetadata();
mediaSession.setMetadata(getMediaSessionMetadata(media));
if (stream) {
mediaPlayer.setDataSource(media.getStreamUrl());
} else {
@ -211,6 +223,13 @@ public class PlaybackServiceMediaPlayer {
}
}
private MediaMetadataCompat getMediaSessionMetadata(Playable p) {
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle());
builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle());
return builder.build();
}
/**
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
@ -586,6 +605,10 @@ public class PlaybackServiceMediaPlayer {
return mediaType;
}
public PlayerStatus getPlayerStatus() {
return playerStatus;
}
public boolean isStreaming() {
return stream;
}
@ -599,6 +622,9 @@ public class PlaybackServiceMediaPlayer {
if (mediaPlayer != null) {
mediaPlayer.release();
}
if (mediaSession != null) {
mediaSession.release();
}
releaseWifiLockIfNecessary();
}
@ -662,6 +688,16 @@ public class PlaybackServiceMediaPlayer {
return new PSMPInfo(playerStatus, media);
}
/**
* Returns a token to this object's MediaSession. The MediaSession should only be used for notifications
* at the moment.
*
* @return The MediaSessionCompat.Token object.
*/
public MediaSessionCompat.Token getSessionToken() {
return mediaSession.getSessionToken();
}
/**
* Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
* so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
@ -679,6 +715,45 @@ public class PlaybackServiceMediaPlayer {
this.playerStatus = newStatus;
this.media = newMedia;
PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder();
int state;
if (playerStatus != null) {
switch (playerStatus) {
case PLAYING:
state = PlaybackStateCompat.STATE_PLAYING;
break;
case PREPARED:
case PAUSED:
state = PlaybackStateCompat.STATE_PAUSED;
break;
case STOPPED:
state = PlaybackStateCompat.STATE_STOPPED;
break;
case SEEKING:
state = PlaybackStateCompat.STATE_FAST_FORWARDING;
break;
case PREPARING:
case INITIALIZING:
state = PlaybackStateCompat.STATE_CONNECTING;
break;
case INITIALIZED:
case INDETERMINATE:
state = PlaybackStateCompat.STATE_NONE;
break;
case ERROR:
state = PlaybackStateCompat.STATE_ERROR;
break;
default:
state = PlaybackStateCompat.STATE_NONE;
break;
}
} else {
state = PlaybackStateCompat.STATE_NONE;
}
sessionState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, getPlaybackSpeed());
callback.statusChanged(new PSMPInfo(playerStatus, media));
}
@ -976,4 +1051,54 @@ public class PlaybackServiceMediaPlayer {
}
});
}
private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
resume();
} else if (playerStatus == PlayerStatus.INITIALIZED) {
setStartWhenPrepared(true);
prepare();
}
}
@Override
public void onPause() {
super.onPause();
if (playerStatus == PlayerStatus.PLAYING) {
pause(false, true);
}
if (UserPreferences.isPersistNotify()) {
pause(false, true);
} else {
pause(true, true);
}
}
@Override
public void onSkipToNext() {
super.onSkipToNext();
endPlayback();
}
@Override
public void onFastForward() {
super.onFastForward();
seekDelta(UserPreferences.getSeekDeltaMs());
}
@Override
public void onRewind() {
super.onRewind();
seekDelta(-UserPreferences.getSeekDeltaMs());
}
@Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
seekTo((int) pos);
}
};
}

View File

@ -0,0 +1,103 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.QueueAccess;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
*/
public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
private static final String TAG = "APCleanupAlgorithm";
@Override
public int performCleanup(Context context, Integer episodeNumber) {
List<FeedItem> candidates = new ArrayList<FeedItem>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
List<FeedItem> delete;
for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded()
&& !queue.contains(item.getId()) && item.isRead()) {
candidates.add(item);
}
}
Collections.sort(candidates, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
Date l = lhs.getMedia().getPlaybackCompletionDate();
Date r = rhs.getMedia().getPlaybackCompletionDate();
if (l == null) {
l = new Date(0);
}
if (r == null) {
r = new Date(0);
}
return l.compareTo(r);
}
});
if (candidates.size() > episodeNumber) {
delete = candidates.subList(0, episodeNumber);
} else {
delete = candidates;
}
for (FeedItem item : delete) {
try {
DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
int counter = delete.size();
Log.i(TAG, String.format(
"Auto-delete deleted %d episodes (%d requested)", counter,
episodeNumber));
return counter;
}
@Override
public Integer getDefaultCleanupParameter(Context context) {
return 0;
}
@Override
public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
return getPerformAutoCleanupArgs(context, items.size());
}
static int getPerformAutoCleanupArgs(Context context,
final int episodeNumber) {
if (episodeNumber >= 0
&& UserPreferences.getEpisodeCacheSize() != UserPreferences
.getEpisodeCacheSizeUnlimited()) {
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
if (downloadedEpisodes + episodeNumber >= UserPreferences
.getEpisodeCacheSize()) {
return downloadedEpisodes + episodeNumber
- UserPreferences.getEpisodeCacheSize();
}
}
return 0;
}
}

View File

@ -0,0 +1,133 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.PowerUtils;
/**
* Implements the automatic download algorithm used by AntennaPod. This class assumes that
* the client uses the APEpisodeCleanupAlgorithm.
*/
public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
private static final String TAG = "APDownloadAlgorithm";
private final APCleanupAlgorithm cleanupAlgorithm = new APCleanupAlgorithm();
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
* 2. The device is charging or the user allows auto download on battery
* 3. There is free space in the episode cache
* This method is executed on an internal single thread executor.
*
* @param context Used for accessing the DB.
* @param mediaIds If this list is not empty, the method will only download a candidate for automatic downloading if
* its media ID is in the mediaIds list.
* @return A Runnable that will be submitted to an ExecutorService.
*/
@Override
public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
return new Runnable() {
@Override
public void run() {
// true if we should auto download based on network status
boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable(context)
&& UserPreferences.isEnableAutodownload();
// true if we should auto download based on power status
boolean powerShouldAutoDl = PowerUtils.deviceCharging(context)
|| UserPreferences.isEnableAutodownloadOnBattery();
// we should only auto download if both network AND power are happy
if (networkShouldAutoDl && powerShouldAutoDl) {
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
final List<FeedItem> queue = DBReader.getQueue(context);
final List<FeedItem> unreadItems = DBReader
.getUnreadItemsList(context);
int undownloadedEpisodes = DBTasks.getNumberOfUndownloadedEpisodes(queue,
unreadItems);
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
APCleanupAlgorithm.getPerformAutoCleanupArgs(context, undownloadedEpisodes));
int episodeSpaceLeft = undownloadedEpisodes;
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
if (!cacheIsUnlimited
&& UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
+ undownloadedEpisodes) {
episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- (downloadedEpisodes - deletedEpisodes);
}
Arrays.sort(mediaIds); // sort for binary search
final boolean ignoreMediaIds = mediaIds.length == 0;
List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (int i = 0; i < queue.size(); i++) { // ignore playing item
FeedItem item = queue.get(i);
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
&& item.hasMedia()
&& !item.getMedia().isDownloaded()
&& !item.getMedia().isPlaying()
&& item.getFeed().getPreferences().getAutoDownload()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (FeedItem item : unreadItems) {
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
&& item.hasMedia()
&& !item.getMedia().isDownloaded()
&& item.getFeed().getPreferences().getAutoDownload()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ " items for download");
try {
DBTasks.downloadFeedItems(false, context,
itemsToDownload.toArray(new FeedItem[itemsToDownload
.size()])
);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
};
}
}

View File

@ -0,0 +1,139 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.feed.FeedItem;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPodSP apps.
*/
public class APSPCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
private static final String TAG = "APSPCleanupAlgorithm";
final int numberOfNewAutomaticallyDownloadedEpisodes;
public APSPCleanupAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
}
/**
* Performs an automatic cleanup. Episodes that have been downloaded first will also be deleted first.
* The episode that is currently playing as well as the n most recent episodes (the exact value is determined
* by AppPreferences.numberOfNewAutomaticallyDownloadedEpisodes) will never be deleted.
*
* @param context
* @param episodeSize The maximum amount of space that should be freed by this method
* @return The number of episodes that have been deleted
*/
@Override
public int performCleanup(Context context, Integer episodeSize) {
Log.i(TAG, String.format("performAutoCleanup(%d)", episodeSize));
if (episodeSize <= 0) {
return 0;
}
List<FeedItem> candidates = getAutoCleanupCandidates(context);
List<FeedItem> deleteList = new ArrayList<FeedItem>();
long deletedEpisodesSize = 0;
Collections.sort(candidates, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
File lFile = new File(lhs.getMedia().getFile_url());
File rFile = new File(rhs.getMedia().getFile_url());
if (!lFile.exists() || !rFile.exists()) {
return 0;
}
if (FileUtils.isFileOlder(lFile, rFile)) {
return -1;
} else {
return 1;
}
}
});
// listened episodes will be deleted first
Iterator<FeedItem> it = candidates.iterator();
if (it.hasNext()) {
for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
if (!i.getMedia().isPlaying() && i.getMedia().getPlaybackCompletionDate() != null) {
it.remove();
deleteList.add(i);
deletedEpisodesSize += i.getMedia().getSize();
}
}
}
// delete unlistened old episodes if necessary
it = candidates.iterator();
if (it.hasNext()) {
for (FeedItem i = it.next(); it.hasNext() && deletedEpisodesSize <= episodeSize; i = it.next()) {
if (!i.getMedia().isPlaying()) {
it.remove();
deleteList.add(i);
deletedEpisodesSize += i.getMedia().getSize();
}
}
}
for (FeedItem item : deleteList) {
try {
DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Log.i(TAG, String.format("performAutoCleanup(%d) deleted %d episodes and freed %d bytes of memory",
episodeSize, deleteList.size(), deletedEpisodesSize));
return deleteList.size();
}
@Override
public Integer getDefaultCleanupParameter(Context context) {
return 0;
}
@Override
public Integer getPerformCleanupParameter(Context context, List<FeedItem> items) {
int episodeSize = 0;
for (FeedItem item : items) {
if (item.hasMedia() && !item.getMedia().isDownloaded()) {
episodeSize += item.getMedia().getSize();
}
}
return episodeSize;
}
/**
* Returns list of FeedItems that have been downloaded, but are not one of the
* [numberOfNewAutomaticallyDownloadedEpisodes] most recent items.
*/
private List<FeedItem> getAutoCleanupCandidates(Context context) {
List<FeedItem> downloaded = new ArrayList<FeedItem>(DBReader.getDownloadedItems(context));
List<FeedItem> recent = new ArrayList<FeedItem>(DBReader.getRecentlyPublishedEpisodes(context,
numberOfNewAutomaticallyDownloadedEpisodes));
for (FeedItem r : recent) {
if (r.hasMedia() && r.getMedia().isDownloaded()) {
for (int i = 0; i < downloaded.size(); i++) {
if (downloaded.get(i).getId() == r.getId()) {
downloaded.remove(i);
break;
}
}
}
}
return downloaded;
}
}

View File

@ -0,0 +1,72 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.util.Log;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils;
/**
* Implements the automatic download algorithm used by AntennaPodSP apps.
*/
public class APSPDownloadAlgorithm implements AutomaticDownloadAlgorithm {
private static final String TAG = "APSPDownloadAlgorithm";
private final int numberOfNewAutomaticallyDownloadedEpisodes;
public APSPDownloadAlgorithm(int numberOfNewAutomaticallyDownloadedEpisodes) {
this.numberOfNewAutomaticallyDownloadedEpisodes = numberOfNewAutomaticallyDownloadedEpisodes;
}
/**
* Downloads the most recent episodes automatically. The exact number of
* episodes that will be downloaded can be set in the AppPreferences.
*
* @param context Used for accessing the DB.
* @return A Runnable that will be submitted to an ExecutorService.
*/
@Override
public Runnable autoDownloadUndownloadedItems(final Context context, final long... mediaIds) {
return new Runnable() {
@Override
public void run() {
if (BuildConfig.DEBUG)
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
if (NetworkUtils.autodownloadNetworkAvailable(context)
&& UserPreferences.isEnableAutodownload()) {
Arrays.sort(mediaIds);
List<FeedItem> itemsToDownload = DBReader.getRecentlyPublishedEpisodes(context,
numberOfNewAutomaticallyDownloadedEpisodes);
Iterator<FeedItem> it = itemsToDownload.iterator();
for (FeedItem item = it.next(); it.hasNext(); item = it.next()) {
if (!item.hasMedia()
|| item.getMedia().isDownloaded()
|| Arrays.binarySearch(mediaIds, item.getMedia().getId()) < 0) {
it.remove();
}
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ " items for automatic download");
if (!itemsToDownload.isEmpty()) {
try {
DBTasks.downloadFeedItems(false, context,
itemsToDownload.toArray(new FeedItem[itemsToDownload
.size()]));
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
}
};
}
}

View File

@ -0,0 +1,20 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
public interface AutomaticDownloadAlgorithm {
/**
* Looks for undownloaded episodes and request a download if
* 1. Network is available
* 2. The device is charging or the user allows auto download on battery
* 3. There is free space in the episode cache
* This method is executed on an internal single thread executor.
*
* @param context Used for accessing the DB.
* @param mediaIds If this list is not empty, the method will only download a candidate for automatic downloading if
* its media ID is in the mediaIds list.
* @return A Runnable that will be submitted to an ExecutorService.
*/
public Runnable autoDownloadUndownloadedItems(Context context, long... mediaIds);
}

View File

@ -8,7 +8,6 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@ -35,7 +34,6 @@ import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
@ -386,8 +384,8 @@ public final class DBTasks {
downloadFeedItems(true, context, items);
}
private static void downloadFeedItems(boolean performAutoCleanup,
final Context context, final FeedItem... items)
static void downloadFeedItems(boolean performAutoCleanup,
final Context context, final FeedItem... items)
throws DownloadRequestException {
final DownloadRequester requester = DownloadRequester.getInstance();
@ -396,8 +394,10 @@ public final class DBTasks {
@Override
public void run() {
performAutoCleanup(context,
getPerformAutoCleanupArgs(context, items.length));
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
.performCleanup(context,
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
.getPerformCleanupParameter(context, Arrays.asList(items)));
}
}.start();
@ -427,7 +427,7 @@ public final class DBTasks {
}
}
private static int getNumberOfUndownloadedEpisodes(
static int getNumberOfUndownloadedEpisodes(
final List<FeedItem> queue, final List<FeedItem> unreadItems) {
int counter = 0;
for (FeedItem item : queue) {
@ -449,7 +449,8 @@ public final class DBTasks {
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
* 2. There is free space in the episode cache
* 2. The device is charging or the user allows auto download on battery
* 3. There is free space in the episode cache
* This method is executed on an internal single thread executor.
*
* @param context Used for accessing the DB.
@ -458,107 +459,9 @@ public final class DBTasks {
* @return A Future that can be used for waiting for the methods completion.
*/
public static Future<?> autodownloadUndownloadedItems(final Context context, final long... mediaIds) {
return autodownloadExec.submit(new Runnable() {
@Override
public void run() {
if (BuildConfig.DEBUG)
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
if (NetworkUtils.autodownloadNetworkAvailable(context)
&& UserPreferences.isEnableAutodownload()) {
final List<FeedItem> queue = DBReader.getQueue(context);
final List<FeedItem> unreadItems = DBReader
.getUnreadItemsList(context);
return autodownloadExec.submit(ClientConfig.dbTasksCallbacks.getAutomaticDownloadAlgorithm()
.autoDownloadUndownloadedItems(context, mediaIds));
int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue,
unreadItems);
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
int deletedEpisodes = performAutoCleanup(context,
getPerformAutoCleanupArgs(context, undownloadedEpisodes));
int episodeSpaceLeft = undownloadedEpisodes;
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
if (!cacheIsUnlimited
&& UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
+ undownloadedEpisodes) {
episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- (downloadedEpisodes - deletedEpisodes);
}
Arrays.sort(mediaIds); // sort for binary search
final boolean ignoreMediaIds = mediaIds.length == 0;
List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (int i = 0; i < queue.size(); i++) { // ignore playing item
FeedItem item = queue.get(i);
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
&& item.hasMedia()
&& !item.getMedia().isDownloaded()
&& !item.getMedia().isPlaying()
&& item.getFeed().getPreferences().getAutoDownload()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (FeedItem item : unreadItems) {
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1;
if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0)
&& item.hasMedia()
&& !item.getMedia().isDownloaded()
&& item.getFeed().getPreferences().getAutoDownload()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (BuildConfig.DEBUG)
Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ " items for download");
try {
downloadFeedItems(false, context,
itemsToDownload.toArray(new FeedItem[itemsToDownload
.size()])
);
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
});
}
private static int getPerformAutoCleanupArgs(Context context,
final int episodeNumber) {
if (episodeNumber >= 0
&& UserPreferences.getEpisodeCacheSize() != UserPreferences
.getEpisodeCacheSizeUnlimited()) {
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
if (downloadedEpisodes + episodeNumber >= UserPreferences
.getEpisodeCacheSize()) {
return downloadedEpisodes + episodeNumber
- UserPreferences.getEpisodeCacheSize();
}
}
return 0;
}
/**
@ -570,63 +473,8 @@ public final class DBTasks {
* @param context Used for accessing the DB.
*/
public static void performAutoCleanup(final Context context) {
performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
}
private static int performAutoCleanup(final Context context,
final int episodeNumber) {
List<FeedItem> candidates = new ArrayList<FeedItem>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
List<FeedItem> delete;
for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded()
&& !queue.contains(item.getId()) && item.isRead()) {
candidates.add(item);
}
}
Collections.sort(candidates, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
Date l = lhs.getMedia().getPlaybackCompletionDate();
Date r = rhs.getMedia().getPlaybackCompletionDate();
if (l == null) {
l = new Date(0);
}
if (r == null) {
r = new Date(0);
}
return l.compareTo(r);
}
});
if (candidates.size() > episodeNumber) {
delete = candidates.subList(0, episodeNumber);
} else {
delete = candidates;
}
for (FeedItem item : delete) {
try {
DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
int counter = delete.size();
if (BuildConfig.DEBUG)
Log.d(TAG, String.format(
"Auto-delete deleted %d episodes (%d requested)", counter,
episodeNumber));
return counter;
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context,
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter(context));
}
/**

View File

@ -92,8 +92,9 @@ public class DownloadRequester {
private void download(Context context, FeedFile item, FeedFile container, File dest,
boolean overwriteIfExists, String username, String password, boolean deleteOnFailure, Bundle arguments) {
final boolean partiallyDownloadedFileExists = item.getFile_url() != null;
if (!isDownloadingFile(item)) {
if (!isFilenameAvailable(dest.toString()) || (deleteOnFailure && dest.exists())) {
if (!isFilenameAvailable(dest.toString()) || (!partiallyDownloadedFileExists && dest.exists())) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Filename already used.");
if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
@ -254,8 +255,7 @@ public class DownloadRequester {
* Cancels all running downloads
*/
public synchronized void cancelAllDownloads(Context context) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Cancelling all running downloads");
Log.d(TAG, "Cancelling all running downloads");
context.sendBroadcast(new Intent(
DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
}
@ -377,10 +377,13 @@ public class DownloadRequester {
String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(),
null, media.getMime_type());
;
if (titleBaseFilename != "") {
if (!titleBaseFilename.equals("")) {
// Append extension
final int FILENAME_MAX_LENGTH = 220;
if (titleBaseFilename.length() > FILENAME_MAX_LENGTH) {
titleBaseFilename = titleBaseFilename.substring(0, FILENAME_MAX_LENGTH);
}
filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR +
FilenameUtils.getExtension(URLBaseFilename);
} else {

View File

@ -0,0 +1,36 @@
package de.danoeh.antennapod.core.storage;
import android.content.Context;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
public interface EpisodeCleanupAlgorithm<T> {
/**
* Deletes downloaded episodes that are no longer needed. What episodes are deleted and how many
* of them depends on the implementation.
*
* @param context Can be used for accessing the database
* @param parameter An additional parameter. This parameter is either returned by getDefaultCleanupParameter
* or getPerformCleanupParameter.
* @return The number of episodes that were deleted.
*/
public int performCleanup(Context context, T parameter);
/**
* Returns a parameter for performCleanup. The implementation of this interface should decide how much
* space to free to satisfy the episode cache conditions. If the conditions are already satisfied, this
* method should not have any effects.
*/
public T getDefaultCleanupParameter(Context context);
/**
* Returns a parameter for performCleanup.
*
* @param items A list of FeedItems that are about to be downloaded. The implementation of this interface
* should decide how much space to free to satisfy the episode cache conditions.
*/
public T getPerformCleanupParameter(Context context, List<FeedItem> items);
}

View File

@ -0,0 +1,32 @@
package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
/**
* Created by Tom on 1/5/15.
*/
public class PowerUtils {
private static final String TAG = "PowerUtils";
private PowerUtils() {
}
/**
* @return true if the device is charging
*/
public static boolean deviceCharging(Context context) {
// from http://developer.android.com/training/monitoring-device-state/battery-monitoring.html
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, iFilter);
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
return (status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL);
}
}

View File

@ -28,7 +28,7 @@ import de.danoeh.antennapod.core.util.ShownotesProvider;
public class Timeline {
private static final String TAG = "Timeline";
private static final String WEBVIEW_STYLE = "@font-face { font-family: 'Roboto-Light'; src: url('file:///android_asset/Roboto-Light.ttf'); } * { color: %s; font-family: roboto-Light; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } a.timecode { color: #669900; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }";
private static final String WEBVIEW_STYLE = "@font-face { font-family: 'Roboto-Light'; src: url('file:///android_asset/Roboto-Light.ttf'); } * { color: %s; font-family: roboto-Light; font-size: 13pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } a.timecode { color: #669900; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }";
private ShownotesProvider shownotesProvider;

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

View File

@ -35,6 +35,7 @@
<attr name="av_pause_big" format="reference"/>
<attr name="av_ff_big" format="reference"/>
<attr name="av_rew_big" format="reference"/>
<attr name="ic_settings" format="reference"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>

View File

@ -11,11 +11,12 @@
<color name="download_failed_red">#CC0000</color>
<color name="status_progress">#E033B5E5</color>
<color name="status_playing">#E0EE5F52</color>
<color name="overlay_dark">#262C31</color>
<color name="overlay_light">#DDDDDD</color>
<color name="overlay_dark">#2C2C2C</color>
<color name="overlay_light">#FFFFFF</color>
<color name="swipe_refresh_secondary_color_light">#EDEDED</color>
<color name="swipe_refresh_secondary_color_dark">#060708</color>
<color name="new_indicator_green">#669900</color>
<color name="image_readability_tint">#80000000</color>
<!-- Use Gingerbread-orange -->
<color name="selection_background_color_dark">#FEBB20</color>

View File

@ -33,4 +33,6 @@
<dimen name="listitem_icon_leftpadding">16dp</dimen>
<dimen name="listitem_icon_rightpadding">16dp</dimen>
<dimen name="audioplayer_playercontrols_length">64dp</dimen>
</resources>

View File

@ -205,6 +205,7 @@
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">Pause playback when the headphones are disconnected</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Resume playback when the headphones are reconnected</string>
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>
<string name="playback_pref">Playback</string>
<string name="network_pref">Network</string>
@ -214,6 +215,7 @@
<string name="pref_followQueue_title">Continuous playback</string>
<string name="pref_downloadMediaOnWifiOnly_title">WiFi media download</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Headphones disconnect</string>
<string name="pref_unpauseOnHeadsetReconnect_title">Headphones reconnect</string>
<string name="pref_mobileUpdate_title">Mobile updates</string>
<string name="pref_mobileUpdate_sum">Allow updates over the mobile data connection</string>
<string name="refreshing_label">Refreshing</string>
@ -233,6 +235,8 @@
<string name="pref_automatic_download_sum">Configure the automatic download of episodes.</string>
<string name="pref_autodl_wifi_filter_title">Enable Wi-Fi filter</string>
<string name="pref_autodl_wifi_filter_sum">Allow automatic download only for selected Wi-Fi networks.</string>
<string name="pref_automatic_download_on_battery_title">Automatic download on battery</string>
<string name="pref_automatic_download_on_battery_sum">Allow automatic download while on battery</string>
<string name="pref_episode_cache_title">Episode cache</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>

View File

@ -40,6 +40,7 @@
<item name="attr/av_pause_big">@drawable/ic_pause_grey600_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
</style>
<style name="Theme.AntennaPod.Dark" parent="@style/Theme.AppCompat">
@ -80,6 +81,94 @@
<item name="attr/av_pause_big">@drawable/ic_pause_white_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
</style>
<style name="Theme.AntennaPod.Light.NoTitle" parent="@style/Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
<item name="colorPrimary">@color/primary_light</item>
<item name="colorAccent">@color/color_accent</item>
<item name="attr/action_about">@drawable/ic_info_grey600_24dp</item>
<item name="attr/action_search">@drawable/ic_search_grey600_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_grey600_24dp</item>
<item name="attr/av_download">@drawable/ic_file_download_grey600_24dp</item>
<item name="attr/av_fast_forward">@drawable/ic_fast_forward_grey600_24dp</item>
<item name="attr/av_pause">@drawable/ic_pause_grey600_24dp</item>
<item name="attr/av_play">@drawable/ic_play_arrow_grey600_24dp</item>
<item name="attr/av_rewind">@drawable/ic_fast_rewind_grey600_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_grey600_24dp</item>
<item name="attr/content_new">@drawable/ic_add_grey600_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_grey600_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_grey600_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_grey600_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_grey600_24dp</item>
<item name="attr/navigation_expand">@drawable/ic_expand_more_grey600_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_grey600_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up</item>
<item name="attr/navigation_shownotes">@drawable/ic_description_grey600_36dp</item>
<item name="attr/navigation_chapters">@drawable/ic_toc_grey600_36dp</item>
<item name="attr/social_share">@drawable/ic_share_grey600_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_grey600_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_grey600_18dp</item>
<item name="attr/type_video">@drawable/ic_remove_red_eye_grey600_18dp</item>
<item name="attr/non_transparent_background">@color/white</item>
<item name="attr/overlay_background">@color/overlay_light</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle</item>
<item name="attr/dragview_float_background">@color/white</item>
<item name="attr/nav_drawer_background">@color/white</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_grey600_24dp</item>
<item name="attr/ic_new">@drawable/ic_new_releases_grey600_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_grey600_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_grey600_36dp</item>
<item name="attr/av_pause_big">@drawable/ic_pause_grey600_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_grey600_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_grey600_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_grey600_24dp</item>
</style>
<style name="Theme.AntennaPod.Dark.NoTitle" parent="@style/Theme.AppCompat.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
<item name="colorAccent">@color/color_accent</item>
<item name="attr/action_about">@drawable/ic_info_white_24dp</item>
<item name="attr/action_search">@drawable/ic_search_white_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_white_24dp</item>
<item name="attr/av_download">@drawable/ic_file_download_white_24dp</item>
<item name="attr/av_fast_forward">@drawable/ic_fast_forward_white_24dp</item>
<item name="attr/av_pause">@drawable/ic_pause_white_24dp</item>
<item name="attr/av_play">@drawable/ic_play_arrow_white_24dp</item>
<item name="attr/av_rewind">@drawable/ic_fast_rewind_white_24dp</item>
<item name="attr/content_discard">@drawable/ic_delete_white_24dp</item>
<item name="attr/content_new">@drawable/ic_add_white_24dp</item>
<item name="attr/device_access_time">@drawable/ic_timer_white_24dp</item>
<item name="attr/location_web_site">@drawable/ic_web_white_24dp</item>
<item name="attr/navigation_accept">@drawable/ic_done_white_24dp</item>
<item name="attr/navigation_cancel">@drawable/ic_cancel_white_24dp</item>
<item name="attr/navigation_expand">@drawable/ic_expand_more_white_36dp</item>
<item name="attr/navigation_refresh">@drawable/ic_refresh_white_24dp</item>
<item name="attr/navigation_up">@drawable/navigation_up_dark</item>
<item name="attr/navigation_shownotes">@drawable/ic_description_white_36dp</item>
<item name="attr/navigation_chapters">@drawable/ic_toc_white_36dp</item>
<item name="attr/social_share">@drawable/ic_share_white_24dp</item>
<item name="attr/stat_playlist">@drawable/ic_list_white_24dp</item>
<item name="attr/type_audio">@drawable/ic_hearing_white_18dp</item>
<item name="attr/type_video">@drawable/ic_remove_red_eye_white_18dp</item>
<item name="attr/non_transparent_background">@color/black</item>
<item name="attr/overlay_background">@color/overlay_dark</item>
<item name="attr/overlay_drawable">@drawable/overlay_drawable_dark</item>
<item name="attr/dragview_background">@drawable/ic_drag_handle_dark</item>
<item name="attr/dragview_float_background">@color/black</item>
<item name="attr/nav_drawer_background">#3B3B3B</item>
<item name="attr/ic_action_overflow">@drawable/ic_more_vert_white_24dp</item>
<item name="attr/ic_new">@drawable/ic_new_releases_white_24dp</item>
<item name="attr/ic_history">@drawable/ic_history_white_24dp</item>
<item name="attr/av_play_big">@drawable/ic_play_arrow_white_36dp</item>
<item name="attr/av_pause_big">@drawable/ic_pause_white_36dp</item>
<item name="attr/av_ff_big">@drawable/ic_fast_forward_white_36dp</item>
<item name="attr/av_rew_big">@drawable/ic_fast_rewind_white_36dp</item>
<item name="attr/ic_settings">@drawable/ic_settings_white_24dp</item>
</style>
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">
@ -157,4 +246,10 @@
<item name="android:text">@string/new_label</item>
</style>
<style name="BigBlurryBackground">
<item name="android:scaleType">centerCrop</item>
<item name="android:tint">@color/image_readability_tint</item>
</style>
</resources>