/* * Copyright 2015. Appsi Mobile * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.appsimobile.appsii.module.home; import android.annotation.SuppressLint; import android.app.LoaderManager; import android.appwidget.AppWidgetHost; import android.content.ContentProviderOperation; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.content.OperationApplicationException; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.PopupMenu; import android.widget.Toast; import com.android.colorpicker.ColorPickerDialog; import com.android.colorpicker.ColorPickerSwatch; import com.appsimobile.appsii.BuildConfig; import com.appsimobile.appsii.R; import com.appsimobile.appsii.ThemingUtils; import com.appsimobile.appsii.dagger.AppInjector; import com.appsimobile.appsii.module.home.config.HomeItemConfiguration; import com.appsimobile.appsii.module.home.provider.HomeContract; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; /** * Editor for the home screen * Created by nick on 24/01/15. */ public class HomeEditorActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<HomeItem>>, OnWrapperClickedListener { /** * An extra that must be provided to identify the page to edit */ public static final String EXTRA_PAGE_ID = BuildConfig.APPLICATION_ID + ".extra_page_id"; public static final String EXTRA_PAGE_TITLE = BuildConfig.APPLICATION_ID + ".extra_page_title"; private static final Interpolator sQuinticInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; } }; /** * The handler thread to which the operations to be performed on the db are executed * The {@link #mSaveHandler} uses this thread */ final HandlerThread mHandlerThread; /** * A rect to recycle when calculating cell positions */ final Rect mPositionRect = new Rect(); /** * The handler used to post position updates to after the content has been loaded. * This is executed with a minor delay to ensure the structural change has been * completely executed and the proper locations can be calculated */ final Handler mHandler; /** * The recycler-view showing the items */ RecyclerView mRecyclerView; /** * The home adapter */ HomeAdapter mHomeAdapter; /** * Layout manager for the editor */ GridLayoutManager mGridLayoutManager; /** * The decorator used to draw the highlights */ SingleSelectionDecoration mSingleSelectionDecoration; /** * The action-mode, if it is active. */ ActionMode mActionMode; /** * The initialized action-mode callback */ ActionModeCallbackImpl mActionModeCallback; /** * The handler used to save the operations to the database. Runs on a different * thread */ Handler mSaveHandler; /** * The callback handling messages on the save-handler. */ Handler.Callback mCallback; /** * The page-id to which the editor is bound */ long mPageId; /** * The page-id to which the editor is bound */ String mPageTitle; /** * View (button) to increase the span */ View mIncreaseSpanView; /** * View (button) to decrease the span */ View mDecreaseSpanView; /** * View (button) to move the cell left */ View mMoveLeftView; /** * View (button) to move the cell right */ View mMoveRightView; /** * View (button) to move the row up */ View mMoveUpView; /** * View (button) to move the row down */ View mMoveDownView; /** * View (button) to decrease the row-height */ View mSmallerView; /** * View (button) to increase the row-height */ View mLargerView; /** * The editor instance of the HomeAdapter. */ HomeAdapter.HomeAdapterEditor mHomeAdapterEditor; boolean mApplyingNewData; ViewGroup mRootView; @Inject SharedPreferences mSharedPreferences; public HomeEditorActivity() { mHandlerThread = new HandlerThread("db-thread"); mHandlerThread.start(); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { return doHandleMessage(msg); } }); } private static void ensureInvisible(View view) { if (view.getVisibility() != View.INVISIBLE) { view.setVisibility(View.INVISIBLE); } } private static void centerView(View view, Rect positionRect) { view.setVisibility(View.INVISIBLE); int x = positionRect.left + (positionRect.width() - view.getWidth()) / 2; int y = positionRect.top + (positionRect.height() - view.getHeight()) / 2; view.setTranslationX(x); view.setTranslationY(y); } /** * Handles a message sent to the update positions handler */ boolean doHandleMessage(Message msg) { if (msg.what == 0) { updateEditViewPosition(); return true; } return false; } /** * Updates the positions of the edit overlay views, and hides/shows them as needed */ void updateEditViewPosition() { if (mApplyingNewData) return; int position = mSingleSelectionDecoration.mSelectedPosition; // if the view is not visible, hide all controls if (!getRecyclerViewChildBounds(position, mPositionRect)) { mMoveLeftView.setVisibility(View.GONE); mMoveRightView.setVisibility(View.GONE); mMoveUpView.setVisibility(View.GONE); mMoveDownView.setVisibility(View.GONE); mIncreaseSpanView.setVisibility(View.GONE); mDecreaseSpanView.setVisibility(View.GONE); mLargerView.setVisibility(View.GONE); mSmallerView.setVisibility(View.GONE); return; } // if the view can move left, show and update the controls positions if (mHomeAdapterEditor.canMoveLeft(position)) { int x = mPositionRect.left - mMoveLeftView.getWidth(); int y = mPositionRect.top + (mPositionRect.height() - mMoveLeftView.getHeight()) / 2; animateToXy(mMoveLeftView, x, y); } else { ensureInvisible(mMoveLeftView); } // if the view can move right, show and update the controls positions if (mHomeAdapterEditor.canMoveRight(position)) { int x = mPositionRect.right; int y = mPositionRect.top + (mPositionRect.height() - mMoveRightView.getHeight()) / 2; animateToXy(mMoveRightView, x, y); } else { ensureInvisible(mMoveRightView); } // if the view's row can move up, show and update the controls positions if (mHomeAdapterEditor.canMoveUp(position)) { int x = mPositionRect.left + (mPositionRect.width() - mMoveUpView.getWidth()) / 2; int y = mPositionRect.top - mMoveUpView.getHeight(); animateToXy(mMoveUpView, x, y); } else { ensureInvisible(mMoveUpView); } // if the view's row can move down, show and update the controls positions if (mHomeAdapterEditor.canMoveDown(position)) { int x = mPositionRect.left + (mPositionRect.width() - mMoveDownView.getWidth()) / 2; int y = mPositionRect.bottom; animateToXy(mMoveDownView, x, y); } else { ensureInvisible(mMoveDownView); } // we need these so we can properly position the actions final boolean canDecreaseHeight = mHomeAdapterEditor.canDecreaseHeight(position); final boolean canIncreaseHeight = mHomeAdapterEditor.canIncreaseHeight(position); final boolean canIncreaseSpan = mHomeAdapterEditor.canIncreaseSpan(position); final boolean canDecreaseSpan = mHomeAdapterEditor.canDecreaseSpan(position); // if the view's row's height can be increased, show and update the controls positions if (canIncreaseHeight) { // when the height can also be decreased, position this item left at 25% // otherwise position it at 50% float fx = canDecreaseHeight ? .25f : .5f; // when the span can be changed, position this item top at 25% // otherwise position it at 50% float fy = (!canIncreaseSpan && !canDecreaseSpan) ? .5f : .25f; int x = (int) (mPositionRect.left + (mPositionRect.width() * fx - mLargerView.getWidth() / 2)); int y = (int) (mPositionRect.top + (mPositionRect.height() * fy - mLargerView.getHeight() / 2)); animateToXy(mLargerView, x, y); } else { ensureInvisible(mLargerView); } // if the view's row's height can be decreased, show and update the controls positions if (canDecreaseHeight) { float fx = canIncreaseHeight ? .75f : .5f; float fy = (!canIncreaseSpan && !canDecreaseSpan) ? .5f : .25f; int x = (int) (mPositionRect.left + (mPositionRect.width() * fx - mSmallerView.getWidth() / 2)); int y = (int) (mPositionRect.top + (mPositionRect.height() * fy - mSmallerView.getHeight() / 2)); animateToXy(mSmallerView, x, y); } else { ensureInvisible(mSmallerView); } // if the view's width can be increased, show and update the controls positions if (canIncreaseSpan) { float fx = canDecreaseSpan ? .75f : .5f; float fy = (!canIncreaseHeight && !canDecreaseHeight) ? .5f : .75f; int x = (int) (mPositionRect.left + (mPositionRect.width() * fx - mIncreaseSpanView.getWidth() / 2)); int y = (int) (mPositionRect.top + (mPositionRect.height() * fy - mIncreaseSpanView.getHeight() / 2)); animateToXy(mIncreaseSpanView, x, y); } else { ensureInvisible(mIncreaseSpanView); } // if the view's width can be decreased, show and update the controls positions if (canDecreaseSpan) { float fx = canIncreaseSpan ? .25f : .5f; float fy = (!canIncreaseHeight && !canDecreaseHeight) ? .5f : .75f; int x = (int) (mPositionRect.left + (mPositionRect.width() * fx - mDecreaseSpanView.getWidth() / 2)); int y = (int) (mPositionRect.top + (mPositionRect.height() * fy - mDecreaseSpanView.getHeight() / 2)); animateToXy(mDecreaseSpanView, x, y); } else { ensureInvisible(mDecreaseSpanView); } } /** * Fills the provided bounds with the position of the view relative to the recycler-view * Returns false if the view does not exist or is not visible. Otherwise true is returned */ boolean getRecyclerViewChildBounds(int position, Rect bounds) { View child = getRecyclerViewChildAt(position); if (child == null) return false; bounds.set(0, 0, child.getWidth(), child.getHeight()); mRecyclerView.offsetDescendantRectToMyCoords(child, bounds); return true; } private void animateToXy(View view, int targetX, int targetY) { view.setVisibility(View.VISIBLE); view.animate(). setDuration(150). translationX(targetX). translationY(targetY); } View getRecyclerViewChildAt(int position) { View view = mRecyclerView.getChildAt(0); if (view == null) return null; int firstVisiblePosition = mRecyclerView.getChildLayoutPosition(view); if (firstVisiblePosition > position) return null; int childPosition = position - firstVisiblePosition; return mRecyclerView.getChildAt(childPosition); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AppInjector.inject(this); Context context = ThemingUtils.createContextThemeWrapper(this, mSharedPreferences); setContentView(R.layout.activity_home_editor); mMoveLeftView = findViewById(R.id.move_left); mMoveRightView = findViewById(R.id.move_right); mMoveUpView = findViewById(R.id.move_up); mMoveDownView = findViewById(R.id.move_down); mSmallerView = findViewById(R.id.decrease_height); mLargerView = findViewById(R.id.increase_height); mDecreaseSpanView = findViewById(R.id.decrease_span); mIncreaseSpanView = findViewById(R.id.increase_span); mRecyclerView = (RecyclerView) findViewById(R.id.recycler); mRootView = (ViewGroup) findViewById(R.id.home_editor_root); mRecyclerView.getItemAnimator().setChangeDuration(250); mRecyclerView.getItemAnimator().setMoveDuration(250); mRecyclerView.getItemAnimator().setRemoveDuration(250); mRecyclerView.getItemAnimator().setAddDuration(250); mMoveLeftView.setVisibility(View.INVISIBLE); mMoveRightView.setVisibility(View.INVISIBLE); mMoveUpView.setVisibility(View.INVISIBLE); mMoveDownView.setVisibility(View.INVISIBLE); mSmallerView.setVisibility(View.INVISIBLE); mLargerView.setVisibility(View.INVISIBLE); mDecreaseSpanView.setVisibility(View.INVISIBLE); mIncreaseSpanView.setVisibility(View.INVISIBLE); final Intent intent = getIntent(); mPageId = intent.getLongExtra(EXTRA_PAGE_ID, -1); mPageTitle = intent.getStringExtra(EXTRA_PAGE_TITLE); getSupportActionBar().setTitle(mPageTitle); mHomeAdapter = new HomeAdapter(context, new InterceptClicksViewWrapperFactory(this), mPageId, false); TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.appsiSidebarBackground}); getWindow().setBackgroundDrawable(a.getDrawable(0)); a.recycle(); mHomeAdapterEditor = mHomeAdapter.getEditor(); HomeAdapter.HomeSpanSizeLookup spanSizeLookup = mHomeAdapter.getHomeSpanSizeLookup(); mGridLayoutManager = new GridLayoutManager(this, 12); mGridLayoutManager.setSpanSizeLookup(spanSizeLookup); mRecyclerView.setLayoutManager(mGridLayoutManager); mRecyclerView.setAdapter(mHomeAdapter); mSingleSelectionDecoration = new SingleSelectionDecoration(context); mRecyclerView.addItemDecoration(new GridLayoutDecoration(context)); mRecyclerView.addItemDecoration(mSingleSelectionDecoration); mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (mActionMode != null) { scrollEditViews(dy); } } }); getLoaderManager().initLoader(0, null, this); mCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { @SuppressWarnings("unchecked") ArrayList<ContentProviderOperation> ops = (ArrayList<ContentProviderOperation>) msg.obj; applyContentProviderOperationsAsync(ops); return false; } }; mSaveHandler = new Handler(mHandlerThread.getLooper(), mCallback); if (savedInstanceState != null) { int selection = savedInstanceState.getInt("selection"); mSingleSelectionDecoration.setSelectedPosition(selection); } } @Override protected void onDestroy() { super.onDestroy(); // terminate the looper to stop leaking this thread if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { mSaveHandler.getLooper().quitSafely(); } else { mSaveHandler.getLooper().quit(); } } /** * Scrolls the edit-views the same amount the recycler-view was scrolled. * This makes it look synchronous. */ void scrollEditViews(int dy) { scrollEditView(mLargerView, dy); scrollEditView(mSmallerView, dy); scrollEditView(mMoveUpView, dy); scrollEditView(mMoveDownView, dy); scrollEditView(mMoveLeftView, dy); scrollEditView(mMoveRightView, dy); scrollEditView(mDecreaseSpanView, dy); scrollEditView(mIncreaseSpanView, dy); } void applyContentProviderOperationsAsync(ArrayList<ContentProviderOperation> ops) { try { Log.wtf("HomeEditorActivity", "applying batch..."); getContentResolver().applyBatch(HomeContract.AUTHORITY, ops); } catch (RemoteException | OperationApplicationException e) { Log.wtf("HomeEditorActivity", "error applying batch", e); } } /** * Updates the scroll, of a single child */ private void scrollEditView(View view, int dy) { view.setTranslationY(view.getTranslationY() - dy); } @Override public Loader<List<HomeItem>> onCreateLoader(int id, Bundle args) { return new HomeLoader(this); } @Override public void onLoadFinished(Loader<List<HomeItem>> loader, List<HomeItem> data) { mApplyingNewData = true; int lastPosition = mSingleSelectionDecoration.mSelectedPosition; long lastId; boolean restored; if (lastPosition == -1) { lastId = -1; restored = false; } else { lastId = mHomeAdapter.getItemId(lastPosition); // we have just been restored and want to // restore the selection. restored = mHomeAdapter.getItemCount() == 0; } mHomeAdapter.setHomeItems(data); int newPosition = mHomeAdapter.getPositionOfId(lastId); if (!restored) { mSingleSelectionDecoration.setSelectedPosition(newPosition); } if (newPosition == -1) { if (mActionMode != null) { mActionMode.finish(); } } postUpdatePositions(); mApplyingNewData = false; } void postUpdatePositions() { Message message = mHandler.obtainMessage(0); mHandler.sendMessageDelayed(message, 10); } @Override public void onLoaderReset(Loader<List<HomeItem>> loader) { } @Override protected void onPause() { super.onPause(); mHandler.removeMessages(0); } @Override protected void onResume() { super.onResume(); if (mActionMode == null && mSingleSelectionDecoration.mSelectedPosition != -1) { startEditActionMode(mSingleSelectionDecoration.mSelectedPosition); ColorPickerDialog dialog = (ColorPickerDialog) getFragmentManager().findFragmentByTag("color_picker"); if (dialog != null) { dialog.setOnColorSelectedListener(mActionModeCallback); } } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("selection", mSingleSelectionDecoration.mSelectedPosition); } private void startEditActionMode(int position) { mActionModeCallback = new ActionModeCallbackImpl(mHomeAdapterEditor); mActionMode = startSupportActionMode(mActionModeCallback); mSingleSelectionDecoration.setSelectedPosition(position); prepositionEditViews(); } void prepositionEditViews() { mRootView.setLayoutTransition(null); int position = mSingleSelectionDecoration.mSelectedPosition; if (getRecyclerViewChildBounds(position, mPositionRect)) { centerView(mMoveLeftView, mPositionRect); centerView(mMoveRightView, mPositionRect); centerView(mMoveUpView, mPositionRect); centerView(mMoveDownView, mPositionRect); centerView(mIncreaseSpanView, mPositionRect); centerView(mDecreaseSpanView, mPositionRect); centerView(mLargerView, mPositionRect); centerView(mSmallerView, mPositionRect); } } @Override public void onWrapperClicked(HomeViewWrapper wrapper) { int position = mRecyclerView.getChildLayoutPosition(wrapper); if (mActionMode != null) { mSingleSelectionDecoration.setSelectedPosition(-1); mActionMode.finish(); } else if (position != mSingleSelectionDecoration.mSelectedPosition) { startEditActionMode(position); } mRecyclerView.invalidate(); } private void performSwitchPositions(int positionFrom, int positionTo) { mActionModeCallback.confirmSwitchItemPositions(positionFrom, positionTo); } class InterceptClicksViewWrapperFactory extends HomeAdapter.DefaultViewWrapperFactory implements View.OnClickListener { final OnWrapperClickedListener mListener; InterceptClicksViewWrapperFactory(OnWrapperClickedListener listener) { mListener = listener; } public HomeViewWrapper wrapView(Context context, View child, int heightPx) { HomeViewWrapper wrapper = super.wrapView(context, child, heightPx); View blocker = new View(context); blocker.setTag(wrapper); wrapper.addView(blocker, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); blocker.setOnClickListener(this); return wrapper; } @Override public void onClick(View v) { HomeViewWrapper wrapper = (HomeViewWrapper) v.getTag(); mListener.onWrapperClicked(wrapper); } } class SingleSelectionDecoration extends RecyclerView.ItemDecoration { final Paint mPaint; final Paint mSelectionOutlinePaint; final Paint mSelectionPaint; final Rect mRect = new Rect(); final RectF mRectf = new RectF(); final float mCornerRadius; private final int[] ATTRS = new int[]{ R.attr.colorAccent, R.attr.appsiSidebarBackground, R.attr.appsiTextShadow, }; int mSelectedPosition = -1; SingleSelectionDecoration(Context context) { mCornerRadius = context.getResources().getDisplayMetrics().density * 1; float strokeWidth = context.getResources().getDisplayMetrics().density * 2; final TypedArray a = context.obtainStyledAttributes(ATTRS); int accentColor = a.getColor(0, Color.WHITE); int textShadow = a.getColor(2, Color.WHITE); a.recycle(); mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(textShadow); mSelectionOutlinePaint = new Paint(); mSelectionOutlinePaint.setStyle(Paint.Style.STROKE); mSelectionOutlinePaint.setStrokeWidth(strokeWidth); mSelectionOutlinePaint.setColor(accentColor); mSelectionPaint = new Paint(); mSelectionPaint.setStyle(Paint.Style.FILL); mSelectionPaint.setStrokeWidth(strokeWidth); mSelectionPaint.setColor(accentColor); mSelectionPaint.setAlpha(0x10); } public void setSelectedPosition(int selectedPosition) { mSelectedPosition = selectedPosition; postUpdatePositions(); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); if (mSelectedPosition == -1) return; int count = parent.getChildCount(); for (int i = 0; i < count; i++) { View child = parent.getChildAt(i); int position = parent.getChildLayoutPosition(child); mRect.set(0, 0, child.getWidth(), child.getHeight()); parent.offsetDescendantRectToMyCoords(child, mRect); mRectf.set(mRect); if (position != mSelectedPosition) { // c.drawRoundRect(mRectf, mCornerRadius, mCornerRadius, mPaint); } else { c.drawRoundRect(mRectf, mCornerRadius, mCornerRadius, mSelectionOutlinePaint); c.drawRoundRect(mRectf, mCornerRadius, mCornerRadius, mSelectionPaint); } } } } public class ActionModeCallbackImpl implements ActionMode.Callback, View.OnClickListener, PopupMenu.OnMenuItemClickListener, ColorPickerSwatch.OnColorSelectedListener { final ArrayList<ContentProviderOperation> mContentProviderOperations = new ArrayList<>(); final HomeAdapter.HomeAdapterEditor mEditor; @Inject AppWidgetHost mAppWidgetHost; View mInsertView; View mDeleteView; View mEditView; View mColorizeView; View mNoColorView; @Inject HomeItemConfiguration mHomeItemConfiguration; public ActionModeCallbackImpl(HomeAdapter.HomeAdapterEditor editor) { mEditor = editor; AppInjector.inject(this); } @Override public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { LayoutInflater layoutInflater = LayoutInflater.from(HomeEditorActivity.this); // suppress this warning. We can't set this properly @SuppressLint("InflateParams") View view = layoutInflater.inflate(R.layout.include_home_editor_actions, null); actionMode.setCustomView(view); mInsertView = view.findViewById(R.id.add); mDeleteView = view.findViewById(R.id.remove); // mMoveView = view.findViewById(R.id.move); mEditView = view.findViewById(R.id.edit); mColorizeView = view.findViewById(R.id.colorize); mNoColorView = view.findViewById(R.id.no_color); mInsertView.setOnClickListener(this); mDeleteView.setOnClickListener(this); mMoveLeftView.setOnClickListener(this); mMoveRightView.setOnClickListener(this); mMoveUpView.setOnClickListener(this); mMoveDownView.setOnClickListener(this); mSmallerView.setOnClickListener(this); mLargerView.setOnClickListener(this); mDecreaseSpanView.setOnClickListener(this); mIncreaseSpanView.setOnClickListener(this); // mMoveView.setOnClickListener(this); mEditView.setOnClickListener(this); mColorizeView.setOnClickListener(this); mNoColorView.setOnClickListener(this); return true; } @Override public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { return false; } @Override public void onDestroyActionMode(ActionMode actionMode) { mActionMode = null; mSingleSelectionDecoration.setSelectedPosition(-1); mRecyclerView.invalidate(); mMoveLeftView.setVisibility(View.GONE); mMoveRightView.setVisibility(View.GONE); mMoveUpView.setVisibility(View.GONE); mMoveDownView.setVisibility(View.GONE); mSmallerView.setVisibility(View.GONE); mLargerView.setVisibility(View.GONE); mDecreaseSpanView.setVisibility(View.GONE); mIncreaseSpanView.setVisibility(View.GONE); } @Override public boolean onMenuItemClick(MenuItem item) { int position = mSingleSelectionDecoration.mSelectedPosition; int itemId = item.getItemId(); ArrayList<ContentProviderOperation> ops = mContentProviderOperations; ops.clear(); switch (itemId) { case R.id.action_remove_cell: if (mHomeAdapter.getItemCount() > 1) { mEditor.removeCellAtPosition(position, ops); } else { Toast.makeText(HomeEditorActivity.this, R.string.need_at_least_one_cell, Toast.LENGTH_SHORT).show(); } break; case R.id.action_remove_row: if (mEditor.hasAtLeastTwoRows()) { mEditor.removeRowAtItemPosition(position, ops); } else { Toast.makeText(HomeEditorActivity.this, R.string.need_at_least_one_cell, Toast.LENGTH_SHORT).show(); } break; case R.id.action_add_cell_left: mEditor.insertCellLeftOfPosition(position, ops); break; case R.id.action_add_cell_right: mEditor.insertCellRightOfPosition(position, ops); break; case R.id.action_add_row_above: mEditor.insertRowAbovePosition(position, ops); break; case R.id.action_add_row_below: mEditor.insertRowBelowPosition(position, ops); break; case R.id.action_set_item_none: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_UNSET, ops); break; case R.id.action_set_item_bluetooth: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_BLUETOOTH_TOGGLE, ops); break; case R.id.action_set_item_clock: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_CLOCK, ops); break; case R.id.action_set_item_app: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_INTENT, ops); break; case R.id.action_set_item_image: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_PROFILE_IMAGE, ops); break; case R.id.action_set_item_sun: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_WEATHER_SUNRISE, ops); break; case R.id.action_set_item_temperature: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_WEATHER_TEMP, ops); break; case R.id.action_set_item_wind: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_WEATHER_WIND, ops); break; case R.id.action_set_app_widget: doChangeType(position, HomeContract.Cells.DISPLAY_TYPE_APP_WIDGET, ops); break; default: return false; } applyContentProviderOperationsAsync(ops); return true; } private void doChangeType(int position, int type, ArrayList<ContentProviderOperation> ops) { HomeItem homeItem = mHomeAdapter.getItemAt(position); // When this cell is an app-widget right now, clean up the id, // and release it. if (homeItem.mDisplayType == HomeContract.Cells.DISPLAY_TYPE_APP_WIDGET) { HomeItemConfiguration configurationHelper = mHomeItemConfiguration; // get the current id. String widgetId = configurationHelper. getProperty(homeItem.mId, "app_widget_id", null); if (widgetId != null) { int id = Integer.parseInt(widgetId); mAppWidgetHost.deleteAppWidgetId(id); } // now remove it from the database and from the app-widget host configurationHelper.removeAllProperties(homeItem.mId); } mEditor.changeCellType(position, type, ops); } public void confirmSwitchItemPositions(int positionFrom, int positionTo) { if (positionFrom != positionTo) { mContentProviderOperations.clear(); mEditor.switchItemPositions(positionFrom, positionTo, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); } mActionModeCallback.unblock(); } public void unblock() { enableActions(true); } void enableActions(boolean enable) { int visibility = enable ? View.VISIBLE : View.GONE; mEditView.setVisibility(visibility); mInsertView.setVisibility(visibility); mDeleteView.setVisibility(visibility); mMoveLeftView.setVisibility(visibility); mMoveRightView.setVisibility(visibility); mMoveUpView.setVisibility(visibility); mMoveDownView.setVisibility(visibility); mSmallerView.setVisibility(visibility); mLargerView.setVisibility(visibility); mDecreaseSpanView.setVisibility(visibility); mIncreaseSpanView.setVisibility(visibility); // mMoveView.setVisibility(visibility); } @Override public void onClick(View v) { // TODO: why does this happen? is there an orientation change involved? if (mSingleSelectionDecoration.mSelectedPosition == -1) { if (mActionMode != null) { mActionMode.finish(); } return; } int id = v.getId(); switch (id) { case R.id.colorize: onColorizeViewPressed(v); break; case R.id.no_color: onNoColorPressed(v); break; case R.id.add: onInsertViewPressed(v); break; case R.id.remove: onRemovePressed(v); break; case R.id.move_left: onMoveLeftPressed(); break; case R.id.move_right: onMoveRightPressed(); break; case R.id.move_up: onMoveUpPressed(); break; case R.id.move_down: onMoveDownPressed(); break; case R.id.decrease_height: onDecreaseHeightPressed(); break; case R.id.increase_height: onIncreaseHeightPressed(); break; case R.id.decrease_span: onDecreaseSpanPressed(); break; case R.id.increase_span: onIncreaseSpanPressed(); break; case R.id.edit: onEditPressed(v); break; } } private void onIncreaseSpanPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; mContentProviderOperations.clear(); mEditor.increaseSpan(selection, mContentProviderOperations); applyOperations(mContentProviderOperations); } private void onDecreaseSpanPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; mContentProviderOperations.clear(); mEditor.decreaseSpan(selection, mContentProviderOperations); applyOperations(mContentProviderOperations); } private void onEditPressed(View v) { PopupMenu popupMenu = new PopupMenu(HomeEditorActivity.this, v); MenuInflater menuInflater = popupMenu.getMenuInflater(); Menu menu = popupMenu.getMenu(); menuInflater.inflate(R.menu.home_editor_edit_options, menu); popupMenu.setOnMenuItemClickListener(this); popupMenu.show(); } private void onIncreaseHeightPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; mContentProviderOperations.clear(); mEditor.increaseHeight(selection, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); } private void onDecreaseHeightPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; mContentProviderOperations.clear(); mEditor.decreaseHeight(selection, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); } private void onMoveDownPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; HomeItem homeItem = mEditor.getItemAt(selection); mContentProviderOperations.clear(); mEditor.moveRowDown(homeItem.mRowPosition, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); updateSelection(homeItem); } private void onMoveUpPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; HomeItem homeItem = mEditor.getItemAt(selection); mContentProviderOperations.clear(); mEditor.moveRowUp(homeItem.mRowPosition, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); updateSelection(homeItem); } void updateSelection(HomeItem homeItem) { // mSingleSelectionDecoration.setSelectedPosition(mEditor.getItemPosition(homeItem.mId)); } private void onMoveRightPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; HomeItem homeItem = mEditor.getItemAt(selection); mContentProviderOperations.clear(); mEditor.moveItemRight(selection, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); updateSelection(homeItem); } private void onMoveLeftPressed() { int selection = mSingleSelectionDecoration.mSelectedPosition; HomeItem homeItem = mEditor.getItemAt(selection); mContentProviderOperations.clear(); mEditor.moveItemLeft(selection, mContentProviderOperations); applyContentProviderOperationsAsync(mContentProviderOperations); updateSelection(homeItem); } private void onRemovePressed(View v) { PopupMenu popupMenu = new PopupMenu(HomeEditorActivity.this, v); MenuInflater menuInflater = popupMenu.getMenuInflater(); Menu menu = popupMenu.getMenu(); menuInflater.inflate(R.menu.home_editor_remove_options, menu); popupMenu.setOnMenuItemClickListener(this); popupMenu.show(); } private void onInsertViewPressed(View v) { int selection = mSingleSelectionDecoration.mSelectedPosition; PopupMenu popupMenu = new PopupMenu(HomeEditorActivity.this, v); MenuInflater menuInflater = popupMenu.getMenuInflater(); Menu menu = popupMenu.getMenu(); boolean canAddCells = mEditor.canAddCellsInItemRow(selection); menuInflater.inflate(R.menu.home_editor_add_options, menu); if (!canAddCells) { menu.findItem(R.id.action_add_cell_left).setEnabled(false); menu.findItem(R.id.action_add_cell_right).setEnabled(false); } popupMenu.setOnMenuItemClickListener(this); popupMenu.show(); } void onNoColorPressed(View v) { onColorSelected(Color.TRANSPARENT); } private void onColorizeViewPressed(View v) { int selection = mSingleSelectionDecoration.mSelectedPosition; if (selection == -1) return; HomeItem homeItem = mHomeAdapter.getItemAt(selection); ColorPickerDialog colorPickerDialog = ColorPickerDialog.newInstance(R.string.color_picker_default_title, new int[]{ // grey tints 0xFFCCCCCC, 0xFF333333, 0xFF999999, 0xFF666666, // 500 values from palette 0xFF607D8B, 0xFF000000, 0xFFF44336, 0xFFE91E63, 0xFF9C27B0, 0xFF673AB7, 0xFF3F51B5, 0xFF2196F3, 0xFF03A9F4, 0xFF00BCD4, 0xFF009688, 0xFF4CAF50, 0xFF8BC34A, 0xFFCDDC39, 0xFFFFEB3B, 0xFFFFC107, 0xFFFF9800, 0xFFFF5722, 0xFF795548, 0xFF9E9E9E, // 100 values from palette 0xFFFFCDD2, 0xFFF8BBD0, 0xFFE1BEE7, 0xFFD1C4E9, 0xFFC5CAE9, 0xFFBBDEFB, 0xFFB3E5FC, 0xFFB2EBF2, 0xFFB2DFDB, 0xFFC8E6C9, 0xFFDCEDC8, 0xFFF0F4C3, 0xFFFFF9C4, 0xFFFFECB3, 0xFFFFE0B2, 0xFFFFCCBC, 0xFFD7CCC8, // 900 values from palette 0xFFB71C1C, 0xFF880E4F, 0xFF4A148C, 0xFF311B92, 0xFF1A237E, 0xFF0D47A1, 0xFF01579B, 0xFF006064, 0xFF004D40, 0xFF1B5E20, 0xFF33691E, 0xFF827717, 0xFFF57F17, 0xFFFF6F00, 0xFFE65100, 0xFFBF360C, 0xFF3E2723 }, homeItem.mEffectColor, 4, ColorPickerDialog.SIZE_SMALL); colorPickerDialog.setOnColorSelectedListener(this); colorPickerDialog.show(getFragmentManager(), "color_picker"); } private void applyOperations(ArrayList<ContentProviderOperation> ops) { if (!ops.isEmpty()) { mSaveHandler.obtainMessage(0, ops).sendToTarget(); } } @Override public void onColorSelected(int color) { int position = mSingleSelectionDecoration.mSelectedPosition; HomeItem item = mHomeAdapter.getItemAt(position); ArrayList<ContentProviderOperation> ops = new ArrayList<>(); ops.add(ContentProviderOperation.newUpdate(mEditor.homeItemUri(item)). withValue(HomeContract.Cells.EFFECT_COLOR, color). build()); applyOperations(ops); } } }