/*
*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license.
*
* Project Oxford: http://ProjectOxford.ai
*
* Project Oxford Mimicker Alarm Github:
* https://github.com/Microsoft/ProjectOxford-Apps-MimickerAlarm
*
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License:
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.microsoft.mimickeralarm.mimics;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import com.microsoft.mimickeralarm.R;
/**
* Simple UI class that implements a button with states
*
* The states are :
* capture image,
* capture audio,
* stop capturing audio,
* processing
**/
public class ProgressButton extends ImageView {
private static final int PRESSED_ANIMATION_DURATION = 200;
private static final float PRESSED_ANIMATION_GROW_FACTOR = 1.2f;
private static final int LOADING_ANIMATION_DURATION = 2000;
private static final long INTERACTION_HINT_ANIMATION_DURATION = 1000;
private static final float INTERACTION_HINT_GROW_FACTOR = 1.1f;
private static int sYellow, sBlue, sGrey, sWhite;
private State mState;
private State mReadyState;
private Paint mBrush;
private float mRadius;
private int mCenterX, mCenterY;
private float mInitialRadius;
private Bitmap mMicrophoneIcon;
private Bitmap mCameraIcon;
private ObjectAnimator mPressedAnimation;
private ObjectAnimator mLoadingAnimation;
// A growing/shrinking animation of the button border to give the hint to the user to press the button
private ObjectAnimator mInteractionHintAnimation;
private float mLoadingAnimationProgress;
private RectF mLoadingAnimationRect;
public ProgressButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true);
setScaleType(ScaleType.CENTER_INSIDE);
sYellow = ContextCompat.getColor(context, R.color.yellow3);
sBlue = ContextCompat.getColor(context, R.color.blue);
sGrey = ContextCompat.getColor(context, R.color.grey);
sWhite = ContextCompat.getColor(context, R.color.white);
mMicrophoneIcon = ((BitmapDrawable)ContextCompat.getDrawable(context, android.R.drawable.ic_btn_speak_now)).getBitmap();
mCameraIcon = ((BitmapDrawable)ContextCompat.getDrawable(context, android.R.drawable.ic_menu_camera)).getBitmap();
mBrush = new Paint(Paint.ANTI_ALIAS_FLAG);
mLoadingAnimation = ObjectAnimator.ofFloat(this, "loadingAnimationProgress", 0f, 360f);
mLoadingAnimation.setDuration(LOADING_ANIMATION_DURATION);
mLoadingAnimation.setRepeatCount(ObjectAnimator.INFINITE);
mPressedAnimation = ObjectAnimator.ofFloat(this, "radius", 0f, 0f);
mPressedAnimation.setDuration(PRESSED_ANIMATION_DURATION);
mPressedAnimation.setInterpolator(new DecelerateInterpolator());
}
public ProgressButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressButton(Context context) {
this(context, null);
}
// These setters and getters are required by animator
public float getRadius() {
return mRadius;
}
public void setRadius(float radius) {
this.mRadius = radius;
this.invalidate();
}
public float getLoadingAnimationProgress() {
return mLoadingAnimationProgress;
}
public void setLoadingAnimationProgress(float loadingAnimationProgress) {
this.mLoadingAnimationProgress = loadingAnimationProgress;
this.invalidate();
}
private void prepareDrawText(int color) {
mBrush.setFlags(Paint.LINEAR_TEXT_FLAG);
mBrush.setStyle(Paint.Style.STROKE);
mBrush.setColor(color);
mBrush.setTextAlign(Paint.Align.CENTER);
mBrush.setStrokeWidth(0f);
mBrush.setTextSize(mInitialRadius);
}
private void prepareDrawFill(int color) {
mBrush.setFlags(Paint.ANTI_ALIAS_FLAG);
mBrush.setStyle(Paint.Style.FILL);
mBrush.setColor(color);
}
private void prepareDrawStroke(int color) {
mBrush.setFlags(Paint.ANTI_ALIAS_FLAG);
mBrush.setStyle(Paint.Style.STROKE);
mBrush.setStrokeWidth(10f);
mBrush.setColor(color);
}
@Override
public void setPressed(boolean pressed) {
super.setPressed(pressed);
if (pressed) {
mPressedAnimation.setFloatValues(mRadius, mInitialRadius * PRESSED_ANIMATION_GROW_FACTOR);
mPressedAnimation.start();
}
else{
mPressedAnimation.setFloatValues(mRadius, mInitialRadius);
mPressedAnimation.start();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == State.ReadyCamera) {
prepareDrawFill(sYellow);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);
canvas.drawBitmap(mCameraIcon,
mCenterX - (mCameraIcon.getWidth() / 2),
mCenterY - (mCameraIcon.getHeight() / 2),
mBrush);
}
else if (mState == State.ReadyAudio) {
prepareDrawFill(sYellow);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);
canvas.drawBitmap(mMicrophoneIcon,
mCenterX - (mMicrophoneIcon.getWidth() / 2),
mCenterY - (mMicrophoneIcon.getHeight() / 2),
mBrush);
}
else if (mState == State.Loading) {
prepareDrawFill(sBlue);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);
prepareDrawStroke(sWhite);
canvas.drawArc(mLoadingAnimationRect, mLoadingAnimationProgress, 300f, false, mBrush);
}
else if (mState == State.Waiting) {
prepareDrawFill(sGrey);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mBrush);
prepareDrawFill(sWhite);
canvas.drawCircle(mCenterX, mCenterY, (float) (mRadius * 0.7), mBrush);
prepareDrawFill(sGrey);
float w = (float)(mRadius * 0.3);
canvas.drawRect(mCenterX - w, mCenterY - w, mCenterX + w, mCenterY + w, mBrush);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mInitialRadius = Math.min(w, h) / 2 - getContext().getResources()
.getDimensionPixelSize(R.dimen.progress_button_padding);
mRadius = mInitialRadius;
prepareDrawText(sWhite);
float radius = mInitialRadius / 2f;
mLoadingAnimationRect = new RectF(mCenterX - radius, mCenterY - radius, mCenterX + radius, mCenterY + radius);
mInteractionHintAnimation = ObjectAnimator.ofFloat(this, "radius", mInitialRadius, mInitialRadius * INTERACTION_HINT_GROW_FACTOR, mInitialRadius);
mInteractionHintAnimation.setDuration(INTERACTION_HINT_ANIMATION_DURATION);
mInteractionHintAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mInteractionHintAnimation.setRepeatCount(ValueAnimator.INFINITE);
mInteractionHintAnimation.start();
}
public void setReady() {
mState = mReadyState;
setClickable(true);
stop();
if (mInteractionHintAnimation != null) {
// When the game starts, setReady() will be called before onSizeChanged() is called.
// However, we will not have mInitialRadius set until OnSizeChanged() is called.
// So we will start animation in OnSizeChanged().
// However, if the user fails the mimics, setReady() will be called for several times.
// In that case, we will re-start the animation here.
mInteractionHintAnimation.start();
}
invalidate();
}
public void setReadyState(State state) {
mReadyState = state;
}
public Boolean isReady() {
return mState == State.ReadyCamera || mState == State.ReadyAudio;
}
public void waiting() {
mState = State.Waiting;
mInteractionHintAnimation.cancel();
invalidate();
}
public void loading() {
mState = State.Loading;
setClickable(false);
mInteractionHintAnimation.cancel();
mLoadingAnimation.start();
}
public void stop() {
mLoadingAnimation.cancel();
mPressedAnimation.cancel();
if (mInteractionHintAnimation != null) {
mInteractionHintAnimation.cancel();
}
}
public enum State {
ReadyCamera,
ReadyAudio,
Loading,
Waiting,
}
}