package tk.wasdennnoch.androidn_ify.systemui.qs;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.robv.android.xposed.XposedHelpers;
import tk.wasdennnoch.androidn_ify.R;
import tk.wasdennnoch.androidn_ify.XposedHook;
import tk.wasdennnoch.androidn_ify.systemui.notifications.StatusBarHeaderHooks;
import tk.wasdennnoch.androidn_ify.utils.ConfigUtils;
import tk.wasdennnoch.androidn_ify.utils.ResourceUtils;
import static tk.wasdennnoch.androidn_ify.systemui.qs.QSTileHostHooks.KEY_EDIT_TILEVIEW;
import static tk.wasdennnoch.androidn_ify.systemui.qs.customize.QSCustomizer.MODE_EDIT_SECURE;
import static tk.wasdennnoch.androidn_ify.systemui.qs.customize.QSCustomizer.MODE_NORMAL;
@SuppressWarnings("WeakerAccess")
public class TileAdapter extends RecyclerView.Adapter<TileAdapter.TileViewHolder> {
protected static final String PACKAGE_SYSTEMUI = XposedHook.PACKAGE_SYSTEMUI;
public static final long MOVE_DURATION = 150;
private static final long DRAG_LENGTH = 100;
private static final float DRAG_SCALE = 1.2f;
public static final float TILE_ASPECT = 1.2f;
private static final String TAG = "TileAdapter";
private final ItemTouchHelper mItemTouchHelper;
private final ListUpdateCallback mTileUpdateCallback = new TileUpdateCallback();
private List<String> mSecureTiles = new ArrayList<>();
private List<String> mPreviousSpecs = new ArrayList<>();
private List<String> mTileSpecs = new ArrayList<>();
protected ArrayList<Object> mRecords = new ArrayList<>();
protected ArrayList<ViewGroup> mTileViews = new ArrayList<>();
protected final Context mContext;
protected final int mCellHeight;
protected final int mCellWidth;
private ResourceUtils mRes;
public int mDividerIndex;
private int mMode = MODE_NORMAL;
public TileAdapter.TileViewHolder mCurrentDrag;
private boolean mIsInvalid = true;
public TileAdapter(Context context) {
mContext = context;
//mQsPanel = qsPanel;
mCellHeight = mContext.getResources().getDimensionPixelSize(mContext.getResources().getIdentifier("qs_tile_height", "dimen", PACKAGE_SYSTEMUI));
mCellWidth = (int) (mCellHeight * TILE_ASPECT);
ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;
if (mMode == MODE_EDIT_SECURE || viewHolder.getItemViewType() == 1) {
dragFlags = 0;
}
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return onItemMove(viewHolder.getAdapterPosition(),
target.getAdapterPosition());
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (mCurrentDrag != null) {
mCurrentDrag.stopDrag();
mCurrentDrag = null;
}
if (viewHolder != null) {
mCurrentDrag = (TileViewHolder) viewHolder;
mCurrentDrag.startDrag();
}
try {
notifyItemChanged(mDividerIndex);
} catch (Throwable ignore) {
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
((TileViewHolder) viewHolder).stopDrag();
super.clearView(recyclerView, viewHolder);
}
};
mItemTouchHelper = new CustomItemTouchHelper(mCallbacks);
XposedHelpers.setIntField(mCallbacks, "mCachedMaxScrollSpeed", ResourceUtils.getInstance(mContext).getDimensionPixelSize(R.dimen.lib_item_touch_helper_max_drag_scroll_per_frame));
}
private void init(ArrayList<Object> records, Context context) {
mIsInvalid = false;
mRes = ResourceUtils.getInstance(context);
setRecords(records);
mTileSpecs = convertToSpecs();
mPreviousSpecs.addAll(mTileSpecs);
if (ConfigUtils.M && QSTileHostHooks.mSecureTiles != null)
mSecureTiles.addAll(QSTileHostHooks.mSecureTiles);
addDivider();
addAvailableTiles();
mDividerIndex = mTileViews.indexOf(null);
}
public void reInit(ArrayList<Object> records, Context context) {
mRecords.clear();
mPreviousSpecs.clear();
mTileSpecs.clear();
mTileViews.clear();
mSecureTiles.clear();
init(records, context);
}
public void setMode(int mode) {
mMode = mode;
notifyDataSetChanged();
}
public int getMode() {
return mMode;
}
private void addDivider() {
mTileSpecs.add(null);
//mRecords.add(null);
mTileViews.add(null);
}
private void addAvailableTiles() {
// TODO completely remove AvailableTileAdapter
AvailableTileAdapter mAvailableTileAdapter = new AvailableTileAdapter(mRecords, mContext);
int count = mAvailableTileAdapter.getItemCount();
for (int i = 0; i < count; i++) {
mTileSpecs.add((String) mAvailableTileAdapter.mRecords.get(i));
mTileViews.add(mAvailableTileAdapter.mTileViews.get(i));
}
}
public void setRecords(ArrayList<Object> records) {
mTileViews.clear();
mRecords.clear();
for (int i = 0; i < records.size(); i++) {
Object tilerecord = records.get(i);
final Object tile = XposedHelpers.getObjectField(tilerecord, "tile");
addTile(i, tile);
mRecords.add(tilerecord);
}
}
private void addTile(int i, Object tile) {
RelativeLayout.LayoutParams tileViewLp = new RelativeLayout.LayoutParams(mCellWidth, mCellHeight);
tileViewLp.addRule(RelativeLayout.CENTER_IN_PARENT);
ViewGroup tileView = (ViewGroup) XposedHelpers.callMethod(tile, "createTileView", mContext);
tileView.setLayoutParams(tileViewLp);
try {
XposedHelpers.callMethod(tileView, "setDual", false);
} catch (Throwable t) {// CM13
try {
XposedHelpers.callMethod(tileView, "setDual", false, false);
} catch (Throwable ignore) {
// Other ROMs
}
}
try {
XposedHelpers.callMethod(tileView, "onStateChanged", XposedHelpers.callMethod(tile, "getState"));
} catch (Throwable t) {
XposedHelpers.callMethod(tileView, "onStateChanged", XposedHelpers.getObjectField(tile, "mState"));
}
XposedHelpers.setAdditionalInstanceField(tile, KEY_EDIT_TILEVIEW, tileView);
mTileViews.add(i, tileView);
}
private int getWidth() {
return StatusBarHeaderHooks.mQsPanel.getWidth() / 3;
}
private GridLayoutManager.LayoutParams generateLayoutParams() {
int i = getWidth();
return new GridLayoutManager.LayoutParams(i, (int) (getWidth() / TILE_ASPECT));
}
@Override
public TileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View itemView;
if (viewType == 1) {
ResourceUtils res = ResourceUtils.getInstance(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
itemView = inflater.inflate(res.getLayout(R.layout.qs_customize_divider), parent, false);
} else {
itemView = new RelativeLayout(parent.getContext());
itemView.setLayoutParams(generateLayoutParams());
}
return new TileViewHolder(itemView);
}
@Override
public void onBindViewHolder(TileViewHolder holder, int position) {
if (holder.getItemViewType() == 1) {
TextView textView = (TextView) holder.itemView.findViewById(android.R.id.title);
int textId;
if (mCurrentDrag == null) {
textId = R.string.drag_to_add_tiles;
} else {
textId = R.string.drag_to_remove_tiles;
}
textView.setText(mRes.getString(textId));
} else {
ViewGroup tileView = mTileViews.get(position);
if (tileView.getParent() != null)
((ViewGroup) tileView.getParent()).removeView(tileView);
holder.setTileView(tileView);
holder.setBackgroundColor(mMode == MODE_EDIT_SECURE && mSecureTiles.contains(mTileSpecs.get(position)) ? 0x30FFFFFF : 0);
}
}
@Override
public int getItemCount() {
return (mMode == MODE_EDIT_SECURE) ? mDividerIndex : mTileViews.size();
}
@Override
public int getItemViewType(int i) {
return mTileViews.get(i) != null ? 0 : 1;
}
public RecyclerView.ItemDecoration getItemDecoration() {
return mDecoration;
}
public GridLayoutManager.SpanSizeLookup getSizeLookup() {
return mSizeLookup;
}
public boolean onItemMove(int fromPosition, int toPosition) {
if (mMode == MODE_EDIT_SECURE) return false;
if (fromPosition > mDividerIndex && toPosition > mDividerIndex)
return false;
if (fromPosition < mDividerIndex && mDividerIndex < 2)
return false;
move(fromPosition, toPosition, mTileViews);
move(fromPosition, toPosition, mTileSpecs);
mDividerIndex = mTileViews.indexOf(null);
notifyItemMoved(fromPosition, toPosition);
return true;
}
@SuppressWarnings("unchecked")
private void move(int from, int to, List list) {
int addIndex;
if (from > to) {
addIndex = to;
} else {
addIndex = to + 1;
}
list.add(addIndex, list.get(from));
int removeIndex = from;
if (from > to) {
removeIndex = from + 1;
}
list.remove(removeIndex);
}
private void addTile(int position) {
onItemMove(position, mDividerIndex);
}
private void removeTile(int position) {
onItemMove(position, mDividerIndex);
}
public ItemTouchHelper getItemTouchHelper() {
return mItemTouchHelper;
}
public void invalidate() {
mIsInvalid = true;
}
public boolean isInvalid() {
return mIsInvalid;
}
public class TileViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
protected RelativeLayout mItemView;
protected TextView mTextView;
public TileViewHolder(View itemView) {
super(itemView);
if (itemView instanceof RelativeLayout)
mItemView = (RelativeLayout) itemView;
else if (itemView instanceof TextView)
mTextView = (TextView) itemView;
}
private void setTileView(ViewGroup tileView) {
if (mItemView == null) return;
mItemView.removeAllViews();
mItemView.addView(tileView);
tileView.setClickable(true);
tileView.setOnClickListener(this);
}
public void startDrag() {
if (mItemView == null) return;
mItemView.animate().setDuration(DRAG_LENGTH).scaleX(DRAG_SCALE).scaleY(DRAG_SCALE);
try {
((View) XposedHelpers.callMethod(mItemView.getChildAt(0), "labelView")).animate().setDuration(100L).alpha(0.0F);
} catch (Throwable ignore) {
}
}
public void stopDrag() {
if (mItemView == null) return;
mItemView.animate().setDuration(DRAG_LENGTH).scaleX(1.0F).scaleY(1.0F);
try {
((View) XposedHelpers.callMethod(mItemView.getChildAt(0), "labelView")).animate().setDuration(100L).alpha(1.0F);
} catch (Throwable ignore) {
}
}
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (mMode == MODE_EDIT_SECURE) {
String spec = mTileSpecs.get(position);
if (mSecureTiles.contains(spec))
mSecureTiles.remove(spec);
else
mSecureTiles.add(spec);
notifyItemChanged(position);
} else {
if (position < mDividerIndex)
removeTile(position);
else
addTile(position);
}
}
public void setBackgroundColor(int color) {
mItemView.setBackgroundColor(color);
}
}
public void saveChanges() {
saveSecureTiles();
saveTiles(getAddedTileSpecs(), false);
}
public void resetTiles() {
mSecureTiles.clear();
saveSecureTiles();
List<String> newSpces = new ArrayList<>();
newSpces.addAll(getDefaultTiles());
saveTiles(newSpces, true);
}
public static List<String> getDefaultTiles() {
return Arrays.asList("wifi", "cell", "battery", "dnd", "flashlight", "rotation", "bt", "airplane", "location");
}
private void saveTiles(List<String> tileSpecs, boolean update) {
XposedHook.logD(TAG, "saveTiles called with update: " + update + " and specs " + TextUtils.join(", ", tileSpecs));
List<String> oldTiles = update ? getAddedTileSpecs() : QSTileHostHooks.mTileSpecs;
if (oldTiles == null || !oldTiles.equals(tileSpecs)) {
if (!compareSpecs(tileSpecs)) invalidate();
QSTileHostHooks.saveTileSpecs(mContext, tileSpecs);
if (update) {
QSTileHostHooks.mTileSpecs = tileSpecs;
calcDiff(tileSpecs).dispatchUpdatesTo(mTileUpdateCallback);
}
return;
}
XposedHook.logD(TAG, "saveTiles: No changes to save");
}
private void saveSecureTiles() {
if (!ConfigUtils.M) return;
XposedHook.logD(TAG, "saveSecureTiles called");
if (!QSTileHostHooks.mSecureTiles.equals(mSecureTiles)) {
QSTileHostHooks.saveSecureTileSpecs(mContext, mSecureTiles);
}
XposedHook.logD(TAG, "saveSecureTiles: No changes to save");
}
private boolean compareSpecs(List<String> newSpecs) {
if (mPreviousSpecs == null || newSpecs == null) return false;
int oldSize = mPreviousSpecs.size();
int newSize = newSpecs.size();
if (oldSize != newSize) return false;
if (mPreviousSpecs.equals(newSpecs)) return true;
for (String spec : mPreviousSpecs)
if (!newSpecs.contains(spec))
return false;
return true;
}
private DiffUtil.DiffResult calcDiff(List<String> specs) {
List<String> newSpecs = new ArrayList<>();
newSpecs.addAll(specs);
newSpecs.add(null);
for (String spec : mTileSpecs) {
if (spec == null) continue;
if (newSpecs.contains(spec)) continue;
newSpecs.add(spec);
}
return DiffUtil.calculateDiff(new TileSpecDiffCallback(mTileSpecs, newSpecs));
}
public List<String> getAddedTileSpecs() {
List<String> specs = new ArrayList<>();
for (String spec : mTileSpecs) {
if (spec == null) return specs;
specs.add(spec);
}
return specs;
}
@NonNull
private List<String> convertToSpecs() {
List<String> tileSpecs = new ArrayList<>();
for (int i = 0; i < mRecords.size(); i++) {
Object tilerecord = mRecords.get(i);
if (tilerecord == null) return tileSpecs;
Object tile = XposedHelpers.getObjectField(tilerecord, "tile");
tileSpecs.add((String) XposedHelpers.getAdditionalInstanceField(tile, QSTileHostHooks.TILE_SPEC_NAME));
}
return tileSpecs;
}
public void handleStateChanged(Object qsTile, Object state) {
ViewGroup tileView = (ViewGroup) XposedHelpers.getAdditionalInstanceField(qsTile, KEY_EDIT_TILEVIEW);
if (tileView != null) {
XposedHelpers.callMethod(tileView, "onStateChanged", state);
}
}
private final GridLayoutManager.SpanSizeLookup mSizeLookup = new GridLayoutManager.SpanSizeLookup() {
public int getSpanSize(int i) {
return (getItemViewType(i) == 1) ? 3 : 1;
}
};
private final RecyclerView.ItemDecoration mDecoration = new RecyclerView.ItemDecoration() {
private final ColorDrawable mDrawable = new ColorDrawable(0xff384248);
@Override
public void onDraw(Canvas canvas, RecyclerView recyclerview, RecyclerView.State state) {
int count = recyclerview.getChildCount();
View child;
for (int i = 0; i < count; i++) {
child = recyclerview.getChildAt(i);
if (recyclerview.getChildViewHolder(child).getAdapterPosition() >= mDividerIndex) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int childTop = child.getTop();
int topMargin = layoutParams.topMargin;
int childTranslationY = Math.round(child.getTranslationY());
mDrawable.setBounds(0, childTop + topMargin + childTranslationY, recyclerview.getWidth(), recyclerview.getBottom());
mDrawable.draw(canvas);
}
}
}
};
private class CustomItemTouchHelper extends ItemTouchHelper {
public CustomItemTouchHelper(Callback callback) {
super(callback);
}
@Override
public void attachToRecyclerView(RecyclerView recyclerView) {
try {
RecyclerView oldRecyclerView = (RecyclerView) XposedHelpers.getObjectField(this, "mRecyclerView");
if (oldRecyclerView == recyclerView) {
return; // nothing to do
}
if (oldRecyclerView != null) {
XposedHelpers.findMethodBestMatch(ItemTouchHelper.class, "destroyCallbacks").invoke(this);
}
XposedHelpers.setObjectField(this, "mRecyclerView", recyclerView);
if (recyclerView != null) {
XposedHelpers.findMethodBestMatch(ItemTouchHelper.class, "setupCallbacks").invoke(this);
}
} catch (Throwable t) {
XposedHook.logE(TAG, "Error attaching ItemTouchCallback to RecyclerView", t);
}
}
}
private class TileSpecDiffCallback extends DiffUtil.Callback {
private List<String> mOldSpecs;
private List<String> mNewSpecs;
public TileSpecDiffCallback(List<String> oldSpecs, List<String> newSpecs) {
mOldSpecs = oldSpecs;
mNewSpecs = newSpecs;
}
@Override
public int getOldListSize() {
return mOldSpecs.size();
}
@Override
public int getNewListSize() {
return mNewSpecs.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
Object oldItem = mOldSpecs.get(oldItemPosition);
Object newItem = mNewSpecs.get(newItemPosition);
if (oldItem == null || newItem == null)
return oldItem == newItem;
return oldItem.equals(newItem);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return areItemsTheSame(oldItemPosition, newItemPosition);
}
}
private class TileUpdateCallback implements ListUpdateCallback {
@Override
public void onInserted(int position, int count) {
XposedHook.logW(TAG, "TileUpdateCallback: onInserted called with position=" + position + " and count=" + count);
}
@Override
public void onRemoved(int position, int count) {
XposedHook.logW(TAG, "TileUpdateCallback: onRemoved called with position=" + position + " and count=" + count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
onItemMove(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
XposedHook.logW(TAG, "TileUpdateCallback: onChanged called with position=" + position + ", count=" + count + " and payload=" + payload);
}
}
}