/* * Copyright 2015 Hippo Seven * * 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.hippo.nimingban.ui; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import com.hippo.app.ProgressDialogBuilder; import com.hippo.io.UriInputStreamPipe; import com.hippo.nimingban.NMBAppConfig; import com.hippo.nimingban.R; import com.hippo.nimingban.util.BitmapUtils; import com.hippo.nimingban.util.ReadableTime; import com.hippo.nimingban.util.Settings; import com.hippo.nimingban.widget.ColorPickerView; import com.hippo.nimingban.widget.DoodleView; import com.hippo.nimingban.widget.ThicknessPreviewView; import com.hippo.ripple.Ripple; import com.hippo.util.AnimationUtils2; import com.hippo.util.DrawableManager; import com.hippo.widget.Slider; import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.ResourcesUtils; import com.hippo.yorozuya.SimpleHandler; import java.io.File; public final class DoodleActivity extends TranslucentActivity implements View.OnClickListener, DoodleView.Helper { public static final int REQUEST_CODE_SELECT_IMAGE = 0; private DoodleView mDoodleView; private View mSide; private View mPalette; private View mThickness; private ImageView mDrawAction; private ImageView mImage; private ImageView mUndo; private ImageView mRedo; private View mClear; private View mOk; private View mMenu; private File mOutputFile; private Dialog mExitWaitingDialog; private ValueAnimator mSideAnimator; private boolean mShowSide = true; private Runnable mHideSideRunnable; @Override protected int getLightThemeResId() { return Settings.getColorStatusBar() ? R.style.NormalActivity : R.style.NormalActivity_NoStatus; } @Override protected int getDarkThemeResId() { return Settings.getColorStatusBar() ? R.style.NormalActivity_Dark : R.style.NormalActivity_Dark_NoStatus; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); File dir = NMBAppConfig.getDoodleDir(); if (dir != null) { String filename = "doodle-" + ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".png"; mOutputFile = new File(dir, filename); } setStatusBarColor(ResourcesUtils.getAttrColor(this, R.attr.colorPrimaryDark)); setContentView(R.layout.activity_doodle); mDoodleView = (DoodleView) findViewById(R.id.doodle_view); mSide = findViewById(R.id.side); mPalette = findViewById(R.id.palette); mThickness = findViewById(R.id.thickness); mDrawAction = (ImageView) findViewById(R.id.draw_action); mImage = (ImageView) findViewById(R.id.image); mUndo = (ImageView) findViewById(R.id.undo); mRedo = (ImageView) findViewById(R.id.redo); mClear = findViewById(R.id.clear); mOk = findViewById(R.id.ok); mMenu = findViewById(R.id.menu); mDoodleView.setHelper(this); StateListDrawable undoDrawable = new StateListDrawable(); undoDrawable.addState(new int[]{-android.R.attr.state_enabled}, DrawableManager.getDrawable(this, R.drawable.v_undo_disabled_dark_x24)); undoDrawable.addState(new int[]{}, DrawableManager.getDrawable(this, R.drawable.v_undo_default_dark_x24)); mUndo.setImageDrawable(undoDrawable); StateListDrawable redoDrawable = new StateListDrawable(); redoDrawable.addState(new int[]{-android.R.attr.state_enabled}, DrawableManager.getDrawable(this, R.drawable.v_redo_disabled_dark_x24)); redoDrawable.addState(new int[]{}, DrawableManager.getDrawable(this, R.drawable.v_redo_default_dark_x24)); mRedo.setImageDrawable(redoDrawable); StateListDrawable actionDrawable = new StateListDrawable(); actionDrawable.addState(new int[]{android.R.attr.state_activated}, DrawableManager.getDrawable(this, R.drawable.v_eraser_dark_x24)); actionDrawable.addState(new int[]{}, DrawableManager.getDrawable(this, R.drawable.v_brush_dark_x24)); mDrawAction.setImageDrawable(actionDrawable); StateListDrawable imageDrawable = new StateListDrawable(); imageDrawable.addState(new int[]{android.R.attr.state_activated}, DrawableManager.getDrawable(this, R.drawable.v_image_off_dark_x24)); imageDrawable.addState(new int[]{}, DrawableManager.getDrawable(this, R.drawable.v_image_dark_x24)); mImage.setImageDrawable(imageDrawable); Ripple.addRipple(mPalette, true); Ripple.addRipple(mThickness, true); Ripple.addRipple(mDrawAction, true); Ripple.addRipple(mImage, true); Ripple.addRipple(mUndo, true); Ripple.addRipple(mRedo, true); Ripple.addRipple(mClear, true); Ripple.addRipple(mOk, true); Ripple.addRipple(mMenu, ResourcesUtils.getAttrBoolean(this, R.attr.dark)); mSide.setOnClickListener(this); mPalette.setOnClickListener(this); mThickness.setOnClickListener(this); mDrawAction.setOnClickListener(this); mImage.setOnClickListener(this); mUndo.setOnClickListener(this); mRedo.setOnClickListener(this); mClear.setOnClickListener(this); mOk.setOnClickListener(this); mMenu.setOnClickListener(this); updateUndoRedo(); if (mOutputFile == null) { Toast.makeText(this, R.string.cant_create_image_file, Toast.LENGTH_SHORT).show(); } mSideAnimator = new ValueAnimator(); mSideAnimator.setDuration(300); mSideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mSide.setTranslationX((Float) animation.getAnimatedValue()); } }); mHideSideRunnable = new Runnable() { @Override public void run() { hideSide(); } }; SimpleHandler.getInstance().postDelayed(mHideSideRunnable, 3000); } @Override public void onBackPressed() { DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { if (mOutputFile != null) { saveDoodle(); } else { Toast.makeText(DoodleActivity.this, R.string.cant_create_image_file, Toast.LENGTH_SHORT).show(); finish(); } } else if (which == DialogInterface.BUTTON_NEUTRAL) { finish(); } } }; AlertDialog dialog = new AlertDialog.Builder(this).setMessage(R.string.save_doodle) .setPositiveButton(R.string.save, listener) .setNegativeButton(android.R.string.cancel, null) .setNeutralButton(R.string.dont_save, listener) .show(); Button button = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); button.setTextColor(getResources().getColor(R.color.red_500)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == RESULT_OK) { Uri uri = data.getData(); if (uri == null) { return; } try { Bitmap bitmap = BitmapUtils.decodeStream(new UriInputStreamPipe(this, uri), mDoodleView.getWidth(), mDoodleView.getHeight()); if (bitmap != null) { mDoodleView.insertBitmap(bitmap); mImage.setActivated(true); } } catch (OutOfMemoryError e) { // Ignore } } } private void showSide() { if (mHideSideRunnable != null) { SimpleHandler.getInstance().removeCallbacks(mHideSideRunnable); mHideSideRunnable = null; } if (!mShowSide) { mShowSide = true; mSideAnimator.cancel(); mSideAnimator.setInterpolator(AnimationUtils2.FAST_SLOW_INTERPOLATOR); mSideAnimator.setFloatValues(mSide.getTranslationX(), 0); mSideAnimator.start(); } } private void hideSide() { if (mHideSideRunnable != null) { SimpleHandler.getInstance().removeCallbacks(mHideSideRunnable); mHideSideRunnable = null; } if (mShowSide) { mShowSide = false; mSideAnimator.cancel(); mSideAnimator.setInterpolator(AnimationUtils2.SLOW_FAST_INTERPOLATOR); mSideAnimator.setFloatValues(mSide.getTranslationX(), -mSide.getWidth()); mSideAnimator.start(); } } private void updateUndoRedo() { mUndo.setEnabled(mDoodleView.canUndo()); mRedo.setEnabled(mDoodleView.canRedo()); } private class PickColorDialogHelper implements DialogInterface.OnClickListener { private final ColorPickerView mView; public PickColorDialogHelper() { mView = new ColorPickerView(DoodleActivity.this); mView.setColor(mDoodleView.getPaintColor()); } public View getView() { return mView; } @Override public void onClick(DialogInterface dialog, int which) { if (DialogInterface.BUTTON_POSITIVE == which) { mDoodleView.setPaintColor(mView.getColor()); } } } private void showPickColorDialog() { PickColorDialogHelper helper = new PickColorDialogHelper(); new AlertDialog.Builder(DoodleActivity.this) .setView(helper.getView()) .setPositiveButton(android.R.string.ok, helper) .setNegativeButton(android.R.string.cancel, null) .show(); } private class ThicknessDialogHelper implements DialogInterface.OnClickListener, Slider.OnSetProgressListener { private final View mView; private final ThicknessPreviewView mTpv; private final Slider mSlider; @SuppressLint("InflateParams") public ThicknessDialogHelper() { mView = getLayoutInflater().inflate(R.layout.dialog_thickness, null); mTpv = (ThicknessPreviewView) mView.findViewById(R.id.thickness_preview_view); mSlider = (Slider) mView.findViewById(R.id.slider); mTpv.setThickness(mDoodleView.getPaintThickness()); mTpv.setColor(mDoodleView.getPaintColor()); mSlider.setProgress((int) LayoutUtils.pix2dp(DoodleActivity.this, mDoodleView.getPaintThickness())); mSlider.setOnSetProgressListener(this); } public View getView() { return mView; } @Override public void onClick(DialogInterface dialog, int which) { if (DialogInterface.BUTTON_POSITIVE == which) { mDoodleView.setPaintThickness(LayoutUtils.dp2pix(DoodleActivity.this, mSlider.getProgress())); } } @Override public void onSetProgress(Slider slider, int newProgress, int oldProgress, boolean byUser, boolean confirm) { mTpv.setThickness(LayoutUtils.dp2pix(DoodleActivity.this, newProgress)); } @Override public void onFingerDown() {} @Override public void onFingerUp() {} } private void showThicknessDialog() { ThicknessDialogHelper helper = new ThicknessDialogHelper(); new AlertDialog.Builder(DoodleActivity.this) .setView(helper.getView()) .setPositiveButton(android.R.string.ok, helper) .show(); } private void saveDoodle() { if (mExitWaitingDialog == null) { mExitWaitingDialog = new ProgressDialogBuilder(this) .setTitle(R.string.please_wait) .setMessage(R.string.saving) .setCancelable(false) .show(); mDoodleView.save(mOutputFile); } else { // Wait here, it might be fast click back button } } @Override public void onClick(View v) { if (mSide == v) { hideSide(); } else if (mPalette == v) { showPickColorDialog(); } else if (mThickness == v) { showThicknessDialog(); } else if (mDrawAction == v) { boolean newActivated = !mDrawAction.isActivated(); mDrawAction.setActivated(newActivated); mDoodleView.setEraser(newActivated); } else if (mImage == v) { if (mDoodleView.hasInsertBitmap()) { mDoodleView.insertBitmap(null); mImage.setActivated(false); } else { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), REQUEST_CODE_SELECT_IMAGE); } } else if (mUndo == v) { mDoodleView.undo(); } else if (mRedo == v) { mDoodleView.redo(); } else if (mClear == v) { mDoodleView.clear(); } else if (mOk == v) { if (mOutputFile != null) { saveDoodle(); } else { Toast.makeText(this, R.string.cant_create_image_file, Toast.LENGTH_SHORT).show(); } } else if (mMenu == v) { if (mShowSide) { hideSide(); } else { showSide(); } } } @Override public void onStoreChange(DoodleView view) { updateUndoRedo(); } @Override public void onSavingFinished(boolean ok) { if (mExitWaitingDialog != null) { mExitWaitingDialog.dismiss(); mExitWaitingDialog = null; } Intent intent = new Intent(); intent.setData(Uri.fromFile(mOutputFile)); setResult(RESULT_OK, intent); finish(); } /* public static class DoodleLayout extends FrameLayout { public DoodleLayout(Context context) { super(context); } public DoodleLayout(Context context, AttributeSet attrs) { super(context, attrs); } public DoodleLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); final int parentLeft = getPaddingLeft(); final int parentRight = right - left - getPaddingRight(); final int parentTop = getPaddingTop(); final int parentBottom = bottom - top - getPaddingBottom(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = Gravity.TOP | Gravity.START; } final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = parentRight - width - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } int offsetX = - (int) (lp.percent * width); child.layout(childLeft + offsetX, childTop, childLeft + width, childTop + height); } } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } public static class LayoutParams extends FrameLayout.LayoutParams { public float percent = 0.0f; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } } } */ }