/*
* Copyright (C) 2013 Fairphone Project
*
* 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 org.fairphone.launcher.edgeswipe;
import com.flurry.android.FlurryAgent;
import org.fairphone.launcher.ApplicationInfo;
import org.fairphone.launcher.DragController;
import org.fairphone.launcher.Launcher;
import org.fairphone.launcher.R;
import org.fairphone.launcher.edgeswipe.edit.FavoritesStorageHelper;
import org.fairphone.launcher.edgeswipe.ui.EdgeSwipeInterceptorViewListener;
import org.fairphone.launcher.util.FlurryHelper;
import org.fairphone.launcher.util.KWMathUtils;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class EdgeSwipeAppMenuHelper implements EdgeSwipeInterceptorViewListener
{
private static final String TAG = EdgeSwipeAppMenuHelper.class.getSimpleName();
private static int MAX_FAVORITE_APPS = 4;
private static int ITEM_COUNT = 5;
public static enum Side
{
LEFT, RIGHT
};
enum IconEndPositions
{
Icon1(0, 0), Icon2(0, 0), Icon3(0, 0), Icon4(0, 0), Icon5(0, 0);
private int x;
private int y;
private IconEndPositions(int x, int y)
{
this.x = x;
this.y = y;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
}
private int mMenuWidth;
private int mMenuHeight;
private int mIconWidth;
private int mIconHeight;
private float mCenterX;
private float mCenterY;
private Side mCurrentSide;
private float innerDeadzone;
private float outerDeadzone;
private ViewGroup menuContainerView;
private View menuRoot;
private View menuContent;
private View menuBackground;
private View editGroup;
private View swipeMenuTopShadow;
private View swipeMenuBottomShadow;
private Context mContext;
private ItemIcon[] icons;
private int prevVisibleIcon;
private Launcher mLauncher;
private DisplayMetrics mDisplayMetrics;
private boolean mIsInitialized;
private boolean isInSelection;
private long mEditMenuButtonStartTime;
public class ItemIcon
{
TextView selectedViewName;
View rootView;
View iconRingView;
Intent intent;
private ItemIcon(View rootView, Drawable iconSelected, String iconSelectedName, Intent intent)
{
this.intent = intent;
this.rootView = rootView;
selectedViewName = (TextView) rootView.findViewById(R.id.iconSelectedName);
iconRingView = rootView.findViewById(R.id.iconPressRing);
Resources r = mContext.getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, r.getDisplayMetrics());
iconSelected.setBounds(0, 0, Math.round(px), Math.round(px));
selectedViewName.setCompoundDrawables(null, iconSelected, null, null);
// App drawer doesn't have a text description
if (selectedViewName != null)
{
selectedViewName.setText(iconSelectedName);
}
}
}
public EdgeSwipeAppMenuHelper(Context context, DragController detectorView, ViewGroup menuContainerView, Launcher launcher)
{
mContext = context;
mIsInitialized = false;
prevVisibleIcon = -1;
isInSelection = false;
icons = new ItemIcon[ITEM_COUNT];
this.setMenuContainerView(menuContainerView);
this.mLauncher = launcher;
detectorView.addOnSelectionListener(this);
mDisplayMetrics = new DisplayMetrics();
launcher.getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
setCurrentSide(null);
setupLayout();
setDeadzones();
}
/**
* Set the inner and outer deadzones to control icon selection
*/
private void setDeadzones()
{
innerDeadzone = convertDpToPixels(mLauncher.getResources().getDimensionPixelSize(R.dimen.edge_swipe_inner_deadzone));
outerDeadzone = convertDpToPixels(mLauncher.getResources().getDimensionPixelSize(R.dimen.edge_swipe_outer_deadzone));
}
private int convertDpToPixels(float dpToConvert)
{
// Get the screen's density scale
final float scale = mLauncher.getResources().getDisplayMetrics().density;
// Convert the dps to pixels, based on density scale
return (int) (dpToConvert * scale + 0.5f);
}
public ItemIcon[] getIcons()
{
return icons;
}
@Override
public void onSelectionStarted(float pointerX, float pointerY)
{
// update the icons
if (!mIsInitialized)
{
updateIcons();
mIsInitialized = true;
}
isInSelection = true;
// set the Y coords
float newPointerY = pointerY;
setupCurrentDisplay(pointerX, newPointerY);
// Animate the menu
// setup the side animation
Animation animation = setTheSwipeSideAnimation(pointerX);
// show the menu and background views
getMenuContainerView().setVisibility(View.VISIBLE);
menuRoot.setVisibility(View.VISIBLE);
menuBackground.setVisibility(View.VISIBLE);
menuContent.setVisibility(View.VISIBLE);
menuContent.startAnimation(animation);
swipeMenuTopShadow.setVisibility(View.VISIBLE);
swipeMenuBottomShadow.setVisibility(View.VISIBLE);
// Animate the background
Animation backAnimation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_background_fade_in);
menuBackground.startAnimation(backAnimation);
// define the side of the swipe
int halfMenuWidth = menuRoot.getWidth() / 2;
int halfContentWidth = menuContent.getWidth() / 2;
// set the X coords
switch (getCurrentSide())
{
case LEFT:
menuRoot.setX(-halfMenuWidth + (halfContentWidth - halfMenuWidth) / 2);
break;
case RIGHT:
menuRoot.setX((int) (mDisplayMetrics.widthPixels - halfContentWidth));
break;
default:
break;
}
setupEditButtonPositionAndTimer(newPointerY);
float menuYCoord = newPointerY - halfMenuWidth;
menuRoot.setY(menuYCoord);
// update the icons position
updateIconPosition();
}
private boolean startEditButtonAnimation() {
boolean isTimeToShow = mEditMenuButtonStartTime < System.currentTimeMillis();
if(isTimeToShow && (editGroup.getVisibility() != View.VISIBLE) && menuRoot.getVisibility() == View.VISIBLE){
Animation editMenuAnimation = getEditButtonAnimation();
if(editMenuAnimation != null){
//set the button visible and animate
editGroup.setVisibility(View.VISIBLE);
editGroup.startAnimation(editMenuAnimation);
}
}else{
Log.d(TAG, "Edit button can't be shown");
}
return isTimeToShow;
}
private void setupEditButtonPositionAndTimer(float pointerY) {
//set edit menu button timer
mEditMenuButtonStartTime = System.currentTimeMillis() + mLauncher.getApplicationContext().getResources().getInteger(R.integer.config_edgeMenuEditButtonTime);
// set the X coords
switch (getCurrentSide())
{
case LEFT:
editGroup.setX((int) (mDisplayMetrics.widthPixels - editGroup.getWidth()));
break;
case RIGHT:
editGroup.setX(0);
break;
default:
break;
}
//set Y coords
editGroup.setY(pointerY-(editGroup.getHeight()/2));
}
private Animation getEditButtonAnimation() {
Animation editMenuAnimation = AnimationUtils.loadAnimation(editGroup.getContext(), R.anim.menu_edge_edit_button_fade_in);
return editMenuAnimation;
}
private Animation setTheSwipeSideAnimation(float pointerX)
{
Animation animation;
if (pointerX < mDisplayMetrics.widthPixels / 2)
{
setCurrentSide(Side.LEFT);
menuContent.setX(-menuContent.getWidth() + menuRoot.getWidth());
animation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_appear_from_left_animation);
}
else
{
setCurrentSide(Side.RIGHT);
menuContent.setX(0);
animation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_appear_from_right_animation);
}
return animation;
}
private void setupCurrentDisplay(float pointerX, float pointerY)
{
// setup the size
mMenuWidth = menuContent.getWidth();
mMenuHeight = menuContent.getHeight();
// setup the center of the menu
mCenterX = pointerX;
mCenterY = pointerY;
// setup the icon size
RelativeLayout v = (RelativeLayout) menuContent.findViewById(R.id.icon1);
mIconHeight = v.getHeight();
mIconWidth = v.getWidth();
}
@Override
public void onSelectionUpdate(float pointerX, float pointerY)
{
startEditButtonAnimation();
//select the edit zone menu with a circle
selectEditFavoritesMenuZone(pointerX, pointerY);
// check to see if the user is above the icon zone
if (isInActiveZone(pointerX, pointerY))
{
// get the icon
int iconIndex = getIconIndex(pointerX, pointerY);
// check to see if the user is changing the icon
if (prevVisibleIcon != iconIndex)
{
//Animate the selected icon ring
startIconRingAppearAnimation(icons[iconIndex].iconRingView);
//Animate the previous icon aka remove the ring
if(prevVisibleIcon != -1){
startIconRingDisappearAnimation(icons[prevVisibleIcon].iconRingView);
}
prevVisibleIcon = iconIndex;
}
}
else
{
if(prevVisibleIcon != -1){
startIconRingDisappearAnimation(icons[prevVisibleIcon].iconRingView);
}
prevVisibleIcon = -1;
}
}
private void selectEditFavoritesMenuZone(float pointerX, float pointerY) {
if(editGroup.getVisibility() == View.VISIBLE){
if(isInEditZone(pointerX, pointerY)){
View editZoneCircle = editGroup.findViewById(R.id.editRing);
if(editZoneCircle != null && editZoneCircle.getVisibility() != View.VISIBLE){
startIconRingAppearAnimation(editZoneCircle);
}
}
else{
View editZoneCircle = editGroup.findViewById(R.id.editRing);
if(editZoneCircle != null && editZoneCircle.getVisibility() == View.VISIBLE){
startIconRingDisappearAnimation(editZoneCircle);
}
}
}
}
private void startIconRingAppearAnimation(View viewToAnimate)
{
menuRoot.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
Animation iconRingAnimation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_icon_ring_fade_in);
viewToAnimate.setVisibility(View.VISIBLE);
viewToAnimate.startAnimation(iconRingAnimation);
}
private void startIconRingDisappearAnimation(View viewToAnimate) {
Animation iconRingFadeOutAnimation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_icon_ring_fade_out);
viewToAnimate.startAnimation(iconRingFadeOutAnimation);
viewToAnimate.setVisibility(View.INVISIBLE);
}
private int getIconIndex(float pointerX, float pointerY)
{
double deg = KWMathUtils.radToDeg(Math.atan2(-(pointerY - mCenterY), pointerX - mCenterX));
if (deg < 0)
{
deg += 360;
}
if (deg > 360)
{
deg -= 360;
}
float ratio = 0;
if (getCurrentSide() == Side.RIGHT)
{
ratio = KWMathUtils.getFloatRatio(90, 270, (float) deg);
}
else
{
if (deg <= 90)
{
ratio = 0.5f - KWMathUtils.getFloatRatio(0, 180, (float) deg);
}
else
{
ratio = 1.5f - KWMathUtils.getFloatRatio(180, 360, (float) deg);
}
}
float dr = 1.0f / ITEM_COUNT;
int iconIndex = (int) (ratio / dr);
if (iconIndex >= ITEM_COUNT)
{
iconIndex = ITEM_COUNT - 1;
}
return iconIndex;
}
private boolean isInActiveZone(float pointerX, float pointerY)
{
double distance = KWMathUtils.getDistance(pointerX, pointerY, mCenterX, mCenterY);
return (distance > innerDeadzone && distance < outerDeadzone);
}
private boolean isInEditZone(float pointerX, float pointerY)
{
boolean validX = false;
boolean validY = false;
if(getCurrentSide() == null){
return false;
}
switch (getCurrentSide())
{
case LEFT:
validX = pointerX >= editGroup.getX();
break;
case RIGHT:
validX = pointerX <= (editGroup.getX() + editGroup.getWidth());
break;
default:
break;
}
validY = pointerY >= editGroup.getY();
validY &= pointerY <= editGroup.getY() + editGroup.getHeight();
return validX && validY;
}
boolean contentVisible = false;
boolean backVisible = false;
@Override
public void onSelectionFinished(float pointerX, float pointerY)
{
//launch the edit menu
if(editGroup.getVisibility() == View.VISIBLE && isInEditZone(pointerX, pointerY)){
View editZoneCircle = editGroup.findViewById(R.id.editRing);
if(editZoneCircle != null && editZoneCircle.getVisibility() == View.VISIBLE){
startIconRingDisappearAnimation(editZoneCircle);
}
//to avoid multiple open actions:
//open edit favorites and then the apps drawer
if (prevVisibleIcon >= 0)
{
startIconRingDisappearAnimation(icons[prevVisibleIcon].iconRingView);
}
prevVisibleIcon = -1;
FlurryAgent.logEvent(FlurryHelper.LAUNCH_EDGE_MENU_EDIT_FAVORITES);
mLauncher.startEditFavorites();
}
if (!isInSelection)
{
return;
}
isInSelection = false;
// verify if the menu is showing
if (menuContent.getVisibility() == View.VISIBLE)
{
setMenuSideExitAnimation();
}
else
{
contentVisible = false;
backVisible = false;
menuRoot.setVisibility(View.INVISIBLE);
contentVisible = false;
getMenuContainerView().setVisibility(View.GONE);
}
setBackgroundExitAnimation();
if (prevVisibleIcon >= 0)
{
startIconRingDisappearAnimation(icons[prevVisibleIcon].iconRingView);
launchMenuItem();
}
prevVisibleIcon = -1;
setCurrentSide(null);
editGroup.setVisibility(View.GONE);
View editZoneCircle = editGroup.findViewById(R.id.editRing);
editZoneCircle.setVisibility(View.GONE);
swipeMenuTopShadow.setVisibility(View.GONE);
swipeMenuBottomShadow.setVisibility(View.GONE);
}
private void launchMenuItem()
{
try
{
if (prevVisibleIcon == 2)
{
FlurryAgent.logEvent(FlurryHelper.LAUNCH_EDGE_MENU_ALL_APPS);
mLauncher.showAllApps(true);
}
else
{
if (icons[prevVisibleIcon] != null && icons[prevVisibleIcon].intent != null) {
FlurryAgent.logEvent(FlurryHelper.LAUNCH_EDGE_MENU_APP, FlurryHelper.getInstance().setFlurryParams(""+(prevVisibleIcon + 1), icons[prevVisibleIcon].selectedViewName.getText().toString(), true));
mLauncher.startActivity(this.getMenuContainerView(), icons[prevVisibleIcon].intent, null);
} else {
//to avoid the addition of fairphone home launcher to appSwitcher
FlurryAgent.logEvent(FlurryHelper.LAUNCH_EDGE_MENU_EDIT_FAVORITES_FROM_ITEM, FlurryHelper.getInstance().setFlurryParams(""+(prevVisibleIcon + 1), FlurryHelper.EDGE_SWIPE_EMPTY_SLOT, true));
mLauncher.startEditFavorites();
}
}
} catch (ActivityNotFoundException e)
{
e.printStackTrace();
}
}
private void setBackgroundExitAnimation()
{
Animation backAnimation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_background_fade_out);
backAnimation.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
backVisible = false;
menuBackground.setVisibility(View.INVISIBLE);
menuRoot.setVisibility(View.INVISIBLE);
}
});
menuBackground.startAnimation(backAnimation);
}
private void setMenuSideExitAnimation()
{
// draw back the menu
// load the correct side for the animation
Animation animation = null;
if (getCurrentSide() == Side.LEFT)
{
animation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_disappear_to_left_animation);
}
else
{
animation = AnimationUtils.loadAnimation(menuRoot.getContext(), R.anim.menu_disappear_to_right_animation);
}
getMenuContainerView().setVisibility(View.VISIBLE);
contentVisible = true;
backVisible = true;
animation.setAnimationListener(new AnimationListener()
{
@Override
public void onAnimationStart(Animation animation)
{
}
@Override
public void onAnimationRepeat(Animation animation)
{
}
@Override
public void onAnimationEnd(Animation animation)
{
menuRoot.setVisibility(View.INVISIBLE);
contentVisible = false;
if (!backVisible)
{
getMenuContainerView().setVisibility(View.GONE);
}
}
});
menuContent.startAnimation(animation);
}
private void setupLayout()
{
LayoutInflater.from(getMenuContainerView().getContext()).inflate(R.layout.fp_fav_apps_layout, getMenuContainerView());
menuRoot = getMenuContainerView().findViewById(R.id.menuRoot);
getMenuContainerView().setVisibility(View.INVISIBLE);
getMenuContainerView().post(new Runnable()
{
@Override
public void run()
{
getMenuContainerView().setVisibility(View.GONE);
}
});
menuContent = menuRoot.findViewById(R.id.menuContent);
menuBackground = getMenuContainerView().findViewById(R.id.menuBackground);
editGroup = getMenuContainerView().findViewById(R.id.editGroup);
swipeMenuTopShadow = getMenuContainerView().findViewById(R.id.swipeMenuTopShadow);
swipeMenuBottomShadow = getMenuContainerView().findViewById(R.id.swipeMenuBottomShadow);
}
public void updateIcons()
{
ApplicationInfo[] selectedApps = FavoritesStorageHelper.loadSelectedApps(getMenuContainerView().getContext(), MAX_FAVORITE_APPS);
// set apps
icons[0] = generateItemForMenu(selectedApps[0], R.id.icon1);
icons[1] = generateItemForMenu(selectedApps[1], R.id.icon2);
icons[2] = generateAllAppsMenuItem(R.id.icon3);
icons[3] = generateItemForMenu(selectedApps[2], R.id.icon4);
icons[4] = generateItemForMenu(selectedApps[3], R.id.icon5);
updateIconPosition();
}
private ItemIcon generateAllAppsMenuItem(int iconId)
{
return new ItemIcon(menuContent.findViewById(iconId), mLauncher.getResources().getDrawable(R.drawable.ic_allapps), "", null);
}
private ItemIcon generateItemForMenu(ApplicationInfo applicationInfo, int iconId)
{
Intent launchIntent = null;
ComponentName componentName = null;
Drawable icon = null;
String label = "";
if (applicationInfo == null)
{
icon = mLauncher.getResources().getDrawable(R.drawable.edit_holder);
label = mLauncher.getString(R.string.edit);
}
else
{
componentName = applicationInfo.componentName;
icon = new BitmapDrawable(mContext.getResources(), applicationInfo.iconBitmap);
label = applicationInfo.getApplicationTitle();
PackageManager pacManager = getMenuContainerView().getContext().getPackageManager();
launchIntent = pacManager.getLaunchIntentForPackage(componentName.getPackageName());
launchIntent.setComponent(componentName);
}
// Set the right ComponentName in order to launch Phone
// or Contacts correctly
return new ItemIcon(menuContent.findViewById(iconId), icon, label, launchIntent);
}
private void updateIconPosition()
{
float radius = mLauncher.getResources().getDimensionPixelSize(R.dimen.edge_swipe_menu_radius);
float minAngle = 95;
float maxAngle = 265;
float angleDif = (maxAngle - minAngle) / ITEM_COUNT;
float curAngle = minAngle + angleDif / 2;
float centerX = (mMenuWidth / 2);
float centerY = mMenuHeight / 2;
for (int i = 0; i < ITEM_COUNT; i++)
{
double posX =
(getCurrentSide() == Side.RIGHT ? centerX + 25 : centerX - 25) + (getCurrentSide() == Side.RIGHT ? 1 : -1)
* Math.cos(KWMathUtils.degToRad(curAngle)) * radius;
double posY = centerY - Math.sin(KWMathUtils.degToRad(curAngle)) * radius;
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icons[i].rootView.getLayoutParams();
lp.leftMargin = (int) posX - mIconWidth / 2;
lp.topMargin = (int) posY - mIconHeight / 2;
icons[i].rootView.setLayoutParams(lp);
curAngle += angleDif;
}
}
public boolean isMenuVisible()
{
return this.contentVisible;
}
public ViewGroup getMenuContainerView()
{
return menuContainerView;
}
protected void setMenuContainerView(ViewGroup menuContainerView)
{
this.menuContainerView = menuContainerView;
}
public Side getCurrentSide()
{
return mCurrentSide;
}
protected void setCurrentSide(Side mCurrentSide)
{
this.mCurrentSide = mCurrentSide;
}
}