/*
* Copyright (C) 2011 The original author or authors.
*
* 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.zapta.apps.maniana.menus;
import static com.zapta.apps.maniana.util.Assertions.checkNotNull;
import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
import com.zapta.apps.maniana.R;
import com.zapta.apps.maniana.annotations.MainActivityScope;
import com.zapta.apps.maniana.main.MainActivityState;
import com.zapta.apps.maniana.util.DisplayUtil;
import com.zapta.apps.maniana.util.PopupsTracker.TrackablePopup;
/**
* Main menu.
*
* @author Tal Dayan (adapted to Maniana) Based on example by Lorensius W. L. T
* <lorenz@londatiga.net>.
*/
@MainActivityScope
public class MainMenu implements OnDismissListener, TrackablePopup {
public interface OnActionItemOutcomeListener {
void onOutcome(MainMenu source, MainMenuEntry selectedEntry);
}
private final MainActivityState mMainActivityState;
/** The window that contains the menu's top view. */
private final PopupWindow mMenuWindow;
private View mTopView;
private ViewGroup mItemContainerView;
private final OnActionItemOutcomeListener mOutcomeListener;
/**
* Constructor allowing orientation override
*
* @param mContext Context
* @param orientation Layout orientation, can be vartical or horizontal
*/
public MainMenu(MainActivityState mainActivityState, OnActionItemOutcomeListener outcomeListener) {
mMainActivityState = mainActivityState;
mMenuWindow = new PopupWindow(mainActivityState.context());
mMenuWindow.setTouchInterceptor(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO: dismiss if outside of mItemContainsView.
//LogUtil.debug("*** onTouch: %f, %f, action=%d", event.getX(), event.getY(), event.getAction());
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
mMenuWindow.dismiss();
return true;
}
return false;
}
});
mOutcomeListener = checkNotNull(outcomeListener);
mTopView = (ViewGroup) mMainActivityState.services().layoutInflater()
.inflate(R.layout.main_menu, null);
mItemContainerView = (ViewGroup) mTopView.findViewById(R.id.items_container);
mTopView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
mMenuWindow.setContentView(mTopView);
mMenuWindow.setOnDismissListener(this);
}
/**
* Show the popup action menu over a given anchor view.
*
* @param anchorView a view to which the action menu's arrow will point it.
*/
public final void show(View anchorView) {
mMainActivityState.services().maybePlayStockSound(AudioManager.FX_KEY_CLICK, false);
for (MainMenuEntry entry : MainMenuEntry.values()) {
if (entry != MainMenuEntry.DEBUG || mMainActivityState.debugController().isDebugMode()) {
addEntry(entry);
}
}
// Set transparent window background. This will clear the horizontal strips above and below
// the menu defined by the two arrows.
mMenuWindow.setBackgroundDrawable(new BitmapDrawable());
mMenuWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
mMenuWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
mMenuWindow.setTouchable(true);
mMenuWindow.setFocusable(true);
mMenuWindow.setOutsideTouchable(true);
mTopView.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// NOTE: without calling setFocusableInTouchMode(), the key listenter is not called.
mTopView.setFocusableInTouchMode(true);
mTopView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && event.getRepeatCount() == 0
&& event.getAction() == KeyEvent.ACTION_DOWN) {
dismiss();
return true;
}
return false;
}
});
mMenuWindow.setAnimationStyle(R.style.Animations_MainMenu);
mMainActivityState.popupsTracker().track(this);
// TODO: normalize dx, dy by density.
//
// NOTE: the large x offset is a workaround for the orientation change issue. To reproduce
// (with xoff param = 0):
// 1. While in portrait mode, open the main menu.
// 2. Rotate the phone to landsape orientation and observe the main menu.
//
// Actual behavior: the main menu is positioned too much to the left on the main
// menu button.
//
final float density = DisplayUtil.getDensity(mMainActivityState.context());
final int xOffset = (int)(1000 * density);
final int yOffset = (int)(-2 * density -0.5f);
mMenuWindow.showAsDropDown(anchorView, xOffset, yOffset);
}
private final boolean isShowing() {
return mMenuWindow.isShowing();
}
/**
* Add an action item to the end of the list.
*/
private final void addEntry(final MainMenuEntry entry) {
final View entryTopView = mMainActivityState.services().layoutInflater()
.inflate(R.layout.main_menu_entry, null);
final View highlightView = entryTopView.findViewById(R.id.main_menu_entry_highlight);
final ImageView imageView = (ImageView) entryTopView
.findViewById(R.id.main_menu_entry_icon);
imageView.setImageResource(entry.iconResourceId);
final TextView textView = (TextView) entryTopView.findViewById(R.id.main_menu_entry_text);
textView.setText(entry.textResourceId);
// Set a listener to track touches and highlight pressed items.
entryTopView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// NOTE: this clears any padding set on the top view (in case it is set in xml).
highlightView.setBackgroundResource(R.drawable.popup_menu_entry_selected);
} else if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP || !entryTopView.isPressed()) {
highlightView.setBackgroundResource(0);
}
return false;
}
});
entryTopView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onMenuEntrySelected(entry);
}
});
entryTopView.setFocusable(true);
entryTopView.setClickable(true);
mItemContainerView.addView(entryTopView);
}
/** Called when a menu entry is clicked */
private final void onMenuEntrySelected(final MainMenuEntry entry) {
mMainActivityState.services().maybePlayStockSound(AudioManager.FX_KEY_CLICK, false);
mMenuWindow.dismiss();
// Short delay to let the dismiss animation complete.
final int animationTimeMillis = mMainActivityState.context().getResources().getInteger(R.integer.popup_menu_dismiss_animation_millis_id);
mMainActivityState.view().getRootView().postDelayed(new Runnable() {
@Override
public void run() {
mOutcomeListener.onOutcome(MainMenu.this, entry);
}
}, animationTimeMillis);
}
/** Called when the window is dismissed. */
@Override
public final void onDismiss() {
mMainActivityState.popupsTracker().untrack(this);
}
/** Public method to dismiss the menu. */
public final void dismiss() {
if (isShowing()) {
mMenuWindow.dismiss();
}
}
/** Called by the popup tracker. */
@Override
public final void closeLeftOver() {
dismiss();
}
}