From 3c033cc0fbaa4b1418cda330ac0287d735c621f2 Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Sat, 4 Jun 2016 01:36:25 +0200 Subject: Create one flavor with Google Cast support and one (free) without --- .../antennapod/activity/CastEnabledActivity.java | 218 +++++ .../danoeh/antennapod/activity/MainActivity.java | 773 +++++++++++++++++ .../antennapod/activity/MediaplayerActivity.java | 876 +++++++++++++++++++ .../danoeh/antennapod/fragment/ItemFragment.java | 590 +++++++++++++ .../preferences/PreferenceController.java | 951 +++++++++++++++++++++ 5 files changed, 3408 insertions(+) create mode 100644 app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java create mode 100644 app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java create mode 100644 app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java create mode 100644 app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java create mode 100644 app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java (limited to 'app/src/free') diff --git a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java new file mode 100644 index 000000000..d82b199db --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -0,0 +1,218 @@ +package de.danoeh.antennapod.activity; + +import android.support.v7.app.AppCompatActivity; + +/** + * Activity that allows for showing the MediaRouter button whenever there's a cast device in the + * network. + */ +public abstract class CastEnabledActivity extends AppCompatActivity { +// implements SharedPreferences.OnSharedPreferenceChangeListener { + public static final String TAG = "CastEnabledActivity"; + +// protected CastManager castManager; +// protected SwitchableMediaRouteActionProvider mediaRouteActionProvider; +// private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(); +// +// @Override +// protected void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// +// PreferenceManager.getDefaultSharedPreferences(getApplicationContext()). +// registerOnSharedPreferenceChangeListener(this); +// +// castManager = CastManager.getInstance(); +// castManager.addCastConsumer(castConsumer); +// castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); +// onCastConnectionChanged(castManager.isConnected()); +// } +// +// @Override +// protected void onDestroy() { +// PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) +// .unregisterOnSharedPreferenceChangeListener(this); +// castManager.removeCastConsumer(castConsumer); +// super.onDestroy(); +// } +// +// @Override +// @CallSuper +// public boolean onCreateOptionsMenu(Menu menu) { +// super.onCreateOptionsMenu(menu); +// getMenuInflater().inflate(R.menu.cast_enabled, menu); +// castButtonVisibilityManager.setMenu(menu); +// return true; +// } +// +// @Override +// @CallSuper +// public boolean onPrepareOptionsMenu(Menu menu) { +// super.onPrepareOptionsMenu(menu); +// mediaRouteActionProvider = castManager +// .addMediaRouterButton(menu.findItem(R.id.media_route_menu_item)); +// mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable()); +// return true; +// } +// +// @Override +// protected void onResume() { +// super.onResume(); +// castButtonVisibilityManager.setResumed(true); +// } +// +// @Override +// protected void onPause() { +// super.onPause(); +// castButtonVisibilityManager.setResumed(false); +// } +// +// @Override +// public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { +// if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { +// boolean newValue = UserPreferences.isCastEnabled(); +// Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue); +// castButtonVisibilityManager.setPrefEnabled(newValue); +// // PlaybackService has its own listener, so if it's active we don't have to take action here. +// if (!newValue && !PlaybackService.isRunning) { +// CastManager.getInstance().disconnect(); +// } +// } +// } +// +// CastConsumer castConsumer = new DefaultCastConsumer() { +// @Override +// public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { +// onCastConnectionChanged(true); +// } +// +// @Override +// public void onDisconnected() { +// onCastConnectionChanged(false); +// } +// }; +// +// private void onCastConnectionChanged(boolean connected) { +// if (connected) { +// castButtonVisibilityManager.onConnected(); +// setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); +// } else { +// castButtonVisibilityManager.onDisconnected(); +// setVolumeControlStream(AudioManager.STREAM_MUSIC); +// } +// } +// +// /** +// * Should be called by any activity or fragment for which the cast button should be shown. +// * +// * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)} +// */ +// public final void requestCastButton(int showAsAction) { +// castButtonVisibilityManager.requestCastButton(showAsAction); +// } +// +// private class CastButtonVisibilityManager { +// private volatile boolean prefEnabled = false; +// private volatile boolean viewRequested = false; +// private volatile boolean resumed = false; +// private volatile boolean connected = false; +// private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; +// private Menu menu; +// +// public synchronized void setPrefEnabled(boolean newValue) { +// if (prefEnabled != newValue && resumed && (viewRequested || connected)) { +// if (newValue) { +// castManager.incrementUiCounter(); +// } else { +// castManager.decrementUiCounter(); +// } +// } +// prefEnabled = newValue; +// if (mediaRouteActionProvider != null) { +// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); +// } +// } +// +// public synchronized void setResumed(boolean newValue) { +// if (resumed == newValue) { +// Log.e(TAG, "resumed should never change to the same value"); +// return; +// } +// resumed = newValue; +// if (prefEnabled && (viewRequested || connected)) { +// if (resumed) { +// castManager.incrementUiCounter(); +// } else { +// castManager.decrementUiCounter(); +// } +// } +// } +// +// public synchronized void setViewRequested(boolean newValue) { +// if (viewRequested != newValue && resumed && prefEnabled && !connected) { +// if (newValue) { +// castManager.incrementUiCounter(); +// } else { +// castManager.decrementUiCounter(); +// } +// } +// viewRequested = newValue; +// if (mediaRouteActionProvider != null) { +// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); +// } +// } +// +// public synchronized void setConnected(boolean newValue) { +// if (connected != newValue && resumed && prefEnabled && !prefEnabled) { +// if (newValue) { +// castManager.incrementUiCounter(); +// } else { +// castManager.decrementUiCounter(); +// } +// } +// connected = newValue; +// if (mediaRouteActionProvider != null) { +// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected)); +// } +// } +// +// public synchronized boolean shouldEnable() { +// return prefEnabled && viewRequested; +// } +// +// public void setMenu(Menu menu) { +// setViewRequested(false); +// showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM; +// this.menu = menu; +// setShowAsAction(); +// } +// +// public void requestCastButton(int showAsAction) { +// setViewRequested(true); +// this.showAsAction = showAsAction; +// setShowAsAction(); +// } +// +// public void onConnected() { +// setConnected(true); +// setShowAsAction(); +// } +// +// public void onDisconnected() { +// setConnected(false); +// setShowAsAction(); +// } +// +// private void setShowAsAction() { +// if (menu == null) { +// Log.d(TAG, "setShowAsAction() without a menu"); +// return; +// } +// MenuItem item = menu.findItem(R.id.media_route_menu_item); +// if (item == null) { +// Log.e(TAG, "setShowAsAction(), but cast button not inflated"); +// return; +// } +// MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction); +// } +// } +} diff --git a/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java new file mode 100644 index 000000000..c4771a583 --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java @@ -0,0 +1,773 @@ +package de.danoeh.antennapod.activity; + +import android.annotation.TargetApi; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.database.DataSetObserver; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import com.bumptech.glide.Glide; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.Validate; + +import java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.NavListAdapter; +import de.danoeh.antennapod.core.asynctask.FeedRemover; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.ProgressEvent; +import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.FeedItemUtil; +import de.danoeh.antennapod.core.util.StorageUtils; +import de.danoeh.antennapod.dialog.RatingDialog; +import de.danoeh.antennapod.fragment.AddFeedFragment; +import de.danoeh.antennapod.fragment.DownloadsFragment; +import de.danoeh.antennapod.fragment.EpisodesFragment; +import de.danoeh.antennapod.fragment.ExternalPlayerFragment; +import de.danoeh.antennapod.fragment.ItemlistFragment; +import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; +import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.menuhandler.NavDrawerActivity; +import de.danoeh.antennapod.preferences.PreferenceController; +import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * The activity that is shown when the user launches the app. + */ +public class MainActivity extends CastEnabledActivity implements NavDrawerActivity { + + private static final String TAG = "MainActivity"; + + private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; + + public static final String PREF_NAME = "MainActivityPrefs"; + public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch"; + public static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag"; + + public static final String EXTRA_NAV_TYPE = "nav_type"; + public static final String EXTRA_NAV_INDEX = "nav_index"; + public static final String EXTRA_FRAGMENT_TAG = "fragment_tag"; + public static final String EXTRA_FRAGMENT_ARGS = "fragment_args"; + public static final String EXTRA_FEED_ID = "fragment_feed_id"; + + public static final String SAVE_BACKSTACK_COUNT = "backstackCount"; + public static final String SAVE_TITLE = "title"; + + public static final String[] NAV_DRAWER_TAGS = { + QueueFragment.TAG, + EpisodesFragment.TAG, + SubscriptionFragment.TAG, + DownloadsFragment.TAG, + PlaybackHistoryFragment.TAG, + AddFeedFragment.TAG, + NavListAdapter.SUBSCRIPTION_LIST_TAG + }; + + private Toolbar toolbar; + private ExternalPlayerFragment externalPlayerFragment; + private DrawerLayout drawerLayout; + + private View navDrawer; + private ListView navList; + private NavListAdapter navAdapter; + private int mPosition = -1; + + private ActionBarDrawerToggle drawerToggle; + + private CharSequence currentTitle; + + private ProgressDialog pd; + + private Subscription subscription; + + @Override + public void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getNoTitleTheme()); + super.onCreate(savedInstanceState); + StorageUtils.checkStorageAvailability(this); + setContentView(R.layout.main); + + toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + findViewById(R.id.shadow).setVisibility(View.GONE); + int elevation = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, + getResources().getDisplayMetrics()); + getSupportActionBar().setElevation(elevation); + } + + currentTitle = getTitle(); + + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + navList = (ListView) findViewById(R.id.nav_list); + navDrawer = findViewById(R.id.nav_layout); + + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close); + if (savedInstanceState != null) { + int backstackCount = savedInstanceState.getInt(SAVE_BACKSTACK_COUNT, 0); + drawerToggle.setDrawerIndicatorEnabled(backstackCount == 0); + } + drawerLayout.setDrawerListener(drawerToggle); + + final FragmentManager fm = getSupportFragmentManager(); + + fm.addOnBackStackChangedListener(() -> { + drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0); + }); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + + navAdapter = new NavListAdapter(itemAccess, this); + navList.setAdapter(navAdapter); + navList.setOnItemClickListener(navListClickListener); + navList.setOnItemLongClickListener(newListLongClickListener); + registerForContextMenu(navList); + + navAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + selectedNavListIndex = getSelectedNavListIndex(); + } + }); + + findViewById(R.id.nav_settings).setOnClickListener(v -> { + drawerLayout.closeDrawer(navDrawer); + startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity())); + }); + + FragmentTransaction transaction = fm.beginTransaction(); + + Fragment mainFragment = fm.findFragmentByTag("main"); + if (mainFragment != null) { + transaction.replace(R.id.main_view, mainFragment); + } else { + String lastFragment = getLastNavFragment(); + if (ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) { + loadFragment(lastFragment, null); + } else { + try { + 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 + // give them a nice default... + loadFragment(QueueFragment.TAG, null); + } + } + } + externalPlayerFragment = new ExternalPlayerFragment(); + transaction.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG); + transaction.commit(); + + checkFirstLaunch(); + } + + private void saveLastNavFragment(String tag) { + Log.d(TAG, "saveLastNavFragment(tag: " + tag +")"); + SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + if(tag != null) { + edit.putString(PREF_LAST_FRAGMENT_TAG, tag); + } else { + edit.remove(PREF_LAST_FRAGMENT_TAG); + } + edit.commit(); + } + + private String getLastNavFragment() { + SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); + String lastFragment = prefs.getString(PREF_LAST_FRAGMENT_TAG, QueueFragment.TAG); + Log.d(TAG, "getLastNavFragment() -> " + lastFragment); + return lastFragment; + } + + private void checkFirstLaunch() { + SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); + if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) { + new Handler().postDelayed(() -> drawerLayout.openDrawer(navDrawer), 1500); + + // for backward compatibility, we only change defaults for fresh installs + UserPreferences.setUpdateInterval(12); + + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean(PREF_IS_FIRST_LAUNCH, false); + edit.commit(); + } + } + + public void showDrawerPreferencesDialog() { + final List hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); + String[] navLabels = new String[NAV_DRAWER_TAGS.length]; + final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length]; + for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) { + String tag = NAV_DRAWER_TAGS[i]; + navLabels[i] = navAdapter.getLabel(tag); + if (!hiddenDrawerItems.contains(tag)) { + checked[i] = true; + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); + builder.setTitle(R.string.drawer_preferences); + builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + + public boolean isDrawerOpen() { + return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); + } + + public List getFeeds() { + return (navDrawerData != null) ? navDrawerData.feeds : null; + } + + public void loadFragment(int index, Bundle args) { + Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")"); + if (index < navAdapter.getSubscriptionOffset()) { + String tag = navAdapter.getTags().get(index); + loadFragment(tag, args); + } else { + int pos = index - navAdapter.getSubscriptionOffset(); + loadFeedFragmentByPosition(pos, args); + } + } + + public void loadFragment(String tag, Bundle args) { + Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")"); + Fragment fragment = null; + switch (tag) { + case QueueFragment.TAG: + fragment = new QueueFragment(); + break; + case EpisodesFragment.TAG: + fragment = new EpisodesFragment(); + break; + case DownloadsFragment.TAG: + fragment = new DownloadsFragment(); + break; + case PlaybackHistoryFragment.TAG: + fragment = new PlaybackHistoryFragment(); + break; + case AddFeedFragment.TAG: + fragment = new AddFeedFragment(); + break; + case SubscriptionFragment.TAG: + SubscriptionFragment subscriptionFragment = new SubscriptionFragment(); + fragment = subscriptionFragment; + break; + default: + // default to the queue + tag = QueueFragment.TAG; + fragment = new QueueFragment(); + args = null; + break; + } + currentTitle = navAdapter.getLabel(tag); + getSupportActionBar().setTitle(currentTitle); + saveLastNavFragment(tag); + if (args != null) { + fragment.setArguments(args); + } + loadFragment(fragment); + } + + private void loadFeedFragmentByPosition(int relPos, Bundle args) { + if(relPos < 0) { + return; + } + Feed feed = itemAccess.getItem(relPos); + loadFeedFragmentById(feed.getId(), args); + } + + public void loadFeedFragmentById(long feedId, Bundle args) { + Fragment fragment = ItemlistFragment.newInstance(feedId); + if(args != null) { + fragment.setArguments(args); + } + saveLastNavFragment(String.valueOf(feedId)); + currentTitle = ""; + getSupportActionBar().setTitle(currentTitle); + loadFragment(fragment); + } + + private void loadFragment(Fragment fragment) { + FragmentManager fragmentManager = getSupportFragmentManager(); + // clear back stack + for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) { + fragmentManager.popBackStack(); + } + FragmentTransaction t = fragmentManager.beginTransaction(); + t.replace(R.id.main_view, fragment, "main"); + fragmentManager.popBackStack(); + // TODO: we have to allow state loss here + // since this function can get called from an AsyncTask which + // could be finishing after our app has already committed state + // and is about to get shutdown. What we *should* do is + // not commit anything in an AsyncTask, but that's a bigger + // change than we want now. + t.commitAllowingStateLoss(); + if (navAdapter != null) { + navAdapter.notifyDataSetChanged(); + } + } + + public void loadChildFragment(Fragment fragment) { + Validate.notNull(fragment); + FragmentManager fm = getSupportFragmentManager(); + fm.beginTransaction() + .replace(R.id.main_view, fragment, "main") + .addToBackStack(null) + .commit(); + } + + public void dismissChildFragment() { + getSupportFragmentManager().popBackStack(); + } + + private int getSelectedNavListIndex() { + String currentFragment = getLastNavFragment(); + if(currentFragment == null) { + // should not happen, but better safe than sorry + return -1; + } + int tagIndex = navAdapter.getTags().indexOf(currentFragment); + if(tagIndex >= 0) { + return tagIndex; + } else if(ArrayUtils.contains(NAV_DRAWER_TAGS, currentFragment)) { + // the fragment was just hidden + return -1; + } else { // last fragment was not a list, but a feed + long feedId = Long.parseLong(currentFragment); + if (navDrawerData != null) { + List feeds = navDrawerData.feeds; + for (int i = 0; i < feeds.size(); i++) { + if (feeds.get(i).getId() == feedId) { + return i + navAdapter.getSubscriptionOffset(); + } + } + } + return -1; + } + } + + private AdapterView.OnItemClickListener navListClickListener = new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + int viewType = parent.getAdapter().getItemViewType(position); + if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER && position != selectedNavListIndex) { + loadFragment(position, null); + } + drawerLayout.closeDrawer(navDrawer); + } + }; + + private AdapterView.OnItemLongClickListener newListLongClickListener = new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if(position < navAdapter.getTags().size()) { + showDrawerPreferencesDialog(); + return true; + } else { + mPosition = position; + return false; + } + } + }; + + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + drawerToggle.syncState(); + if (savedInstanceState != null) { + currentTitle = savedInstanceState.getString(SAVE_TITLE); + if (!drawerLayout.isDrawerOpen(navDrawer)) { + getSupportActionBar().setTitle(currentTitle); + } + selectedNavListIndex = getSelectedNavListIndex(); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + drawerToggle.onConfigurationChanged(newConfig); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(SAVE_TITLE, getSupportActionBar().getTitle().toString()); + outState.putInt(SAVE_BACKSTACK_COUNT, getSupportFragmentManager().getBackStackEntryCount()); + } + + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().register(this); + RatingDialog.init(this); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + StorageUtils.checkStorageAvailability(this); + + Intent intent = getIntent(); + if (intent.hasExtra(EXTRA_FEED_ID) || + (navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) && + (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) { + handleNavIntent(); + } + loadData(); + RatingDialog.check(); + } + + @Override + protected void onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); + if(subscription != null) { + subscription.unsubscribe(); + } + if(pd != null) { + pd.dismiss(); + } + } + + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + Glide.get(this).trimMemory(level); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + boolean retVal = super.onCreateOptionsMenu(menu); + switch (getLastNavFragment()) { + case QueueFragment.TAG: + case EpisodesFragment.TAG: +// requestCastButton(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return retVal; + case DownloadsFragment.TAG: + case PlaybackHistoryFragment.TAG: + case AddFeedFragment.TAG: + case SubscriptionFragment.TAG: + return retVal; + default: +// requestCastButton(MenuItem.SHOW_AS_ACTION_NEVER); + return retVal; + } + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (drawerToggle.onOptionsItemSelected(item)) { + return true; + } else if (item.getItemId() == android.R.id.home) { + if (getSupportFragmentManager().getBackStackEntryCount() > 0) { + dismissChildFragment(); + } + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + if(v.getId() != R.id.nav_list) { + return; + } + AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; + int position = adapterInfo.position; + if(position < navAdapter.getSubscriptionOffset()) { + return; + } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + menu.setHeaderTitle(feed.getTitle()); + // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! + } + + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int position = mPosition; + mPosition = -1; // reset + if(position < 0) { + return false; + } + Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + switch(item.getItemId()) { + case R.id.mark_all_seen_item: + DBWriter.markFeedSeen(feed.getId()); + return true; + case R.id.mark_all_read_item: + DBWriter.markFeedRead(feed.getId()); + return true; + case R.id.remove_item: + final FeedRemover remover = new FeedRemover(this, feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + if(getSelectedNavListIndex() == position) { + loadFragment(EpisodesFragment.TAG, null); + } + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(this, + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); + if (mediaId > 0 && + FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); + if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { + sendBroadcast(new Intent( + PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE)); + } + } + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public void onBackPressed() { + if(isDrawerOpen()) { + drawerLayout.closeDrawer(navDrawer); + } else { + super.onBackPressed(); + } + } + + private DBReader.NavDrawerData navDrawerData; + private int selectedNavListIndex = 0; + + private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { + @Override + public int getCount() { + if (navDrawerData != null) { + return navDrawerData.feeds.size(); + } else { + return 0; + } + } + + @Override + public Feed getItem(int position) { + if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { + return navDrawerData.feeds.get(position); + } else { + return null; + } + } + + @Override + public int getSelectedItemIndex() { + return selectedNavListIndex; + } + + @Override + public int getQueueSize() { + return (navDrawerData != null) ? navDrawerData.queueSize : 0; + } + + @Override + public int getNumberOfNewItems() { + return (navDrawerData != null) ? navDrawerData.numNewItems : 0; + } + + @Override + public int getNumberOfDownloadedItems() { + return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0; + } + + @Override + public int getReclaimableItems() { + return (navDrawerData != null) ? navDrawerData.reclaimableSpace : 0; + } + + @Override + public int getFeedCounter(long feedId) { + return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; + } + + @Override + public int getFeedCounterSum() { + if(navDrawerData == null) { + return 0; + } + int sum = 0; + for(int counter : navDrawerData.feedCounters.values()) { + sum += counter; + } + return sum; + } + + }; + + private void loadData() { + subscription = Observable.fromCallable(DBReader::getNavDrawerData) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + boolean handleIntent = (navDrawerData == null); + + navDrawerData = result; + navAdapter.notifyDataSetChanged(); + + if (handleIntent) { + handleNavIntent(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + public void onEvent(QueueEvent event) { + Log.d(TAG, "onEvent(" + event + ")"); + // we are only interested in the number of queue items, not download status or position + if(event.action == QueueEvent.Action.DELETED_MEDIA || + event.action == QueueEvent.Action.SORTED || + event.action == QueueEvent.Action.MOVED) { + return; + } + loadData(); + } + + public void onEventMainThread(ProgressEvent event) { + Log.d(TAG, "onEvent(" + event + ")"); + switch(event.action) { + case START: + pd = new ProgressDialog(this); + pd.setMessage(event.message); + pd.setIndeterminate(true); + pd.setCancelable(false); + pd.show(); + break; + case END: + if(pd != null) { + pd.dismiss(); + } + break; + } + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + Log.d(TAG, "Received contentUpdate Intent."); + loadData(); + } + } + }; + + private void handleNavIntent() { + Log.d(TAG, "handleNavIntent()"); + Intent intent = getIntent(); + if (intent.hasExtra(EXTRA_FEED_ID) || + (intent.hasExtra(EXTRA_NAV_TYPE) && + (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) { + int index = intent.getIntExtra(EXTRA_NAV_INDEX, -1); + String tag = intent.getStringExtra(EXTRA_FRAGMENT_TAG); + Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS); + long feedId = intent.getLongExtra(EXTRA_FEED_ID, 0); + if (index >= 0) { + loadFragment(index, args); + } else if (tag != null) { + loadFragment(tag, args); + } else if(feedId > 0) { + loadFeedFragmentById(feedId, args); + } + } + setIntent(new Intent(MainActivity.this, MainActivity.class)); // to avoid handling the intent twice when the configuration changes + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } +} diff --git a/app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java new file mode 100644 index 000000000..0d6e5504a --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -0,0 +1,876 @@ +package de.danoeh.antennapod.activity; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog; +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; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.StorageUtils; +import de.danoeh.antennapod.core.util.playback.MediaPlayerError; +import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.dialog.SleepTimerDialog; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + + +/** + * Provides general features which are both needed for playing audio and video + * files. + */ +public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener { + private static final String TAG = "MediaplayerActivity"; + private static final String PREFS = "MediaPlayerActivityPreferences"; + private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft"; + + protected PlaybackController controller; + + protected TextView txtvPosition; + protected TextView txtvLength; + protected SeekBar sbPosition; + protected ImageButton butRev; + protected TextView txtvRev; + protected ImageButton butPlay; + protected ImageButton butFF; + protected TextView txtvFF; + protected ImageButton butSkip; + + protected boolean showTimeLeft = false; + + private boolean isFavorite = false; + + private PlaybackController newPlaybackController() { + return new PlaybackController(this, false) { + + @Override + public void setupGUI() { + MediaplayerActivity.this.setupGUI(); + } + + @Override + public void onPositionObserverUpdate() { + MediaplayerActivity.this.onPositionObserverUpdate(); + } + + @Override + public void onBufferStart() { + MediaplayerActivity.this.onBufferStart(); + } + + @Override + public void onBufferEnd() { + MediaplayerActivity.this.onBufferEnd(); + } + + @Override + public void onBufferUpdate(float progress) { + MediaplayerActivity.this.onBufferUpdate(progress); + } + + @Override + public void handleError(int code) { + MediaplayerActivity.this.handleError(code); + } + + @Override + public void onReloadNotification(int code) { + MediaplayerActivity.this.onReloadNotification(code); + } + + @Override + public void onSleepTimerUpdate() { + supportInvalidateOptionsMenu(); + } + + @Override + public ImageButton getPlayButton() { + return butPlay; + } + + @Override + public void postStatusMsg(int msg, boolean showToast) { + MediaplayerActivity.this.postStatusMsg(msg, showToast); + } + + @Override + public void clearStatusMsg() { + MediaplayerActivity.this.clearStatusMsg(); + } + + @Override + public boolean loadMediaInfo() { + return MediaplayerActivity.this.loadMediaInfo(); + } + + @Override + public void onAwaitingVideoSurface() { + MediaplayerActivity.this.onAwaitingVideoSurface(); + } + + @Override + public void onServiceQueried() { + MediaplayerActivity.this.onServiceQueried(); + } + + @Override + public void onShutdownNotification() { + finish(); + } + + @Override + public void onPlaybackEnd() { + finish(); + } + + @Override + public void onPlaybackSpeedChange() { + MediaplayerActivity.this.onPlaybackSpeedChange(); + } + + @Override + protected void setScreenOn(boolean enable) { + super.setScreenOn(enable); + MediaplayerActivity.this.setScreenOn(enable); + } + + @Override + public void onSetSpeedAbilityChanged() { + MediaplayerActivity.this.onSetSpeedAbilityChanged(); + } + }; + } + + protected void onSetSpeedAbilityChanged() { + Log.d(TAG, "onSetSpeedAbilityChanged()"); + updatePlaybackSpeedButton(); + } + + protected void onPlaybackSpeedChange() { + updatePlaybackSpeedButtonText(); + } + + protected void onServiceQueried() { + supportInvalidateOptionsMenu(); + } + + protected void chooseTheme() { + setTheme(UserPreferences.getTheme()); + } + + protected void setScreenOn(boolean enable) { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + chooseTheme(); + super.onCreate(savedInstanceState); + + Log.d(TAG, "onCreate()"); + StorageUtils.checkStorageAvailability(this); + + orientation = getResources().getConfiguration().orientation; + getWindow().setFormat(PixelFormat.TRANSPARENT); + } + + @Override + protected void onPause() { + if(controller != null) { + controller.reinitServiceIfPaused(); + controller.pause(); + } + super.onPause(); + } + + /** + * Should be used to switch to another player activity if the mime type is + * not the correct one for the current activity. + */ + protected abstract void onReloadNotification(int notificationCode); + + /** + * Should be used to inform the user that the PlaybackService is currently + * buffering. + */ + protected abstract void onBufferStart(); + + /** + * Should be used to hide the view that was showing the 'buffering'-message. + */ + protected abstract void onBufferEnd(); + + protected void onBufferUpdate(float progress) { + if (sbPosition != null) { + sbPosition.setSecondaryProgress((int) progress * sbPosition.getMax()); + } + } + + /** + * Current screen orientation. + */ + protected int orientation; + + @Override + protected void onStart() { + super.onStart(); + if (controller != null) { + controller.release(); + } + controller = newPlaybackController(); + } + + @Override + protected void onStop() { + Log.d(TAG, "onStop()"); + if (controller != null) { + controller.release(); + controller = null; // prevent leak + } + super.onStop(); + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + Glide.get(this).trimMemory(level); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); +// requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.mediaplayer, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + if (controller == null) { + return false; + } + Playable media = controller.getMedia(); + + menu.findItem(R.id.support_item).setVisible( + media != null && media.getPaymentLink() != null && + (media instanceof FeedMedia) && + ((FeedMedia) media).getItem() != null && + ((FeedMedia) media).getItem().getFlattrStatus().flattrable() + ); + + boolean hasWebsiteLink = media != null && media.getWebsiteLink() != null; + menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink); + + boolean isItemAndHasLink = media != null && (media instanceof FeedMedia) && + ((FeedMedia) media).getItem() != null && ((FeedMedia) media).getItem().getLink() != null; + menu.findItem(R.id.share_link_item).setVisible(isItemAndHasLink); + menu.findItem(R.id.share_link_with_position_item).setVisible(isItemAndHasLink); + + boolean isItemHasDownloadLink = media != null && (media instanceof FeedMedia) && ((FeedMedia) media).getDownload_url() != null; + menu.findItem(R.id.share_download_url_item).setVisible(isItemHasDownloadLink); + menu.findItem(R.id.share_download_url_with_position_item).setVisible(isItemHasDownloadLink); + + menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink); + + menu.findItem(R.id.add_to_favorites_item).setVisible(false); + menu.findItem(R.id.remove_from_favorites_item).setVisible(false); + if(media != null && media instanceof FeedMedia) { + menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite); + menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite); + } + + boolean sleepTimerSet = controller.sleepTimerActive(); + boolean sleepTimerNotSet = controller.sleepTimerNotActive(); + menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet); + menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet); + + if (this instanceof AudioplayerActivity) { + int[] attrs = {R.attr.action_bar_icon_color}; + TypedArray ta = obtainStyledAttributes(UserPreferences.getTheme(), attrs); + int textColor = ta.getColor(0, Color.GRAY); + ta.recycle(); + menu.findItem(R.id.audio_controls).setIcon(new IconDrawable(this, + FontAwesomeIcons.fa_sliders).color(textColor).actionBarSize()); + } else { + menu.findItem(R.id.audio_controls).setVisible(false); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (controller == null) { + return false; + } + Playable media = controller.getMedia(); + if (item.getItemId() == android.R.id.home) { + Intent intent = new Intent(MediaplayerActivity.this, + MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + return true; + } else { + if (media != null) { + switch (item.getItemId()) { + case R.id.add_to_favorites_item: + if(media instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia)media).getItem(); + if(feedItem != null) { + DBWriter.addFavoriteItem(feedItem); + isFavorite = true; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT) + .show(); + } + } + break; + case R.id.remove_from_favorites_item: + if(media instanceof FeedMedia) { + FeedItem feedItem = ((FeedMedia)media).getItem(); + if(feedItem != null) { + DBWriter.removeFavoriteItem(feedItem); + isFavorite = false; + invalidateOptionsMenu(); + Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT) + .show(); + } + } + break; + case R.id.disable_sleeptimer_item: + if (controller.serviceAvailable()) { + + MaterialDialog.Builder stDialog = new MaterialDialog.Builder(this); + stDialog.title(R.string.sleep_timer_label); + stDialog.content(getString(R.string.time_left_label) + + Converter.getDurationStringLong((int) controller + .getSleepTimerTimeLeft())); + stDialog.positiveText(R.string.disable_sleeptimer_label); + stDialog.negativeText(R.string.cancel_label); + stDialog.onPositive((dialog, which) -> { + dialog.dismiss(); + controller.disableSleepTimer(); + }); + stDialog.onNegative((dialog, which) -> dialog.dismiss()); + stDialog.build().show(); + } + break; + case R.id.set_sleeptimer_item: + if (controller.serviceAvailable()) { + SleepTimerDialog td = new SleepTimerDialog(this) { + @Override + public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) { + controller.setSleepTimer(millis, shakeToReset, vibrate); + } + }; + td.createNewDialog().show(); + } + break; + case R.id.audio_controls: + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title(R.string.audio_controls) + .customView(R.layout.audio_controls, true) + .neutralText(R.string.close_label) + .onNeutral((dialog1, which) -> { + final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left); + final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right); + UserPreferences.setVolume(left.getProgress(), right.getProgress()); + }) + .show(); + final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed); + final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed); + butDecSpeed.setOnClickListener(v -> { + if(controller != null && controller.canSetPlaybackSpeed()) { + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 2); + } else { + VariableSpeedDialog.showGetPluginDialog(this); + } + }); + final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed); + butIncSpeed.setOnClickListener(v -> { + if(controller != null && controller.canSetPlaybackSpeed()) { + barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 2); + } else { + VariableSpeedDialog.showGetPluginDialog(this); + } + }); + + final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed); + float currentSpeed = 1.0f; + try { + currentSpeed = Float.parseFloat(UserPreferences.getPlaybackSpeed()); + } catch (NumberFormatException e) { + Log.e(TAG, Log.getStackTraceString(e)); + UserPreferences.setPlaybackSpeed(String.valueOf(currentSpeed)); + } + + txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed)); + barPlaybackSpeed.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if(controller != null && controller.canSetPlaybackSpeed()) { + float playbackSpeed = (progress + 10) / 20.0f; + controller.setPlaybackSpeed(playbackSpeed); + 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(() -> barPlaybackSpeed.setProgress((int) (20 * speed) - 10)); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if(controller != null && !controller.canSetPlaybackSpeed()) { + VariableSpeedDialog.showGetPluginDialog(MediaplayerActivity.this); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + barPlaybackSpeed.setProgress((int) (20 * currentSpeed) - 10); + + final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left); + barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage()); + final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right); + barRightVolume.setProgress(UserPreferences.getRightVolumePercentage()); + final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono); + stereoToMono.setChecked(UserPreferences.stereoToMono()); + if (controller != null && !controller.canDownmix()) { + stereoToMono.setEnabled(false); + String sonicOnly = getString(R.string.sonic_only); + stereoToMono.setText(stereoToMono.getText() + " [" + sonicOnly + "]"); + } + + barLeftVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + controller.setVolume( + Converter.getVolumeFromPercentage(progress), + Converter.getVolumeFromPercentage(barRightVolume.getProgress())); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + barRightVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + controller.setVolume( + Converter.getVolumeFromPercentage(barLeftVolume.getProgress()), + Converter.getVolumeFromPercentage(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + stereoToMono.setOnCheckedChangeListener((buttonView, isChecked) -> { + UserPreferences.stereoToMono(isChecked); + if (controller != null) { + controller.setDownmix(isChecked); + } + }); + break; + case R.id.visit_website_item: + Uri uri = Uri.parse(media.getWebsiteLink()); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + break; + case R.id.support_item: + if (media instanceof FeedMedia) { + DBTasks.flattrItemIfLoggedIn(this, ((FeedMedia) media).getItem()); + } + break; + case R.id.share_link_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem()); + } + break; + case R.id.share_download_url_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem()); + } + break; + case R.id.share_link_with_position_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem(), true); + } + break; + case R.id.share_download_url_with_position_item: + if (media instanceof FeedMedia) { + ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem(), true); + } + break; + default: + return false; + } + return true; + } else { + return false; + } + } + } + + @Override + protected void onResume() { + super.onResume(); + Log.d(TAG, "onResume()"); + StorageUtils.checkStorageAvailability(this); + if(controller != null) { + controller.init(); + } + } + + /** + * Called by 'handleStatus()' when the PlaybackService is waiting for + * a video surface. + */ + protected abstract void onAwaitingVideoSurface(); + + protected abstract void postStatusMsg(int resId, boolean showToast); + + protected abstract void clearStatusMsg(); + + protected void onPositionObserverUpdate() { + 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())); + } + + /** + * Load information about the media that is going to be played or currently + * being played. This method will be called when the activity is connected + * to the PlaybackService to ensure that the activity has the right + * FeedMedia object. + */ + protected boolean loadMediaInfo() { + Log.d(TAG, "loadMediaInfo()"); + Playable media = controller.getMedia(); + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + if (media != null) { + onPositionObserverUpdate(); + checkFavorite(); + updatePlaybackSpeedButton(); + return true; + } else { + return false; + } + } + + protected void updatePlaybackSpeedButton() { + // Only meaningful on AudioplayerActivity, where it is overridden. + } + + protected void updatePlaybackSpeedButtonText() { + // Only meaningful on AudioplayerActivity, where it is overridden. + } + + + protected void setupGUI() { + setContentView(getContentViewResourceId()); + sbPosition = (SeekBar) findViewById(R.id.sbPosition); + txtvPosition = (TextView) findViewById(R.id.txtvPosition); + + SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false); + Log.d("timeleft", showTimeLeft ? "true" : "false"); + txtvLength = (TextView) findViewById(R.id.txtvLength); + if (txtvLength != null) { + txtvLength.setOnClickListener(v -> { + showTimeLeft = !showTimeLeft; + Playable media = controller.getMedia(); + if (media == null) { + return; + } + + String length; + if (showTimeLeft) { + length = "-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()); + } else { + length = Converter.getDurationStringLong(media.getDuration()); + } + txtvLength.setText(length); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft); + editor.apply(); + Log.d("timeleft on click", showTimeLeft ? "true" : "false"); + }); + } + + butRev = (ImageButton) findViewById(R.id.butRev); + txtvRev = (TextView) findViewById(R.id.txtvRev); + if (txtvRev != null) { + txtvRev.setText(String.valueOf(UserPreferences.getRewindSecs())); + } + butPlay = (ImageButton) findViewById(R.id.butPlay); + butFF = (ImageButton) findViewById(R.id.butFF); + txtvFF = (TextView) findViewById(R.id.txtvFF); + if (txtvFF != null) { + txtvFF.setText(String.valueOf(UserPreferences.getFastFowardSecs())); + } + butSkip = (ImageButton) findViewById(R.id.butSkip); + + // SEEKBAR SETUP + + sbPosition.setOnSeekBarChangeListener(this); + + // BUTTON SETUP + + if (butRev != null) { + butRev.setOnClickListener(v -> onRewind()); + butRev.setOnLongClickListener(new View.OnLongClickListener() { + + int choice; + + @Override + public boolean onLongClick(View v) { + int checked = 0; + int rewindSecs = UserPreferences.getRewindSecs(); + final int[] values = getResources().getIntArray(R.array.seek_delta_values); + final String[] choices = new String[values.length]; + for (int i = 0; i < values.length; i++) { + if (rewindSecs == values[i]) { + checked = i; + } + choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds); + } + choice = values[checked]; + AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this); + builder.setTitle(R.string.pref_rewind); + builder.setSingleChoiceItems(choices, checked, + (dialog, which) -> { + choice = values[which]; + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setPrefRewindSecs(choice); + if(txtvRev != null){ + txtvRev.setText(String.valueOf(choice)); + } + }); + builder.create().show(); + return true; + } + }); + } + + butPlay.setOnClickListener(v -> onPlayPause()); + + if (butFF != null) { + butFF.setOnClickListener(v -> onFastForward()); + butFF.setOnLongClickListener(new View.OnLongClickListener() { + + int choice; + + @Override + public boolean onLongClick(View v) { + int checked = 0; + int rewindSecs = UserPreferences.getFastFowardSecs(); + final int[] values = getResources().getIntArray(R.array.seek_delta_values); + final String[] choices = new String[values.length]; + for (int i = 0; i < values.length; i++) { + if (rewindSecs == values[i]) { + checked = i; + } + choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds); + } + choice = values[checked]; + AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this); + builder.setTitle(R.string.pref_fast_forward); + builder.setSingleChoiceItems(choices, checked, + (dialog, which) -> { + choice = values[which]; + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setPrefFastForwardSecs(choice); + if(txtvFF != null) { + txtvFF.setText(String.valueOf(choice)); + } + }); + builder.create().show(); + return true; + } + }); + } + + if (butSkip != null) { + butSkip.setOnClickListener(v -> sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE))); + } + } + + 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.setNeutralButton("OK", + (dialog, which) -> { + dialog.dismiss(); + finish(); + } + ); + errorDialog.create().show(); + } + + float prog; + + @Override + public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) { + 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); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (controller != null) { + controller.onSeekBarStartTrackingTouch(seekBar); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (controller != null) { + controller.onSeekBarStopTrackingTouch(seekBar, prog); + } + } + + 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(); + } + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + } + ); + } + } + } + +} diff --git a/app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java new file mode 100644 index 000000000..2b2808adc --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -0,0 +1,590 @@ +package de.danoeh.antennapod.fragment; + +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.GestureDetectorCompat; +import android.text.Layout; +import android.text.TextUtils; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.joanzapata.iconify.Iconify; +import com.joanzapata.iconify.widget.IconButton; + +import org.apache.commons.lang3.ArrayUtils; + +import java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; +import de.danoeh.antennapod.core.event.DownloadEvent; +import de.danoeh.antennapod.core.event.DownloaderUpdate; +import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.core.feed.EventDistributor; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.Downloader; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.DownloadRequestException; +import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.ShareUtils; +import de.danoeh.antennapod.core.util.playback.Timeline; +import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.view.OnSwipeGesture; +import de.danoeh.antennapod.view.SwipeGestureDetector; +import de.greenrobot.event.EventBus; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Displays information about a FeedItem and actions. + */ +public class ItemFragment extends Fragment implements OnSwipeGesture { + + private static final String TAG = "ItemFragment"; + + private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE; + + private static final String ARG_FEEDITEMS = "feeditems"; + private static final String ARG_FEEDITEM_POS = "feeditem_pos"; + + private GestureDetectorCompat headerGestureDetector; + private GestureDetectorCompat webviewGestureDetector; + + /** + * Creates a new instance of an ItemFragment + * + * @param feeditem The ID of the FeedItem that should be displayed. + * @return The ItemFragment instance + */ + public static ItemFragment newInstance(long feeditem) { + return newInstance(new long[] { feeditem }, 0); + } + + /** + * Creates a new instance of an ItemFragment + * + * @param feeditems The IDs of the FeedItems that belong to the same list + * @param feedItemPos The position of the FeedItem that is currently shown + * @return The ItemFragment instance + */ + public static ItemFragment newInstance(long[] feeditems, int feedItemPos) { + ItemFragment fragment = new ItemFragment(); + Bundle args = new Bundle(); + args.putLongArray(ARG_FEEDITEMS, feeditems); + args.putInt(ARG_FEEDITEM_POS, feedItemPos); + fragment.setArguments(args); + return fragment; + } + + private boolean itemsLoaded = false; + private long[] feedItems; + private int feedItemPos; + private FeedItem item; + private String webviewData; + private List downloaderList; + + private ViewGroup root; + private WebView webvDescription; + private TextView txtvPodcast; + private TextView txtvTitle; + private TextView txtvDuration; + private TextView txtvPublished; + private ImageView imgvCover; + private ProgressBar progbarDownload; + private ProgressBar progbarLoading; + private IconButton butAction1; + private IconButton butAction2; + private Menu popupMenu; + + private Subscription subscription; + + /** + * URL that was selected via long-press. + */ + private String selectedURL; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setHasOptionsMenu(true); + + feedItems = getArguments().getLongArray(ARG_FEEDITEMS); + feedItemPos = getArguments().getInt(ARG_FEEDITEM_POS); + + headerGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this)); + webviewGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this) { + // necessary for the longclick context menu to work properly + @Override + public boolean onDown(MotionEvent e) { + return false; + } + }); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + View layout = inflater.inflate(R.layout.feeditem_fragment, container, false); + + root = (ViewGroup) layout.findViewById(R.id.content_root); + + LinearLayout header = (LinearLayout) root.findViewById(R.id.header); + if(feedItems.length > 0) { + header.setOnTouchListener((v, event) -> headerGestureDetector.onTouchEvent(event)); + } + + txtvPodcast = (TextView) layout.findViewById(R.id.txtvPodcast); + txtvPodcast.setOnClickListener(v -> openPodcast()); + txtvTitle = (TextView) layout.findViewById(R.id.txtvTitle); + if(Build.VERSION.SDK_INT >= 23) { + txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); + } + txtvDuration = (TextView) layout.findViewById(R.id.txtvDuration); + txtvPublished = (TextView) layout.findViewById(R.id.txtvPublished); + if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448 + txtvTitle.setEllipsize(TextUtils.TruncateAt.END); + } + webvDescription = (WebView) layout.findViewById(R.id.webvDescription); + if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { + if (Build.VERSION.SDK_INT >= 11 + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + webvDescription.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black)); + } + webvDescription.getSettings().setUseWideViewPort(false); + webvDescription.getSettings().setLayoutAlgorithm( + WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + webvDescription.getSettings().setLoadWithOverviewMode(true); + if(feedItems.length > 0) { + webvDescription.setOnLongClickListener(webViewLongClickListener); + } + webvDescription.setOnTouchListener((v, event) -> webviewGestureDetector.onTouchEvent(event)); + webvDescription.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + if(IntentUtils.isCallable(getActivity(), intent)) { + startActivity(intent); + } + return true; + } + }); + registerForContextMenu(webvDescription); + + imgvCover = (ImageView) layout.findViewById(R.id.imgvCover); + imgvCover.setOnClickListener(v -> openPodcast()); + progbarDownload = (ProgressBar) layout.findViewById(R.id.progbarDownload); + progbarLoading = (ProgressBar) layout.findViewById(R.id.progbarLoading); + butAction1 = (IconButton) layout.findViewById(R.id.butAction1); + butAction2 = (IconButton) layout.findViewById(R.id.butAction2); + + butAction1.setOnClickListener(v -> { + if (item == null) { + return; + } + DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getActivity()); + actionButtonCallback.onActionButtonPressed(item); + FeedMedia media = item.getMedia(); + if (media != null && media.isDownloaded()) { + // playback was started, dialog should close itself + ((MainActivity) getActivity()).dismissChildFragment(); + } + }); + + butAction2.setOnClickListener(v -> { + if (item == null) { + return; + } + + if (item.hasMedia()) { + FeedMedia media = item.getMedia(); + if (!media.isDownloaded()) { + DBTasks.playMedia(getActivity(), media, true, true, true); + ((MainActivity) getActivity()).dismissChildFragment(); + } else { + DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); + } + } else if (item.getLink() != null) { + Uri uri = Uri.parse(item.getLink()); + getActivity().startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + }); + + return layout; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + load(); + } + + @Override + public void onResume() { + super.onResume(); + EventDistributor.getInstance().register(contentUpdate); + EventBus.getDefault().registerSticky(this); + if(itemsLoaded) { + progbarLoading.setVisibility(View.GONE); + updateAppearance(); + } + } + + @Override + public void onPause() { + super.onPause(); + EventDistributor.getInstance().unregister(contentUpdate); + EventBus.getDefault().unregister(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if(subscription != null) { + subscription.unsubscribe(); + } + if (webvDescription != null && root != null) { + root.removeView(webvDescription); + webvDescription.destroy(); + } + } + + @Override + public boolean onSwipeLeftToRight() { + Log.d(TAG, "onSwipeLeftToRight()"); + feedItemPos = feedItemPos - 1; + if(feedItemPos < 0) { + feedItemPos = feedItems.length - 1; + } + load(); + return true; + } + + @Override + public boolean onSwipeRightToLeft() { + Log.d(TAG, "onSwipeRightToLeft()"); + feedItemPos = (feedItemPos + 1) % feedItems.length; + load(); + return true; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if(!isAdded() || item == null) { + return; + } +// ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + inflater.inflate(R.menu.feeditem_options, menu); + popupMenu = menu; + if (item.hasMedia()) { + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null); + } else { + // these are already available via button1 and button2 + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null, + R.id.mark_read_item, R.id.visit_website_item); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch(menuItem.getItemId()) { + case R.id.open_podcast: + openPodcast(); + return true; + default: + try { + return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } + } + } + + private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() { + @Override + public void setItemVisibility(int id, boolean visible) { + MenuItem item = popupMenu.findItem(id); + if (item != null) { + item.setVisible(visible); + } + } + }; + + + private void onFragmentLoaded() { + if (webviewData != null) { + webvDescription.loadDataWithBaseURL(null, webviewData, "text/html", "utf-8", "about:blank"); + } + updateAppearance(); + } + + private void updateAppearance() { + if (item == null) { + Log.d(TAG, "updateAppearance item is null"); + return; + } + getActivity().supportInvalidateOptionsMenu(); + txtvPodcast.setText(item.getFeed().getTitle()); + txtvTitle.setText(item.getTitle()); + + if (item.getPubDate() != null) { + String pubDateStr = DateUtils.formatAbbrev(getActivity(), item.getPubDate()); + txtvPublished.setText(pubDateStr); + } + + Glide.with(getActivity()) + .load(item.getImageLocation()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) + .fitCenter() + .dontAnimate() + .into(imgvCover); + + progbarDownload.setVisibility(View.GONE); + if (item.hasMedia() && downloaderList != null) { + for (Downloader downloader : downloaderList) { + if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { + progbarDownload.setVisibility(View.VISIBLE); + progbarDownload.setProgress(downloader.getDownloadRequest().getProgressPercent()); + } + } + } + + FeedMedia media = item.getMedia(); + String butAction1Icon = null; + int butAction1Text = 0; + String butAction2Icon = null; + int butAction2Text = 0; + if (media == null) { + if (!item.isPlayed()) { + butAction1Icon = "{fa-check 24sp}"; + butAction1Text = R.string.mark_read_label; + } + if (item.getLink() != null) { + butAction2Icon = "{md-web 24sp}"; + butAction2Text = R.string.visit_website_label; + } + } else { + if(media.getDuration() > 0) { + txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); + } + boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); + if (!media.isDownloaded()) { + butAction2Icon = "{md-settings-input-antenna 24sp}"; + butAction2Text = R.string.stream_label; + } else { + butAction2Icon = "{md-delete 24sp}"; + butAction2Text = R.string.remove_label; + } + if (isDownloading) { + butAction1Icon = "{md-cancel 24sp}"; + butAction1Text = R.string.cancel_label; + } else if (media.isDownloaded()) { + butAction1Icon = "{md-play-arrow 24sp}"; + butAction1Text = R.string.play_label; + } else { + butAction1Icon = "{md-file-download 24sp}"; + butAction1Text = R.string.download_label; + } + } + if(butAction1Icon != null && butAction1Text != 0) { + butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text)); + Iconify.addIcons(butAction1); + butAction1.setVisibility(View.VISIBLE); + } else { + butAction1.setVisibility(View.INVISIBLE); + } + if(butAction2Icon != null && butAction2Text != 0) { + butAction2.setText(butAction2Icon +"\u0020\u0020" + getActivity().getString(butAction2Text)); + Iconify.addIcons(butAction2); + butAction2.setVisibility(View.VISIBLE); + } else { + butAction2.setVisibility(View.INVISIBLE); + } + } + + private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + WebView.HitTestResult r = webvDescription.getHitTestResult(); + if (r != null + && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { + Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); + selectedURL = r.getExtra(); + webvDescription.showContextMenu(); + return true; + } + selectedURL = null; + return false; + } + }; + + @Override + public boolean onContextItemSelected(MenuItem item) { + boolean handled = selectedURL != null; + if (selectedURL != null) { + switch (item.getItemId()) { + case R.id.open_in_browser_item: + Uri uri = Uri.parse(selectedURL); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + getActivity().startActivity(intent); + } + break; + case R.id.share_url_item: + ShareUtils.shareLink(getActivity(), selectedURL); + break; + case R.id.copy_url_item: + if (android.os.Build.VERSION.SDK_INT >= 11) { + ClipData clipData = ClipData.newPlainText(selectedURL, + selectedURL); + android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(clipData); + } else { + android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(selectedURL); + } + Toast t = Toast.makeText(getActivity(), + R.string.copied_url_msg, Toast.LENGTH_SHORT); + t.show(); + break; + default: + handled = false; + break; + + } + selectedURL = null; + } + return handled; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenu.ContextMenuInfo menuInfo) { + if (selectedURL != null) { + super.onCreateContextMenu(menu, v, menuInfo); + Uri uri = Uri.parse(selectedURL); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if(IntentUtils.isCallable(getActivity(), intent)) { + menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, + R.string.open_in_browser_label); + } + menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, + R.string.copy_url_label); + menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, + R.string.share_url_label); + menu.setHeaderTitle(selectedURL); + } + } + + private void openPodcast() { + Fragment fragment = ItemlistFragment.newInstance(item.getFeedId()); + ((MainActivity)getActivity()).loadChildFragment(fragment); + } + + public void onEventMainThread(FeedItemEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + for(FeedItem item : event.items) { + if(feedItems[feedItemPos] == item.getId()) { + load(); + return; + } + } + } + + public void onEventMainThread(DownloadEvent event) { + Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); + DownloaderUpdate update = event.update; + downloaderList = update.downloaders; + if(item == null || item.getMedia() == null) { + return; + } + long mediaId = item.getMedia().getId(); + if(ArrayUtils.contains(update.mediaIds, mediaId)) { + if (itemsLoaded && getActivity() != null) { + updateAppearance(); + } + } + } + + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EVENTS) != 0) { + load(); + } + } + }; + + private void load() { + if(subscription != null) { + subscription.unsubscribe(); + } + progbarLoading.setVisibility(View.VISIBLE); + subscription = Observable.fromCallable(this::loadInBackground) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + progbarLoading.setVisibility(View.GONE); + item = result; + itemsLoaded = true; + onFragmentLoaded(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + }); + } + + private FeedItem loadInBackground() { + FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]); + if (feedItem != null) { + Timeline t = new Timeline(getActivity(), feedItem); + webviewData = t.processShownotes(false); + } + return feedItem; + } + +} diff --git a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java new file mode 100644 index 000000000..5cd44c0a7 --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -0,0 +1,951 @@ +package de.danoeh.antennapod.preferences; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.TimePickerDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.Uri; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; +import android.text.format.DateFormat; +import android.util.Log; +import android.widget.EditText; +import android.widget.Toast; +import android.widget.ListView; + +import com.afollestad.materialdialogs.MaterialDialog; + +import org.apache.commons.lang3.ArrayUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import de.danoeh.antennapod.CrashReportWriter; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.AboutActivity; +import de.danoeh.antennapod.activity.DirectoryChooserActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.activity.PreferenceActivityGingerbread; +import de.danoeh.antennapod.activity.StatisticsActivity; +import de.danoeh.antennapod.asynctask.OpmlExportWorker; +import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.GpodnetSyncService; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.util.StorageUtils; +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; + +/** + * Sets up a preference UI that lets the user change user preferences. + */ + +public class PreferenceController implements SharedPreferences.OnSharedPreferenceChangeListener { + + private static final String TAG = "PreferenceController"; + + public static final String PREF_FLATTR_SETTINGS = "prefFlattrSettings"; + public static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; + public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; + public static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs"; + public static final String PREF_OPML_EXPORT = "prefOpmlExport"; + public static final String STATISTICS = "statistics"; + public static final String PREF_ABOUT = "prefAbout"; + public static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; + public static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; + public static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher"; + public static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; + public static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; + public static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; + 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"; + public static final String PREF_KNOWN_ISSUES = "prefKnownIssues"; + public static final String PREF_FAQ = "prefFaq"; + public static final String PREF_SEND_CRASH_REPORT = "prefSendCrashReport"; + + private final PreferenceUI ui; + + private CheckBoxPreference[] selectedNetworks; + + private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE }; + private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; + + public PreferenceController(PreferenceUI ui) { + this.ui = ui; + PreferenceManager.getDefaultSharedPreferences(ui.getActivity().getApplicationContext()) + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if(key.equals(UserPreferences.PREF_SONIC)) { + CheckBoxPreference prefSonic = (CheckBoxPreference) ui.findPreference(UserPreferences.PREF_SONIC); + if(prefSonic != null) { + prefSonic.setChecked(sharedPreferences.getBoolean(UserPreferences.PREF_SONIC, false)); + } + } + } + + /** + * Returns the preference activity that should be used on this device. + * + * @return PreferenceActivity if the API level is greater than 10, PreferenceActivityGingerbread otherwise. + */ + public static Class getPreferenceActivity() { + if (Build.VERSION.SDK_INT > 10) { + return PreferenceActivity.class; + } else { + return PreferenceActivityGingerbread.class; + } + } + + public void onCreate() { + final Activity activity = ui.getActivity(); + + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + // disable expanded notification option on unsupported android versions + ui.findPreference(PreferenceController.PREF_EXPANDED_NOTIFICATION).setEnabled(false); + ui.findPreference(PreferenceController.PREF_EXPANDED_NOTIFICATION).setOnPreferenceClickListener( + preference -> { + Toast toast = Toast.makeText(activity, + R.string.pref_expand_notify_unsupport_toast, Toast.LENGTH_SHORT); + toast.show(); + return true; + } + ); + } + ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setOnPreferenceClickListener( + preference -> { + FlattrUtils.revokeAccessToken(activity); + checkItemVisibility(); + return true; + } + ); + ui.findPreference(PreferenceController.PREF_ABOUT).setOnPreferenceClickListener( + preference -> { + activity.startActivity(new Intent(activity, AboutActivity.class)); + return true; + } + ); + ui.findPreference(PreferenceController.STATISTICS).setOnPreferenceClickListener( + preference -> { + activity.startActivity(new Intent(activity, StatisticsActivity.class)); + return true; + } + ); + ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener( + preference -> { + new OpmlExportWorker(activity).executeAsync(); + return true; + } + ); + ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( + preference -> { + if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && + Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + showChooseDataFolderDialog(); + } else { + int readPermission = ActivityCompat.checkSelfPermission( + activity, Manifest.permission.READ_EXTERNAL_STORAGE); + int writePermission = ActivityCompat.checkSelfPermission( + activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (readPermission == PackageManager.PERMISSION_GRANTED && + writePermission == PackageManager.PERMISSION_GRANTED) { + openDirectoryChooser(); + } else { + requestPermission(); + } + } + return true; + } + ); + ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) + .setOnPreferenceClickListener( + preference -> { + if (Build.VERSION.SDK_INT >= 19) { + showChooseDataFolderDialog(); + } else { + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, + DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); + } + return true; + } + ); + ui.findPreference(UserPreferences.PREF_THEME) + .setOnPreferenceChangeListener( + (preference, newValue) -> { + Intent i = new Intent(activity, MainActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NEW_TASK); + activity.finish(); + activity.startActivity(i); + return true; + } + ); + ui.findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS) + .setOnPreferenceClickListener(preference -> { + showDrawerPreferencesDialog(); + return true; + }); + + ui.findPreference(UserPreferences.PREF_COMPACT_NOTIFICATION_BUTTONS) + .setOnPreferenceClickListener(preference -> { + showNotificationButtonsDialog(); + return true; + }); + + ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL) + .setOnPreferenceClickListener(preference -> { + showUpdateIntervalTimePreferencesDialog(); + return true; + }); + + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL).setOnPreferenceChangeListener( + (preference, newValue) -> { + if (newValue instanceof Boolean) { + boolean enabled = (Boolean) newValue; + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(enabled); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(enabled); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(enabled); + setSelectedNetworksEnabled(enabled && UserPreferences.isEnableAutodownloadWifiFilter()); + } + return true; + }); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) + .setOnPreferenceChangeListener( + (preference, newValue) -> { + if (newValue instanceof Boolean) { + setSelectedNetworksEnabled((Boolean) newValue); + return true; + } else { + return false; + } + } + ); + ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS) + .setOnPreferenceChangeListener( + (preference, o) -> { + if (o instanceof String) { + try { + int value = Integer.parseInt((String) o); + if (1 <= value && value <= 50) { + setParallelDownloadsText(value); + return true; + } + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + ); + // validate and set correct value: number of downloads between 1 and 50 (inclusive) + final EditText ev = ((EditTextPreference) ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText(); + ev.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 0) { + try { + int value = Integer.parseInt(s.toString()); + if (value <= 0) { + ev.setText("1"); + } else if (value > 50) { + ev.setText("50"); + } + } catch (NumberFormatException e) { + ev.setText("6"); + } + ev.setSelection(ev.getText().length()); + } + } + }); + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE) + .setOnPreferenceChangeListener( + (preference, o) -> { + if (o instanceof String) { + setEpisodeCacheSizeText(UserPreferences.readEpisodeCacheSize((String) o)); + } + return true; + } + ); + ui.findPreference(PreferenceController.PREF_PLAYBACK_SPEED_LAUNCHER) + .setOnPreferenceClickListener(preference -> { + VariableSpeedDialog.showDialog(activity); + return true; + }); + ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION) + .setOnPreferenceClickListener(preference -> { + AuthenticationDialog dialog = new AuthenticationDialog(activity, + R.string.pref_gpodnet_setlogin_information_title, false, false, GpodnetPreferences.getUsername(), + null) { + + @Override + protected void onConfirmed(String username, String password, boolean saveUsernamePassword) { + GpodnetPreferences.setPassword(password); + } + }; + dialog.show(); + return true; + }); + ui.findPreference(PreferenceController.PREF_GPODNET_SYNC). + setOnPreferenceClickListener(preference -> { + GpodnetSyncService.sendSyncIntent(ui.getActivity().getApplicationContext()); + Toast toast = Toast.makeText(ui.getActivity(), R.string.pref_gpodnet_sync_started, + Toast.LENGTH_SHORT); + toast.show(); + return true; + }); + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setOnPreferenceClickListener( + preference -> { + GpodnetPreferences.logout(); + Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT); + toast.show(); + updateGpodnetPreferenceScreen(); + return true; + }); + ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener( + preference -> { + GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(dialog -> updateGpodnetPreferenceScreen()); + return true; + }); + + ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS) + .setOnPreferenceClickListener(preference -> { + AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(activity, + new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() { + @Override + public void onCancelled() { + + } + + @Override + public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) { + UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue); + checkItemVisibility(); + } + }); + return true; + }); + ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( + (preference, o) -> { + if (o instanceof String) { + 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); + dialog.setMessage(R.string.pref_restart_required); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.show(); + } + return true; + } + return false; + } + ); + ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> { + ProxyDialog dialog = new ProxyDialog(ui.getActivity()); + dialog.createDialog().show(); + return true; + }); + ui.findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> { + openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug"); + return true; + }); + ui.findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { + openInBrowser("http://antennapod.org/faq.html"); + return true; + }); + ui.findPreference(PREF_SEND_CRASH_REPORT).setOnPreferenceClickListener(preference -> { + Intent emailIntent = new Intent(Intent.ACTION_SEND); + emailIntent.setType("text/plain"); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"}); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "AntennaPod Crash Report"); + emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe what you were doing when the app crashed"); + // the attachment + emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(CrashReportWriter.getFile())); + String intentTitle = ui.getActivity().getString(R.string.send_email); + ui.getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); + return true; + }); + //checks whether Google Play Services is installed on the device (condition necessary for Cast support) +// ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { +// if (o instanceof Boolean && ((Boolean) o)) { +// final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() +// .isGooglePlayServicesAvailable(ui.getActivity()); +// if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { +// return true; +// } else { +// GoogleApiAvailability.getInstance() +// .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) +// .show(); +// return false; +// } +// } +// return true; +// }); + buildEpisodeCleanupPreference(); + buildSmartMarkAsPlayedPreference(); + buildAutodownloadSelectedNetworsPreference(); + setSelectedNetworksEnabled(UserPreferences.isEnableAutodownloadWifiFilter()); + } + + private void openInBrowser(String url) { + try { + Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + ui.getActivity().startActivity(myIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(ui.getActivity(), R.string.pref_no_browser_found, Toast.LENGTH_LONG).show(); + Log.e(TAG, Log.getStackTraceString(e)); + } + } + + public void onResume() { + checkItemVisibility(); + setUpdateIntervalText(); + setParallelDownloadsText(UserPreferences.getParallelDownloads()); + setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize()); + setDataFolderText(); + updateGpodnetPreferenceScreen(); + } + + @SuppressLint("NewApi") + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && + requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); + + File path; + if(dir != null) { + path = new File(dir); + } else { + path = ui.getActivity().getExternalFilesDir(null); + } + String message = null; + final Context context= ui.getActivity().getApplicationContext(); + if(!path.exists()) { + message = String.format(context.getString(R.string.folder_does_not_exist_error), dir); + } else if(!path.canRead()) { + message = String.format(context.getString(R.string.folder_not_readable_error), dir); + } else if(!path.canWrite()) { + message = String.format(context.getString(R.string.folder_not_writable_error), dir); + } + + if(message == null) { + Log.d(TAG, "Setting data folder: " + dir); + UserPreferences.setDataFolder(dir); + setDataFolderText(); + } else { + AlertDialog.Builder ab = new AlertDialog.Builder(ui.getActivity()); + ab.setMessage(message); + ab.setPositiveButton(android.R.string.ok, null); + ab.show(); + } + } + } + + + private void updateGpodnetPreferenceScreen() { + final boolean loggedIn = GpodnetPreferences.loggedIn(); + ui.findPreference(PreferenceController.PREF_GPODNET_LOGIN).setEnabled(!loggedIn); + ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn); + ui.findPreference(PreferenceController.PREF_GPODNET_SYNC).setEnabled(loggedIn); + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setEnabled(loggedIn); + if(loggedIn) { + String format = ui.getActivity().getString(R.string.pref_gpodnet_login_status); + String summary = String.format(format, GpodnetPreferences.getUsername(), + GpodnetPreferences.getDeviceID()); + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary)); + } else { + ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(null); + } + ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); + } + + private String[] getUpdateIntervalEntries(final String[] values) { + final Resources res = ui.getActivity().getResources(); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + Integer v = Integer.parseInt(values[x]); + switch (v) { + case 0: + entries[x] = res.getString(R.string.pref_update_interval_hours_manual); + break; + case 1: + entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_singular); + break; + default: + entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_plural); + break; + + } + } + return entries; + } + + private void buildEpisodeCleanupPreference() { + final Resources res = ui.getActivity().getResources(); + + ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_EPISODE_CLEANUP); + String[] values = res.getStringArray( + R.array.episode_cleanup_values); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + int v = Integer.parseInt(values[x]); + if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) { + entries[x] = res.getString(R.string.episode_cleanup_queue_removal); + } else if (v == UserPreferences.EPISODE_CLEANUP_NULL){ + entries[x] = res.getString(R.string.episode_cleanup_never); + } else if (v == 0) { + entries[x] = res.getString(R.string.episode_cleanup_after_listening); + } else { + entries[x] = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, v, v); + } + } + pref.setEntries(entries); + } + + private void buildSmartMarkAsPlayedPreference() { + final Resources res = ui.getActivity().getResources(); + + ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS); + String[] values = res.getStringArray(R.array.smart_mark_as_played_values); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + if(x == 0) { + entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled); + } else { + Integer v = Integer.parseInt(values[x]); + if(v < 60) { + entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v); + } else { + v /= 60; + entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v); + } + } + } + pref.setEntries(entries); + } + + private void setSelectedNetworksEnabled(boolean b) { + if (selectedNetworks != null) { + for (Preference p : selectedNetworks) { + p.setEnabled(b); + } + } + } + + @SuppressWarnings("deprecation") + private void checkItemVisibility() { + boolean hasFlattrToken = FlattrUtils.hasToken(); + ui.findPreference(PreferenceController.PREF_FLATTR_SETTINGS).setEnabled(FlattrUtils.hasAPICredentials()); + ui.findPreference(PreferenceController.PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); + ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); + ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken); + + boolean autoDownload = UserPreferences.isEnableAutodownload(); + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(autoDownload); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(autoDownload); + ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(autoDownload); + setSelectedNetworksEnabled(autoDownload && UserPreferences.isEnableAutodownloadWifiFilter()); + + ui.findPreference(PREF_SEND_CRASH_REPORT).setEnabled(CrashReportWriter.getFile().exists()); + + if (Build.VERSION.SDK_INT >= 16) { + ui.findPreference(UserPreferences.PREF_SONIC).setEnabled(true); + } else { + Preference prefSonic = ui.findPreference(UserPreferences.PREF_SONIC); + prefSonic.setSummary("[Android 4.1+]\n" + prefSonic.getSummary()); + } + } + + private void setUpdateIntervalText() { + Context context = ui.getActivity().getApplicationContext(); + String val; + long interval = UserPreferences.getUpdateInterval(); + if(interval > 0) { + int hours = (int) TimeUnit.MILLISECONDS.toHours(interval); + String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours); + val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr); + } else { + int[] timeOfDay = UserPreferences.getUpdateTimeOfDay(); + if(timeOfDay.length == 2) { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]); + cal.set(Calendar.MINUTE, timeOfDay[1]); + String timeOfDayStr = DateFormat.getTimeFormat(context).format(cal.getTime()); + val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_at), + timeOfDayStr); + } else { + val = context.getString(R.string.pref_smart_mark_as_played_disabled); + } + } + String summary = context.getString(R.string.pref_autoUpdateIntervallOrTime_sum) + "\n" + + String.format(context.getString(R.string.pref_current_value), val); + ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL).setSummary(summary); + } + + private void setParallelDownloadsText(int downloads) { + final Resources res = ui.getActivity().getResources(); + + String s = Integer.toString(downloads) + + res.getString(R.string.parallel_downloads_suffix); + ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s); + } + + private void setEpisodeCacheSizeText(int cacheSize) { + final Resources res = ui.getActivity().getResources(); + + String s; + if (cacheSize == res.getInteger( + R.integer.episode_cache_size_unlimited)) { + s = res.getString(R.string.pref_episode_cache_unlimited); + } else { + s = Integer.toString(cacheSize) + + res.getString(R.string.episodes_suffix); + } + ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s); + } + + private void setDataFolderText() { + File f = UserPreferences.getDataFolder(null); + if (f != null) { + ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) + .setSummary(f.getAbsolutePath()); + } + } + + private void buildAutodownloadSelectedNetworsPreference() { + final Activity activity = ui.getActivity(); + + if (selectedNetworks != null) { + clearAutodownloadSelectedNetworsPreference(); + } + // get configured networks + WifiManager wifiservice = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE); + List networks = wifiservice.getConfiguredNetworks(); + + if (networks != null) { + selectedNetworks = new CheckBoxPreference[networks.size()]; + List prefValues = Arrays.asList(UserPreferences + .getAutodownloadSelectedNetworks()); + PreferenceScreen prefScreen = (PreferenceScreen) ui.findPreference(PreferenceController.AUTO_DL_PREF_SCREEN); + Preference.OnPreferenceClickListener clickListener = preference -> { + if (preference instanceof CheckBoxPreference) { + String key = preference.getKey(); + List prefValuesList = new ArrayList<>( + Arrays.asList(UserPreferences + .getAutodownloadSelectedNetworks()) + ); + boolean newValue = ((CheckBoxPreference) preference) + .isChecked(); + Log.d(TAG, "Selected network " + key + ". New state: " + newValue); + + int index = prefValuesList.indexOf(key); + if (index >= 0 && !newValue) { + // remove network + prefValuesList.remove(index); + } else if (index < 0 && newValue) { + prefValuesList.add(key); + } + + UserPreferences.setAutodownloadSelectedNetworks( + prefValuesList.toArray(new String[prefValuesList.size()]) + ); + return true; + } else { + return false; + } + }; + // create preference for each known network. attach listener and set + // value + for (int i = 0; i < networks.size(); i++) { + WifiConfiguration config = networks.get(i); + + CheckBoxPreference pref = new CheckBoxPreference(activity); + String key = Integer.toString(config.networkId); + pref.setTitle(config.SSID); + pref.setKey(key); + pref.setOnPreferenceClickListener(clickListener); + pref.setPersistent(false); + pref.setChecked(prefValues.contains(key)); + selectedNetworks[i] = pref; + prefScreen.addPreference(pref); + } + } else { + Log.e(TAG, "Couldn't get list of configure Wi-Fi networks"); + } + } + + private void clearAutodownloadSelectedNetworsPreference() { + if (selectedNetworks != null) { + PreferenceScreen prefScreen = (PreferenceScreen) ui.findPreference(PreferenceController.AUTO_DL_PREF_SCREEN); + + for (CheckBoxPreference network : selectedNetworks) { + if (network != null) { + prefScreen.removePreference(network); + } + } + } + } + + private void showDrawerPreferencesDialog() { + final Context context = ui.getActivity(); + final List hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); + final String[] navTitles = context.getResources().getStringArray(R.array.nav_drawer_titles); + final String[] NAV_DRAWER_TAGS = MainActivity.NAV_DRAWER_TAGS; + boolean[] checked = new boolean[MainActivity.NAV_DRAWER_TAGS.length]; + for(int i=0; i < NAV_DRAWER_TAGS.length; i++) { + String tag = NAV_DRAWER_TAGS[i]; + if(!hiddenDrawerItems.contains(tag)) { + checked[i] = true; + } + } + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.drawer_preferences); + builder.setMultiChoiceItems(navTitles, checked, (dialog, which, isChecked) -> { + if (isChecked) { + hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]); + } else { + hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + + private void showNotificationButtonsDialog() { + final Context context = ui.getActivity(); + final List preferredButtons = UserPreferences.getCompactNotificationButtons(); + final String[] allButtonNames = context.getResources().getStringArray( + R.array.compact_notification_buttons_options); + boolean[] checked = new boolean[allButtonNames.length]; // booleans default to false in java + + for(int i=0; i < checked.length; i++) { + if(preferredButtons.contains(i)) { + checked[i] = true; + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(String.format(context.getResources().getString( + R.string.pref_compact_notification_buttons_dialog_title), 2)); + builder.setMultiChoiceItems(allButtonNames, checked, (dialog, which, isChecked) -> { + checked[which] = isChecked; + + if (isChecked) { + if (preferredButtons.size() < 2) { + preferredButtons.add(which); + } else { + // Only allow a maximum of two selections. This is because the notification + // on the lock screen can only display 3 buttons, and the play/pause button + // is always included. + checked[which] = false; + ListView selectionView = ((AlertDialog) dialog).getListView(); + selectionView.setItemChecked(which, false); + Snackbar.make( + selectionView, + String.format(context.getResources().getString( + R.string.pref_compact_notification_buttons_dialog_error), 2), + Snackbar.LENGTH_SHORT).show(); + } + } else { + preferredButtons.remove((Integer) which); + } + }); + builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { + UserPreferences.setCompactNotificationButtons(preferredButtons); + }); + builder.setNegativeButton(R.string.cancel_label, null); + builder.create().show(); + } + + // CHOOSE DATA FOLDER + + private void requestPermission() { + ActivityCompat.requestPermissions(ui.getActivity(), EXTERNAL_STORAGE_PERMISSIONS, + PERMISSION_REQUEST_EXTERNAL_STORAGE); + } + + private void openDirectoryChooser() { + Activity activity = ui.getActivity(); + Intent intent = new Intent(activity, DirectoryChooserActivity.class); + activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); + } + + private void showChooseDataFolderDialog() { + Context context = ui.getActivity(); + File dataFolder = UserPreferences.getDataFolder(null); + if(dataFolder == null) { + new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.error_label) + .content(R.string.external_storage_error_msg) + .neutralText(android.R.string.ok) + .show(); + return; + } + String dataFolderPath = dataFolder.getAbsolutePath(); + int selectedIndex = -1; + File[] mediaDirs = ContextCompat.getExternalFilesDirs(context, null); + List folders = new ArrayList<>(mediaDirs.length); + List choices = new ArrayList<>(mediaDirs.length); + for(int i=0; i < mediaDirs.length; i++) { + File dir = mediaDirs[i]; + if(dir == null || !dir.exists() || !dir.canRead() || !dir.canWrite()) { + continue; + } + String path = mediaDirs[i].getAbsolutePath(); + folders.add(path); + if(dataFolderPath.equals(path)) { + selectedIndex = i; + } + int index = path.indexOf("Android"); + String choice; + if(index >= 0) { + choice = path.substring(0, index); + } else { + choice = path; + } + long bytes = StorageUtils.getFreeSpaceAvailable(path); + String freeSpace = String.format(context.getString(R.string.free_space_label), + Converter.byteToString(bytes)); + choices.add(Html.fromHtml("" + choice + + " [" + freeSpace + "]" + "")); + } + if(choices.size() == 0) { + new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.error_label) + .content(R.string.external_storage_error_msg) + .neutralText(android.R.string.ok) + .show(); + return; + } + MaterialDialog dialog = new MaterialDialog.Builder(ui.getActivity()) + .title(R.string.choose_data_directory) + .content(R.string.choose_data_directory_message) + .items(choices.toArray(new CharSequence[choices.size()])) + .itemsCallbackSingleChoice(selectedIndex, (dialog1, itemView, which, text) -> { + String folder = folders.get(which); + Log.d(TAG, "data folder: " + folder); + UserPreferences.setDataFolder(folder); + setDataFolderText(); + return true; + }) + .negativeText(R.string.cancel_label) + .cancelable(true) + .build(); + dialog.show(); + } + + // UPDATE TIME/INTERVAL DIALOG + + private void showUpdateIntervalTimePreferencesDialog() { + final Context context = ui.getActivity(); + + MaterialDialog.Builder builder = new MaterialDialog.Builder(context); + builder.title(R.string.pref_autoUpdateIntervallOrTime_title); + builder.content(R.string.pref_autoUpdateIntervallOrTime_message); + builder.positiveText(R.string.pref_autoUpdateIntervallOrTime_Interval); + builder.negativeText(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay); + builder.neutralText(R.string.pref_autoUpdateIntervallOrTime_Disable); + builder.onPositive((dialog, which) -> { + AlertDialog.Builder builder1 = new AlertDialog.Builder(context); + builder1.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_Interval)); + final String[] values = context.getResources().getStringArray(R.array.update_intervall_values); + final String[] entries = getUpdateIntervalEntries(values); + long currInterval = UserPreferences.getUpdateInterval(); + int checkedItem = -1; + if(currInterval > 0) { + String currIntervalStr = String.valueOf(TimeUnit.MILLISECONDS.toHours(currInterval)); + checkedItem = ArrayUtils.indexOf(values, currIntervalStr); + } + builder1.setSingleChoiceItems(entries, checkedItem, (dialog1, which1) -> { + int hours = Integer.parseInt(values[which1]); + UserPreferences.setUpdateInterval(hours); + dialog1.dismiss(); + setUpdateIntervalText(); + }); + builder1.setNegativeButton(context.getString(R.string.cancel_label), null); + builder1.show(); + }); + builder.onNegative((dialog, which) -> { + int hourOfDay = 7, minute = 0; + int[] updateTime = UserPreferences.getUpdateTimeOfDay(); + if (updateTime.length == 2) { + hourOfDay = updateTime[0]; + minute = updateTime[1]; + } + TimePickerDialog timePickerDialog = new TimePickerDialog(context, + (view, selectedHourOfDay, selectedMinute) -> { + if (view.getTag() == null) { // onTimeSet() may get called twice! + view.setTag("TAGGED"); + UserPreferences.setUpdateTimeOfDay(selectedHourOfDay, selectedMinute); + setUpdateIntervalText(); + } + }, hourOfDay, minute, DateFormat.is24HourFormat(context)); + timePickerDialog.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay)); + timePickerDialog.show(); + }); + builder.onNeutral((dialog, which) -> { + UserPreferences.setUpdateInterval(0); + setUpdateIntervalText(); + }); + builder.show(); + } + + + public interface PreferenceUI { + + /** + * Finds a preference based on its key. + */ + Preference findPreference(CharSequence key); + + Activity getActivity(); + } +} -- cgit v1.2.3