package org.wordpress.android.ui.main;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import com.android.volley.VolleyError;
import com.wordpress.rest.RestRequest;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.fluxc.Dispatcher;
import org.wordpress.android.fluxc.generated.SiteActionBuilder;
import org.wordpress.android.fluxc.model.SiteModel;
import org.wordpress.android.fluxc.store.AccountStore;
import org.wordpress.android.fluxc.store.SiteStore;
import org.wordpress.android.fluxc.store.SiteStore.OnSiteChanged;
import org.wordpress.android.fluxc.store.SiteStore.OnSiteRemoved;
import org.wordpress.android.ui.ActivityId;
import org.wordpress.android.ui.ActivityLauncher;
import org.wordpress.android.ui.RequestCodes;
import org.wordpress.android.ui.main.SitePickerAdapter.SiteList;
import org.wordpress.android.ui.main.SitePickerAdapter.SiteRecord;
import org.wordpress.android.ui.prefs.AppPrefs;
import org.wordpress.android.ui.stats.datasets.StatsTable;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.util.WPActivityUtils;
import org.wordpress.android.util.helpers.Debouncer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class SitePickerActivity extends AppCompatActivity
implements SitePickerAdapter.OnSiteClickListener,
SitePickerAdapter.OnSelectedCountChangedListener,
SearchView.OnQueryTextListener {
public static final String KEY_LOCAL_ID = "local_id";
private static final String KEY_IS_IN_SEARCH_MODE = "is_in_search_mode";
private static final String KEY_LAST_SEARCH = "last_search";
private SitePickerAdapter mAdapter;
private RecyclerView mRecycleView;
private ActionMode mActionMode;
private MenuItem mMenuEdit;
private MenuItem mMenuAdd;
private MenuItem mMenuSearch;
private SearchView mSearchView;
private int mCurrentLocalId;
private boolean mDidUserSelectSite;
private Debouncer mDebouncer = new Debouncer();
@Inject AccountStore mAccountStore;
@Inject SiteStore mSiteStore;
@Inject Dispatcher mDispatcher;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((WordPress) getApplication()).component().inject(this);
setContentView(R.layout.site_picker_activity);
restoreSavedInstanceState(savedInstanceState);
setupActionBar();
setupRecycleView();
}
@Override
public void onResume() {
super.onResume();
ActivityId.trackLastActivity(ActivityId.SITE_PICKER);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(KEY_LOCAL_ID, mCurrentLocalId);
outState.putBoolean(KEY_IS_IN_SEARCH_MODE, getAdapter().getIsInSearchMode());
outState.putString(KEY_LAST_SEARCH, getAdapter().getLastSearch());
super.onSaveInstanceState(outState);
}
@Override
public void finish() {
super.finish();
if (mDidUserSelectSite) {
overridePendingTransition(R.anim.do_nothing, R.anim.activity_slide_out_to_left);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.site_picker, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
mMenuEdit = menu.findItem(R.id.menu_edit);
mMenuAdd = menu.findItem(R.id.menu_add);
mMenuSearch = menu.findItem(R.id.menu_search);
updateMenuItemVisibility();
setupSearchView();
return true;
}
private void updateMenuItemVisibility() {
if (mMenuAdd == null || mMenuEdit == null || mMenuSearch == null) return;
if (getAdapter().getIsInSearchMode()) {
mMenuEdit.setVisible(false);
mMenuAdd.setVisible(false);
} else {
// don't allow editing visibility unless there are multiple wp.com and jetpack sites
mMenuEdit.setVisible(mSiteStore.getSitesAccessedViaWPComRestCount() > 1);
mMenuAdd.setVisible(true);
}
// no point showing search if there aren't multiple blogs
mMenuSearch.setVisible(mSiteStore.getSitesCount() > 1);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
onBackPressed();
return true;
} else if (itemId == R.id.menu_edit) {
startEditingVisibility();
return true;
} else if (itemId == R.id.menu_search) {
mSearchView.requestFocus();
showSoftKeyboard();
return true;
} else if (itemId == R.id.menu_add) {
addSite(this, mAccountStore.hasAccessToken());
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case RequestCodes.ADD_ACCOUNT:
case RequestCodes.CREATE_SITE:
if (resultCode == RESULT_OK) {
getAdapter().loadSites();
setResult(resultCode, data);
finish();
}
break;
}
}
@Override
protected void onStop() {
mDispatcher.unregister(this);
mDebouncer.shutdown();
super.onStop();
}
@Override
protected void onStart() {
super.onStart();
mDispatcher.register(this);
}
@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSiteRemoved(OnSiteRemoved event) {
if (!event.isError()) {
getAdapter().loadSites();
} else {
// shouldn't happen
AppLog.e(AppLog.T.DB, "Encountered unexpected error while attempting to remove site: " + event.error);
ToastUtils.showToast(this, R.string.site_picker_remove_site_error);
}
}
@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSiteChanged(OnSiteChanged event) {
mDebouncer.debounce(Void.class, new Runnable() {
@Override public void run() {
if (!isFinishing()) {
getAdapter().loadSites();
}
}
}, 200, TimeUnit.MILLISECONDS);
}
private void setupRecycleView() {
mRecycleView = (RecyclerView) findViewById(R.id.recycler_view);
mRecycleView.setLayoutManager(new LinearLayoutManager(this));
mRecycleView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
mRecycleView.setItemAnimator(null);
mRecycleView.setAdapter(getAdapter());
}
private void restoreSavedInstanceState(Bundle savedInstanceState) {
boolean isInSearchMode = false;
String lastSearch = "";
if (savedInstanceState != null) {
mCurrentLocalId = savedInstanceState.getInt(KEY_LOCAL_ID);
isInSearchMode = savedInstanceState.getBoolean(KEY_IS_IN_SEARCH_MODE);
lastSearch = savedInstanceState.getString(KEY_LAST_SEARCH);
} else if (getIntent() != null) {
mCurrentLocalId = getIntent().getIntExtra(KEY_LOCAL_ID, 0);
}
setNewAdapter(lastSearch, isInSearchMode);
}
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeAsUpIndicator(R.drawable.ic_cross_white_24dp);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.site_picker_title);
}
}
private void setIsInSearchModeAndSetNewAdapter(boolean isInSearchMode) {
String lastSearch = getAdapter().getLastSearch();
setNewAdapter(lastSearch, isInSearchMode);
}
private SitePickerAdapter getAdapter() {
if (mAdapter == null) {
setNewAdapter("", false);
}
return mAdapter;
}
private void setNewAdapter(String lastSearch, boolean isInSearchMode) {
mAdapter = new SitePickerAdapter(
this,
mCurrentLocalId,
lastSearch,
isInSearchMode,
new SitePickerAdapter.OnDataLoadedListener() {
@Override
public void onBeforeLoad(boolean isEmpty) {
if (isEmpty) {
showProgress(true);
}
}
@Override
public void onAfterLoad() {
showProgress(false);
}
});
mAdapter.setOnSiteClickListener(this);
mAdapter.setOnSelectedCountChangedListener(this);
}
private void saveSiteVisibility(SiteRecord siteRecord) {
Set<SiteRecord> siteRecords = new HashSet<>();
siteRecords.add(siteRecord);
saveSitesVisibility(siteRecords);
}
private void saveSitesVisibility(Set<SiteRecord> changeSet) {
boolean skippedCurrentSite = false;
String currentSiteName = null;
SiteList hiddenSites = getAdapter().getHiddenSites();
List<SiteModel> siteList = new ArrayList<>();
for (SiteRecord siteRecord : changeSet) {
SiteModel siteModel = mSiteStore.getSiteByLocalId(siteRecord.localId);
if (hiddenSites.contains(siteRecord)) {
if (siteRecord.localId == mCurrentLocalId) {
skippedCurrentSite = true;
currentSiteName = siteRecord.getBlogNameOrHomeURL();
continue;
}
siteModel.setIsVisible(false);
// Remove stats data for hidden sites
StatsTable.deleteStatsForBlog(this, siteRecord.localId);
} else {
siteModel.setIsVisible(true);
}
// Save the site
mDispatcher.dispatch(SiteActionBuilder.newUpdateSiteAction(siteModel));
siteList.add(siteModel);
}
updateVisibilityOfSitesOnRemote(siteList);
// let user know the current site wasn't hidden
if (skippedCurrentSite) {
String cantHideCurrentSite = getString(R.string.site_picker_cant_hide_current_site);
ToastUtils.showToast(this,
String.format(cantHideCurrentSite, currentSiteName),
ToastUtils.Duration.LONG);
}
}
private void updateVisibilityOfSitesOnRemote(List<SiteModel> siteList) {
// Example json format for the request: {"sites":{"100001":{"visible":false}}}
JSONObject jsonObject = new JSONObject();
try {
JSONObject sites = new JSONObject();
for (SiteModel siteModel : siteList) {
JSONObject visible = new JSONObject();
visible.put("visible", siteModel.isVisible());
sites.put(Long.toString(siteModel.getSiteId()), visible);
}
jsonObject.put("sites", sites);
} catch (JSONException e) {
AppLog.e(AppLog.T.API, "Could not build me/sites json object");
}
if (jsonObject.length() == 0) {
return;
}
WordPress.getRestClientUtilsV1_1().post("me/sites", jsonObject, null, new RestRequest.Listener() {
@Override
public void onResponse(JSONObject response) {
AppLog.v(AppLog.T.API, "Site visibility successfully updated");
}
}, new RestRequest.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
AppLog.e(AppLog.T.API, "An error occurred while updating site visibility: " + volleyError);
}
});
}
private void updateActionModeTitle() {
if (mActionMode != null) {
int numSelected = getAdapter().getNumSelected();
String cabSelected = getString(R.string.cab_selected);
mActionMode.setTitle(String.format(cabSelected, numSelected));
}
}
private void setupSearchView() {
mSearchView = (SearchView) mMenuSearch.getActionView();
mSearchView.setIconifiedByDefault(false);
mSearchView.setOnQueryTextListener(this);
MenuItemCompat.setOnActionExpandListener(mMenuSearch, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
enableSearchMode();
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
disableSearchMode();
return true;
}
});
setQueryIfInSearch();
}
private void setQueryIfInSearch() {
if (getAdapter().getIsInSearchMode()) {
mMenuSearch.expandActionView();
mSearchView.setQuery(getAdapter().getLastSearch(), false);
}
}
private void enableSearchMode() {
setIsInSearchModeAndSetNewAdapter(true);
mRecycleView.swapAdapter(getAdapter(), true);
updateMenuItemVisibility();
}
private void disableSearchMode() {
hideSoftKeyboard();
setIsInSearchModeAndSetNewAdapter(false);
mRecycleView.swapAdapter(getAdapter(), true);
updateMenuItemVisibility();
}
private void hideSoftKeyboard() {
if (!hasHardwareKeyboard()) {
WPActivityUtils.hideKeyboard(mSearchView);
}
}
private void showSoftKeyboard() {
if (!hasHardwareKeyboard()) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
private boolean hasHardwareKeyboard() {
return (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
}
@Override
public void onSelectedCountChanged(int numSelected) {
if (mActionMode != null) {
updateActionModeTitle();
mActionMode.invalidate();
}
}
@Override
public boolean onSiteLongClick(final SiteRecord siteRecord) {
final SiteModel site = mSiteStore.getSiteByLocalId(siteRecord.localId);
if (site == null) {
return false;
}
if (site.isWPCom()) {
if (mActionMode != null) {
return false;
}
startEditingVisibility();
} else {
showRemoveSelfHostedSiteDialog(site);
}
return true;
}
@Override
public void onSiteClick(SiteRecord siteRecord) {
if (mActionMode == null) {
hideSoftKeyboard();
AppPrefs.addRecentlyPickedSiteId(siteRecord.localId);
setResult(RESULT_OK, new Intent().putExtra(KEY_LOCAL_ID, siteRecord.localId));
mDidUserSelectSite = true;
// If the site is hidden, make sure to make it visible
if (siteRecord.isHidden) {
siteRecord.isHidden = false;
saveSiteVisibility(siteRecord);
}
finish();
}
}
@Override
public boolean onQueryTextSubmit(String s) {
hideSoftKeyboard();
return true;
}
@Override
public boolean onQueryTextChange(String s) {
getAdapter().setLastSearch(s);
getAdapter().searchSites(s);
return true;
}
public void showProgress(boolean show) {
findViewById(R.id.progress).setVisibility(show ? View.VISIBLE : View.GONE);
}
private final class ActionModeCallback implements ActionMode.Callback {
private boolean mHasChanges;
private Set<SiteRecord> mChangeSet;
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
mActionMode = actionMode;
mHasChanges = false;
mChangeSet = new HashSet<>();
updateActionModeTitle();
actionMode.getMenuInflater().inflate(R.menu.site_picker_action_mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
MenuItem mnuShow = menu.findItem(R.id.menu_show);
mnuShow.setEnabled(getAdapter().getNumHiddenSelected() > 0);
MenuItem mnuHide = menu.findItem(R.id.menu_hide);
mnuHide.setEnabled(getAdapter().getNumVisibleSelected() > 0);
MenuItem mnuSelectAll = menu.findItem(R.id.menu_select_all);
mnuSelectAll.setEnabled(getAdapter().getNumSelected() != getAdapter().getItemCount());
MenuItem mnuDeselectAll = menu.findItem(R.id.menu_deselect_all);
mnuDeselectAll.setEnabled(getAdapter().getNumSelected() > 0);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.menu_show) {
Set<SiteRecord> changeSet = getAdapter().setVisibilityForSelectedSites(true);
mChangeSet.addAll(changeSet);
mHasChanges = true;
mActionMode.finish();
} else if (itemId == R.id.menu_hide) {
Set<SiteRecord> changeSet = getAdapter().setVisibilityForSelectedSites(false);
mChangeSet.addAll(changeSet);
mHasChanges = true;
mActionMode.finish();
} else if (itemId == R.id.menu_select_all) {
getAdapter().selectAll();
} else if (itemId == R.id.menu_deselect_all) {
getAdapter().deselectAll();
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
if (mHasChanges) {
saveSitesVisibility(mChangeSet);
}
getAdapter().setEnableEditMode(false);
mActionMode = null;
}
}
public static void addSite(Activity activity, boolean isSignedInWpCom) {
// if user is signed into wp.com use the dialog to enable choosing whether to
// create a new wp.com blog or add a self-hosted one
if (isSignedInWpCom) {
DialogFragment dialog = new AddSiteDialog();
dialog.show(activity.getFragmentManager(), AddSiteDialog.ADD_SITE_DIALOG_TAG);
} else {
// user isn't signed into wp.com, so simply enable adding self-hosted
ActivityLauncher.addSelfHostedSiteForResult(activity);
}
}
/*
* dialog which appears after user taps "Add site" - enables choosing whether to create
* a new wp.com blog or add an existing self-hosted one
*/
public static class AddSiteDialog extends DialogFragment {
static final String ADD_SITE_DIALOG_TAG = "add_site_dialog";
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
CharSequence[] items =
{getString(R.string.site_picker_create_dotcom),
getString(R.string.site_picker_add_self_hosted)};
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.site_picker_add_site);
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
ActivityLauncher.newBlogForResult(getActivity());
} else {
ActivityLauncher.addSelfHostedSiteForResult(getActivity());
}
}
});
return builder.create();
}
}
private void startEditingVisibility() {
mRecycleView.setItemAnimator(new DefaultItemAnimator());
getAdapter().setEnableEditMode(true);
startSupportActionMode(new ActionModeCallback());
}
private void showRemoveSelfHostedSiteDialog(@NonNull final SiteModel site) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.setTitle(getResources().getText(R.string.remove_account));
dialogBuilder.setMessage(getResources().getText(R.string.sure_to_remove_account));
dialogBuilder.setPositiveButton(getResources().getText(R.string.yes), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mDispatcher.dispatch(SiteActionBuilder.newRemoveSiteAction(site));
}
});
dialogBuilder.setNegativeButton(getResources().getText(R.string.no), null);
dialogBuilder.setCancelable(false);
dialogBuilder.create().show();
}
}