Introduce FancyMenu.

A menu-item like menu which gives us better layout control.
This commit is contained in:
Adrian Ulrich 2018-11-01 20:55:32 +01:00
parent 885618ef36
commit 5a0fd2b538
7 changed files with 542 additions and 48 deletions

View File

@ -454,17 +454,17 @@ public class FileSystemAdapter
} }
/** /**
* Context menu of a row: this was dispatched by LibraryPAgerAdapter * Context menu of a row: this was dispatched by LibraryPagerAdapter
* *
* @param menu the context menu to populate
* @param intent likely created by createData() * @param intent likely created by createData()
*/ */
public void onCreateContextMenu(ContextMenu menu, Intent intent) { public boolean onCreateFancyMenu(Intent intent) {
String path = intent.getStringExtra(LibraryAdapter.DATA_FILE); String path = intent.getStringExtra(LibraryAdapter.DATA_FILE);
boolean isParentRow = (path != null && pointsToParentFolder(new File(path))); boolean isParentRow = (path != null && pointsToParentFolder(new File(path)));
if (!isParentRow) if (!isParentRow)
mActivity.onCreateContextMenu(menu, intent); return mActivity.onCreateFancyMenu(intent);
// else: no context menu // else: no context menu, but consume event.
return true;
} }
} }

View File

@ -24,6 +24,8 @@
package ch.blinkenlights.android.vanilla; package ch.blinkenlights.android.vanilla;
import ch.blinkenlights.android.medialibrary.MediaLibrary; import ch.blinkenlights.android.medialibrary.MediaLibrary;
import ch.blinkenlights.android.vanilla.ui.FancyMenu;
import ch.blinkenlights.android.vanilla.ui.FancyMenuItem;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -67,6 +69,7 @@ public class LibraryActivity
implements DialogInterface.OnClickListener implements DialogInterface.OnClickListener
, DialogInterface.OnDismissListener , DialogInterface.OnDismissListener
, SearchView.OnQueryTextListener , SearchView.OnQueryTextListener
, FancyMenu.Callback
{ {
@ -626,56 +629,59 @@ public class LibraryActivity
* @param menu The menu to create. * @param menu The menu to create.
* @param rowData Data for the adapter row. * @param rowData Data for the adapter row.
*/ */
public void onCreateContextMenu(ContextMenu menu, Intent rowData) { public boolean onCreateFancyMenu(Intent rowData) {
// Add to -> Playlist is always available. FancyMenu fm = new FancyMenu(this, this);
SubMenu subAddTo = menu.addSubMenu(0, CTX_MENU_NOOP, 10, R.string.add_to); fm.show(getFragmentManager(), "LibraryActivityContext");
subAddTo.add(0, CTX_MENU_ADD_TO_PLAYLIST, 0, R.string.playlist).setIntent(rowData);
// Add to playlist is always available.
fm.addSpacer(20);
fm.add(CTX_MENU_ADD_TO_PLAYLIST, 20, R.drawable.folder, R.string.add_to_playlist).setIntent(rowData);
if (rowData.getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID) == LibraryAdapter.HEADER_ID) { if (rowData.getLongExtra(LibraryAdapter.DATA_ID, LibraryAdapter.INVALID_ID) == LibraryAdapter.HEADER_ID) {
menu.setHeaderTitle(getString(R.string.all_songs)); fm.setHeaderTitle(getString(R.string.all_songs));
menu.add(0, CTX_MENU_PLAY_ALL, 0, R.string.play_all).setIntent(rowData); fm.add(CTX_MENU_PLAY_ALL, 10, R.drawable.folder, R.string.play_all).setIntent(rowData);
menu.add(0, CTX_MENU_ENQUEUE_ALL, 0, R.string.enqueue_all).setIntent(rowData); fm.add(CTX_MENU_ENQUEUE_ALL, 10, R.drawable.folder, R.string.enqueue_all).setIntent(rowData);
} else { } else {
int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID); int type = rowData.getIntExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_INVALID);
menu.setHeaderTitle(rowData.getStringExtra(LibraryAdapter.DATA_TITLE)); fm.setHeaderTitle(rowData.getStringExtra(LibraryAdapter.DATA_TITLE));
if (type != MediaUtils.TYPE_FILE) if (type != MediaUtils.TYPE_FILE)
subAddTo.add(0, CTX_MENU_ADD_TO_HOMESCREEN, 0, R.string.homescreen).setIntent(rowData); fm.add(CTX_MENU_ADD_TO_HOMESCREEN, 20, R.drawable.folder, R.string.add_to_homescreen).setIntent(rowData);
if (FileUtils.canDispatchIntent(rowData)) if (FileUtils.canDispatchIntent(rowData))
menu.add(0, CTX_MENU_OPEN_EXTERNAL, 0, R.string.open).setIntent(rowData); fm.add(CTX_MENU_OPEN_EXTERNAL, 10, R.drawable.folder, R.string.open).setIntent(rowData);
menu.add(0, CTX_MENU_PLAY, 0, R.string.play).setIntent(rowData); fm.add(CTX_MENU_PLAY, 0, R.drawable.folder, R.string.play).setIntent(rowData);
if (type <= MediaUtils.TYPE_SONG) { if (type <= MediaUtils.TYPE_SONG)
menu.add(0, CTX_MENU_PLAY_ALL, 0, R.string.play_all).setIntent(rowData); fm.add(CTX_MENU_PLAY_ALL, 1, R.drawable.folder, R.string.play_all).setIntent(rowData);
}
menu.add(0, CTX_MENU_ENQUEUE_AS_NEXT, 0, R.string.enqueue_as_next).setIntent(rowData); fm.add(CTX_MENU_ENQUEUE_AS_NEXT, 1, R.drawable.folder, R.string.enqueue_as_next).setIntent(rowData);
menu.add(0, CTX_MENU_ENQUEUE, 0, R.string.enqueue).setIntent(rowData); fm.add(CTX_MENU_ENQUEUE, 1, R.drawable.folder, R.string.enqueue).setIntent(rowData);
if (type == MediaUtils.TYPE_PLAYLIST) { if (type == MediaUtils.TYPE_PLAYLIST) {
menu.add(0, CTX_MENU_RENAME_PLAYLIST, 0, R.string.rename).setIntent(rowData); fm.add(CTX_MENU_RENAME_PLAYLIST, 0, R.drawable.folder, R.string.rename).setIntent(rowData);
} else if (rowData.getBooleanExtra(LibraryAdapter.DATA_EXPANDABLE, false)) { } else if (rowData.getBooleanExtra(LibraryAdapter.DATA_EXPANDABLE, false)) {
menu.add(0, CTX_MENU_EXPAND, 0, R.string.expand).setIntent(rowData); fm.add(CTX_MENU_EXPAND, 2, R.drawable.folder, R.string.expand).setIntent(rowData);
} }
if (type == MediaUtils.TYPE_SONG || type == MediaUtils.TYPE_ALBUM) { if (type == MediaUtils.TYPE_SONG || type == MediaUtils.TYPE_ALBUM) {
SubMenu subMoreFrom = menu.addSubMenu(0, CTX_MENU_NOOP, 0, R.string.more_from_current); fm.addSpacer(30);
subMoreFrom.add(0, CTX_MENU_MORE_FROM_ARTIST, 0, R.string.artist).setIntent(rowData); fm.add(CTX_MENU_MORE_FROM_ARTIST, 30, R.drawable.folder, R.string.more_from_artist).setIntent(rowData);
if (type == MediaUtils.TYPE_SONG) { if (type == MediaUtils.TYPE_SONG) {
subMoreFrom.add(0, CTX_MENU_MORE_FROM_ALBUM, 0, R.string.album).setIntent(rowData); fm.add(CTX_MENU_MORE_FROM_ALBUM, 30, R.drawable.folder, R.string.more_from_album).setIntent(rowData);
subMoreFrom.add(0, CTX_MENU_SHOW_DETAILS, 0, R.string.details).setIntent(rowData); fm.add(CTX_MENU_SHOW_DETAILS, 99, R.drawable.folder, R.string.details).setIntent(rowData);
if (PluginUtils.checkPlugins(this)) { if (PluginUtils.checkPlugins(this)) {
// not part of submenu: just last item in normal menu. // not part of submenu: just last item in normal menu.
menu.add(0, CTX_MENU_PLUGINS, 99, R.string.plugins).setIntent(rowData); fm.add(CTX_MENU_PLUGINS, 99, R.drawable.folder, R.string.plugins).setIntent(rowData);
} }
} }
} }
fm.addSpacer(90);
menu.add(0, CTX_MENU_DELETE, 90, R.string.delete).setIntent(rowData); fm.add(CTX_MENU_DELETE, 90, R.drawable.folder, R.string.delete).setIntent(rowData);
} }
return true;
} }
/** /**
@ -690,11 +696,7 @@ public class LibraryActivity
} }
@Override @Override
public boolean onContextItemSelected(MenuItem item) public boolean onFancyItemSelected(FancyMenuItem item) {
{
if (item.getGroupId() != 0)
return super.onContextItemSelected(item);
final Intent intent = item.getIntent(); final Intent intent = item.getIntent();
switch (item.getItemId()) { switch (item.getItemId()) {
case CTX_MENU_NOOP: case CTX_MENU_NOOP:
@ -795,7 +797,6 @@ public class LibraryActivity
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
} }
return true; return true;
} }

View File

@ -50,7 +50,7 @@ public class LibraryPagerAdapter
extends PagerAdapter extends PagerAdapter
implements Handler.Callback implements Handler.Callback
, ViewPager.OnPageChangeListener , ViewPager.OnPageChangeListener
, View.OnCreateContextMenuListener , AdapterView.OnItemLongClickListener
, AdapterView.OnItemClickListener , AdapterView.OnItemClickListener
{ {
/** /**
@ -324,7 +324,7 @@ public class LibraryPagerAdapter
} }
view = (ListView)inflater.inflate(R.layout.listview, null); view = (ListView)inflater.inflate(R.layout.listview, null);
view.setOnCreateContextMenuListener(this); view.setOnItemLongClickListener(this);
view.setOnItemClickListener(this); view.setOnItemClickListener(this);
view.setTag(type); view.setTag(type);
@ -852,18 +852,24 @@ public class LibraryPagerAdapter
} }
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) /**
{ * Dispatch long click event of a row.
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; *
View targetView = info.targetView; * @param parent the parent adapter view
Intent intent = info.id == LibraryAdapter.HEADER_ID ? createHeaderIntent(targetView) : mCurrentAdapter.createData(targetView); * @param view the long clicked view
int type = (Integer)((View)targetView.getParent()).getTag(); * @param position row position
* @param id id of the long clicked row
*
* @return true if the event was consumed
*/
public boolean onItemLongClick (AdapterView<?> parent, View view, int position, long id) {
Intent intent = id == LibraryAdapter.HEADER_ID ? createHeaderIntent(view) : mCurrentAdapter.createData(view);
int type = (Integer)((View)view.getParent()).getTag();
if (type == MediaUtils.TYPE_FILE) { if (type == MediaUtils.TYPE_FILE) {
mFilesAdapter.onCreateContextMenu(menu, intent); return mFilesAdapter.onCreateFancyMenu(intent);
} else {
mActivity.onCreateContextMenu(menu, intent);
} }
return mActivity.onCreateFancyMenu(intent);
} }
@Override @Override

View File

@ -0,0 +1,213 @@
/*
* Copyright (C) 2018 Adrian Ulrich <adrian@blinkenlights.ch>
*
* 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 ch.blinkenlights.android.vanilla.ui;
import ch.blinkenlights.android.vanilla.R;
import ch.blinkenlights.android.vanilla.ThemeHelper;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.ImageView;
import java.util.ArrayList;
import android.util.Log;
public class FancyMenu extends DialogFragment
implements DialogInterface.OnClickListener {
/**
* Title to use for this FancyMenu
*/
private String mTitle;
/**
* Context of this instance
*/
private Context mContext;
/**
* Callback to call
*/
private FancyMenu.Callback mCallback;
/**
* List of all items and possible children
*/
private ArrayList<ArrayList<FancyMenuItem>> mItems;
/**
* The built adapter used by the visible dialog
*/
private FancyMenu.Adapter mAdapter;
/**
* The callback interface to implement
*/
public interface Callback {
boolean onFancyItemSelected(FancyMenuItem item);
}
/**
* Create a new FancyMenu instance.
*
* @param context the context to use
*/
public FancyMenu(Context context, FancyMenu.Callback callback) {
mContext = context;
mCallback = callback;
mItems = new ArrayList<ArrayList<FancyMenuItem>>();
}
/**
* Set title of this FancyMenu
*
* @param title the title to set
*/
public void setHeaderTitle(String title) {
mTitle = title;
}
/**
* Adds a new item to the menu
*
* @param id the id which identifies this object.
* @param order how to sort this item
* @param drawable icon drawable to use
* @param textRes id of the text resource to use as label
*/
public FancyMenuItem add(int id, int order, int drawable, int textRes) {
CharSequence text = mContext.getResources().getString(textRes);
return addInternal(id, order, drawable, text, false);
}
/**
* Adds a new item to the menu
*
* @param id the id which identifies this object.
* @param order how to sort this item
* @param drawable icon drawable to use
* @param text string label
*/
public FancyMenuItem add(int id, int order, int drawable, CharSequence text) {
return addInternal(id, order, drawable, text, false);
}
/**
* Adds a new spacer item
*
* @param order how to sort this item
*/
public FancyMenuItem addSpacer(int order) {
return addInternal(0, order, 0, null, true);
}
private FancyMenuItem addInternal(int id, int order, int icon, CharSequence text, boolean spacer) {
FancyMenuItem item = new FancyMenuItem(mContext, id)
.setIcon(icon)
.setTitle(text)
.setIsSpacer(spacer);
while (order >= mItems.size()) {
mItems.add(new ArrayList<FancyMenuItem>());
}
mItems.get(order).add(item);
return item;
}
/**
* Called when this dialog is about to be shown.
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// spacers look awful on holo themes
final boolean usesSpacers = !ThemeHelper.usesHoloTheme();
// The adaper will back this list
mAdapter = new FancyMenu.Adapter(mContext, 0);
for (ArrayList<FancyMenuItem> sub : mItems) {
for (FancyMenuItem item : sub ) {
if (usesSpacers || !item.isSpacer()) {
mAdapter.add(item);
}
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(mTitle)
.setAdapter(mAdapter, this);
return builder.create();
}
/**
* Callback for click events on the displayed dialog
*/
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
FancyMenuItem item = mAdapter.getItem(which);
if (!item.isSpacer()) {
mCallback.onFancyItemSelected(item);
}
}
/**
* Adapter class backing all menu entries
*/
private class Adapter extends ArrayAdapter<FancyMenuItem> {
private LayoutInflater mInflater;
Adapter(Context context, int resource) {
super(context, resource);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int pos, View convertView, ViewGroup parent) {
FancyMenuItem item = (FancyMenuItem)getItem(pos);
int res = (item.isSpacer() ? R.layout.fancymenu_spacer : R.layout.fancymenu_row);
View row = (View)mInflater.inflate(res, parent, false);
TextView text = (TextView)row.findViewById(R.id.text);
ImageView icon = (ImageView)row.findViewById(R.id.icon);
if (item.isSpacer()) {
text.setText(item.getTitle());
} else {
text.setText(item.getTitle());
icon.setImageDrawable(item.getIcon());
}
return row;
}
}
}

View File

@ -0,0 +1,272 @@
/*
* Copyright (C) 2018 Adrian Ulrich <adrian@blinkenlights.ch>
*
* 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 ch.blinkenlights.android.vanilla.ui;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.ActionProvider;
import android.view.SubMenu;
import android.view.View;
public class FancyMenuItem implements MenuItem {
private int mId;
private int mIconRes;
private CharSequence mTitle;
private Intent mIntent;
private boolean mSpacer;
private Drawable mIcon;
private Context mContext;
FancyMenuItem(Context context, int id) {
mContext = context;
mId = id;
}
/**
* Returns whether or not this is a spacer
*
* @return true if this is a spacer
*/
public boolean isSpacer() {
return mSpacer;
}
public FancyMenuItem setIsSpacer(boolean spacer) {
mSpacer = spacer;
return this;
}
@Override
public CharSequence getTitle() {
return mTitle;
}
@Override
public Drawable getIcon() {
if (mIconRes != 0)
return mContext.getResources().getDrawable(mIconRes);
return mIcon;
}
@Override
public FancyMenuItem setIcon(Drawable drawable) {
mIcon = drawable;
return this;
}
@Override
public FancyMenuItem setIcon(int res) {
mIconRes = res;
return this;
}
@Override
public FancyMenuItem setIntent(Intent intent) {
mIntent = intent;
return this;
}
@Override
public FancyMenuItem setTitle(CharSequence title) {
mTitle = title;
return this;
}
@Override
public FancyMenuItem setTitle(int title) {
return setTitle(mContext.getResources().getString(title));
}
@Override
public Intent getIntent() {
return mIntent;
}
@Override
public int getItemId() {
return mId;
}
// Dummy functions to satisfy MenuItem interface.
@Override
public ActionProvider getActionProvider() {
return null;
}
@Override
public CharSequence getTitleCondensed() {
return null;
}
@Override
public ContextMenu.ContextMenuInfo getMenuInfo() {
return null;
}
@Override
public FancyMenuItem setActionProvider(ActionProvider actionProvider) {
return this;
}
@Override
public FancyMenuItem setActionView(int view) {
return this;
}
@Override
public FancyMenuItem setAlphabeticShortcut(char alphaChar) {
return this;
}
@Override
public FancyMenuItem setCheckable(boolean checkable) {
return this;
}
@Override
public FancyMenuItem setChecked(boolean checked) {
return this;
}
@Override
public FancyMenuItem setEnabled(boolean enabled) {
return this;
}
@Override
public FancyMenuItem setNumericShortcut(char numericChar) {
return this;
}
@Override
public FancyMenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
return this;
}
@Override
public FancyMenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener) {
return this;
}
@Override
public FancyMenuItem setShortcut(char numericChar, char alphaChar) {
return this;
}
@Override
public FancyMenuItem setShowAsActionFlags(int actionEnum) {
return this;
}
@Override
public FancyMenuItem setTitleCondensed(CharSequence text) {
return this;
}
@Override
public FancyMenuItem setVisible(boolean visible) {
return this;
}
@Override
public MenuItem setActionView(View view) {
return this;
}
@Override
public SubMenu getSubMenu() {
return null;
}
@Override
public View getActionView() {
return null;
}
@Override
public boolean collapseActionView() {
return false;
}
@Override
public boolean expandActionView() {
return false;
}
@Override
public boolean hasSubMenu() {
return false;
}
@Override
public boolean isActionViewExpanded() {
return false;
}
@Override
public boolean isCheckable() {
return false;
}
@Override
public boolean isChecked() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isVisible() {
return true;
}
@Override
public char getAlphabeticShortcut() {
return 0;
}
@Override
public char getNumericShortcut() {
return 0;
}
@Override
public int getGroupId() {
return 0;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void setShowAsAction(int actionEnum) {
}
}

View File

@ -7,6 +7,7 @@
<dimen name="cover_padding_with_pmark">10sp</dimen> <dimen name="cover_padding_with_pmark">10sp</dimen>
<dimen name="text_padding">14sp</dimen> <dimen name="text_padding">14sp</dimen>
<dimen name="row_normal_height">56sp</dimen> <dimen name="row_normal_height">56sp</dimen>
<dimen name="row_narrow_height">38sp</dimen>
<dimen name="controls_padding">4dip</dimen> <dimen name="controls_padding">4dip</dimen>
<dimen name="track_details_dialog_padding">8dp</dimen> <dimen name="track_details_dialog_padding">8dp</dimen>
</resources> </resources>

View File

@ -68,6 +68,7 @@ THE SOFTWARE.
<string name="rename">Rename</string> <string name="rename">Rename</string>
<string name="save_as_playlist">Save as playlist…</string> <string name="save_as_playlist">Save as playlist…</string>
<string name="add_to_playlist">Add to playlist…</string> <string name="add_to_playlist">Add to playlist…</string>
<string name="add_to_homescreen">Add to homescreen…</string>
<string name="add_to">Add to…</string> <string name="add_to">Add to…</string>
<string name="playlist">Playlist</string> <string name="playlist">Playlist</string>
<string name="homescreen">Homescreen</string> <string name="homescreen">Homescreen</string>