diff options
48 files changed, 978 insertions, 281 deletions
diff --git a/app/build.gradle b/app/build.gradle index 55c1080ae..240fd6f9f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,6 +71,7 @@ android { versionName "${getMyVersionName()}" testApplicationId "de.test.antennapod" testInstrumentationRunner "de.test.antennapod.AntennaPodTestRunner" + generatedDensities = [] } signingConfigs { @@ -132,6 +133,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + aaptOptions { + additionalParameters "--no-version-vectors" + } } // about.html is templatized so that we can automatically insert diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0c47f5a1..af23c287c 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="1050008" - android:versionName="1.5.0.8"> + android:versionCode="1050200" + android:versionName="1.5.2.0"> <!-- Version code schema: "1.2.3-SNAPSHOT" -> 1020300 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 8af6f3552..b4e8d4d71 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -94,8 +94,8 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe private int mPosition = -1; private Playable media; - private ViewPager mPager; - private AudioplayerPagerAdapter mPagerAdapter; + private ViewPager pager; + private AudioplayerPagerAdapter pagerAdapter; private Subscription subscription; @@ -116,8 +116,8 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe // don't risk creating memory leaks navAdapter = null; drawerToggle = null; - mPager = null; - mPagerAdapter = null; + pager = null; + pagerAdapter = null; } @Override @@ -126,27 +126,29 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe } private void saveCurrentFragment() { - if(mPager == null) { + if(pager == null) { return; } Log.d(TAG, "Saving preferences"); SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); prefs.edit() - .putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, mPager.getCurrentItem()) + .putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, pager.getCurrentItem()) .commit(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - drawerToggle.onConfigurationChanged(newConfig); + if(drawerToggle != null) { + drawerToggle.onConfigurationChanged(newConfig); + } } private void loadLastFragment() { Log.d(TAG, "Restoring instance state"); SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); int lastPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1); - mPager.setCurrentItem(lastPosition); + pager.setCurrentItem(lastPosition); } @Override @@ -166,9 +168,9 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe true); startService(launchIntent); } - if(mPagerAdapter != null && controller != null && controller.getMedia() != media) { + if(pagerAdapter != null && controller != null && controller.getMedia() != media) { media = controller.getMedia(); - mPagerAdapter.onMediaChanged(media); + pagerAdapter.onMediaChanged(media); } EventDistributor.getInstance().register(contentUpdate); @@ -255,13 +257,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe startActivity(new Intent(AudioplayerActivity.this, PreferenceController.getPreferenceActivity())); }); - mPager = (ViewPager) findViewById(R.id.pager); - mPagerAdapter = new AudioplayerPagerAdapter(getSupportFragmentManager()); - mPager.setAdapter(mPagerAdapter); + pager = (ViewPager) findViewById(R.id.pager); + pagerAdapter = new AudioplayerPagerAdapter(getSupportFragmentManager()); + pager.setAdapter(pagerAdapter); CirclePageIndicator pageIndicator = (CirclePageIndicator) findViewById(R.id.page_indicator); - pageIndicator.setViewPager(mPager); + pageIndicator.setViewPager(pager); loadLastFragment(); - mPager.onSaveInstanceState(); + pager.onSaveInstanceState(); } @Override @@ -277,13 +279,16 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe } if(controller.getMedia() != media) { media = controller.getMedia(); - mPagerAdapter.onMediaChanged(media); + pagerAdapter.onMediaChanged(media); } return true; } public void notifyMediaPositionChanged() { - ChaptersFragment chaptersFragment = mPagerAdapter.getChaptersFragment(); + if(pagerAdapter == null) { + return; + } + ChaptersFragment chaptersFragment = pagerAdapter.getChaptersFragment(); if(chaptersFragment != null) { ChaptersListAdapter adapter = (ChaptersListAdapter) chaptersFragment.getListAdapter(); if (adapter != null) { @@ -411,13 +416,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements NavDrawe public void onBackPressed() { if(isDrawerOpen()) { drawerLayout.closeDrawer(navDrawer); - } else if (mPager.getCurrentItem() == 0) { + } else if (pager == null || pager.getCurrentItem() == 0) { // If the user is currently looking at the first step, allow the system to handle the // Back button. This calls finish() on this activity and pops the back stack. super.onBackPressed(); } else { // Otherwise, select the previous step. - mPager.setCurrentItem(mPager.getCurrentItem() - 1); + pager.setCurrentItem(pager.getCurrentItem() - 1); } } 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 bd7da7c03..9874d83a6 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -184,7 +184,7 @@ public class MainActivity extends AppCompatActivity implements NavDrawerActivity loadFragment(lastFragment, null); } else { try { - loadFeedFragmentById(Integer.valueOf(lastFragment), null); + loadFeedFragmentById(Integer.parseInt(lastFragment), null); } catch (NumberFormatException e) { // it's not a number, this happens if we removed // a label from the NAV_DRAWER_TAGS 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 e890f03b7..dce3d2172 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -31,6 +31,8 @@ import com.bumptech.glide.Glide; import com.joanzapata.iconify.IconDrawable; import com.joanzapata.iconify.fonts.FontAwesomeIcons; +import java.util.Locale; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; @@ -206,8 +208,10 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O @Override protected void onPause() { super.onPause(); - controller.reinitServiceIfPaused(); - controller.pause(); + if(controller != null) { + controller.reinitServiceIfPaused(); + controller.pause(); + } } /** @@ -229,8 +233,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O protected void onBufferUpdate(float progress) { if (sbPosition != null) { - sbPosition.setSecondaryProgress((int) progress - * sbPosition.getMax()); + sbPosition.setSecondaryProgress((int) progress * sbPosition.getMax()); } } @@ -246,6 +249,9 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O controller.release(); } controller = newPlaybackController(); + if(butPlay != null) { + butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + } } @Override @@ -454,9 +460,10 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O if(controller != null && controller.canSetPlaybackSpeed()) { float playbackSpeed = (progress + 10) / 20.0f; controller.setPlaybackSpeed(playbackSpeed); - String speed = String.format("%.2f", playbackSpeed); - UserPreferences.setPlaybackSpeed(speed); - txtvPlaybackSpeed.setText(speed + "x"); + String speedPref = String.format(Locale.US, "%.2f", playbackSpeed); + UserPreferences.setPlaybackSpeed(speedPref); + String speedStr = String.format("%.2fx", playbackSpeed); + txtvPlaybackSpeed.setText(speedStr); } else if(fromUser) { float speed = Float.valueOf(UserPreferences.getPlaybackSpeed()); barPlaybackSpeed.post(() -> { @@ -583,7 +590,9 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O super.onResume(); Log.d(TAG, "onResume()"); StorageUtils.checkStorageAvailability(this); - controller.init(); + if(controller != null) { + controller.init(); + } } /** @@ -597,32 +606,31 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O protected abstract void clearStatusMsg(); protected void onPositionObserverUpdate() { - if (controller != null) { - int currentPosition = controller.getPosition(); - int duration = controller.getDuration(); - Log.d(TAG, "currentPosition " + Converter - .getDurationStringLong(currentPosition)); - if (currentPosition != PlaybackService.INVALID_TIME - && duration != PlaybackService.INVALID_TIME - && controller.getMedia() != null) { - txtvPosition.setText(Converter - .getDurationStringLong(currentPosition)); - if (showTimeLeft) { - txtvLength.setText("-" + Converter - .getDurationStringLong(duration - currentPosition)); - } else { - txtvLength.setText(Converter - .getDurationStringLong(duration)); - } - updateProgressbarPosition(currentPosition, duration); - } else { - Log.w(TAG, "Could not react to position observer update because of invalid time"); - } + if (controller == null || txtvPosition == null || txtvLength == null) { + return; + } + int currentPosition = controller.getPosition(); + int duration = controller.getDuration(); + Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition)); + if (currentPosition == PlaybackService.INVALID_TIME || + duration == PlaybackService.INVALID_TIME) { + Log.w(TAG, "Could not react to position observer update because of invalid time"); + return; } + txtvPosition.setText(Converter.getDurationStringLong(currentPosition)); + if (showTimeLeft) { + txtvLength.setText("-" + Converter.getDurationStringLong(duration - currentPosition)); + } else { + txtvLength.setText(Converter.getDurationStringLong(duration)); + } + updateProgressbarPosition(currentPosition, duration); } private void updateProgressbarPosition(int position, int duration) { Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")"); + if(sbPosition == null) { + return; + } float progress = ((float) position) / duration; sbPosition.setProgress((int) (progress * sbPosition.getMax())); } @@ -639,17 +647,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); if (media != null) { - txtvPosition.setText(Converter.getDurationStringLong((media.getPosition()))); - - if (media.getDuration() != 0) { - txtvLength.setText(Converter.getDurationStringLong(media.getDuration())); - float progress = ((float) media.getPosition()) / media.getDuration(); - sbPosition.setProgress((int) (progress * sbPosition.getMax())); - if (showTimeLeft) { - int timeLeft = media.getDuration() - media.getPosition(); - txtvLength.setText("-" + Converter.getDurationStringLong(timeLeft)); - } - } + onPositionObserverUpdate(); checkFavorite(); if(butPlaybackSpeed != null) { if (controller == null) { @@ -763,8 +761,7 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O if (butRev != null) { butRev.setOnClickListener(v -> { - int curr = controller.getPosition(); - controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); + onRewind(); }); butRev.setOnLongClickListener(new View.OnLongClickListener() { @@ -802,12 +799,13 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O }); } - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> { + onPlayPause(); + }); if (butFF != null) { butFF.setOnClickListener(v -> { - int curr = controller.getPosition(); - controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); + onFastForward(); }); butFF.setOnLongClickListener(new View.OnLongClickListener() { @@ -852,13 +850,35 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O } } + protected void onRewind() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000); + } + + protected void onPlayPause() { + if(controller == null) { + return; + } + controller.playPause(); + } + + protected void onFastForward() { + if (controller == null) { + return; + } + int curr = controller.getPosition(); + controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000); + } + protected abstract int getContentViewResourceId(); void handleError(int errorCode) { final AlertDialog.Builder errorDialog = new AlertDialog.Builder(this); errorDialog.setTitle(R.string.error_label); - errorDialog - .setMessage(MediaPlayerError.getErrorString(this, errorCode)); + errorDialog.setMessage(MediaPlayerError.getErrorString(this, errorCode)); errorDialog.setNeutralButton("OK", (dialog, which) -> { dialog.dismiss(); @@ -872,19 +892,28 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O @Override public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) { - if (controller != null) { - prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, txtvPosition); - if (showTimeLeft && prog != 0) { - int duration = controller.getDuration(); - String length = "-" + Converter.getDurationStringLong(duration - (int) (prog * duration)); - txtvLength.setText(length); - } + if (controller == null || txtvLength == null) { + return; + } + prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, txtvPosition); + if (showTimeLeft && prog != 0) { + int duration = controller.getDuration(); + String length = "-" + Converter.getDurationStringLong(duration - (int) (prog * duration)); + txtvLength.setText(length); } } private void updateButPlaybackSpeed() { if (controller != null && butPlaybackSpeed != null) { - butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed() + "x"); + float speed = 1.0f; + try { + speed = Float.parseFloat(UserPreferences.getPlaybackSpeed()); + } catch(NumberFormatException e) { + Log.e(TAG, Log.getStackTraceString(e)); + UserPreferences.setPlaybackSpeed(String.valueOf(speed)); + } + String speedStr = String.format("%.2fx", speed); + butPlaybackSpeed.setText(speedStr); } } @@ -904,21 +933,25 @@ public abstract class MediaplayerActivity extends AppCompatActivity implements O private void checkFavorite() { Playable playable = controller.getMedia(); - if (playable != null && playable instanceof FeedMedia) { - FeedItem feedItem = ((FeedMedia) playable).getItem(); - if (feedItem != null) { - Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId())) - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(item -> { - boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE); - if(isFavorite != isFav) { - isFavorite = isFav; - invalidateOptionsMenu(); - } - }); - } + if (playable != null && playable instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia) playable).getItem(); + if (feedItem != null) { + Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId())) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + item -> { + boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE); + if (isFavorite != isFav) { + isFavorite = isFav; + invalidateOptionsMenu(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + } + ); } + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java index 8a9ec58d3..bc3e686e5 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java @@ -2,23 +2,22 @@ package de.danoeh.antennapod.activity; import android.content.Intent; import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; import android.support.v7.app.ActionBarActivity; import android.util.SparseBooleanArray; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.opml.OpmlElement; -import de.danoeh.antennapod.core.preferences.UserPreferences; import java.util.ArrayList; import java.util.List; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.opml.OpmlElement; +import de.danoeh.antennapod.core.preferences.UserPreferences; + /** * Displays the feeds that the OPML-Importer has read and lets the user choose * which feeds he wants to import. @@ -33,6 +32,9 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { private ListView feedlist; private ArrayAdapter<String> listAdapter; + private MenuItem selectAll; + private MenuItem deselectAll; + @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getTheme()); @@ -44,45 +46,54 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { feedlist = (ListView) findViewById(R.id.feedlist); feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - listAdapter = new ArrayAdapter<String>(this, + listAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice, getTitleList()); feedlist.setAdapter(listAdapter); - - butCancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - setResult(RESULT_CANCELED); - finish(); + feedlist.setOnItemClickListener((parent, view, position, id) -> { + SparseBooleanArray checked = feedlist.getCheckedItemPositions(); + int checkedCount = 0; + for (int i = 0; i < checked.size(); i++) { + if (checked.valueAt(i)) { + checkedCount++; + } + } + if(checkedCount == listAdapter.getCount()) { + selectAll.setVisible(false); + deselectAll.setVisible(true); + } else { + deselectAll.setVisible(false); + selectAll.setVisible(true); } }); - butConfirm.setOnClickListener(new OnClickListener() { + butCancel.setOnClickListener(v -> { + setResult(RESULT_CANCELED); + finish(); + }); - @Override - public void onClick(View v) { - Intent intent = new Intent(); - SparseBooleanArray checked = feedlist.getCheckedItemPositions(); + butConfirm.setOnClickListener(v -> { + Intent intent = new Intent(); + SparseBooleanArray checked = feedlist.getCheckedItemPositions(); - int checkedCount = 0; - // Get number of checked items - for (int i = 0; i < checked.size(); i++) { - if (checked.valueAt(i)) { - checkedCount++; - } + int checkedCount = 0; + // Get number of checked items + for (int i = 0; i < checked.size(); i++) { + if (checked.valueAt(i)) { + checkedCount++; } - int[] selection = new int[checkedCount]; - for (int i = 0, collected = 0; collected < checkedCount; i++) { - if (checked.valueAt(i)) { - selection[collected] = checked.keyAt(i); - collected++; - } + } + int[] selection = new int[checkedCount]; + for (int i = 0, collected = 0; collected < checkedCount; i++) { + if (checked.valueAt(i)) { + selection[collected] = checked.keyAt(i); + collected++; } - intent.putExtra(EXTRA_SELECTED_ITEMS, selection); - setResult(RESULT_OK, intent); - finish(); } + intent.putExtra(EXTRA_SELECTED_ITEMS, selection); + setResult(RESULT_OK, intent); + finish(); }); } @@ -93,7 +104,6 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { for (OpmlElement element : OpmlImportHolder.getReadElements()) { result.add(element.getText()); } - } return result; } @@ -101,13 +111,11 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE, - R.string.select_all_label), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE, - R.string.deselect_all_label), - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.opml_selection_options, menu); + selectAll = menu.findItem(R.id.select_all_item); + deselectAll = menu.findItem(R.id.deselect_all_item); + deselectAll.setVisible(false); return true; } @@ -115,10 +123,14 @@ public class OpmlFeedChooserActivity extends ActionBarActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.select_all_item: + selectAll.setVisible(false); selectAllItems(true); + deselectAll.setVisible(true); return true; case R.id.deselect_all_item: + deselectAll.setVisible(false); selectAllItems(false); + selectAll.setVisible(true); return true; default: return false; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java index 46dabec12..fb145db1f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; +import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v7.app.ActionBarActivity; import android.util.Log; @@ -31,7 +32,7 @@ public class OpmlImportBaseActivity extends ActionBarActivity { private OpmlImportWorker importWorker; private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; - private Uri uri; + @Nullable private Uri uri; /** * Handles the choices made by the user in the OpmlFeedChooserActivity and @@ -67,7 +68,14 @@ public class OpmlImportBaseActivity extends ActionBarActivity { } } - protected void importUri(Uri uri) { + protected void importUri(@Nullable Uri uri) { + if(uri == null) { + new MaterialDialog.Builder(this) + .content(R.string.opml_import_error_no_file) + .positiveText(android.R.string.ok) + .show(); + return; + } this.uri = uri; if(uri.toString().contains(Environment.getExternalStorageDirectory().toString())) { int permission = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE); @@ -127,8 +135,9 @@ public class OpmlImportBaseActivity extends ActionBarActivity { importWorker.executeAsync(); } catch (Exception e) { Log.d(TAG, Log.getStackTraceString(e)); + String message = getString(R.string.opml_reader_error); new MaterialDialog.Builder(this) - .content("Cannot open OPML file: " + e.getMessage()) + .content(message + " " + e.getMessage()) .positiveText(android.R.string.ok) .show(); } 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 ee459dbc6..42c9edd99 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -202,6 +202,24 @@ public class VideoplayerActivity extends MediaplayerActivity { videoControlsShowing = !videoControlsShowing; } + @Override + protected void onRewind() { + super.onRewind(); + setupVideoControlsToggler(); + } + + @Override + protected void onPlayPause() { + super.onPlayPause(); + setupVideoControlsToggler(); + } + + @Override + protected void onFastForward() { + super.onFastForward(); + setupVideoControlsToggler(); + } + private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -312,7 +330,7 @@ public class VideoplayerActivity extends MediaplayerActivity { private static class VideoControlsHider extends Handler { - private static final int DELAY = 5000; + private static final int DELAY = 2500; private WeakReference<VideoplayerActivity> activity; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java index 6d4fc6f1e..07f895468 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -202,7 +202,8 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR @Override public long getItemId(int position) { - return position; + FeedItem item = itemAccess.getItem(position); + return item != null ? item.getId() : RecyclerView.NO_POSITION; } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index 9ff80424c..34999757f 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -13,6 +13,7 @@ import android.widget.Toast; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconButton; +import com.joanzapata.iconify.widget.IconTextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; @@ -48,7 +49,7 @@ public class DownloadLogAdapter extends BaseAdapter { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.downloadlog_item, parent, false); - holder.icon = (TextView) convertView.findViewById(R.id.txtvIcon); + holder.icon = (IconTextView) convertView.findViewById(R.id.txtvIcon); holder.retry = (IconButton) convertView.findViewById(R.id.btnRetry); holder.date = (TextView) convertView.findViewById(R.id.txtvDate); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); @@ -78,14 +79,12 @@ public class DownloadLogAdapter extends BaseAdapter { holder.icon.setTextColor(ContextCompat.getColor(convertView.getContext(), R.color.download_success_green)); holder.icon.setText("{fa-check-circle}"); - Iconify.addIcons(holder.icon); holder.retry.setVisibility(View.GONE); holder.reason.setVisibility(View.GONE); } else { holder.icon.setTextColor(ContextCompat.getColor(convertView.getContext(), R.color.download_failed_red)); holder.icon.setText("{fa-times-circle}"); - Iconify.addIcons(holder.icon); String reasonText = status.getReason().getErrorString(context); if (status.getReasonDetailed() != null) { reasonText += ": " + status.getReasonDetailed(); @@ -160,7 +159,7 @@ public class DownloadLogAdapter extends BaseAdapter { } static class Holder { - TextView icon; + IconTextView icon; IconButton retry; TextView title; TextView type; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index 269614d35..6d7e6dcac 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -232,7 +232,7 @@ public class NavListAdapter extends BaseAdapter int spaceUsed = itemAccess.getNumberOfDownloadedItems() - itemAccess.getReclaimableItems(); - if (spaceUsed >= epCacheSize) { + if (epCacheSize > 0 && spaceUsed >= epCacheSize) { holder.count.setText("{md-disc-full 150%}"); Iconify.addIcons(holder.count); holder.count.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index aa3368258..6cf61f90b 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -109,6 +109,12 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap return selectedItem; } + @Override + public long getItemId(int position) { + FeedItem item = itemAccess.getItem(position); + return item != null ? item.getId() : RecyclerView.NO_POSITION; + } + public int getItemCount() { return itemAccess.getCount(); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index 47ac4c757..e9756b467 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -167,7 +167,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { for(int i=0; imageUrl == null && i < images.length(); i++) { JSONObject image = images.getJSONObject(i); String height = image.getJSONObject("attributes").getString("height"); - if(Integer.valueOf(height) >= 100) { + if(Integer.parseInt(height) >= 100) { imageUrl = image.getString("label"); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java new file mode 100644 index 000000000..d745bb51c --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java @@ -0,0 +1,323 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.content.ContextCompat; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.materialdialogs.internal.MDButton; +import com.squareup.okhttp.Credentials; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.service.download.ProxyConfig; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +public class ProxyDialog { + + private static final String TAG = "ProxyDialog"; + + private Context context; + + private MaterialDialog dialog; + + private Spinner spType; + private EditText etHost; + private EditText etPort; + private EditText etUsername; + private EditText etPassword; + + private boolean testSuccessful = false; + private TextView txtvMessage; + private Subscription subscription; + + public ProxyDialog(Context context) { + this.context = context; + } + + public Dialog createDialog() { + dialog = new MaterialDialog.Builder(context) + .title(R.string.pref_proxy_title) + .customView(R.layout.proxy_settings, true) + .positiveText(R.string.proxy_test_label) + .negativeText(R.string.cancel_label) + .onPositive((dialog1, which) -> { + if(!testSuccessful) { + dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); + test(); + return; + } + String type = (String) ((Spinner) dialog1.findViewById(R.id.spType)).getSelectedItem(); + ProxyConfig proxy; + if(Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { + proxy = ProxyConfig.direct(); + } else { + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + if(TextUtils.isEmpty(username)) { + username = null; + } + String password = etPassword.getText().toString(); + if(TextUtils.isEmpty(password)) { + password = null; + } + int portValue = 0; + if(!TextUtils.isEmpty(port)) { + portValue = Integer.valueOf(port); + } + proxy = ProxyConfig.http(host, portValue, username, password); + } + UserPreferences.setProxyConfig(proxy); + AntennapodHttpClient.reinit(); + dialog.dismiss(); + }) + .onNegative((dialog1, which) -> { + dialog1.dismiss(); + }) + .autoDismiss(false) + .build(); + View view = dialog.getCustomView(); + spType = (Spinner) view.findViewById(R.id.spType); + String[] types = { Proxy.Type.DIRECT.name(), Proxy.Type.HTTP.name() }; + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, types); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spType.setAdapter(adapter); + ProxyConfig proxyConfig = UserPreferences.getProxyConfig(); + spType.setSelection(adapter.getPosition(proxyConfig.type.name())); + etHost = (EditText) view.findViewById(R.id.etHost); + if(!TextUtils.isEmpty(proxyConfig.host)) { + etHost.setText(proxyConfig.host); + } + etHost.addTextChangedListener(requireTestOnChange); + etPort = (EditText) view.findViewById(R.id.etPort); + if(proxyConfig.port > 0) { + etPort.setText(String.valueOf(proxyConfig.port)); + } + etPort.addTextChangedListener(requireTestOnChange); + etUsername = (EditText) view.findViewById(R.id.etUsername); + if(!TextUtils.isEmpty(proxyConfig.username)) { + etUsername.setText(proxyConfig.username); + } + etUsername.addTextChangedListener(requireTestOnChange); + etPassword = (EditText) view.findViewById(R.id.etPassword); + if(!TextUtils.isEmpty(proxyConfig.password)) { + etPassword.setText(proxyConfig.username); + } + etPassword.addTextChangedListener(requireTestOnChange); + if(proxyConfig.type == Proxy.Type.DIRECT) { + enableSettings(false); + setTestRequired(false); + } + spType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + enableSettings(position > 0); + setTestRequired(position > 0); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + enableSettings(false); + } + }); + txtvMessage = (TextView) view.findViewById(R.id.txtvMessage); + checkValidity(); + return dialog; + } + + private final TextWatcher requireTestOnChange = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + setTestRequired(true); + } + }; + + private void enableSettings(boolean enable) { + etHost.setEnabled(enable); + etPort.setEnabled(enable); + etUsername.setEnabled(enable); + etPassword.setEnabled(enable); + } + + private boolean checkValidity() { + boolean valid = true; + if(spType.getSelectedItemPosition() > 0) { + valid &= checkHost(); + } + valid &= checkPort(); + return valid; + } + + private boolean checkHost() { + String host = etHost.getText().toString(); + if(host.length() == 0) { + etHost.setError(context.getString(R.string.proxy_host_empty_error)); + return false; + } + if(!"localhost".equals(host) && !Patterns.DOMAIN_NAME.matcher(host).matches()) { + etHost.setError(context.getString(R.string.proxy_host_invalid_error)); + return false; + } + return true; + } + + private boolean checkPort() { + int port = getPort(); + if(port < 0 && port > 65535) { + etPort.setError(context.getString(R.string.proxy_port_invalid_error)); + return false; + } + return true; + } + + private int getPort() { + String port = etPort.getText().toString(); + if(port.length() > 0) { + try { + int portValue = Integer.parseInt(port); + return portValue; + } catch(NumberFormatException e) { + // ignore + } + } + return 0; + } + + private void setTestRequired(boolean required) { + if(required) { + testSuccessful = false; + MDButton button = dialog.getActionButton(DialogAction.POSITIVE); + button.setText(context.getText(R.string.proxy_test_label)); + button.setEnabled(true); + } else { + testSuccessful = true; + MDButton button = dialog.getActionButton(DialogAction.POSITIVE); + button.setText(context.getText(android.R.string.ok)); + button.setEnabled(true); + } + } + + private void test() { + if(subscription != null) { + subscription.unsubscribe(); + } + if(!checkValidity()) { + setTestRequired(true); + return; + } + TypedArray res = context.getTheme().obtainStyledAttributes(new int[] { android.R.attr.textColorPrimary }); + int textColorPrimary = res.getColor(0, 0); + res.recycle(); + String checking = context.getString(R.string.proxy_checking); + txtvMessage.setTextColor(textColorPrimary); + txtvMessage.setText("{fa-circle-o-notch spin} " + checking); + txtvMessage.setVisibility(View.VISIBLE); + subscription = Observable.create(new Observable.OnSubscribe<Response>() { + @Override + public void call(Subscriber<? super Response> subscriber) { + String type = (String) spType.getSelectedItem(); + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + String password = etPassword.getText().toString(); + int portValue = 8080; + if(!TextUtils.isEmpty(port)) { + portValue = Integer.valueOf(port); + } + SocketAddress address = InetSocketAddress.createUnresolved(host, portValue); + Proxy.Type proxyType = Proxy.Type.valueOf(type.toUpperCase()); + Proxy proxy = new Proxy(proxyType, address); + OkHttpClient client = AntennapodHttpClient.newHttpClient(); + client.setConnectTimeout(10, TimeUnit.SECONDS); + client.setProxy(proxy); + client.interceptors().clear(); + if(!TextUtils.isEmpty(username)) { + String credentials = Credentials.basic(username, password); + client.interceptors().add(chain -> { + Request request = chain.request().newBuilder() + .header("Proxy-Authorization", credentials).build(); + return chain.proceed(request); + }); + } + Request request = new Request.Builder() + .url("http://www.google.com") + .head() + .build(); + try { + Response response = client.newCall(request).execute(); + subscriber.onNext(response); + } catch(IOException e) { + subscriber.onError(e); + } + subscriber.onCompleted(); + } + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + response -> { + int colorId; + String icon; + String result; + if(response.isSuccessful()) { + colorId = R.color.download_success_green; + icon = "{fa-check}"; + result = context.getString(R.string.proxy_test_successful); + } else { + colorId = R.color.download_failed_red; + icon = "{fa-close}"; + result = context.getString(R.string.proxy_test_failed); + } + int color = ContextCompat.getColor(context, colorId); + txtvMessage.setTextColor(color); + String message = String.format("%s %s: %s", icon, result, response.message()); + txtvMessage.setText(message); + setTestRequired(!response.isSuccessful()); + }, + error -> { + String icon = "{fa-close}"; + String result = context.getString(R.string.proxy_test_failed); + int color = ContextCompat.getColor(context, R.color.download_failed_red); + txtvMessage.setTextColor(color); + String message = String.format("%s %s: %s", icon, result, error.getMessage()); + txtvMessage.setText(message); + setTestRequired(true); + } + ); + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index 930079e40..89e24f882 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -138,7 +138,7 @@ public abstract class SleepTimerDialog { private long readTimeMillis() { TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()]; - long value = Long.valueOf(etxtTime.getText().toString()); + long value = Long.parseLong(etxtTime.getText().toString()); return selectedUnit.toMillis(value); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index 273c75240..aa2879330 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -337,6 +337,7 @@ public class AllEpisodesFragment extends Fragment { MainActivity mainActivity = activity.get(); listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, new DefaultActionButtonCallback(mainActivity), showOnlyNewEpisodes()); + listAdapter.setHasStableIds(true); recyclerView.setAdapter(listAdapter); } listAdapter.notifyDataSetChanged(); 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 931d14924..d3b97f9df 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -63,12 +63,7 @@ public class CoverFragment extends Fragment implements AudioplayerContentFragmen } private void loadMediaInfo() { - if(imgvCover == null) { - return; - } if (media != null) { - Log.d(TAG, "feed title: " + media.getFeedTitle()); - Log.d(TAG, "episode title: " + media.getEpisodeTitle()); txtvPodcastTitle.setText(media.getFeedTitle()); txtvEpisodeTitle.setText(media.getEpisodeTitle()); Glide.with(this) @@ -103,7 +98,7 @@ public class CoverFragment extends Fragment implements AudioplayerContentFragmen @Override public void onMediaChanged(Playable media) { - if(this.media == media) { + if(!isAdded() || this.media == media) { return; } this.media = media; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 80a9bf0b3..3fd69214d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -72,7 +72,11 @@ public class ExternalPlayerFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); controller = setupPlaybackController(); - butPlay.setOnClickListener(controller.newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> { + if(controller != null) { + controller.playPause(); + } + }); } private PlaybackController setupPlaybackController() { @@ -88,7 +92,6 @@ public class ExternalPlayerFragment extends Fragment { return butPlay; } - @Override public boolean loadMediaInfo() { ExternalPlayerFragment fragment = ExternalPlayerFragment.this; @@ -145,8 +148,11 @@ public class ExternalPlayerFragment extends Fragment { } controller = setupPlaybackController(); if (butPlay != null) { - butPlay.setOnClickListener(controller - .newOnPlayButtonClickListener()); + butPlay.setOnClickListener(v -> { + if(controller != null) { + controller.playPause(); + } + }); } controller.init(); } 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 4c723e5ff..c1c1aab6c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -62,7 +62,6 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont private static final String ARG_HIGHLIGHT_TIMECODES = "arg.highlightTimecodes"; private WebView webvDescription; - private String webvData; private ShownotesProvider shownotesProvider; private Playable media; @@ -309,7 +308,6 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> { - webvData = data; webvDescription.loadDataWithBaseURL(null, data, "text/html", "utf-8", "about:blank"); Log.d(TAG, "Webview loaded"); @@ -384,7 +382,7 @@ public class ItemDescriptionFragment extends Fragment implements AudioplayerCont @Override public void onMediaChanged(Playable media) { - if(this.media == media) { + if(this.media == media || webvDescription == null) { return; } this.media = media; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java index eb947dc2b..b736688b9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java @@ -124,7 +124,7 @@ public class ItunesSearchFragment extends Fragment { } else { gridView.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); - rx.Observable.create((Observable.OnSubscribe<String>) subscriber -> { + subscription = Observable.create((Observable.OnSubscribe<String>) subscriber -> { OkHttpClient client = AntennapodHttpClient.getHttpClient(); Request.Builder httpReq = new Request.Builder() .url(podcast.feedUrl) @@ -233,7 +233,7 @@ public class ItunesSearchFragment extends Fragment { butRetry.setVisibility(View.GONE); txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); - subscription = rx.Observable.create((Observable.OnSubscribe<List<Podcast>>) subscriber -> { + subscription = Observable.create((Observable.OnSubscribe<List<Podcast>>) subscriber -> { String lang = Locale.getDefault().getLanguage(); String url = "https://itunes.apple.com/" + lang + "/rss/toppodcasts/limit=25/explicit=true/json"; OkHttpClient client = AntennapodHttpClient.getHttpClient(); 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 b3f6c3534..e5bb1d895 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -474,6 +474,7 @@ public class QueueFragment extends Fragment { MainActivity activity = (MainActivity) getActivity(); recyclerAdapter = new QueueRecyclerAdapter(activity, itemAccess, new DefaultActionButtonCallback(activity), itemTouchHelper); + recyclerAdapter.setHasStableIds(true); recyclerView.setAdapter(recyclerAdapter); } if(queue == null || queue.size() == 0) { 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 6d76047be..152412089 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -59,6 +59,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrUtils; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; +import de.danoeh.antennapod.dialog.ProxyDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; /** @@ -84,6 +85,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc public static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; public static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + public static final String PREF_PROXY = "prefProxy"; private final PreferenceUI ui; @@ -243,7 +245,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc (preference, o) -> { if (o instanceof String) { try { - int value = Integer.valueOf((String) o); + int value = Integer.parseInt((String) o); if (1 <= value && value <= 50) { setParallelDownloadsText(value); return true; @@ -270,7 +272,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc public void afterTextChanged(Editable s) { if (s.length() > 0) { try { - int value = Integer.valueOf(s.toString()); + int value = Integer.parseInt(s.toString()); if (value <= 0) { ev.setText("1"); } else if (value > 50) { @@ -353,7 +355,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( (preference, o) -> { if (o instanceof String) { - int newValue = Integer.valueOf((String) o) * 1024 * 1024; + int newValue = Integer.parseInt((String) o) * 1024 * 1024; if (newValue != UserPreferences.getImageCacheSize()) { AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity()); dialog.setTitle(android.R.string.dialog_alert_title); @@ -366,6 +368,11 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc return false; } ); + ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> { + ProxyDialog dialog = new ProxyDialog(ui.getActivity()); + dialog.createDialog().show(); + return true; + }); ui.findPreference("prefSendCrashReport").setOnPreferenceClickListener(preference -> { Intent emailIntent = new Intent(Intent.ACTION_SEND); emailIntent.setType("text/plain"); @@ -792,7 +799,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc checkedItem = ArrayUtils.indexOf(values, currIntervalStr); } builder1.setSingleChoiceItems(entries, checkedItem, (dialog1, which1) -> { - int hours = Integer.valueOf(values[which1]); + int hours = Integer.parseInt(values[which1]); UserPreferences.setUpdateInterval(hours); dialog1.dismiss(); setUpdateIntervalText(); diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java index 7000827c6..cf9866097 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/PlayerWidget.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.receiver; +import java.util.Arrays; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; @@ -44,7 +46,7 @@ public class PlayerWidget extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + appWidgetIds + "]"); + Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]"); startUpdate(context); } diff --git a/app/src/main/res/layout/downloadlog_item.xml b/app/src/main/res/layout/downloadlog_item.xml index 20d879933..7b4773bca 100644 --- a/app/src/main/res/layout/downloadlog_item.xml +++ b/app/src/main/res/layout/downloadlog_item.xml @@ -10,14 +10,13 @@ android:paddingBottom="8dp" tools:background="@android:color/darker_gray"> - <TextView + <com.joanzapata.iconify.widget.IconTextView android:id="@+id/txtvIcon" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:textSize="48sp" - tools:text="[Icon]" android:gravity="center" /> <com.joanzapata.iconify.widget.IconButton diff --git a/app/src/main/res/layout/proxy_settings.xml b/app/src/main/res/layout/proxy_settings.xml new file mode 100644 index 000000000..983325030 --- /dev/null +++ b/app/src/main/res/layout/proxy_settings.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/txtvType" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/proxy_type_label" + android:textColor="?android:attr/textColorSecondary" /> + + <Spinner + android:id="@+id/spType" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/txtvHost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/host_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etHost" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:inputType="textUri" + android:hint="www.example.com" /> + + <TextView + android:id="@+id/txtvPort" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/port_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etPort" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="8080" + android:inputType="number" /> + + <TextView + android:id="@+id/txtvUsername" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:singleLine="true" + android:text="@string/username_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etUsername" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/optional_hint" /> + + <TextView + android:id="@+id/txtvPassword" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/password_label" + android:textColor="?android:attr/textColorSecondary" /> + + <EditText + android:id="@+id/etPassword" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/optional_hint" + android:inputType="textPassword" /> + + <com.joanzapata.iconify.widget.IconTextView + android:id="@+id/txtvMessage" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:visibility="invisible" + android:gravity="center"/> + +</LinearLayout> diff --git a/app/src/main/res/layout/queue_fragment.xml b/app/src/main/res/layout/queue_fragment.xml index 901ac49fc..71bf16d30 100644 --- a/app/src/main/res/layout/queue_fragment.xml +++ b/app/src/main/res/layout/queue_fragment.xml @@ -23,7 +23,7 @@ <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_below="@id/divider" android:scrollbars="vertical"/> diff --git a/app/src/main/res/menu/opml_selection_options.xml b/app/src/main/res/menu/opml_selection_options.xml new file mode 100644 index 000000000..26d2a0519 --- /dev/null +++ b/app/src/main/res/menu/opml_selection_options.xml @@ -0,0 +1,19 @@ +<?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/select_all_item" + android:icon="?attr/ic_check_box_outline" + android:title="@string/select_all_label" + custom:showAsAction="ifRoom"> + </item> + + <item + android:id="@id/deselect_all_item" + android:icon="?attr/ic_check_box" + android:title="@string/deselect_all_label" + custom:showAsAction="ifRoom"> + </item> + +</menu> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f290d3031..e0671e67b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -200,8 +200,11 @@ android:key="prefEnableAutoDownloadWifiFilter" android:title="@string/pref_autodl_wifi_filter_title" android:summary="@string/pref_autodl_wifi_filter_sum"/> - </PreferenceScreen> + <Preference + android:key="prefProxy" + android:summary="@string/pref_proxy_sum" + android:title="@string/pref_proxy_title" /> </PreferenceCategory> diff --git a/build.gradle b/build.gradle index 53ea77e5d..dd7cd4385 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ project.ext { minSdkVersion = 10 targetSdkVersion = 23 - supportVersion = "23.1.1" + supportVersion = "23.2.1" commonsioVersion = "2.4" commonslangVersion = "3.4" eventbusVersion = "2.4.0" @@ -49,7 +49,7 @@ project.ext { glideVersion = "3.6.1" iconifyVersion = "2.1.1" jsoupVersion = "1.7.3" - materialDialogsVersion = "0.8.5.3@aar" + materialDialogsVersion = "0.8.5.6@aar" okhttpVersion = "2.7.4" okioVersion = "1.6.0" recyclerviewFlexibledividerVersion = "1.2.6" @@ -57,7 +57,7 @@ project.ext { rxJavaVersion = "1.1.0" rxJavaRulesVersion = "1.1.0.0" - audioPlayerVersion = "v1.0.12" + audioPlayerVersion = "v1.0.14" } task wrapper(type: Wrapper) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java index ac032fcc0..c0ec8d08a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java @@ -17,6 +17,7 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; @@ -46,8 +47,8 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke public static enum ExitCode {EXIT_NORMAL, NO_TOKEN, NO_NETWORK, NO_THINGS} - private volatile int countFailed = 0; - private volatile int countSuccess = 0; + private final AtomicInteger countFailed = new AtomicInteger(); + private final AtomicInteger countSuccess = new AtomicInteger(); private volatile FlattrThing extraFlattrThing; @@ -114,12 +115,12 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke FlattrUtils.clickUrl(context, thing.getPaymentLink()); thing.getFlattrStatus().setFlattred(); publishProgress(R.string.flattr_click_success); - countSuccess++; + countSuccess.incrementAndGet(); } catch (FlattrException e) { e.printStackTrace(); - countFailed++; - if (countFailed == 1) { + int failed = countFailed.incrementAndGet(); + if (failed == 1) { exception = e; } } @@ -148,7 +149,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke super.onPostExecute(exitCode); switch (exitCode) { case EXIT_NORMAL: - if (countFailed > 0) { + if (countFailed.get() > 0) { postFlattrFailedNotification(); } break; @@ -190,7 +191,8 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke } private void postFlattrFailedNotification() { - if (countFailed == 0) { + int failed = countFailed.get(); + if (failed == 0) { return; } @@ -198,15 +200,15 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke String title; String subtext; - if (countFailed == 1) { + if (failed == 1) { title = context.getString(R.string.flattrd_failed_label); String exceptionMsg = (exception.getMessage() != null) ? exception.getMessage() : ""; subtext = context.getString(R.string.flattr_click_failure, extraFlattrThing.getTitle()) + "\n" + exceptionMsg; } else { title = context.getString(R.string.flattrd_label); - subtext = context.getString(R.string.flattr_click_success_count, countSuccess) + "\n" - + context.getString(R.string.flattr_click_failure_count, countFailed); + subtext = context.getString(R.string.flattr_click_success_count, countSuccess.get()) + "\n" + + context.getString(R.string.flattr_click_failure_count, failed); } Notification notification = new NotificationCompat.Builder(context) diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java index 2d174a6bc..01c888d6b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java @@ -63,10 +63,10 @@ public class GpodnetEpisodeAction { Action action = Action.valueOf(fields[3]); GpodnetEpisodeAction result = new Builder(podcast, episode, action) .deviceId(deviceId) - .timestamp(new Date(Long.valueOf(fields[4]))) - .started(Integer.valueOf(fields[5])) - .position(Integer.valueOf(fields[6])) - .total(Integer.valueOf(fields[7])) + .timestamp(new Date(Long.parseLong(fields[4]))) + .started(Integer.parseInt(fields[5])) + .position(Integer.parseInt(fields[6])) + .total(Integer.parseInt(fields[7])) .build(); return result; } catch(IllegalArgumentException e) { 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 6c0aff15e..3631f881b 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 @@ -17,6 +17,7 @@ import org.json.JSONException; import java.io.File; import java.io.IOException; +import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -25,6 +26,7 @@ import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver; +import de.danoeh.antennapod.core.service.download.ProxyConfig; import de.danoeh.antennapod.core.storage.APCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm; import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm; @@ -78,6 +80,11 @@ public class UserPreferences { public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery"; public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; + public static final String PREF_PROXY_TYPE = "prefProxyType"; + public static final String PREF_PROXY_HOST = "prefProxyHost"; + public static final String PREF_PROXY_PORT = "prefProxyPort"; + public static final String PREF_PROXY_USER = "prefProxyUser"; + public static final String PREF_PROXY_PASSWORD = "prefProxyPassword"; // Services public static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; @@ -158,12 +165,12 @@ public class UserPreferences { public static int getFeedOrder() { String value = prefs.getString(PREF_DRAWER_FEED_ORDER, "0"); - return Integer.valueOf(value); + return Integer.parseInt(value); } public static int getFeedCounterSetting() { String value = prefs.getString(PREF_DRAWER_FEED_COUNTER, "0"); - return Integer.valueOf(value); + return Integer.parseInt(value); } /** @@ -243,7 +250,7 @@ public class UserPreferences { } public static int getSmartMarkAsPlayedSecs() { - return Integer.valueOf(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")); + return Integer.parseInt(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")); } public static boolean isAutoFlattr() { @@ -295,8 +302,8 @@ public class UserPreferences { String datetime = prefs.getString(PREF_UPDATE_INTERVAL, ""); if(datetime.length() >= 3 && datetime.contains(":")) { String[] parts = datetime.split(":"); - int hourOfDay = Integer.valueOf(parts[0]); - int minute = Integer.valueOf(parts[1]); + int hourOfDay = Integer.parseInt(parts[0]); + int minute = Integer.parseInt(parts[1]); return new int[] { hourOfDay, minute }; } else { return new int[0]; @@ -308,7 +315,7 @@ public class UserPreferences { } public static int getParallelDownloads() { - return Integer.valueOf(prefs.getString(PREF_PARALLEL_DOWNLOADS, "4")); + return Integer.parseInt(prefs.getString(PREF_PARALLEL_DOWNLOADS, "4")); } public static int getEpisodeCacheSizeUnlimited() { @@ -338,12 +345,12 @@ public class UserPreferences { public static int getImageCacheSize() { String cacheSizeString = prefs.getString(PREF_IMAGE_CACHE_SIZE, IMAGE_CACHE_DEFAULT_VALUE); - int cacheSizeInt = Integer.valueOf(cacheSizeString); + int cacheSizeInt = Integer.parseInt(cacheSizeString); // if the cache size is too small the user won't get any images at all // that's bad, force it back to the default. if (cacheSizeInt < IMAGE_CACHE_SIZE_MINIMUM) { prefs.edit().putString(PREF_IMAGE_CACHE_SIZE, IMAGE_CACHE_DEFAULT_VALUE).apply(); - cacheSizeInt = Integer.valueOf(IMAGE_CACHE_DEFAULT_VALUE); + cacheSizeInt = Integer.parseInt(IMAGE_CACHE_DEFAULT_VALUE); } int cacheSizeMB = cacheSizeInt * 1024 * 1024; return cacheSizeMB; @@ -371,6 +378,42 @@ public class UserPreferences { return TextUtils.split(selectedNetWorks, ","); } + public static void setProxyConfig(ProxyConfig config) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(PREF_PROXY_TYPE, config.type.name()); + if(TextUtils.isEmpty(config.host)) { + editor.remove(PREF_PROXY_HOST); + } else { + editor.putString(PREF_PROXY_HOST, config.host); + } + if(config.port <= 0 || config.port > 65535) { + editor.remove(PREF_PROXY_PORT); + } else { + editor.putInt(PREF_PROXY_PORT, config.port); + } + if(TextUtils.isEmpty(config.username)) { + editor.remove(PREF_PROXY_USER); + } else { + editor.putString(PREF_PROXY_USER, config.username); + } + if(TextUtils.isEmpty(config.password)) { + editor.remove(PREF_PROXY_PASSWORD); + } else { + editor.putString(PREF_PROXY_PASSWORD, config.password); + } + editor.apply(); + } + + public static ProxyConfig getProxyConfig() { + Proxy.Type type = Proxy.Type.valueOf(prefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name())); + String host = prefs.getString(PREF_PROXY_HOST, null); + int port = prefs.getInt(PREF_PROXY_PORT, 0); + String username = prefs.getString(PREF_PROXY_USER, null); + String password = prefs.getString(PREF_PROXY_PASSWORD, null); + ProxyConfig config = new ProxyConfig(type, host, port, username, password); + return config; + } + public static boolean shouldResumeAfterCall() { return prefs.getBoolean(PREF_RESUME_AFTER_CALL, true); } @@ -494,7 +537,7 @@ public class UserPreferences { if (valueFromPrefs.equals(context.getString(R.string.pref_episode_cache_unlimited))) { return EPISODE_CACHE_SIZE_UNLIMITED; } else { - return Integer.valueOf(valueFromPrefs); + return Integer.parseInt(valueFromPrefs); } } @@ -548,7 +591,7 @@ public class UserPreferences { public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() { - int cleanupValue = Integer.valueOf(prefs.getString(PREF_EPISODE_CLEANUP, "-1")); + int cleanupValue = Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1")); if (cleanupValue == EPISODE_CLEANUP_QUEUE) { return new APQueueCleanupAlgorithm(); } else if (cleanupValue == EPISODE_CLEANUP_NULL) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java index b23819ef7..5dd1e2dfa 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java @@ -2,8 +2,10 @@ package de.danoeh.antennapod.core.service.download; import android.os.Build; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; +import com.squareup.okhttp.Credentials; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; @@ -14,7 +16,10 @@ import java.net.CookieManager; import java.net.CookiePolicy; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.Socket; +import java.net.SocketAddress; import java.net.URL; import java.security.GeneralSecurityException; import java.util.concurrent.TimeUnit; @@ -23,6 +28,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBWriter; /** @@ -44,12 +50,15 @@ public class AntennapodHttpClient { */ public static synchronized OkHttpClient getHttpClient() { if (httpClient == null) { - httpClient = newHttpClient(); } return httpClient; } + public static synchronized void reinit() { + httpClient = newHttpClient(); + } + /** * Creates a new HTTP client. Most users should just use * getHttpClient() to get the standard AntennaPod client, @@ -69,13 +78,13 @@ public class AntennapodHttpClient { client.networkInterceptors().add(chain -> { Request request = chain.request(); Response response = chain.proceed(request); - if(response.code() == HttpURLConnection.HTTP_MOVED_PERM || + if (response.code() == HttpURLConnection.HTTP_MOVED_PERM || response.code() == StatusLine.HTTP_PERM_REDIRECT) { String location = response.header("Location"); - if(location.startsWith("/")) { // URL is not absolute, but relative + if (location.startsWith("/")) { // URL is not absolute, but relative URL url = request.url(); location = url.getProtocol() + "://" + url.getHost() + location; - } else if(!location.toLowerCase().startsWith("http://") && + } else if (!location.toLowerCase().startsWith("http://") && !location.toLowerCase().startsWith("https://")) { // Reference is relative to current path URL url = request.url(); @@ -106,6 +115,21 @@ public class AntennapodHttpClient { client.setFollowRedirects(true); client.setFollowSslRedirects(true); + ProxyConfig config = UserPreferences.getProxyConfig(); + if (config.type != Proxy.Type.DIRECT) { + int port = config.port > 0 ? config.port : ProxyConfig.DEFAULT_PORT; + SocketAddress address = InetSocketAddress.createUnresolved(config.host, port); + Proxy proxy = new Proxy(config.type, address); + client.setProxy(proxy); + if (!TextUtils.isEmpty(config.username)) { + String credentials = Credentials.basic(config.username, config.password); + client.interceptors().add(chain -> { + Request request = chain.request().newBuilder() + .header("Proxy-Authorization", credentials).build(); + return chain.proceed(request); + }); + } + } if(16 <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT < 21) { client.setSslSocketFactory(new CustomSslSocketFactory()); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java index 7f40ea6e2..de91916a9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadRequest.java @@ -128,7 +128,8 @@ public class DownloadRequest implements Parcelable { DownloadRequest that = (DownloadRequest) o; - if (lastModified != that.lastModified) return false; + if (lastModified != null ? !lastModified.equals(that.lastModified) : that.lastModified != null) + return false; if (deleteOnFailure != that.deleteOnFailure) return false; if (feedfileId != that.feedfileId) return false; if (feedfileType != that.feedfileType) return false; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index 090c48ca0..be6cd88cb 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -188,7 +188,7 @@ public class DownloadService extends Service { if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { postAuthenticationNotification(downloader.getDownloadRequest()); } else if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR - && Integer.valueOf(status.getReasonDetailed()) == 416) { + && Integer.parseInt(status.getReasonDetailed()) == 416) { Log.d(TAG, "Requested invalid range, restarting download from the beginning"); FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination())); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java index e3a195253..2b26f58c0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java @@ -27,6 +27,7 @@ import java.util.Date; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.feed.FeedImage; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.DownloadError; import de.danoeh.antennapod.core.util.StorageUtils; @@ -67,6 +68,12 @@ public class HttpDownloader extends Downloader { final URI uri = URIUtil.getURIFromRequestUrl(request.getSource()); Request.Builder httpReq = new Request.Builder().url(uri.toURL()) .header("User-Agent", ClientConfig.USER_AGENT); + if(request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + // set header explicitly so that okhttp doesn't do transparent gzip + Log.d(TAG, "addHeader(\"Accept-Encoding\", \"identity\")"); + httpReq.addHeader("Accept-Encoding", "identity"); + } + if(!TextUtils.isEmpty(request.getLastModified())) { String lastModified = request.getLastModified(); Date lastModifiedDate = DateUtils.parse(lastModified); @@ -117,6 +124,15 @@ public class HttpDownloader extends Downloader { throw e; } } + + if(request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + String contentType = response.header("Content-Type"); + if(!contentType.startsWith("audio/") && !contentType.startsWith("video/")) { + onFail(DownloadError.ERROR_FILE_TYPE, null); + return; + } + } + responseBody = response.body(); String contentEncodingHeader = response.header("Content-Encoding"); boolean isGzip = false; @@ -180,7 +196,7 @@ public class HttpDownloader extends Downloader { && !TextUtils.isEmpty(contentRangeHeader)) { String start = contentRangeHeader.substring("bytes ".length(), contentRangeHeader.indexOf("-")); - request.setSoFar(Long.valueOf(start)); + request.setSoFar(Long.parseLong(start)); Log.d(TAG, "Starting download at position " + request.getSoFar()); out = new RandomAccessFile(destination, "rw"); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java new file mode 100644 index 000000000..e886932f2 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/ProxyConfig.java @@ -0,0 +1,32 @@ +package de.danoeh.antennapod.core.service.download; + +import android.support.annotation.Nullable; + +import java.net.Proxy; + +public class ProxyConfig { + + public final Proxy.Type type; + @Nullable public final String host; + @Nullable public final int port; + @Nullable public final String username; + @Nullable public final String password; + + public final static int DEFAULT_PORT = 8080; + + public static ProxyConfig direct() { + return new ProxyConfig(Proxy.Type.DIRECT, null, 0, null, null); + } + + public static ProxyConfig http(String host, int port, String username, String password) { + return new ProxyConfig(Proxy.Type.HTTP, host, port, username, password); + } + + public ProxyConfig(Proxy.Type type, String host, int port, String username, String password) { + this.type = type; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + } +} 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 a82e82506..d3a5f576a 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 @@ -604,7 +604,11 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved. */ public int getPosition() { - if (!playerLock.tryLock()) { + try { + if (!playerLock.tryLock(50, TimeUnit.MILLISECONDS)) { + return INVALID_TIME; + } + } catch (InterruptedException e) { return INVALID_TIME; } @@ -693,7 +697,7 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre } /** - * Sets the playback speed. + * Sets the playback volume. * This method is executed on an internal executor service. */ public void setVolume(final float volumeLeft, float volumeRight) { @@ -701,7 +705,7 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre } /** - * Sets the playback speed. + * Sets the playback volume. * This method is executed on the caller's thread. */ private void setVolumeSync(float volumeLeft, float volumeRight) { @@ -946,15 +950,16 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre if (pausedBecauseOfTransientAudiofocusLoss) { // we paused => play now resume(); } else { // we ducked => raise audio level back - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_RAISE, 0); + setVolumeSync(UserPreferences.getLeftVolume(), + UserPreferences.getRightVolume()); } } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { if (playerStatus == PlayerStatus.PLAYING) { if (!UserPreferences.shouldPauseForFocusLoss()) { Log.d(TAG, "Lost audio focus temporarily. Ducking..."); - audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, - AudioManager.ADJUST_LOWER, 0); + final float DUCK_FACTOR = 0.25f; + setVolumeSync(DUCK_FACTOR * UserPreferences.getLeftVolume(), + DUCK_FACTOR * UserPreferences.getRightVolume()); pausedBecauseOfTransientAudiofocusLoss = false; } else { Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing..."); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 68187306d..4f2400e1d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -4,6 +4,7 @@ import android.database.Cursor; import android.support.v4.util.ArrayMap; import android.util.Log; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -676,7 +677,7 @@ public final class DBReader { * as well as chapter marks of the FeedItems will also be loaded from the database. */ public static List<FeedItem> getFeedItems(final long... itemIds) { - Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + itemIds + "]"); + Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + Arrays.toString(itemIds) + "]"); PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); List<FeedItem> items = getFeedItems(adapter, itemIds); diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java index 99c4cd67a..769a796e3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java +++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSITunes.java @@ -60,12 +60,12 @@ public class NSITunes extends Namespace { try { int duration = 0; if (parts.length == 2) { - duration += TimeUnit.MINUTES.toMillis(Long.valueOf(parts[0])) + - TimeUnit.SECONDS.toMillis(Long.valueOf(parts[1])); + duration += TimeUnit.MINUTES.toMillis(Long.parseLong(parts[0])) + + TimeUnit.SECONDS.toMillis(Long.parseLong(parts[1])); } else if (parts.length >= 3) { - duration += TimeUnit.HOURS.toMillis(Long.valueOf(parts[0])) + - TimeUnit.MINUTES.toMillis(Long.valueOf(parts[1])) + - TimeUnit.SECONDS.toMillis(Long.valueOf(parts[2])); + duration += TimeUnit.HOURS.toMillis(Long.parseLong(parts[0])) + + TimeUnit.MINUTES.toMillis(Long.parseLong(parts[1])) + + TimeUnit.SECONDS.toMillis(Long.parseLong(parts[2])); } else { return; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java index 1b929b214..2e3afefa9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java @@ -88,9 +88,9 @@ public final class Converter { if (parts.length != 3) { return 0; } - return Integer.valueOf(parts[0]) * 3600 * 1000 + - Integer.valueOf(parts[1]) * 60 * 1000 + - Integer.valueOf(parts[2]) * 1000; + return Integer.parseInt(parts[0]) * 3600 * 1000 + + Integer.parseInt(parts[1]) * 60 * 1000 + + Integer.parseInt(parts[2]) * 1000; } /** Converts short duration string (HH:MM) to milliseconds. */ @@ -99,8 +99,8 @@ public final class Converter { if (parts.length != 2) { return 0; } - return Integer.valueOf(parts[0]) * 3600 * 1000 + - Integer.valueOf(parts[1]) * 1000 * 60; + return Integer.parseInt(parts[0]) * 3600 * 1000 + + Integer.parseInt(parts[1]) * 1000 * 60; } /** Converts milliseconds to a localized string containing hours and minutes */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java index 6b0f423df..314062e52 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java @@ -19,14 +19,8 @@ public class DateUtils { private static final String TAG = "DateUtils"; - private static final SimpleDateFormat parser = new SimpleDateFormat("", Locale.US); private static final TimeZone defaultTimezone = TimeZone.getTimeZone("GMT"); - static { - parser.setLenient(false); - parser.setTimeZone(defaultTimezone); - } - public static Date parse(final String input) { if(input == null) { throw new IllegalArgumentException("Date must not be null"); @@ -86,6 +80,10 @@ public class DateUtils { "yyyy-MM-dd" }; + SimpleDateFormat parser = new SimpleDateFormat("", Locale.US); + parser.setLenient(false); + parser.setTimeZone(defaultTimezone); + ParsePosition pos = new ParsePosition(0); for(String pattern : patterns) { parser.applyPattern(pattern); @@ -117,13 +115,13 @@ public class DateUtils { int idx = 0; if (parts.length == 3) { // string has hours - result += Integer.valueOf(parts[idx]) * 3600000L; + result += Integer.parseInt(parts[idx]) * 3600000L; idx++; } if (parts.length >= 2) { - result += Integer.valueOf(parts[idx]) * 60000L; + result += Integer.parseInt(parts[idx]) * 60000L; idx++; - result += (Float.valueOf(parts[idx])) * 1000L; + result += (Float.parseFloat(parts[idx])) * 1000L; } return result; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java index 602c221bf..d3f6c6b79 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java @@ -19,7 +19,8 @@ public enum DownloadError { ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host), ERROR_REQUEST_ERROR(12, R.string.download_error_request_error), ERROR_DB_ACCESS_ERROR(13, R.string.download_error_db_access), - ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized); + ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized), + ERROR_FILE_TYPE(15, R.string.download_error_file_type_type); private final int code; private final int resId; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java index 673c81235..1cbe7fade 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java @@ -35,7 +35,7 @@ public final class IntList { int hashCode = 1; for (int i = 0; i < size; i++) { int value = values[i]; - hashCode = 31 * hashCode + (int)(value ^ (value >>> 32)); + hashCode = 31 * hashCode + value; } return hashCode; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index 27935978c..1a011a90b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -19,7 +19,6 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; -import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.TextView; @@ -424,6 +423,7 @@ public abstract class PlaybackController { clearStatusMsg(); checkMediaInfoLoaded(); cancelPositionObserver(); + onPositionObserverUpdate(); updatePlayButtonAppearance(playResource, playText); if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) { setScreenOn(false); @@ -574,34 +574,32 @@ public abstract class PlaybackController { } - public OnClickListener newOnPlayButtonClickListener() { - return v -> { - if (playbackService == null) { - Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!"); - return; - } - switch (status) { - case PLAYING: - playbackService.pause(true, reinitOnPause); - break; - case PAUSED: - case PREPARED: - playbackService.resume(); - break; - case PREPARING: - playbackService.setStartWhenPrepared(!playbackService - .isStartWhenPrepared()); - if (reinitOnPause - && playbackService.isStartWhenPrepared() == false) { - playbackService.reinit(); - } - break; - case INITIALIZED: - playbackService.setStartWhenPrepared(true); - playbackService.prepare(); - break; - } - }; + public void playPause() { + if (playbackService == null) { + Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!"); + return; + } + switch (status) { + case PLAYING: + playbackService.pause(true, reinitOnPause); + break; + case PAUSED: + case PREPARED: + playbackService.resume(); + break; + case PREPARING: + playbackService.setStartWhenPrepared(!playbackService + .isStartWhenPrepared()); + if (reinitOnPause + && playbackService.isStartWhenPrepared() == false) { + playbackService.reinit(); + } + break; + case INITIALIZED: + playbackService.setStartWhenPrepared(true); + playbackService.prepare(); + break; + } } public boolean serviceAvailable() { 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 2eee1ac87..8d9151396 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 @@ -2,7 +2,10 @@ package de.danoeh.antennapod.core.util.playback; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Color; +import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; @@ -14,6 +17,7 @@ import org.jsoup.select.Elements; import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.ShownotesProvider; @@ -32,26 +36,32 @@ public class Timeline { private ShownotesProvider shownotesProvider; - - private final String colorString; + private final String noShownotesLabel; + private final String colorPrimaryString; + private final String colorSecondaryString; private final int pageMargin; public Timeline(Context context, ShownotesProvider shownotesProvider) { if (shownotesProvider == null) throw new IllegalArgumentException("shownotesProvider = null"); this.shownotesProvider = shownotesProvider; - TypedArray res = context - .getTheme() - .obtainStyledAttributes( - new int[]{android.R.attr.textColorPrimary}); - int colorResource = res.getColor(0, 0); - colorString = String.format("#%06X", - 0xFFFFFF & colorResource); + noShownotesLabel = context.getString(R.string.no_shownotes_label); + + TypedArray res = context.getTheme().obtainStyledAttributes( + new int[]{ android.R.attr.textColorPrimary}); + @ColorInt int col = res.getColor(0, 0); + colorPrimaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," + + Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")"; + res.recycle(); + res = context.getTheme().obtainStyledAttributes( + new int[]{android.R.attr.textColorSecondary}); + col = res.getColor(0, 0); + colorSecondaryString = "rgba(" + Color.red(col) + "," + Color.green(col) + "," + + Color.blue(col) + "," + (Color.alpha(col)/256.0) + ")"; res.recycle(); - pageMargin = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 8, context.getResources() - .getDisplayMetrics() + pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, + context.getResources().getDisplayMetrics() ); } @@ -82,9 +92,24 @@ public class Timeline { e.printStackTrace(); return null; } - if (shownotes == null) { - Log.d(TAG, "shownotesProvider contained no shownotes. Returning empty string"); - return ""; + + if(TextUtils.isEmpty(shownotes)) { + Log.d(TAG, "shownotesProvider contained no shownotes. Returning 'no shownotes' message"); + shownotes ="<html>" + + "<head>" + + "<style type='text/css'>" + + "html, body { margin: 0; padding: 0; width: 100%; height: 100%; } " + + "html { display: table; }" + + "body { display: table-cell; vertical-align: middle; text-align:center;" + + "-webkit-text-size-adjust: none; font-size: 87%; color: " + colorSecondaryString + ";} " + + "</style>" + + "</head>" + + "<body>" + + "<p>" + noShownotesLabel + "</p>" + + "</body>" + + "</html>"; + Log.d(TAG, "shownotes: " + shownotes); + return shownotes; } // replace ASCII line breaks with HTML ones if shownotes don't contain HTML line breaks already @@ -95,7 +120,7 @@ public class Timeline { Document document = Jsoup.parse(shownotes); // apply style - String styleStr = String.format(WEBVIEW_STYLE, colorString, "100%", pageMargin, + String styleStr = String.format(WEBVIEW_STYLE, colorPrimaryString, "100%", pageMargin, pageMargin, pageMargin, pageMargin); document.head().appendElement("style").attr("type", "text/css").text(styleStr); @@ -125,8 +150,7 @@ public class Timeline { element.html(buffer.toString()); } } - - Log.i(TAG, "Out: " + document.toString()); + return document.toString(); } @@ -148,7 +172,7 @@ public class Timeline { try { if (m.find()) { - return Integer.valueOf(m.group(1)); + return Integer.parseInt(m.group(1)); } } catch (NumberFormatException e) { e.printStackTrace(); diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index d4891eee8..777db5046 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -178,6 +178,7 @@ <string name="download_error_connection_error">Connection Error</string> <string name="download_error_unknown_host">Unknown Host</string> <string name="download_error_unauthorized">Authentication Error</string> + <string name="download_error_file_type_type">File Type Error</string> <string name="cancel_all_downloads_label">Cancel all downloads</string> <string name="download_canceled_msg">Download canceled</string> <string name="download_canceled_autodownload_enabled_msg">Download canceled\nDisabled <i>Auto Download</i> for this item</string> @@ -278,6 +279,8 @@ <string name="no_items_label">There are no items in this list.</string> <string name="no_feeds_label">You haven\'t subscribed to any feeds yet.</string> <string name="no_chapters_label">This episode has no chapters.</string> + <string name="no_shownotes_label">This episode has no shownotes.</string> + <!-- Preferences --> <string name="other_pref">Other</string> @@ -388,6 +391,8 @@ <string name="pref_sonic_title">Sonic media player</string> <string name="pref_sonic_message">Use built-in sonic media player as a replacement for Android\'s native mediaplayer and Prestissimo</string> <string name="pref_current_value">Current value: %1$s</string> + <string name="pref_proxy_title">Proxy</string> + <string name="pref_proxy_sum">Set a network proxy</string> <!-- Auto-Flattr dialog --> <string name="auto_flattr_enable">Enable automatic flattring</string> @@ -412,8 +417,8 @@ <string name="opml_import_label">OPML Import</string> <string name="opml_directory_error">ERROR!</string> <string name="reading_opml_label">Reading OPML file</string> - <string name="opml_reader_error">An error has occurred while reading the opml document:</string> - <string name="opml_import_error_dir_empty">The import directory is empty.</string> + <string name="opml_reader_error">An error has occurred while reading the OPML document:</string> + <string name="opml_import_error_no_file">No file selected!</string> <string name="select_all_label">Select all</string> <string name="deselect_all_label">Deselect all</string> <string name="select_options_label">Select…</string> @@ -594,4 +599,17 @@ <string name="stereo_to_mono">Downmix: Stereo to mono</string> <string name="sonic_only">Sonic only</string> + <!-- proxy settings --> + <string name="proxy_type_label">Type</string> + <string name="host_label">Host</string> + <string name="port_label">Port</string> + <string name="optional_hint">(Optional)</string> + <string name="proxy_test_label">Test</string> + <string name="proxy_checking">Checking…</string> + <string name="proxy_test_successful">Test successful</string> + <string name="proxy_test_failed">Test failed</string> + <string name="proxy_host_empty_error">Host must not be empty</string> + <string name="proxy_host_invalid_error">Host not valid UP or domain</string> + <string name="proxy_port_invalid_error">Port not valid</string> + </resources> diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index c4a731a53..d79ba6b45 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -63,6 +63,7 @@ <style name="Theme.AntennaPod.Dark" parent="Theme.AppCompat"> <item name="colorAccent">@color/holo_blue_dark</item> + <item name="colorControlNormal">@color/white</item> <item name="buttonStyle">@style/Widget.AntennaPod.Button</item> <item name="progressBarTheme">@style/ProgressBarDark</item> <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item> @@ -186,6 +187,7 @@ <item name="windowActionModeOverlay">true</item> <item name="progressBarTheme">@style/ProgressBarDark</item> <item name="colorAccent">@color/holo_blue_dark</item> + <item name="colorControlNormal">@color/white</item> <item name="buttonStyle">@style/Widget.AntennaPod.Button</item> <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item> <item name="attr/action_about">@drawable/ic_info_white_24dp</item> |