Back to project page AudioBook.
The source code is released under:
Creative Commons Legal Code Attribution-NonCommercial 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT C...
If you think the Android project AudioBook listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/** * This work is licensed under the Creative Commons Attribution-NonCommercial- * NoDerivs 3.0 Unported License. To view a copy of this license, visit * http://creativecommons.org/licenses/by-nc-nd/3.0/ or send a letter to * Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, * 94041, USA.//from www. j av a2s .c o m * * Use of this work is permitted only in accordance with license rights granted. * Materials provided "AS IS"; no representations or warranties provided. * * Copyright ? 2012 Marcus Parkkinen, Aki K?kel?, Fredrik ?hs. **/ package edu.chalmers.dat255.audiobookplayer.view; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import edu.chalmers.dat255.audiobookplayer.R; import edu.chalmers.dat255.audiobookplayer.constants.Constants; import edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfGUIEvents; import edu.chalmers.dat255.audiobookplayer.model.Bookshelf; import edu.chalmers.dat255.audiobookplayer.util.TextFormatter; /** * Graphical representation of the bookshelf. * * @author Marcus Parkkinen, Fredrik ?hs * @version 0.6 */ public class BookshelfFragment extends Fragment { private static final String TAG = "BookshelfFragment.class"; private ExpandableBookshelfAdapter adapter; private IBookshelfGUIEvents fragmentOwner; private ExpandableListView bookshelfList; /** * Simple interface forcing the enum classes to provide a method to get * text. * */ private interface IContextMenuItem { /** * This method should be used to provide the text to be shown by the * menu items. * * @return The text to be displayed. */ String getText(); } /** * Enum providing name and ID for context menu items * */ private enum GroupContextMenuItem implements IContextMenuItem { Delete { public String getText() { return "Delete"; } }, Edit { public String getText() { return "Edit"; } }; } /** * Enum providing name and ID for context menu items * */ private enum ChildContextMenuItem implements IContextMenuItem { Delete { public String getText() { return "Delete"; } }, MoveUp { public String getText() { return "Move Up"; } }, MoveDown { public String getText() { return "Move Down"; } }; } /* * (non-Javadoc) * * @see android.support.v4.app.Fragment#onAttach(android.app.Activity) */ @Override public void onAttach(Activity activity) { super.onAttach(activity); boolean ownerImplementsEvents = true; try { fragmentOwner = (IBookshelfGUIEvents) activity; } catch (ClassCastException e) { ownerImplementsEvents = false; } if (!ownerImplementsEvents) { throw new ClassCastException(activity.toString() + " does not implement " + IBookshelfGUIEvents.class.getName()); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Provide the layout of this fragment to the parent container View view = inflater.inflate(R.layout.fragment_bookshelf, container, false); /**************************************************************/ /**//**/ /* Instantiate member variables */ /**//**/ /**************************************************************/ if (adapter == null) { adapter = new ExpandableBookshelfAdapter(view.getContext(), null); } /**************************************************************/ /**//**/ /* Get button layout, and add a listener method to it */ /**//**/ /**************************************************************/ ImageButton addButton = (ImageButton) view.findViewById(R.id.addBook); // adds a listener to the button addButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { // Get the parent activity // Make sure that the activity is not at the end of its // lifecycle if (getActivity() != null) { // inform mainactivity this button has been pressed addBookButtonPressed(); } } }); /******************************************************************************/ /**//**/ /* Get the list layout, and add listener methods and an adapter to it */ /**//**/ /******************************************************************************/ bookshelfList = (ExpandableListView) view .findViewById(R.id.bookshelfList); /* * hides the by default visible arrow which indicates whether a group is * expanded or not */ bookshelfList.setGroupIndicator(null); // set the bookshelf list as a context menu registerForContextMenu(bookshelfList); // sets the bookshelf lists adapter bookshelfList.setAdapter(adapter); // Access the bookshelf reference if (getArguments().getSerializable(Constants.Reference.BOOKSHELF) instanceof Bookshelf) { bookshelfUpdated((Bookshelf) getArguments().getSerializable( Constants.Reference.BOOKSHELF)); } // return the view when it has been processed. return view; } // these warnings can be suppressed as the type has already been checked @SuppressWarnings("rawtypes") @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // check that the menuInfo is of the correct type // this method is allowed to have quite a high cyclomatic complexity as // it would otherwise cause code duplication if (menuInfo instanceof ExpandableListContextMenuInfo && adapter != null) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo; // get the provided book position int bookIndex = ExpandableListView .getPackedPositionGroup(info.packedPosition); // trackIndex will be -1 if group is clicked int trackIndex = ExpandableListView .getPackedPositionChild(info.packedPosition); // get the type of the context menu int type = ExpandableListView .getPackedPositionType(info.packedPosition); // create an empty array to prevent trying to loop over an // uninitialized variable IContextMenuItem[] menuItems = new IContextMenuItem[0]; String title = ""; // fill the context menu with the correct items if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { // get all menu items from the child context menu menuItems = ChildContextMenuItem.values(); // set the context menu's title to that of the value of the // child title = adapter.getChild(bookIndex, trackIndex); } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { // get all menu items from the group context menu menuItems = GroupContextMenuItem.values(); // set the context menu's title to that of the value of the book title = adapter.getGroup(bookIndex); } // set the title menu.setHeaderTitle(title); // populate the context menu with items in the order they were // declared in the enum declaration. for (IContextMenuItem item : menuItems) { // as this only loops when menuItems is of either of type // GroupContextMenuItem[] or ChildContextMenuItem[], Enum can be // used as a raw type menu.add(Menu.NONE, ((Enum) item).ordinal(), ((Enum) item).ordinal(), item.getText()); } } } /* * (non-Javadoc) * * @see * android.support.v4.app.Fragment#onContextItemSelected(android.view.MenuItem * ) */ @Override public boolean onContextItemSelected(MenuItem item) { if (getActivity() == null) { return false; } // this method is allowed to have quite a high cyclomatic complexity as // it would otherwise cause code duplication ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item .getMenuInfo(); // get the provided book position int bookIndex = ExpandableListView .getPackedPositionGroup(info.packedPosition); // will be -1 if the type is group int trackIndex = ExpandableListView .getPackedPositionChild(info.packedPosition); // get the type of the context menu int type = ExpandableListView .getPackedPositionType(info.packedPosition); // create an empty array to prevent trying to loop over an uninitialized // variable IContextMenuItem[] menuItems = new IContextMenuItem[0]; // fill the array with the correct items if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { menuItems = ChildContextMenuItem.values(); } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { menuItems = GroupContextMenuItem.values(); } // get an item and store it with its dynamic type IContextMenuItem menuItem = menuItems[item.getItemId()]; // if the type of the context menu is group if (menuItem instanceof GroupContextMenuItem) { // perform the correct task executeGroupMenuItem((GroupContextMenuItem) menuItem, bookIndex); } // if the type of the context menu is that of a child else if (menuItem instanceof ChildContextMenuItem) { // perform the correct task executeChildMenuItem((ChildContextMenuItem) menuItem, bookIndex, trackIndex); } return true; } /** * Private method which executes the correct task depending on which menu * item was chosen * * @param menuItem * The menu item * @param bookIndex * The index of the book to execute the task on */ private void executeGroupMenuItem(GroupContextMenuItem menuItem, int bookIndex) { switch (menuItem) { case Delete: removeBook(bookIndex); break; case Edit: // not fully implemented, some method to retrieve a real name should // be used setBookTitleAt(bookIndex, "NEWNAME"); break; default: break; } } /** * Private method which executes the correct task depending on which menu * item was chosen * * @param menuItem * The menu item * @param bookIndex * The index of the book containing the track * @param trackIndex * The index of the track to execute the task on */ private void executeChildMenuItem(ChildContextMenuItem menuItem, int bookIndex, int trackIndex) { switch (menuItem) { case Delete: removeTrack(bookIndex, trackIndex); break; case MoveUp: moveTrack(bookIndex, trackIndex, -1); break; case MoveDown: moveTrack(bookIndex, trackIndex, 1); break; default: break; } } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#moveTrack * (int, int, int) */ public void moveTrack(int bookIndex, int trackIndex, int offset) { fragmentOwner.moveTrack(bookIndex, trackIndex, offset); } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeTrack * (int, int) */ public void removeTrack(int bookIndex, int trackIndex) { fragmentOwner.removeTrack(bookIndex, trackIndex); } /** * @param bookIndex * @param trackIndex */ private void childClicked(int bookIndex, int trackIndex) { if (getActivity() != null) { Log.d(TAG, "Child clicked at + [" + bookIndex + ", " + trackIndex + "]"); setSelectedTrack(bookIndex, trackIndex); } } /** * @param bookIndex */ private void groupClicked(int bookIndex) { if (getActivity() != null) { setSelectedBook(bookIndex); } } /** * Updates the adapter with the updated bookshelf and informs it that its * data has been added. * * @param bs * The updated bookshelf */ public void bookshelfUpdated(Bookshelf bs) { Log.d(TAG, "Book added"); if (adapter != null) { adapter.setBookshelf(bs); adapter.notifyDataSetChanged(); } } /** * @param newTime */ public void selectedBookElapsedTimeUpdated(int newTime) { adapter.selectedBookElapsedTimeUpdated(newTime); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfGUIEvents# * addBookButtonPressed() */ public void addBookButtonPressed() { fragmentOwner.addBookButtonPressed(); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents# * setSelectedBook(int) */ public void setSelectedBook(int bookIndex) { fragmentOwner.setSelectedBook(bookIndex); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents# * setSelectedTrack(int, int) */ public void setSelectedTrack(int bookIndex, int trackIndex) { fragmentOwner.setSelectedTrack(bookIndex, trackIndex); } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeBook * (int) */ public void removeBook(int bookIndex) { fragmentOwner.removeBook(bookIndex); } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeTrack * (int) */ public void removeTrack(int trackIndex) { fragmentOwner.removeTrack(trackIndex); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents# * setBookTitleAt(int, java.lang.String) */ public void setBookTitleAt(int bookIndex, String newTitle) { fragmentOwner.setBookTitleAt(bookIndex, "NEWNAME"); } /** * Private class used to populate the ExpandableListView used in * BookshelfFragment. * * @author Fredrik ?hs * */ private class ExpandableBookshelfAdapter extends BaseExpandableListAdapter { private static final int PROGRESSBAR_MAX = 100; private static final int MILLIS_PER_SECOND = 1000; private Context context; private Bookshelf bookshelf; // used to get synchronized time label and progress bar private int bookElapsedTime; private int bookProgress; private View selectedBookView; /** * Constructs an ExpandableListAdapter with context and listData. * * @param context * The context of the application. * @param listData * The data to be used to fill the list. */ public ExpandableBookshelfAdapter(Context context, Bookshelf bookshelf) { this.context = context; setBookshelf(bookshelf); } /** * Updates the adapters class variables. * * @param newTime * @return */ public void selectedBookElapsedTimeUpdated(int newTime) { // if a second has passed if (newTime / MILLIS_PER_SECOND > bookElapsedTime / MILLIS_PER_SECOND) { // store this new value bookElapsedTime = newTime; // update the label setTextViewText( selectedBookView, R.id.bookshelfBookPosition, "Position: " + TextFormatter .formatTimeFromMillis(bookElapsedTime)); // get duration of book in seconds int bookDuration = bookshelf.getSelectedBookDuration(); // calculate the progress int calculatedProgress = calculateProgress(newTime, bookDuration); // if there is no errors and the progress has progressed since // the last saved progress if ((calculatedProgress >= 0) && (calculatedProgress <= PROGRESSBAR_MAX) && (calculatedProgress > bookProgress)) { // save this new progress bookProgress = calculatedProgress; // get the progressbar ProgressBar pb = (ProgressBar) selectedBookView .findViewById(R.id.bookshelfProgressBar); // check that the progressbar is not null and set it to the // new progress if (pb != null) { pb.setProgress(bookProgress); } } } } /** * Method that calculates a roofed progress, i.e. time = 51, duration = * 1000 would return a progress of 6 * * @param elapsedTime * The time elapsed. * @param duration * The total duration. * @return A roofed progress. */ private int calculateProgress(int elapsedTime, int duration) { if (elapsedTime < 0 || duration <= 0) { return 0; } int floored = PROGRESSBAR_MAX * elapsedTime / duration; int rest = PROGRESSBAR_MAX * elapsedTime % duration; if (rest == 0) { return floored; } return floored + 1; } /** * Set the bookshelf for when the data should update * * @param bookshelf * The new bookshelf copy. */ public final void setBookshelf(Bookshelf bookshelf) { this.bookshelf = bookshelf; } /** * @param bookIndex * The position of the book. * @param trackIndex * The position of the track. * @return The title of the track at given position, null if incorrect * index. */ public String getChild(int bookIndex, int trackIndex) { if (bookshelf == null) { return ""; } return bookshelf.getTrackTitleAt(bookIndex, trackIndex); } /** * @param bookIndex * The position of the book. * @param trackIndex * The position of the track. * @return The id of the track at given position. */ public long getChildId(int bookIndex, int trackIndex) { return trackIndex; } /** * @param bookIndex * The position of the book. * @return The amount of tracks in the book at given position. */ public int getChildrenCount(int bookIndex) { if (bookshelf != null) { return bookshelf.getNumberOfTracksAt(bookIndex); } return 0; } /** * Converts the view of a track to display properly. * * @param bookIndex * The position of the book. * @param trackIndex * The position of the track. * @param isLastChild * Whether the track is the last child in the expandable list * view group. * @param convertView * The view to be converted. * @param parent * The view of the ExpandableListView. * @return The converted view. */ public View getChildView(final int bookIndex, final int trackIndex, boolean isLastChild, View convertView, ViewGroup parent) { View cView = convertView; if (cView == null) { LayoutInflater vi = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // inflate it into parent. cView = vi.inflate(R.layout.bookshelf_child_row, parent, false); } // the expandable list view is stored as final to use it in // listeners final ExpandableListView expandableListView = (ExpandableListView) parent; // set the text of the child setTextViewText(cView, R.id.bookshelfTrackTitle, bookshelf.getTrackTitleAt(bookIndex, trackIndex)); // get the position of the track from the book int duration = bookshelf.getTrackDurationAt(bookIndex, trackIndex); // convert and set the duration of the track setTextViewText(cView, R.id.bookshelfTrackTime, TextFormatter.formatTimeFromMillis(duration)); cView.setOnClickListener(new OnClickListener() { public void onClick(View v) { trackClicked(bookIndex, trackIndex, expandableListView); } }); // set long click to show the child's context menu cView.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { expandableListView.showContextMenu(); return false; } }); return cView; } /** * Private method to be called when a track is clicked * * @param bookIndex * The index of the book the track resides within * @param trackIndex * The index of the track * @param expandableListView * The expandable list view */ private void trackClicked(final int bookIndex, final int trackIndex, final ExpandableListView expandableListView) { // store the index of the book of the track clicked for // correct redrawal // force the expandable list view to redraw expandableListView.invalidateViews(); // inform the bookshelfFragment's listener that a child has // been selected BookshelfFragment.this.childClicked(bookIndex, trackIndex); // store for synchronization selectedBookView = expandableListView.getChildAt(bookIndex); try { bookElapsedTime = bookshelf.getBookElapsedTime(); } catch (IndexOutOfBoundsException e) { bookElapsedTime = 0; } bookProgress = calculateProgress(bookElapsedTime, bookshelf.getSelectedBookDuration()); } /** * @param bookIndex * The position of the book. * @return The books title at given position */ public String getGroup(int bookIndex) { if (bookshelf == null) { return ""; } try { return bookshelf.getBookTitleAt(bookIndex); } catch (IndexOutOfBoundsException e) { return ""; } } /** * @return The number of books. */ public int getGroupCount() { if (bookshelf != null) { return bookshelf.getNumberOfBooks(); } return 0; } /** * @param bookIndex * The position of the book. * @return The id of the book. */ public long getGroupId(int bookIndex) { return bookIndex; } /** * Converts the view of a book to display it properly. * * @param bookIndex * The position of the book. * @param isExpanded * Whether the book is expanded or not. * @param convertView * The book's view. * @param parent * The expandable list view * @return The converted view. */ public View getGroupView(final int bookIndex, final boolean isExpanded, View convertView, ViewGroup parent) { View cView = convertView; if (cView == null) { LayoutInflater vi = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // inflate it into its parent cView = vi.inflate(R.layout.bookshelf_group_row, parent, false); } // set book as final to use in listeners // set the expandableListView as final to use in listeners final ExpandableListView expandableListView = (ExpandableListView) parent; // get duration from book int duration = bookshelf.getBookDurationAt(bookIndex); // prevent problems with a duration of 0 if (duration == 0) { return cView; } // get the elapsed time of the book int time = 0; int progress = 0; // set title, author, time and duration of book setTextViewText(cView, R.id.bookshelfBookTitle, bookshelf.getBookTitleAt(bookIndex)); // set the color of the books title to red if selected, if (bookIndex == bookshelf.getSelectedBookIndex()) { setTextViewTextColor(cView, R.id.bookshelfBookTitle, Color.RED); time = bookElapsedTime; progress = bookProgress; } // and white otherwise. else { setTextViewTextColor(cView, R.id.bookshelfBookTitle, Color.WHITE); try { time = bookshelf.getBookElapsedTime(); } catch (IndexOutOfBoundsException e) { time = 0; } progress = calculateProgress(time, duration); } setTextViewText(cView, R.id.bookshelfAuthor, bookshelf.getBookAuthorAt(bookIndex)); // format the string of the elapsed time of the book String timeString = time == 0 ? "N/A" : TextFormatter .formatTimeFromMillis(time); setTextViewText(cView, R.id.bookshelfBookPosition, "Position: " + timeString); setTextViewText(cView, R.id.bookshelfBookDuration, "Duration: " + TextFormatter.formatTimeFromMillis(duration)); // set the progress of the progress bar ProgressBar progressBar = (ProgressBar) cView .findViewById(R.id.bookshelfProgressBar); // make sure progress is within its boundaries before setting it if (progress >= 0 && progress <= PROGRESSBAR_MAX) { progressBar.setProgress(progress); } // set cover art ImageView imageView = (ImageView) cView .findViewById(R.id.bookshelfBookCover); // in future versions this cover art should be gotten from the file imageView.setImageResource(R.drawable.img_no_cover); // click the cover art to toggle the books tracks visibility imageView.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (isExpanded) { expandableListView.collapseGroup(bookIndex); } else { expandableListView.expandGroup(bookIndex); } } }); // sets the on click listener for the rest of the view // store convertview as final to reach it inside this method final View finalConvertView = cView; cView.setOnClickListener(new OnClickListener() { public void onClick(View v) { bookClicked(bookIndex, isExpanded, expandableListView, finalConvertView); } }); // set long click to show the group's context menu cView.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { expandableListView.showContextMenu(); return false; } }); return cView; } /** * Private method to be called when a book is clicked. * * @param bookIndex * Index of the book clicked * @param isExpanded * Whether or not the book is expanded in the expandable list * view * @param expandableListView * The expandable list view * @param convertView * The view to be set as selected */ private void bookClicked(final int bookIndex, final boolean isExpanded, final ExpandableListView expandableListView, final View convertView) { // expand the selected item if (!isExpanded) { expandableListView.expandGroup(bookIndex); } // scroll to the selected item expandableListView.setSelectionFromTop(bookIndex, 0); // invalidates views to force redraw thus setting the // correct textcolor expandableListView.invalidateViews(); // inform the BookshelfFragment that this button has been // pressed. BookshelfFragment.this.groupClicked(bookIndex); // store the view so that text can update selectedBookView = convertView; try { bookElapsedTime = bookshelf.getBookElapsedTime(); } catch (IndexOutOfBoundsException e) { bookElapsedTime = 0; } bookProgress = calculateProgress(bookElapsedTime, bookshelf.getSelectedBookDuration()); } /** * Private help class used to prevent code duplication * * @param view * The TextView to where the text will be displayed. * @param id * The id of the TextView. * @param text * The text to be displayed. */ private void setTextViewText(View view, int id, String text) { if (view != null) { TextView textView = (TextView) view.findViewById(id); if (textView != null) { textView.setText(text); } } } /** * Private help class used to prevent code duplication. * * @param view * The TextView to where the text color will be set. * @param id * The id of the TextView. * @param color * The color to be set. */ private void setTextViewTextColor(View view, int id, int color) { if (view != null) { TextView textView = (TextView) view.findViewById(id); if (textView != null) { textView.setTextColor(color); } } } /* * (non-Javadoc) * * @see android.widget.ExpandableListAdapter#hasStableIds() */ public boolean hasStableIds() { return true; } /** * @param bookIndex * The position of the book. * @param trackIndex * The position of the track. * @return Whether the track at given position is selectable. */ public boolean isChildSelectable(int bookIndex, int trackIndex) { return true; } } }