package com.battlelancer.seriesguide.extensions; import android.content.ComponentName; import android.graphics.Point; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.text.TextUtils; 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.widget.AdapterView; import android.widget.PopupMenu; import android.widget.Toast; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; import com.battlelancer.seriesguide.BuildConfig; import com.battlelancer.seriesguide.R; import com.battlelancer.seriesguide.adapters.ExtensionsAdapter; import com.battlelancer.seriesguide.loaders.AvailableExtensionsLoader; import com.battlelancer.seriesguide.util.Utils; import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; import org.greenrobot.eventbus.EventBus; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import timber.log.Timber; /** * Provides tools to display all installed extensions and enable or disable them. */ public class ExtensionsConfigurationFragment extends Fragment implements AdapterView.OnItemClickListener { public static final int EXTENSION_LIMIT_FREE = 2; private static final String TAG = "Extension Configuration"; @BindView(R.id.listViewExtensionsConfiguration) DragSortListView listView; private ExtensionsAdapter adapter; private PopupMenu addExtensionPopupMenu; private Unbinder unbinder; private List<ExtensionManager.Extension> availableExtensions = new ArrayList<>(); private List<ComponentName> enabledExtensions; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_extensions_configuration, container, false); unbinder = ButterKnife.bind(this, v); final ExtensionsDragSortController dragSortController = new ExtensionsDragSortController(); listView.setFloatViewManager(dragSortController); listView.setOnTouchListener(dragSortController); listView.setDropListener(new DragSortListView.DropListener() { @Override public void drop(int from, int to) { ComponentName extension = enabledExtensions.remove(from); enabledExtensions.add(to, extension); getLoaderManager().restartLoader(ExtensionsConfigurationActivity.LOADER_ACTIONS_ID, null, mExtensionsLoaderCallbacks); } }); listView.setOnItemClickListener(this); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); adapter = new ExtensionsAdapter(getActivity()); listView.setAdapter(adapter); getLoaderManager().initLoader(ExtensionsConfigurationActivity.LOADER_ACTIONS_ID, null, mExtensionsLoaderCallbacks); setHasOptionsMenu(true); } @Override public void onResume() { super.onResume(); EventBus.getDefault().register(this); } @Override public void onPause() { super.onPause(); EventBus.getDefault().unregister(this); if (enabledExtensions != null) { // might not have finished loading, yet ExtensionManager.getInstance(getActivity()).setEnabledExtensions(enabledExtensions); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (BuildConfig.DEBUG) { // add debug options to enable/disable all extensions inflater.inflate(R.menu.extensions_configuration_menu, menu); } } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.menu_action_extensions_enable) { List<ExtensionManager.Extension> extensions = ExtensionManager.getInstance( getActivity()).queryAllAvailableExtensions(); List<ComponentName> enabledExtensions = new ArrayList<>(); for (ExtensionManager.Extension extension : extensions) { enabledExtensions.add(extension.componentName); } ExtensionManager.getInstance(getActivity()).setEnabledExtensions(enabledExtensions); Toast.makeText(getActivity(), "Enabled all available extensions", Toast.LENGTH_LONG) .show(); return true; } if (itemId == R.id.menu_action_extensions_disable) { ExtensionManager.getInstance(getActivity()) .setEnabledExtensions(new ArrayList<ComponentName>()); Toast.makeText(getActivity(), "Disabled all available extensions", Toast.LENGTH_LONG) .show(); return true; } return super.onOptionsItemSelected(item); } @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } private LoaderManager.LoaderCallbacks<List<ExtensionManager.Extension>> mExtensionsLoaderCallbacks = new LoaderManager.LoaderCallbacks<List<ExtensionManager.Extension>>() { @Override public Loader<List<ExtensionManager.Extension>> onCreateLoader(int id, Bundle args) { return new AvailableExtensionsLoader(getActivity()); } @Override public void onLoadFinished(Loader<List<ExtensionManager.Extension>> loader, @NonNull List<ExtensionManager.Extension> availableExtensions) { if (availableExtensions.size() == 0) { Timber.d("Did not find any extension"); } else { Timber.d("Found %d extensions", availableExtensions.size()); } if (enabledExtensions == null) { enabledExtensions = ExtensionManager.getInstance(getActivity()) .getEnabledExtensions(); } Set<ComponentName> enabledExtensions = new HashSet<>( ExtensionsConfigurationFragment.this.enabledExtensions); // find all extensions not yet enabled ExtensionsConfigurationFragment.this.availableExtensions.clear(); Map<ComponentName, ExtensionManager.Extension> enabledExtensionsMap = new HashMap<>(); for (ExtensionManager.Extension extension : availableExtensions) { if (enabledExtensions.contains(extension.componentName)) { // extension is already enabled enabledExtensionsMap.put(extension.componentName, extension); continue; } ExtensionsConfigurationFragment.this.availableExtensions.add(extension); } // sort available extensions alphabetically Collections.sort(ExtensionsConfigurationFragment.this.availableExtensions, new Comparator<ExtensionManager.Extension>() { @Override public int compare(ExtensionManager.Extension extension1, ExtensionManager.Extension extension2) { String title1 = createTitle(extension1); String title2 = createTitle(extension2); return title1.compareToIgnoreCase(title2); } private String createTitle(ExtensionManager.Extension extension) { String title = extension.label; if (TextUtils.isEmpty(title)) { title = extension.componentName.flattenToShortString(); } return title; } }); // force re-creation of extension add menu if (addExtensionPopupMenu != null) { addExtensionPopupMenu.dismiss(); addExtensionPopupMenu = null; } // list enabled extensions in order dictated by extension manager List<ExtensionManager.Extension> enabledExtensionsList = new ArrayList<>(); List<ComponentName> enabledExtensionNames = new ArrayList<>( ExtensionsConfigurationFragment.this.enabledExtensions); for (ComponentName extensionName : enabledExtensionNames) { ExtensionManager.Extension extension = enabledExtensionsMap.get(extensionName); if (extension == null) { // filter out any unavailable/uninstalled extensions ExtensionsConfigurationFragment.this.enabledExtensions.remove(extensionName); continue; } enabledExtensionsList.add(extension); } // refresh enabled extensions list adapter.clear(); adapter.addAll(enabledExtensionsList); } @Override public void onLoaderReset(Loader<List<ExtensionManager.Extension>> loader) { adapter.clear(); } }; @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position == adapter.getCount() - 1) { // non-supporters only can add a few extensions if (adapter.getCount() - 1 == EXTENSION_LIMIT_FREE && !Utils.hasAccessToX(getActivity())) { Utils.advertiseSubscription(getActivity()); return; } showAddExtensionPopupMenu(view.findViewById(R.id.textViewItemExtensionAddLabel)); Utils.trackAction(getActivity(), TAG, "Add extension"); } } @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(ExtensionsAdapter.ExtensionDisableRequestEvent event) { enabledExtensions.remove(event.position); getLoaderManager().restartLoader(ExtensionsConfigurationActivity.LOADER_ACTIONS_ID, null, mExtensionsLoaderCallbacks); Utils.trackAction(getActivity(), TAG, "Remove extension"); } private void showAddExtensionPopupMenu(View anchorView) { if (addExtensionPopupMenu != null) { addExtensionPopupMenu.dismiss(); } addExtensionPopupMenu = new PopupMenu(getActivity(), anchorView); Menu menu = addExtensionPopupMenu.getMenu(); // list of installed, but disabled extensions for (int i = 0; i < availableExtensions.size(); i++) { ExtensionManager.Extension extension = availableExtensions.get(i); menu.add(Menu.NONE, i + 1, Menu.NONE, extension.label); } // no third-party extensions supported on Amazon app store for now if (!Utils.isAmazonVersion()) { // link to get more extensions menu.add(Menu.NONE, 0, Menu.NONE, R.string.action_extensions_search); } addExtensionPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(android.view.MenuItem item) { if (item.getItemId() == 0) { // special item: search for more extensions onGetMoreExtensions(); return true; } // add to enabled extensions ExtensionManager.Extension extension = availableExtensions.get( item.getItemId() - 1); enabledExtensions.add(extension.componentName); // re-populate extension list getLoaderManager().restartLoader(ExtensionsConfigurationActivity.LOADER_ACTIONS_ID, null, mExtensionsLoaderCallbacks); // scroll to end of list listView.smoothScrollToPosition(adapter.getCount() - 1); return true; } }); addExtensionPopupMenu.show(); } private void onGetMoreExtensions() { Utils.launchWebsite(getActivity(), getString(R.string.url_extensions_search), TAG, "Get more extensions"); } private class ExtensionsDragSortController extends DragSortController { private int mFloatViewOriginPosition; public ExtensionsDragSortController() { super(listView, R.id.drag_handle, DragSortController.ON_DOWN, DragSortController.CLICK_REMOVE); setRemoveEnabled(false); } @Override public int startDragPosition(MotionEvent ev) { int hitPosition = super.dragHandleHitPosition(ev); if (hitPosition >= adapter.getCount() - 1) { return DragSortController.MISS; } return hitPosition; } @Override public View onCreateFloatView(int position) { mFloatViewOriginPosition = position; return super.onCreateFloatView(position); } private int mFloatViewHeight = -1; // cache height @Override public void onDragFloatView(View floatView, Point floatPoint, Point touchPoint) { final int addButtonPosition = adapter.getCount() - 1; final int first = listView.getFirstVisiblePosition(); final int lvDivHeight = listView.getDividerHeight(); if (mFloatViewHeight == -1) { mFloatViewHeight = floatView.getHeight(); } View div = listView.getChildAt(addButtonPosition - first); if (touchPoint.x > listView.getWidth() / 2) { float scale = touchPoint.x - listView.getWidth() / 2; scale /= (float) (listView.getWidth() / 5); ViewGroup.LayoutParams lp = floatView.getLayoutParams(); lp.height = Math.max(mFloatViewHeight, (int) (scale * mFloatViewHeight)); floatView.setLayoutParams(lp); } if (div != null) { if (mFloatViewOriginPosition > addButtonPosition) { // don't allow floating View to go above // section divider final int limit = div.getBottom() + lvDivHeight; if (floatPoint.y < limit) { floatPoint.y = limit; } } else { // don't allow floating View to go below // section divider final int limit = div.getTop() - lvDivHeight - floatView.getHeight(); if (floatPoint.y > limit) { floatPoint.y = limit; } } } } } }