package com.boardgamegeek.ui;
import android.annotation.SuppressLint;
import android.content.AsyncQueryHandler;
import android.content.ContentProviderOperation;
import android.content.ContentProviderOperation.Builder;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.TextView;
import com.boardgamegeek.R;
import com.boardgamegeek.provider.BggContract.PlayerColors;
import com.boardgamegeek.ui.PlayerColorsActivity.RecyclerViewAdapter.ColorViewHolder;
import com.boardgamegeek.ui.dialog.ColorPickerDialogFragment;
import com.boardgamegeek.ui.model.PlayerColor;
import com.boardgamegeek.util.ActivityUtils;
import com.boardgamegeek.util.AnimationUtils;
import com.boardgamegeek.util.ColorUtils;
import com.boardgamegeek.util.DialogUtils;
import com.boardgamegeek.util.RandomUtils;
import com.boardgamegeek.util.ResolverUtils;
import com.boardgamegeek.util.fabric.PlayerColorsManipulationEvent;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.ContentViewEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import butterknife.BindDimen;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import hugo.weaving.DebugLog;
import timber.log.Timber;
public class PlayerColorsActivity extends BaseActivity {
private QueryHandler queryHandler;
private String buddyName;
private String playerName;
private List<PlayerColor> colors;
private RecyclerViewAdapter adapter;
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(android.R.id.progress) ContentLoadingProgressBar progressView;
@BindView(android.R.id.empty) View emptyView;
@BindView(android.R.id.list) RecyclerView recyclerView;
@BindView(R.id.coordinator) CoordinatorLayout coordinator;
private final Paint swipePaint = new Paint();
private Bitmap deleteIcon;
@BindDimen(R.dimen.material_margin_horizontal) float horizontalPadding;
private ItemTouchHelper itemTouchHelper;
@SuppressLint("HandlerLeak")
private class QueryHandler extends AsyncQueryHandler {
public QueryHandler(ContentResolver cr) {
super(cr);
}
@DebugLog
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// If the query didn't return a cursor for some reason return
if (cursor == null) {
return;
}
// If the Activity is finishing, then close the cursor
if (isFinishing()) {
cursor.close();
return;
}
colors = new ArrayList<>(cursor.getCount());
try {
while (cursor.moveToNext()) {
colors.add(PlayerColor.fromCursor(cursor));
}
adapter = new RecyclerViewAdapter(PlayerColorsActivity.this);
recyclerView.setAdapter(adapter);
} finally {
cursor.close();
}
showData();
adapter.notifyDataSetChanged();
}
}
@DebugLog
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player_colors);
ButterKnife.bind(this);
buddyName = getIntent().getStringExtra(ActivityUtils.KEY_BUDDY_NAME);
playerName = getIntent().getStringExtra(ActivityUtils.KEY_PLAYER_NAME);
if (TextUtils.isEmpty(buddyName) && TextUtils.isEmpty(playerName)) {
Timber.w("Can't launch - missing both buddy name and username.");
finish();
}
setSubtitle(TextUtils.isEmpty(buddyName) ? playerName : buddyName);
setUpRecyclerView();
if (toolbar != null) {
setSupportActionBar(toolbar);
}
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
queryHandler = new QueryHandler(getContentResolver());
startQuery();
if (savedInstanceState == null) {
final ContentViewEvent event = new ContentViewEvent()
.putContentType("PlayerColors");
if (!TextUtils.isEmpty(buddyName)) event.putContentId(buddyName);
if (!TextUtils.isEmpty(playerName)) event.putContentName(playerName);
Answers.getInstance().logContentView(event);
}
}
private void setUpRecyclerView() {
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
recyclerView.setHasFixedSize(true);
swipePaint.setColor(ContextCompat.getColor(this, R.color.medium_blue));
deleteIcon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_delete_white);
itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
if (colors == null) return false;
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
PlayerColor colorMoving = adapter.getItem(fromPosition);
if (colorMoving == null) return false;
if (fromPosition < toPosition) {
// dragging down
for (PlayerColor color : colors) {
if (color.getSortOrder() > fromPosition + 1 &&
color.getSortOrder() <= toPosition + 1) {
Timber.d("Moving %s up!", color.getColor());
color.setSortOrder(color.getSortOrder() - 1);
}
}
} else {
// dragging up
for (PlayerColor color : colors) {
if (color.getSortOrder() >= toPosition + 1 &&
color.getSortOrder() < fromPosition + 1) {
Timber.d("Moving %s down!", color.getColor());
color.setSortOrder(color.getSortOrder() + 1);
}
}
}
for (PlayerColor color : colors) {
if (color.getColor().equals(colorMoving.getColor())) {
Timber.d("Moving %s to %d!", color.getColor(), toPosition + 1);
color.setSortOrder(toPosition + 1);
break;
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
ColorViewHolder colorViewHolder = (ColorViewHolder) viewHolder;
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
colorViewHolder.onItemDragging();
} else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
colorViewHolder.onItemSwiping();
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
ColorViewHolder colorViewHolder = (ColorViewHolder) viewHolder;
colorViewHolder.onItemClear();
super.clearView(recyclerView, viewHolder);
}
@Override
public void onSwiped(ViewHolder viewHolder, int swipeDir) {
final PlayerColor color = adapter.getItem(viewHolder.getAdapterPosition());
if (color == null) return;
Snackbar.make(coordinator, getString(R.string.removed_suffix, color.getColor()), Snackbar.LENGTH_LONG)
.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
adapter.add(color);
PlayerColorsManipulationEvent.log("UndoDelete", color.getColor());
}
})
.setActionTextColor(ContextCompat.getColor(PlayerColorsActivity.this, R.color.light_blue))
.show();
adapter.remove(color);
PlayerColorsManipulationEvent.log("Delete", color.getColor());
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
// fade and slide item
float width = (float) itemView.getWidth();
float alpha = 1.0f - Math.abs(dX) / width;
itemView.setAlpha(alpha);
itemView.setTranslationX(dX);
// show background with delete icon
float verticalPadding = (itemView.getHeight() - deleteIcon.getHeight()) / 2;
RectF background;
Rect iconSrc;
RectF iconDst;
if (dX > 0) {
background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom());
iconSrc = new Rect(0, 0, (int) (dX - itemView.getLeft() - horizontalPadding), deleteIcon.getHeight());
iconDst = new RectF((float) itemView.getLeft() + horizontalPadding, (float) itemView.getTop() + verticalPadding, Math.min(itemView.getLeft() + horizontalPadding + deleteIcon.getWidth(), dX), (float) itemView.getBottom() - verticalPadding);
} else {
background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
iconSrc = new Rect(Math.max(deleteIcon.getWidth() + (int) horizontalPadding + (int) dX, 0), 0, deleteIcon.getWidth(), deleteIcon.getHeight());
iconDst = new RectF(Math.max((float) itemView.getRight() + dX, (float) itemView.getRight() - horizontalPadding - deleteIcon.getWidth()), (float) itemView.getTop() + verticalPadding, (float) itemView.getRight() - horizontalPadding, (float) itemView.getBottom() - verticalPadding);
}
c.drawRect(background, swipePaint);
c.drawBitmap(deleteIcon, iconSrc, iconDst, swipePaint);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
itemTouchHelper.attachToRecyclerView(recyclerView);
}
@DebugLog
@Override
protected void onStop() {
super.onStop();
if (colors != null) {
final ContentResolver resolver = getContentResolver();
deleteColors(resolver);
if (colors.size() > 0) {
ArrayList<ContentProviderOperation> batch = new ArrayList<>();
for (int i = 0; i < colors.size(); i++) {
final String color = colors.get(i).getColor();
final int sortOrder = colors.get(i).getSortOrder();
if (!TextUtils.isEmpty(color)) {
Builder builder = createCpoBuilder(resolver, sortOrder);
batch.add(builder.withValue(PlayerColors.PLAYER_COLOR, color).build());
}
}
ResolverUtils.applyBatch(this, batch);
}
colors = null; // to force a load from cursor
}
}
@DebugLog
private void deleteColors(ContentResolver resolver) {
if (TextUtils.isEmpty(buddyName)) {
resolver.delete(PlayerColors.buildPlayerUri(playerName), null, null);
} else {
resolver.delete(PlayerColors.buildUserUri(buddyName), null, null);
}
}
@DebugLog
private Builder createCpoBuilder(ContentResolver resolver, int sortOrder) {
Builder builder;
if (TextUtils.isEmpty(buddyName)) {
if (ResolverUtils.rowExists(resolver, PlayerColors.buildPlayerUri(playerName, sortOrder))) {
builder = ContentProviderOperation.newUpdate(PlayerColors.buildPlayerUri(playerName, sortOrder));
} else {
builder = ContentProviderOperation.newInsert(PlayerColors.buildPlayerUri(playerName))
.withValue(PlayerColors.PLAYER_COLOR_SORT_ORDER, sortOrder);
}
} else {
if (ResolverUtils.rowExists(resolver, PlayerColors.buildUserUri(buddyName, sortOrder))) {
builder = ContentProviderOperation.newUpdate(PlayerColors.buildUserUri(buddyName, sortOrder));
} else {
builder = ContentProviderOperation.newInsert(PlayerColors.buildUserUri(buddyName))
.withValue(PlayerColors.PLAYER_COLOR_SORT_ORDER, sortOrder);
}
}
return builder;
}
@DebugLog
@Override
protected int getOptionsMenuId() {
return R.menu.player_colors;
}
@DebugLog
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_clear:
DialogUtils.createConfirmationDialog(this, R.string.are_you_sure_clear_colors, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PlayerColorsManipulationEvent.log("Clear");
if (colors != null) colors.clear();
if (adapter != null) adapter.notifyDataSetChanged();
showData();
}
}).show();
break;
case android.R.id.home:
ActivityUtils.navigateUpToBuddy(this, buddyName, playerName);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@DebugLog
private void startQuery() {
if (colors != null) {
// we already have the play from the saved instance
showData();
adapter.notifyDataSetChanged();
} else {
queryHandler.startQuery(0, null,
TextUtils.isEmpty(buddyName) ? PlayerColors.buildPlayerUri(playerName) : PlayerColors.buildUserUri(buddyName),
PlayerColor.PROJECTION, null, null, null);
}
}
private void showData() {
progressView.hide();
if (colors == null || colors.size() == 0) {
AnimationUtils.fadeIn(emptyView);
AnimationUtils.fadeOut(recyclerView);
} else {
AnimationUtils.fadeOut(emptyView);
AnimationUtils.fadeIn(recyclerView);
}
}
@DebugLog
@OnClick(R.id.empty_button)
void onEmptyClick() {
List<Pair<String, Integer>> colors = ColorUtils.getLimitedColorList();
if (this.colors == null) {
this.colors = new ArrayList<>(colors.size());
}
Random r = RandomUtils.getRandom();
int order = 1;
while (colors.size() > 0) {
int i = r.nextInt(colors.size());
PlayerColor color = new PlayerColor(colors.remove(i).first, order++);
this.colors.add(color);
}
PlayerColorsManipulationEvent.log("Generate");
showData();
adapter.notifyItemRangeInserted(0, colors.size());
}
@OnClick(R.id.fab)
void onFabClick() {
if (colors == null) {
return;
}
ArrayList<String> usedColors = new ArrayList<>(colors.size());
for (PlayerColor color : colors) {
usedColors.add(color.getColor());
}
ColorPickerDialogFragment fragment = ColorPickerDialogFragment.newInstance(R.string.title_add_color,
ColorUtils.getColorList(), null, null, null, usedColors, 4);
fragment.setOnColorSelectedListener(new ColorPickerDialogFragment.OnColorSelectedListener() {
@Override
public void onColorSelected(String description, int color) {
if (colors != null) {
PlayerColorsManipulationEvent.log("Add", description);
colors.add(new PlayerColor(description, colors.size() + 1));
showData();
adapter.notifyItemInserted(colors.size());
}
}
});
fragment.show(getSupportFragmentManager(), "color_picker");
}
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ColorViewHolder> {
private final LayoutInflater inflater;
public RecyclerViewAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
@Override
public ColorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ColorViewHolder(inflater.inflate(R.layout.row_player_color, parent, false));
}
@Override
public void onBindViewHolder(ColorViewHolder holder, int position) {
PlayerColor color = getItem(position);
if (color == null) return;
holder.bind(color);
}
@Override
public int getItemCount() {
return colors == null ? 0 : colors.size();
}
@Override
public long getItemId(int position) {
return position;
}
public PlayerColor getItem(int position) {
if (colors == null) return null;
for (PlayerColor color : colors) {
if (color.getSortOrder() == position + 1) {
return color;
}
}
return null;
}
public void add(PlayerColor color) {
if (colors == null) return;
for (PlayerColor c : colors) {
if (c.getSortOrder() >= color.getSortOrder()) {
Timber.d("Moving %s down!", c.getColor());
c.setSortOrder(c.getSortOrder() + 1);
}
}
Timber.d("Re-adding %s!", color);
colors.add(color);
notifyItemInserted(color.getSortOrder() - 1);
}
public void remove(PlayerColor color) {
if (colors == null) return;
Timber.d("Removing %s!", color);
colors.remove(color);
for (PlayerColor c : colors) {
if (c.getSortOrder() >= color.getSortOrder()) {
Timber.d("Moving %s up!", c.getColor());
c.setSortOrder(c.getSortOrder() - 1);
}
}
notifyItemRemoved(color.getSortOrder() - 1);
}
public class ColorViewHolder extends RecyclerView.ViewHolder {
@BindView(android.R.id.title) TextView titleView;
@BindView(R.id.color_view) View colorView;
@BindView(R.id.drag_handle) View dragHandle;
public ColorViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void bind(final PlayerColor color) {
if (color == null) return;
titleView.setText(color.getColor());
ColorUtils.setColorViewValue(colorView, ColorUtils.parseColor(color.getColor()));
dragHandle.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (itemTouchHelper != null &&
MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(ColorViewHolder.this);
}
return false;
}
});
}
public void onItemDragging() {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.getContext(), R.color.light_blue_transparent));
}
public void onItemSwiping() {
itemView.setBackgroundColor(Color.WHITE);
}
public void onItemClear() {
itemView.setBackgroundColor(Color.TRANSPARENT);
}
}
}
}