diff options
79 files changed, 2520 insertions, 1566 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0d83fd483..2c8c15bf5 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,6 +9,7 @@ wseemann hzulla andrewgaul peschmae0 +TomHennen Translations: diff --git a/app/build.gradle b/app/build.gradle index 14897139b..8f829d0af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java index daae4bd62..2235ee6f4 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java @@ -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(); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 50d1d2874..5b00941de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.danoeh.antennapod" - android:versionCode="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"/> diff --git a/app/src/main/assets/about.html b/app/src/main/assets/about.html index 172b49aab..3398dbb67 100644 --- a/app/src/main/assets/about.html +++ b/app/src/main/assets/about.html @@ -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> diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index eb7a844db..821c86044 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -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(); @@ -139,9 +143,13 @@ 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 diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index df6ff1046..b3e95f0c0 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -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; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 561188291..099e96be9 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -174,7 +174,6 @@ public abstract class MediaplayerActivity extends ActionBarActivity orientation = getResources().getConfiguration().orientation; getWindow().setFormat(PixelFormat.TRANSPARENT); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index c40489374..3802de2a6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -73,15 +73,6 @@ public class PreferenceActivity extends ActionBarActivity { } @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); return true; @@ -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: diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java index c58593f77..633f8d66d 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java @@ -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(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index 727b25296..60eb290b5 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -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); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java index 9e59a2a1a..67fb4c3b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChapterListAdapter.java @@ -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); + } + } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 8941a5d71..10666aa36 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -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(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java new file mode 100644 index 000000000..75dcb2ef1 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java @@ -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(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java index ebb3780b7..10a3c1b32 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/StorageCallbacksImpl.java @@ -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)); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java deleted file mode 100644 index eeed10b98..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FeedItemDialog.java +++ /dev/null @@ -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; - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 645e7ebd9..d63d66966 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -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(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index efe3e7ab4..3076f8136 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -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; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java index 5a71cb36b..712db1421 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadsFragment.java @@ -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) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index c0222de8e..a7c6d62e6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -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; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java new file mode 100644 index 000000000..ac9e744ed --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -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(); + } + } + }; +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java index be9a9c12d..5312beeeb 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -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() { @@ -259,6 +255,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); ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(""); @@ -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,6 +365,7 @@ 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); @@ -370,6 +374,14 @@ public class ItemlistFragment extends ListFragment { 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() .into(imgvCover); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index d423c335a..d97ede0ef 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -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 diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index e226c5c4f..f6d2d5d07 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -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 diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 3192a84de..ce77229d9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -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 diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 0f98a2780..cf96bb094 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -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() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java index ec8f69368..45b2403c8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java @@ -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 { diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java index a1e666df0..05d6ded4d 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java @@ -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(); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java index edaba5df3..43f942308 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -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) { diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java new file mode 100644 index 000000000..0e7784381 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PowerConnectionReceiver.java @@ -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"); + } + } + + } +} diff --git a/app/src/main/res/layout-land/audioplayer_activity.xml b/app/src/main/res/layout-land/audioplayer_activity.xml deleted file mode 100644 index 1f78902c9..000000000 --- a/app/src/main/res/layout-land/audioplayer_activity.xml +++ /dev/null @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/audioplayer_activity.xml b/app/src/main/res/layout/audioplayer_activity.xml index 770ced350..4d04771dd 100644 --- a/app/src/main/res/layout/audioplayer_activity.xml +++ b/app/src/main/res/layout/audioplayer_activity.xml @@ -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"> - - <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> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - <View - android:id="@+id/navBarDivider" + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_below="@id/navBar" - android:background="@color/bright_blue"/> + android:layout_height="wrap_content" + android:background="?attr/colorPrimary" + android:minHeight="?attr/actionBarSize"> - <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" + <LinearLayout + android:layout_width="match_parent" 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: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="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="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"/> - - <TextView - android:id="@+id/txtvLength" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="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> + + <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"/> - - <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"/> + android:foreground="?android:windowContentOverlay" /> + </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>
\ No newline at end of file diff --git a/app/src/main/res/layout/cover_fragment.xml b/app/src/main/res/layout/cover_fragment.xml index f9c88ac02..e6325da4b 100644 --- a/app/src/main/res/layout/cover_fragment.xml +++ b/app/src/main/res/layout/cover_fragment.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/feeditem_dialog.xml b/app/src/main/res/layout/feeditem_dialog.xml deleted file mode 100644 index 5937480df..000000000 --- a/app/src/main/res/layout/feeditem_dialog.xml +++ /dev/null @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml new file mode 100644 index 000000000..5e1b580d2 --- /dev/null +++ b/app/src/main/res/layout/feeditem_fragment.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/feeditem_fragment_header.xml b/app/src/main/res/layout/feeditem_fragment_header.xml new file mode 100644 index 000000000..bab089d3b --- /dev/null +++ b/app/src/main/res/layout/feeditem_fragment_header.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml index fc38c6797..e55ef4c3e 100644 --- a/app/src/main/res/layout/feeditemlist_header.xml +++ b/app/src/main/res/layout/feeditemlist_header.xml @@ -7,6 +7,12 @@ 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" android:layout_height="@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" /> diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 0a7b7ef74..914d5fa6f 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/nav_list.xml b/app/src/main/res/layout/nav_list.xml index 536946ca1..a22520b2d 100644 --- a/app/src/main/res/layout/nav_list.xml +++ b/app/src/main/res/layout/nav_list.xml @@ -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" />
\ No newline at end of file + 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>
\ No newline at end of file diff --git a/app/src/main/res/layout/nav_section_item.xml b/app/src/main/res/layout/nav_section_item.xml index 6eb26291e..3682ca811 100644 --- a/app/src/main/res/layout/nav_section_item.xml +++ b/app/src/main/res/layout/nav_section_item.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/pager_fragment.xml b/app/src/main/res/layout/pager_fragment.xml index cb7ae0151..ed639a2db 100644 --- a/app/src/main/res/layout/pager_fragment.xml +++ b/app/src/main/res/layout/pager_fragment.xml @@ -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>
\ No newline at end of file diff --git a/app/src/main/res/layout/simplechapter_item.xml b/app/src/main/res/layout/simplechapter_item.xml index 422458d5d..b7f4cdb18 100644 --- a/app/src/main/res/layout/simplechapter_item.xml +++ b/app/src/main/res/layout/simplechapter_item.xml @@ -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>
\ No newline at end of file +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/menu/feeditem.xml b/app/src/main/res/menu/feeditem.xml index 5b25e8f2c..8227f8b14 100644 --- a/app/src/main/res/menu/feeditem.xml +++ b/app/src/main/res/menu/feeditem.xml @@ -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 diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml deleted file mode 100644 index a968f51ce..000000000 --- a/app/src/main/res/menu/main.xml +++ /dev/null @@ -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> diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml index 0eb2ab067..288e44401 100644 --- a/app/src/main/res/menu/mediaplayer.xml +++ b/app/src/main/res/menu/mediaplayer.xml @@ -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> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index dd2de8faf..11644c5ba 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -41,6 +41,13 @@ 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" android:key="prefFollowQueue" @@ -97,6 +104,11 @@ 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" android:summary="@string/pref_autodl_wifi_filter_sum"/> diff --git a/build.gradle b/build.gradle index efa6e1773..9f1a2d6ab 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/core/build.gradle b/core/build.gradle index cfe33c98c..dfe0fb133 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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' }
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java index e5e609f5f..1a2671555 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java @@ -22,4 +22,6 @@ public class ClientConfig { public static FlattrCallbacks flattrCallbacks; public static StorageCallbacks storageCallbacks; + + public static DBTasksCallbacks dbTasksCallbacks; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java new file mode 100644 index 000000000..edf3e3199 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java @@ -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(); +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java new file mode 100644 index 000000000..0f402f44a --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DBTaskLoader.java @@ -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(); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java index 1ed29c23a..b6ece6dc8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java @@ -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); + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java index 42e4191f6..c63b61f55 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java @@ -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; } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java new file mode 100644 index 000000000..b8d17bcce --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/menuhandler/MenuItemUtils.java @@ -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(); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index f18028e8f..a3b9f6049 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -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); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index a5560e3fb..866f1cba3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -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 diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java index dbf870eac..c143d7f2c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java @@ -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(); } @@ -663,6 +689,16 @@ public class PlaybackServiceMediaPlayer { } /** + * 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). * <p/> @@ -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); + } + }; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java new file mode 100644 index 000000000..499fddf74 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java @@ -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; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java new file mode 100644 index 000000000..c5f871f48 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java @@ -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(); + } + + } + } + }; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java new file mode 100644 index 000000000..420bbc09d --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPCleanupAlgorithm.java @@ -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; + + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java new file mode 100644 index 000000000..f760ec0ce --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APSPDownloadAlgorithm.java @@ -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(); + } + } + } + } + }; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java new file mode 100644 index 000000000..9ca9620a7 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java @@ -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); +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index b1aff5594..e73f9599d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -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); - - 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(); - } + return autodownloadExec.submit(ClientConfig.dbTasksCallbacks.getAutomaticDownloadAlgorithm() + .autoDownloadUndownloadedItems(context, mediaIds)); - } - } - }); - - } - - 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)); } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java index f5ee9e28c..d0cdad649 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java @@ -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 { diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java new file mode 100644 index 000000000..6a8b4a441 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java @@ -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); +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/PowerUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/PowerUtils.java new file mode 100644 index 000000000..39deea36a --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/PowerUtils.java @@ -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); + + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java index 443ff0ad1..f31297b41 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Timeline.java @@ -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; diff --git a/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..20d2b66e0 --- /dev/null +++ b/core/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png diff --git a/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png Binary files differnew file mode 100644 index 000000000..f9a8915fd --- /dev/null +++ b/core/src/main/res/drawable-hdpi/ic_settings_white_24dp.png diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..2251d2bbb --- /dev/null +++ b/core/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png diff --git a/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png Binary files differnew file mode 100644 index 000000000..12e5d100d --- /dev/null +++ b/core/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..6a70402b4 --- /dev/null +++ b/core/src/main/res/drawable-xxhdpi/ic_settings_grey600_24dp.png diff --git a/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png b/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png Binary files differnew file mode 100644 index 000000000..6bb8f6e08 --- /dev/null +++ b/core/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png diff --git a/app/src/main/res/layout/refresh_action_view.xml b/core/src/main/res/layout/refresh_action_view.xml index 66148a553..66148a553 100644 --- a/app/src/main/res/layout/refresh_action_view.xml +++ b/core/src/main/res/layout/refresh_action_view.xml diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml index c46a2118f..f36119c8d 100644 --- a/core/src/main/res/values/attrs.xml +++ b/core/src/main/res/values/attrs.xml @@ -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"/> diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index ab48fafe7..e558a5c4e 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -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> diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index 38c14b024..81a55142a 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -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>
\ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 532b5d75d..86208becb 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -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> diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index df7ae385c..a2f180395 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -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> |