diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java index d5bce2c5..3297b5b1 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java @@ -10,10 +10,10 @@ import android.util.SparseArray; import android.view.ViewGroup; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.ui.fragments.libraryfragments.AlbumsPagerFragment; -import com.kabouzeid.gramophone.ui.fragments.libraryfragments.ArtistsPagerFragment; -import com.kabouzeid.gramophone.ui.fragments.libraryfragments.PlaylistsPagerFragment; -import com.kabouzeid.gramophone.ui.fragments.libraryfragments.SongsPagerFragment; +import com.kabouzeid.gramophone.ui.fragments.libraryfragments.AlbumsFragment; +import com.kabouzeid.gramophone.ui.fragments.libraryfragments.ArtistsFragment; +import com.kabouzeid.gramophone.ui.fragments.libraryfragments.PlaylistsFragment; +import com.kabouzeid.gramophone.ui.fragments.libraryfragments.SongsFragment; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -107,10 +107,10 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter { } public enum MusicFragments { - SONG(SongsPagerFragment.class), - ALBUM(AlbumsPagerFragment.class), - ARTIST(ArtistsPagerFragment.class), - PLAYLIST(PlaylistsPagerFragment.class); + SONG(SongsFragment.class), + ALBUM(AlbumsFragment.class), + ARTIST(ArtistsFragment.class), + PLAYLIST(PlaylistsFragment.class); private final Class mFragmentClass; diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/SongFileAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/SongFileAdapter.java new file mode 100644 index 00000000..dd220050 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/SongFileAdapter.java @@ -0,0 +1,211 @@ +package com.kabouzeid.gramophone.adapter; + +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.signature.MediaStoreSignature; +import com.kabouzeid.appthemehelper.util.ATHUtil; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; +import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; +import com.kabouzeid.gramophone.glide.audiocover.AudioFileCover; +import com.kabouzeid.gramophone.interfaces.CabHolder; +import com.kabouzeid.gramophone.util.Util; + +import java.io.File; +import java.text.DecimalFormat; +import java.util.ArrayList; + +public class SongFileAdapter extends AbsMultiSelectAdapter { + + private static final int FILE = 0; + private static final int FOLDER = 1; + + private final AppCompatActivity activity; + private ArrayList dataSet; + private final int itemLayoutRes; + @Nullable + private final Callbacks callbacks; + @Nullable + private final CabHolder cabHolder; + + public SongFileAdapter(@NonNull AppCompatActivity activity, @NonNull ArrayList dataSet, @LayoutRes int itemLayoutRes, @Nullable Callbacks callback, @Nullable CabHolder cabHolder) { + super(activity, cabHolder, R.menu.menu_media_selection); + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + this.callbacks = callback; + this.cabHolder = cabHolder; + setHasStableIds(true); + } + + @Override + public int getItemViewType(int position) { + return dataSet.get(position).isDirectory() ? FOLDER : FILE; + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).hashCode(); + } + + public void swapDataSet(@NonNull ArrayList files) { + this.dataSet = files; + notifyDataSetChanged(); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int index) { + final File file = dataSet.get(index); + + holder.itemView.setActivated(isChecked(file)); + + if (holder.getAdapterPosition() == getItemCount() - 1) { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + + if (holder.title != null) { + holder.title.setText(getFileTitle(file)); + } + if (holder.text != null) { + if (holder.getItemViewType() == FILE) { + holder.text.setText(getFileText(file)); + } else { + holder.text.setVisibility(View.GONE); + } + } + + if (holder.image != null) { + loadFileImage(file, holder); + } + } + + protected String getFileTitle(File file) { + return file.getName(); + } + + protected String getFileText(File file) { + return file.isDirectory() ? null : readableFileSize(file.length()); + } + + @SuppressWarnings("ConstantConditions") + protected void loadFileImage(File file, final ViewHolder holder) { + final int iconColor = ATHUtil.resolveColor(activity, R.attr.iconColor); + if (file.isDirectory()) { + holder.image.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); + holder.image.setImageResource(R.drawable.ic_folder_white_24dp); + } else { + Drawable error = Util.getTintedDrawable(activity, R.drawable.ic_music_note_white_24dp, iconColor); + Glide.with(activity) + .load(new AudioFileCover(file.getPath())) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .error(error) + .placeholder(error) + .animate(android.R.anim.fade_in) + .signature(new MediaStoreSignature("", file.lastModified(), 0)) + .into(holder.image); + } + } + + public static String readableFileSize(long size) { + if (size <= 0) return size + " B"; + final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + protected File getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(File object) { + return getFileTitle(object); + } + + @Override + protected void onMultipleItemAction(MenuItem menuItem, ArrayList selection) { + if (callbacks == null) return; + switch (menuItem.getItemId()) { + case R.id.action_add_to_current_playing: + callbacks.onAddToCurrentPlaying(selection); + break; + case R.id.action_add_to_playlist: + callbacks.onAddToPlaylist(selection); + break; + case R.id.action_delete_from_device: + callbacks.onDeleteFromDevice(selection); + break; + } + } + + public class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(View itemView) { + super(itemView); + if (menu != null && callbacks != null) { + menu.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + callbacks.onFileMenuClicked(dataSet.get(getAdapterPosition())); + } + }); + } + } + + @Override + public void onClick(View v) { + if (isInQuickSelectMode()) { + toggleChecked(getAdapterPosition()); + } else { + if (callbacks != null) { + callbacks.onFileSelected(dataSet.get(getAdapterPosition())); + } + } + } + + @Override + public boolean onLongClick(View view) { + return toggleChecked(getAdapterPosition()); + } + } + + public interface Callbacks { + void onFileSelected(File file); + + void onFileMenuClicked(File file); + + void onAddToPlaylist(ArrayList files); + + void onAddToCurrentPlaying(ArrayList files); + + void onDeleteFromDevice(ArrayList files); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java index a086b12d..00dcd89e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java @@ -168,7 +168,7 @@ public class AlbumAdapter extends AbsMultiSelectAdapter selection) { switch (menuItem.getItemId()) { - case R.id.action_delete_from_disk: + case R.id.action_delete_from_device: DeleteSongsDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); break; case R.id.action_add_to_playlist: diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java index 866c753d..f91641e7 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java @@ -173,7 +173,7 @@ public class ArtistAdapter extends AbsMultiSelectAdapter selection) { switch (menuItem.getItemId()) { - case R.id.action_delete_from_disk: + case R.id.action_delete_from_device: DeleteSongsDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); break; case R.id.action_add_to_playlist: diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java index f35df230..eeb8bf5c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java @@ -136,7 +136,7 @@ public class ArtistSongAdapter extends ArrayAdapter implements MaterialCab private void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList selection) { switch (menuItem.getItemId()) { - case R.id.action_delete_from_disk: + case R.id.action_delete_from_device: DeleteSongsDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); break; case R.id.action_add_to_playlist: diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java index a1f07c9a..1bac7536 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java @@ -172,7 +172,7 @@ public class SongAdapter extends AbsMultiSelectAdapter selection) { switch (menuItem.getItemId()) { - case R.id.action_delete_from_disk: + case R.id.action_delete_from_device: DeleteSongsDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); break; case R.id.action_add_to_playlist: diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java index e9f8d524..b7bdc9e0 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java @@ -33,7 +33,7 @@ public class SongMenuHelper { case R.id.action_share: activity.startActivity(Intent.createChooser(MusicUtil.createShareSongFileIntent(song), null)); return true; - case R.id.action_delete_from_disk: + case R.id.action_delete_from_device: DeleteSongsDialog.create(song).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); return true; case R.id.action_add_to_playlist: diff --git a/app/src/main/java/com/kabouzeid/gramophone/interfaces/CabHolder.java b/app/src/main/java/com/kabouzeid/gramophone/interfaces/CabHolder.java index cf836d88..7818c234 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/interfaces/CabHolder.java +++ b/app/src/main/java/com/kabouzeid/gramophone/interfaces/CabHolder.java @@ -1,5 +1,7 @@ package com.kabouzeid.gramophone.interfaces; +import android.support.annotation.NonNull; + import com.afollestad.materialcab.MaterialCab; /** @@ -7,5 +9,6 @@ import com.afollestad.materialcab.MaterialCab; */ public interface CabHolder { + @NonNull MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java index cd20f547..617392b6 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java @@ -83,12 +83,12 @@ public class SongLoader { } @Nullable - public static Cursor makeSongCursor(@NonNull final Context context, final String selection, final String[] values) { - return makeSongCursor(context, selection, values, PreferenceUtil.getInstance(context).getSongSortOrder()); + public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] selectionValues) { + return makeSongCursor(context, selection, selectionValues, PreferenceUtil.getInstance(context).getSongSortOrder()); } @Nullable - public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] values, final String sortOrder) { + public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] selectionValues, final String sortOrder) { String baseSelection = BASE_SELECTION; if (selection != null && !selection.trim().equals("")) { baseSelection += " AND " + selection; @@ -109,7 +109,7 @@ public class SongLoader { AudioColumns.ARTIST_ID,// 9 AudioColumns.ARTIST,// 10 - }, baseSelection, values, sortOrder); + }, baseSelection, selectionValues, sortOrder); } catch (SecurityException e) { return null; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/SortedCursor.java b/app/src/main/java/com/kabouzeid/gramophone/loader/SortedCursor.java index e09c928a..a501848b 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SortedCursor.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SortedCursor.java @@ -23,7 +23,8 @@ import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.List; + +import hugo.weaving.DebugLog; /** * This cursor basically wraps a song cursor and is given a list of the order of the ids of the @@ -36,97 +37,77 @@ public class SortedCursor extends AbstractCursor { // the map of external indices to internal indices private ArrayList mOrderedPositions; // this contains the ids that weren't found in the underlying cursor - private ArrayList mMissingIds; + private ArrayList mMissingValues; // this contains the mapped cursor positions and afterwards the extra ids that weren't found - private HashMap mMapCursorPositions; - // extra we want to store with the cursor - private ArrayList mExtraData; + private HashMap mMapCursorPositions; /** * @param cursor to wrap * @param order the list of unique ids in sorted order to display * @param columnName the column name of the id to look up in the internal cursor */ - public SortedCursor(final Cursor cursor, final long[] order, final String columnName, - final List extraData) { - if (cursor == null) { - throw new IllegalArgumentException("Non-null cursor is needed"); - } - + @DebugLog + public SortedCursor(@NonNull final Cursor cursor, @Nullable final String[] order, final String columnName) { mCursor = cursor; - mMissingIds = buildCursorPositionMapping(order, columnName, extraData); + mMissingValues = buildCursorPositionMapping(order, columnName); } /** * This function populates mOrderedPositions with the cursor positions in the order based * on the order passed in * - * @param order the target order of the internal cursor - * @param extraData Extra data we want to add to the cursor + * @param order the target order of the internal cursor * @return returns the ids that aren't found in the underlying cursor */ @NonNull - private ArrayList buildCursorPositionMapping(@Nullable final long[] order, - final String columnName, @Nullable final List extraData) { - ArrayList missingIds = new ArrayList<>(); + private ArrayList buildCursorPositionMapping(@Nullable final String[] order, final String columnName) { + ArrayList missingValues = new ArrayList<>(); mOrderedPositions = new ArrayList<>(mCursor.getCount()); - mExtraData = new ArrayList<>(); mMapCursorPositions = new HashMap<>(mCursor.getCount()); - final int idPosition = mCursor.getColumnIndex(columnName); + final int valueColumnIndex = mCursor.getColumnIndex(columnName); if (mCursor.moveToFirst()) { // first figure out where each of the ids are in the cursor do { - mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition()); + mMapCursorPositions.put(mCursor.getString(valueColumnIndex), mCursor.getPosition()); } while (mCursor.moveToNext()); - // now create the ordered positions to map to the internal cursor given the - // external sort order - for (int i = 0; order != null && i < order.length; i++) { - final long id = order[i]; - if (mMapCursorPositions.containsKey(id)) { - mOrderedPositions.add(mMapCursorPositions.get(id)); - mMapCursorPositions.remove(id); - if (extraData != null) { - mExtraData.add(extraData.get(i)); + if (order != null) { + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (final String value : order) { + if (mMapCursorPositions.containsKey(value)) { + mOrderedPositions.add(mMapCursorPositions.get(value)); + mMapCursorPositions.remove(value); + } else { + missingValues.add(value); } - } else { - missingIds.add(id); } } mCursor.moveToFirst(); } - return missingIds; + return missingValues; } /** * @return the list of ids that weren't found in the underlying cursor */ - public ArrayList getMissingIds() { - return mMissingIds; + public ArrayList getMissingValues() { + return mMissingValues; } /** * @return the list of ids that were in the underlying cursor but not part of the ordered list */ @NonNull - public Collection getExtraIds() { + public Collection getExtraValues() { return mMapCursorPositions.keySet(); } - /** - * @return the extra object data that was passed in to be attached to the current row - */ - @Nullable - public Object getExtraData() { - int position = getPosition(); - return position < mExtraData.size() ? mExtraData.get(position) : null; - } - @Override public void close() { mCursor.close(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/SortedLongCursor.java b/app/src/main/java/com/kabouzeid/gramophone/loader/SortedLongCursor.java new file mode 100644 index 00000000..60336f22 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SortedLongCursor.java @@ -0,0 +1,169 @@ +/* +* Copyright (C) 2014 The CyanogenMod 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 com.kabouzeid.gramophone.loader; + +import android.database.AbstractCursor; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +/** + * This cursor basically wraps a song cursor and is given a list of the order of the ids of the + * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted + * by moving the point to the appropriate spot + */ +public class SortedLongCursor extends AbstractCursor { + // cursor to wrap + private final Cursor mCursor; + // the map of external indices to internal indices + private ArrayList mOrderedPositions; + // this contains the ids that weren't found in the underlying cursor + private ArrayList mMissingIds; + // this contains the mapped cursor positions and afterwards the extra ids that weren't found + private HashMap mMapCursorPositions; + + /** + * @param cursor to wrap + * @param order the list of unique ids in sorted order to display + * @param columnName the column name of the id to look up in the internal cursor + */ + public SortedLongCursor(final Cursor cursor, final long[] order, final String columnName) { + + mCursor = cursor; + mMissingIds = buildCursorPositionMapping(order, columnName); + } + + /** + * This function populates mOrderedPositions with the cursor positions in the order based + * on the order passed in + * + * @param order the target order of the internal cursor + * @return returns the ids that aren't found in the underlying cursor + */ + @NonNull + private ArrayList buildCursorPositionMapping(@Nullable final long[] order, final String columnName) { + ArrayList missingIds = new ArrayList<>(); + + mOrderedPositions = new ArrayList<>(mCursor.getCount()); + + mMapCursorPositions = new HashMap<>(mCursor.getCount()); + final int idPosition = mCursor.getColumnIndex(columnName); + + if (mCursor.moveToFirst()) { + // first figure out where each of the ids are in the cursor + do { + mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition()); + } while (mCursor.moveToNext()); + + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (int i = 0; order != null && i < order.length; i++) { + final long id = order[i]; + if (mMapCursorPositions.containsKey(id)) { + mOrderedPositions.add(mMapCursorPositions.get(id)); + mMapCursorPositions.remove(id); + } else { + missingIds.add(id); + } + } + + mCursor.moveToFirst(); + } + + return missingIds; + } + + /** + * @return the list of ids that weren't found in the underlying cursor + */ + public ArrayList getMissingIds() { + return mMissingIds; + } + + /** + * @return the list of ids that were in the underlying cursor but not part of the ordered list + */ + @NonNull + public Collection getExtraIds() { + return mMapCursorPositions.keySet(); + } + + @Override + public void close() { + mCursor.close(); + + super.close(); + } + + @Override + public int getCount() { + return mOrderedPositions.size(); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (newPosition >= 0 && newPosition < getCount()) { + mCursor.moveToPosition(mOrderedPositions.get(newPosition)); + return true; + } + + return false; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java index 0bc24f9f..1c98eff7 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/TopAndRecentlyPlayedTracksLoader.java @@ -43,7 +43,7 @@ public class TopAndRecentlyPlayedTracksLoader { @Nullable public static Cursor makeRecentTracksCursorAndClearUpDatabase(@NonNull final Context context) { - SortedCursor retCursor = makeRecentTracksCursorImpl(context); + SortedLongCursor retCursor = makeRecentTracksCursorImpl(context); // clean up the databases with any ids not found if (retCursor != null) { @@ -59,7 +59,7 @@ public class TopAndRecentlyPlayedTracksLoader { @Nullable public static Cursor makeTopTracksCursorAndClearUpDatabase(@NonNull final Context context) { - SortedCursor retCursor = makeTopTracksCursorImpl(context); + SortedLongCursor retCursor = makeTopTracksCursorImpl(context); // clean up the databases with any ids not found if (retCursor != null) { @@ -74,7 +74,7 @@ public class TopAndRecentlyPlayedTracksLoader { } @Nullable - private static SortedCursor makeRecentTracksCursorImpl(@NonNull final Context context) { + private static SortedLongCursor makeRecentTracksCursorImpl(@NonNull final Context context) { // first get the top results ids from the internal database Cursor songs = HistoryStore.getInstance(context).queryRecentIds(); @@ -89,7 +89,7 @@ public class TopAndRecentlyPlayedTracksLoader { } @Nullable - private static SortedCursor makeTopTracksCursorImpl(@NonNull final Context context) { + private static SortedLongCursor makeTopTracksCursorImpl(@NonNull final Context context) { // first get the top results ids from the internal database Cursor songs = SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS); @@ -104,8 +104,7 @@ public class TopAndRecentlyPlayedTracksLoader { } @Nullable - private static SortedCursor makeSortedCursor(@NonNull final Context context, @Nullable final Cursor cursor, - final int idColumn) { + private static SortedLongCursor makeSortedCursor(@NonNull final Context context, @Nullable final Cursor cursor, final int idColumn) { if (cursor != null && cursor.moveToFirst()) { // create the list of ids to select against StringBuilder selection = new StringBuilder(); @@ -133,7 +132,7 @@ public class TopAndRecentlyPlayedTracksLoader { Cursor songCursor = SongLoader.makeSongCursor(context, selection.toString(), null); if (songCursor != null) { // now return the wrapped TopTracksCursor to handle sorting given order - return new SortedCursor(songCursor, order, BaseColumns._ID, null); + return new SortedLongCursor(songCursor, order, BaseColumns._ID); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index 8d0f3b31..514ed8a1 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -66,6 +66,7 @@ public class MainActivity extends AbsSlidingMusicPanelActivity @Bind(R.id.drawer_layout) DrawerLayout drawerLayout; + @Nullable MainActivityFragmentCallbacks currentFragment; @Nullable @@ -88,7 +89,11 @@ public class MainActivity extends AbsSlidingMusicPanelActivity setUpDrawerLayout(); - setMusicChooser(PreferenceUtil.getInstance(this).getLastMusicChooser()); + if (savedInstanceState == null) { + setMusicChooser(PreferenceUtil.getInstance(this).getLastMusicChooser()); + } else { + restoreCurrentFragment(); + } if (!checkShowIntro()) { checkShowChangelog(); @@ -100,20 +105,24 @@ public class MainActivity extends AbsSlidingMusicPanelActivity switch (key) { case LIBRARY: navigationView.setCheckedItem(R.id.nav_library); - setCurrentFragment(new LibraryFragment()); + setCurrentFragment(LibraryFragment.newInstance()); break; case FOLDERS: navigationView.setCheckedItem(R.id.nav_folders); - setCurrentFragment(new FolderFragment()); + setCurrentFragment(FolderFragment.newInstance()); break; } } - private void setCurrentFragment(Fragment fragment) { + private void setCurrentFragment(@SuppressWarnings("NullableProblems") Fragment fragment) { getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, LibraryFragment.TAG).commit(); currentFragment = (MainActivityFragmentCallbacks) fragment; } + private void restoreCurrentFragment() { + currentFragment = (MainActivityFragmentCallbacks) getSupportFragmentManager().findFragmentById(R.id.fragment_container); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -254,12 +263,13 @@ public class MainActivity extends AbsSlidingMusicPanelActivity return super.onOptionsItemSelected(item); } - @Override - public void onBackPressed() { - if (drawerLayout.isDrawerOpen(navigationView)) drawerLayout.closeDrawers(); - else if (currentFragment == null || !currentFragment.onBackPressed()) - super.onBackPressed(); + public boolean handleBackPress() { + if (drawerLayout.isDrawerOpen(navigationView)) { + drawerLayout.closeDrawers(); + return true; + } + return super.handleBackPress() || (currentFragment != null && currentFragment.handleBackPress()); } private void handlePlaybackIntent(@Nullable Intent intent) { @@ -391,6 +401,6 @@ public class MainActivity extends AbsSlidingMusicPanelActivity } public interface MainActivityFragmentCallbacks { - boolean onBackPressed(); + boolean handleBackPress(); } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsSlidingMusicPanelActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsSlidingMusicPanelActivity.java index f0c75581..bac5a1e2 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsSlidingMusicPanelActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsSlidingMusicPanelActivity.java @@ -173,13 +173,18 @@ public abstract class AbsSlidingMusicPanelActivity extends AbsMusicServiceActivi @Override public void onBackPressed() { + if (!handleBackPress()) + super.onBackPressed(); + } + + public boolean handleBackPress() { if (slidingUpPanelLayout.getPanelHeight() != 0 && playerFragment.onBackPressed()) - return; + return true; if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) { collapsePanel(); - return; + return true; } - super.onBackPressed(); + return false; } @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/FolderFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/FolderFragment.java index e85b5bf4..e9d1b223 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/FolderFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/FolderFragment.java @@ -1,24 +1,141 @@ package com.kabouzeid.gramophone.ui.fragments; +import android.content.Context; +import android.database.Cursor; import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; +import com.afollestad.materialcab.MaterialCab; +import com.kabouzeid.appthemehelper.ThemeStore; +import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.adapter.SongFileAdapter; +import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; +import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; +import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.interfaces.CabHolder; +import com.kabouzeid.gramophone.loader.SongLoader; +import com.kabouzeid.gramophone.loader.SortedCursor; +import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.ui.activities.MainActivity; +import com.kabouzeid.gramophone.util.PhonographColorUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; +import com.kabouzeid.gramophone.util.ViewUtil; +import com.kabouzeid.gramophone.views.BreadCrumbLayout; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import butterknife.Bind; import butterknife.ButterKnife; +import hugo.weaving.DebugLog; -public class FolderFragment extends AbsMainActivityFragment implements MainActivity.MainActivityFragmentCallbacks { +public class FolderFragment extends AbsMainActivityFragment implements MainActivity.MainActivityFragmentCallbacks, CabHolder, BreadCrumbLayout.SelectionCallback, SongFileAdapter.Callbacks, AppBarLayout.OnOffsetChangedListener { public static final String TAG = FolderFragment.class.getSimpleName(); + protected static final String PATH = "path"; + protected static final String CRUMBS = "crumbs"; + + @Bind(R.id.container) + View container; + @Bind(android.R.id.empty) + View empty; + @Bind(R.id.toolbar) + Toolbar toolbar; + @Bind(R.id.bread_crumbs) + BreadCrumbLayout breadCrumbs; + @Bind(R.id.appbar) + AppBarLayout appbar; + @Bind(R.id.recycler_view) + FastScrollRecyclerView recyclerView; + + private SongFileAdapter adapter; + private MaterialCab cab; + public FolderFragment() { } + public static FolderFragment newInstance() { + return newInstance(getDefaultStartFolder()); + } + + public static FolderFragment newInstance(File directory) { + FolderFragment frag = new FolderFragment(); + Bundle b = new Bundle(); + b.putSerializable(PATH, directory); + frag.setArguments(b); + return frag; + } + + public void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { + saveScrollPosition(); + updateAdapter(crumb.getFile()); + recyclerView.getLayoutManager().scrollToPosition(crumb.getScrollPosition()); + breadCrumbs.setActiveOrAdd(crumb, false); + if (addToHistory) + breadCrumbs.addHistory(crumb); + } + + private void saveScrollPosition() { + if (breadCrumbs.size() > 0) { + BreadCrumbLayout.Crumb crumb = breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()); + crumb.setScrollPosition(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); + } + + private void updateAdapter(File directory) { + ArrayList files = loadFiles(directory); + if (adapter == null) { + adapter = new SongFileAdapter(getMainActivity(), files, R.layout.item_list, this, this); + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + } + }); + recyclerView.setAdapter(adapter); + checkIsEmpty(); + } else { + adapter.swapDataSet(files); + } + } + + @NonNull + private ArrayList loadFiles(File directory) { + ArrayList fileList = new ArrayList<>(); + File[] files = directory.listFiles(); + if (files != null) { + Collections.addAll(fileList, files); + } + return fileList; + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -30,16 +147,249 @@ public class FolderFragment extends AbsMainActivityFragment implements MainActiv @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { PreferenceUtil.getInstance(getActivity()).setLastPage(-2); + + getMainActivity().setStatusbarColorAuto(); + getMainActivity().setNavigationbarColorAuto(); + getMainActivity().setTaskDescriptionColorAuto(); + + setUpAppbarColor(); + setUpToolbar(); + setUpBreadCrumbs(); + setUpRecyclerView(); + + if (savedInstanceState == null) { + setCrumb(new BreadCrumbLayout.Crumb((File) getArguments().getSerializable(PATH)), true); + } else { + breadCrumbs.restoreFromStateWrapper((BreadCrumbLayout.SavedStateWrapper) savedInstanceState.getParcelable(CRUMBS)); + setCrumb(breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()), true); + } + } + + private void setUpAppbarColor() { + int primaryColor = ThemeStore.primaryColor(getActivity()); + appbar.setBackgroundColor(primaryColor); + toolbar.setBackgroundColor(primaryColor); + breadCrumbs.setBackgroundColor(primaryColor); + breadCrumbs.setActivatedContentColor(ToolbarContentTintHelper.toolbarTitleColor(getActivity(), primaryColor)); + breadCrumbs.setDeactivatedContentColor(ToolbarContentTintHelper.toolbarSubtitleColor(getActivity(), primaryColor)); + } + + private void setUpToolbar() { + toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp); + getActivity().setTitle(R.string.app_name); + getMainActivity().setSupportActionBar(toolbar); + } + + private void setUpBreadCrumbs() { + breadCrumbs.setCallback(this); + } + + private void setUpRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), recyclerView, ThemeStore.accentColor(getActivity())); + + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + appbar.addOnOffsetChangedListener(this); + } + + @Override + public void onPause() { + super.onPause(); + saveScrollPosition(); } @Override public void onDestroyView() { + appbar.removeOnOffsetChangedListener(this); ButterKnife.unbind(this); super.onDestroyView(); } @Override - public boolean onBackPressed() { + public boolean handleBackPress() { + if (cab != null && cab.isActive()) { + cab.finish(); + return true; + } + if (breadCrumbs.popHistory()) { + setCrumb(breadCrumbs.lastHistory(), false); + return true; + } return false; } + + @NonNull + @Override + public MaterialCab openCab(int menuRes, MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) cab.finish(); + cab = new MaterialCab(getMainActivity(), R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor(PhonographColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(getActivity()))) + .start(callback); + return cab; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.menu_folders, menu); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_go_to_standard_folder: + setCrumb(new BreadCrumbLayout.Crumb(getDefaultStartFolder()), true); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { + setCrumb(crumb, true); + } + + public static File getDefaultStartFolder() { + File externalStorageDir = Environment.getExternalStorageDirectory(); + File musicFolder = new File(externalStorageDir, "Music"); + File startFolder; + if (musicFolder.exists() && musicFolder.isDirectory()) { + startFolder = musicFolder; + } else if (externalStorageDir.exists() && externalStorageDir.isDirectory()) { + startFolder = externalStorageDir; + } else { + startFolder = new File("/"); // root + } + return startFolder; + } + + @Override + public void onFileSelected(File file) { + if (file.isDirectory()) { + setCrumb(new BreadCrumbLayout.Crumb(file), true); + } else { + ArrayList songs = getAllSongs(file.getParentFile(), new FileFilter() { + @Override + public boolean accept(File pathname) { + return !pathname.isDirectory(); + } + }); + + int startIndex = -1; + for (int i = 0; i < songs.size(); i++) { + if (file.getPath().equals(songs.get(i).data)) { + startIndex = i; + break; + } + } + if (startIndex > -1) { + MusicPlayerRemote.openQueue(songs, startIndex, true); + } else { + Toast.makeText(getActivity(), "Could not play selected file.", Toast.LENGTH_SHORT).show(); // TODO replace with proper text. + } + } + } + + @Nullable + private static SortedCursor makeSongCursor(@NonNull final Context context, @Nullable final List files) { + String selection = null; + String[] values = null; + + if (files != null && files.size() > 0 && files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle. + selection = AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")"; + + values = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + values[i] = files.get(i).getPath(); + } + } + + Cursor songCursor = SongLoader.makeSongCursor(context, selection, values); + + return songCursor == null ? null : new SortedCursor(songCursor, values, AudioColumns.DATA); + } + + private static String makePlaceholders(int len) { + StringBuilder sb = new StringBuilder(len * 2 - 1); + sb.append("?"); + for (int i = 1; i < len; i++) { + sb.append(",?"); + } + return sb.toString(); + } + + @Override + public void onFileMenuClicked(File file) { + + } + + @Override + public void onAddToPlaylist(ArrayList files) { + AddToPlaylistDialog.create(getAllSongs(files, null)).show(getFragmentManager(), "ADD_PLAYLIST"); + } + + @Override + public void onAddToCurrentPlaying(ArrayList files) { + MusicPlayerRemote.enqueue(getAllSongs(files, null)); + } + + @Override + public void onDeleteFromDevice(ArrayList files) { + DeleteSongsDialog.create(getAllSongs(files, null)).show(getFragmentManager(), "DELETE_SONGS"); + } + + private ArrayList getAllSongs(File dir, @Nullable FileFilter fileFilter) { + ArrayList songs = new ArrayList<>(); + + ArrayList files = new ArrayList<>(); + File[] fileArray = dir.listFiles(fileFilter); + if (fileArray != null) Collections.addAll(files, fileArray); + Iterator iterator = files.iterator(); + while (iterator.hasNext()) { + File file = iterator.next(); + if (file.isDirectory()) { + songs.addAll(getAllSongs(file, fileFilter)); + iterator.remove(); + } + } + + SortedCursor cursor = makeSongCursor(getActivity(), files); + songs.addAll(SongLoader.getSongs(cursor)); + + return songs; + } + + @DebugLog + private ArrayList getAllSongs(List files, @Nullable FileFilter fileFilter) { + ArrayList songs = new ArrayList<>(); + + Iterator iterator = files.iterator(); + while (iterator.hasNext()) { + File file = iterator.next(); + if (fileFilter != null && !fileFilter.accept(file)) { + iterator.remove(); + } else if (file.isDirectory()) { + iterator.remove(); + songs.addAll(getAllSongs(file, fileFilter)); + } + } + + SortedCursor cursor = makeSongCursor(getActivity(), files); + songs.addAll(SongLoader.getSongs(cursor)); + + return songs; + } + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), container.getPaddingRight(), appbar.getTotalScrollRange() + verticalOffset); + } + + private void checkIsEmpty() { + if (empty != null) { + empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/LibraryFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/LibraryFragment.java index 4d0e5208..fc0abb84 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/LibraryFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/LibraryFragment.java @@ -31,7 +31,7 @@ import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.loader.SongLoader; import com.kabouzeid.gramophone.ui.activities.MainActivity; import com.kabouzeid.gramophone.ui.activities.SearchActivity; -import com.kabouzeid.gramophone.ui.fragments.libraryfragments.AbsLibraryRecyclerViewCustomGridSizePagerFragment; +import com.kabouzeid.gramophone.ui.fragments.libraryfragments.AbsLibraryPagerRecyclerViewCustomGridSizeFragment; import com.kabouzeid.gramophone.util.NavigationUtil; import com.kabouzeid.gramophone.util.PhonographColorUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; @@ -55,6 +55,10 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde private MusicLibraryPagerAdapter pagerAdapter; private MaterialCab cab; + public static LibraryFragment newInstance() { + return new LibraryFragment(); + } + public LibraryFragment() { } @@ -87,7 +91,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde appbar.setBackgroundColor(primaryColor); toolbar.setBackgroundColor(primaryColor); toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp); - getActivity().setTitle(getResources().getString(R.string.app_name)); + getActivity().setTitle(R.string.app_name); getMainActivity().setSupportActionBar(toolbar); } @@ -120,11 +124,12 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde return pager.getCurrentItem() == MusicLibraryPagerAdapter.MusicFragments.PLAYLIST.ordinal(); } + @NonNull @Override - public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { + public MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback) { if (cab != null && cab.isActive()) cab.finish(); cab = new MaterialCab(getMainActivity(), R.id.cab_stub) - .setMenu(menu) + .setMenu(menuRes) .setCloseDrawableRes(R.drawable.ic_close_white_24dp) .setBackgroundColor(PhonographColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(getActivity()))) .start(callback); @@ -151,8 +156,8 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde menu.add(0, R.id.action_new_playlist, 0, R.string.new_playlist_title); } Fragment currentFragment = getCurrentFragment(); - if (currentFragment instanceof AbsLibraryRecyclerViewCustomGridSizePagerFragment && currentFragment.isAdded()) { - AbsLibraryRecyclerViewCustomGridSizePagerFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryRecyclerViewCustomGridSizePagerFragment) currentFragment; + if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment && currentFragment.isAdded()) { + AbsLibraryPagerRecyclerViewCustomGridSizeFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment; MenuItem gridSizeItem = menu.findItem(R.id.action_grid_size); if (Util.isLandscape(getResources())) { @@ -171,8 +176,8 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { Fragment currentFragment = getCurrentFragment(); - if (currentFragment instanceof AbsLibraryRecyclerViewCustomGridSizePagerFragment) { - AbsLibraryRecyclerViewCustomGridSizePagerFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryRecyclerViewCustomGridSizePagerFragment) currentFragment; + if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment) { + AbsLibraryPagerRecyclerViewCustomGridSizeFragment absLibraryRecyclerViewCustomGridSizeFragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment; if (item.getItemId() == R.id.action_colored_footers) { item.setChecked(!item.isChecked()); absLibraryRecyclerViewCustomGridSizeFragment.setAndSaveUsePalette(item.isChecked()); @@ -204,7 +209,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde return super.onOptionsItemSelected(item); } - private void setUpGridSizeMenu(@NonNull AbsLibraryRecyclerViewCustomGridSizePagerFragment fragment, @NonNull SubMenu gridSizeMenu) { + private void setUpGridSizeMenu(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull SubMenu gridSizeMenu) { switch (fragment.getGridSize()) { case 1: gridSizeMenu.findItem(R.id.action_grid_size_1).setChecked(true); @@ -252,7 +257,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde } } - private boolean handleGridSizeMenuItem(@NonNull AbsLibraryRecyclerViewCustomGridSizePagerFragment fragment, @NonNull MenuItem item) { + private boolean handleGridSizeMenuItem(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) { int gridSize = 0; switch (item.getItemId()) { case R.id.action_grid_size_1: @@ -290,7 +295,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde } @Override - public boolean onBackPressed() { + public boolean handleBackPress() { if (cab != null && cab.isActive()) { cab.finish(); return true; diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewCustomGridSizePagerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java similarity index 96% rename from app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewCustomGridSizePagerFragment.java rename to app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java index 200e5d6e..dd23ae03 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewCustomGridSizePagerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java @@ -9,7 +9,7 @@ import com.kabouzeid.gramophone.util.Util; /** * @author Karim Abou Zeid (kabouzeid) */ -public abstract class AbsLibraryRecyclerViewCustomGridSizePagerFragment extends AbsLibraryRecyclerViewPagerFragment { +public abstract class AbsLibraryPagerRecyclerViewCustomGridSizeFragment extends AbsLibraryPagerRecyclerViewFragment { private int gridSize; private boolean usePaletteInitialized; diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewPagerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewFragment.java similarity index 83% rename from app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewPagerFragment.java rename to app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewFragment.java index 50926fc0..3f68718f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryRecyclerViewPagerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/AbsLibraryPagerRecyclerViewFragment.java @@ -14,11 +14,9 @@ import android.view.ViewGroup; import android.widget.TextView; import com.kabouzeid.appthemehelper.ThemeStore; -import com.kabouzeid.appthemehelper.util.ATHUtil; -import com.kabouzeid.appthemehelper.util.ColorUtil; -import com.kabouzeid.appthemehelper.util.MaterialValueHelper; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.interfaces.MusicServiceEventListener; +import com.kabouzeid.gramophone.util.ViewUtil; import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; import butterknife.Bind; @@ -27,9 +25,9 @@ import butterknife.ButterKnife; /** * @author Karim Abou Zeid (kabouzeid) */ -public abstract class AbsLibraryRecyclerViewPagerFragment extends AbsLibraryPagerFragment implements OnOffsetChangedListener, MusicServiceEventListener { +public abstract class AbsLibraryPagerRecyclerViewFragment extends AbsLibraryPagerFragment implements OnOffsetChangedListener, MusicServiceEventListener { - public static final String TAG = AbsLibraryRecyclerViewPagerFragment.class.getSimpleName(); + public static final String TAG = AbsLibraryPagerRecyclerViewFragment.class.getSimpleName(); @Bind(R.id.container) View container; @@ -63,11 +61,7 @@ public abstract class AbsLibraryRecyclerViewPagerFragment { - public static final String TAG = AlbumsPagerFragment.class.getSimpleName(); +public class AlbumsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment { + public static final String TAG = AlbumsFragment.class.getSimpleName(); @Override protected GridLayoutManager createLayoutManager() { diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsPagerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsFragment.java similarity index 92% rename from app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsPagerFragment.java rename to app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsFragment.java index deb869cb..3be19fe8 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsPagerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/ArtistsFragment.java @@ -11,9 +11,9 @@ import com.kabouzeid.gramophone.util.PreferenceUtil; /** * @author Karim Abou Zeid (kabouzeid) */ -public class ArtistsPagerFragment extends AbsLibraryRecyclerViewCustomGridSizePagerFragment { +public class ArtistsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment { - public static final String TAG = ArtistsPagerFragment.class.getSimpleName(); + public static final String TAG = ArtistsFragment.class.getSimpleName(); @NonNull @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsPagerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsFragment.java similarity index 88% rename from app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsPagerFragment.java rename to app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsFragment.java index b58ebd89..d1d9ce89 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsPagerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/PlaylistsFragment.java @@ -16,9 +16,9 @@ import java.util.ArrayList; /** * @author Karim Abou Zeid (kabouzeid) */ -public class PlaylistsPagerFragment extends AbsLibraryRecyclerViewPagerFragment { +public class PlaylistsFragment extends AbsLibraryPagerRecyclerViewFragment { - public static final String TAG = PlaylistsPagerFragment.class.getSimpleName(); + public static final String TAG = PlaylistsFragment.class.getSimpleName(); @NonNull @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsPagerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsFragment.java similarity index 93% rename from app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsPagerFragment.java rename to app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsFragment.java index 39873a22..fd6d3411 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsPagerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/libraryfragments/SongsFragment.java @@ -15,9 +15,9 @@ import java.util.ArrayList; /** * @author Karim Abou Zeid (kabouzeid) */ -public class SongsPagerFragment extends AbsLibraryRecyclerViewCustomGridSizePagerFragment { +public class SongsFragment extends AbsLibraryPagerRecyclerViewCustomGridSizeFragment { - public static final String TAG = SongsPagerFragment.class.getSimpleName(); + public static final String TAG = SongsFragment.class.getSimpleName(); @NonNull @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/Util.java b/app/src/main/java/com/kabouzeid/gramophone/util/Util.java index 31a4b33c..60e0b2cb 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/Util.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/Util.java @@ -13,6 +13,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; import android.support.annotation.AttrRes; +import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -94,7 +95,7 @@ public class Util { return dimensionPixelSize; } - public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableResId, int color) { + public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableResId, @ColorInt int color) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) { // vector support context = TintContextWrapper.wrap(context); diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/ViewUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/ViewUtil.java index 17f04601..583852e4 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/ViewUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/ViewUtil.java @@ -17,6 +17,12 @@ import android.view.View; import android.view.animation.PathInterpolator; import android.widget.TextView; +import com.kabouzeid.appthemehelper.util.ATHUtil; +import com.kabouzeid.appthemehelper.util.ColorUtil; +import com.kabouzeid.appthemehelper.util.MaterialValueHelper; +import com.kabouzeid.gramophone.R; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + /** * @author Karim Abou Zeid (kabouzeid) */ @@ -71,4 +77,11 @@ public class ViewUtil { return (x >= left) && (x <= right) && (y >= top) && (y <= bottom); } + + public static void setUpFastScrollRecyclerViewColor(Context context, FastScrollRecyclerView recyclerView, int accentColor) { + recyclerView.setPopupBgColor(accentColor); + recyclerView.setPopupTextColor(MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(accentColor))); + recyclerView.setThumbColor(accentColor); + recyclerView.setTrackColor(ColorUtil.withAlpha(ATHUtil.resolveColor(context, R.attr.colorControlNormal), 0.12f)); + } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/views/BreadCrumbLayout.java b/app/src/main/java/com/kabouzeid/gramophone/views/BreadCrumbLayout.java new file mode 100644 index 00000000..48d75f26 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/views/BreadCrumbLayout.java @@ -0,0 +1,407 @@ +package com.kabouzeid.gramophone.views; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.kabouzeid.appthemehelper.ThemeStore; +import com.kabouzeid.gramophone.R; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) + */ +public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener { + + @ColorInt + private int contentColorActivated; + @ColorInt + private int contentColorDeactivated; + + public static class Crumb implements Parcelable { + + public Crumb(File file) { + mFile = file; + } + + private final File mFile; + private int mScrollPos; + + public int getScrollPosition() { + return mScrollPos; + } + + public void setScrollPosition(int scrollY) { + this.mScrollPos = scrollY; + } + + public String getTitle() { + return mFile.getPath().equals("/") ? "root" : mFile.getName(); + } + + public File getFile() { + return mFile; + } + + @Override + public boolean equals(Object o) { + return (o instanceof Crumb) && ((Crumb) o).getFile() != null && + ((Crumb) o).getFile().equals(getFile()); + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSerializable(this.mFile); + dest.writeInt(this.mScrollPos); + } + + protected Crumb(Parcel in) { + this.mFile = (File) in.readSerializable(); + this.mScrollPos = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public Crumb createFromParcel(Parcel source) { + return new Crumb(source); + } + + public Crumb[] newArray(int size) { + return new Crumb[size]; + } + }; + } + + public interface SelectionCallback { + void onCrumbSelection(Crumb crumb, int index); + } + + public BreadCrumbLayout(Context context) { + super(context); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + // Stores currently visible crumbs + private List mCrumbs; + // Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards + private List mOldCrumbs; + // Stores user's navigation history, like a fragment back stack + private List mHistory; + + private LinearLayout mChildFrame; + private int mActive; + private SelectionCallback mCallback; + + private void init() { + contentColorActivated = ThemeStore.textColorPrimary(getContext()); + contentColorDeactivated = ThemeStore.textColorSecondary(getContext()); + setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height)); + setClipToPadding(false); + setHorizontalScrollBarEnabled(false); + mCrumbs = new ArrayList<>(); + mHistory = new ArrayList<>(); + mChildFrame = new LinearLayout(getContext()); + addView(mChildFrame, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + + public void addHistory(Crumb crumb) { + mHistory.add(crumb); + } + + public Crumb lastHistory() { + if (mHistory.size() == 0) return null; + return mHistory.get(mHistory.size() - 1); + } + + public boolean popHistory() { + if (mHistory.size() == 0) return false; + mHistory.remove(mHistory.size() - 1); + return mHistory.size() != 0; + } + + public int historySize() { + return mHistory.size(); + } + + public void clearHistory() { + mHistory.clear(); + } + + public void reverseHistory() { + Collections.reverse(mHistory); + } + + public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) { + LinearLayout view = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false); + view.setTag(mCrumbs.size()); + view.setOnClickListener(this); + + ImageView iv = (ImageView) view.getChildAt(1); + if (Build.VERSION.SDK_INT >= 19 && iv.getDrawable() != null) { + iv.getDrawable().setAutoMirrored(true); + } + iv.setVisibility(View.GONE); + + mChildFrame.addView(view, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + mCrumbs.add(crumb); + if (refreshLayout) { + mActive = mCrumbs.size() - 1; + requestLayout(); + } + invalidateActivatedAll(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + //RTL works fine like this + View child = mChildFrame.getChildAt(mActive); + if (child != null) + smoothScrollTo(child.getLeft(), 0); + } + + public Crumb findCrumb(@NonNull File forDir) { + for (int i = 0; i < mCrumbs.size(); i++) { + if (mCrumbs.get(i).getFile().equals(forDir)) + return mCrumbs.get(i); + } + return null; + } + + public void clearCrumbs() { + try { + mOldCrumbs = new ArrayList<>(mCrumbs); + mCrumbs.clear(); + mChildFrame.removeAllViews(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + public Crumb getCrumb(int index) { + return mCrumbs.get(index); + } + + public void setCallback(SelectionCallback callback) { + mCallback = callback; + } + + private boolean setActive(Crumb newActive) { + mActive = mCrumbs.indexOf(newActive); + invalidateActivatedAll(); + boolean success = mActive > -1; + if (success) + requestLayout(); + return success; + } + + void invalidateActivatedAll() { + for (int i = 0; i < mCrumbs.size(); i++) { + Crumb crumb = mCrumbs.get(i); + invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false, i < mCrumbs.size() - 1).setText(crumb.getTitle()); + } + } + + void removeCrumbAt(int index) { + mCrumbs.remove(index); + mChildFrame.removeViewAt(index); + } + + public boolean trim(String path, boolean dir) { + if (!dir) return false; + int index = -1; + for (int i = mCrumbs.size() - 1; i >= 0; i--) { + File fi = mCrumbs.get(i).getFile(); + if (fi.getPath().equals(path)) { + index = i; + break; + } + } + + boolean removedActive = index >= mActive; + if (index > -1) { + while (index <= mCrumbs.size() - 1) + removeCrumbAt(index); + if (mChildFrame.getChildCount() > 0) { + int lastIndex = mCrumbs.size() - 1; + invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false); + } + } + return removedActive || mCrumbs.size() == 0; + } + + public boolean trim(File file) { + return trim(file.getPath(), file.isDirectory()); + } + + void updateIndices() { + for (int i = 0; i < mChildFrame.getChildCount(); i++) + mChildFrame.getChildAt(i).setTag(i); + } + + public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) { + if (forceRecreate || !setActive(crumb)) { + clearCrumbs(); + final List newPathSet = new ArrayList<>(); + + newPathSet.add(0, crumb.getFile()); + + File p = crumb.getFile(); + while ((p = p.getParentFile()) != null) { + newPathSet.add(0, p); + } + + for (int index = 0; index < newPathSet.size(); index++) { + final File fi = newPathSet.get(index); + crumb = new Crumb(fi); + + // Restore scroll positions saved before clearing + if (mOldCrumbs != null) { + for (Iterator iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) { + Crumb old = iterator.next(); + if (old.equals(crumb)) { + crumb.setScrollPosition(old.getScrollPosition()); + iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history + break; + } + } + } + + addCrumb(crumb, true); + } + + // History no longer needed + mOldCrumbs = null; + } + } + + public int size() { + return mCrumbs.size(); + } + + private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone, final boolean allowArrowVisible) { + int contentColor = isActive ? contentColorActivated : contentColorDeactivated; + LinearLayout child = (LinearLayout) view; + TextView tv = (TextView) child.getChildAt(0); + tv.setTextColor(contentColor); + ImageView iv = (ImageView) child.getChildAt(1); + iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN); + if (noArrowIfAlone && getChildCount() == 1) + iv.setVisibility(View.GONE); + else if (allowArrowVisible) + iv.setVisibility(View.VISIBLE); + else + iv.setVisibility(View.GONE); + return tv; + } + + public int getActiveIndex() { + return mActive; + } + + public void setActivatedContentColor(@ColorInt int contentColorActivated) { + this.contentColorActivated = contentColorActivated; + } + + public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) { + this.contentColorDeactivated = contentColorDeactivated; + } + + @Override + public void onClick(View v) { + if (mCallback != null) { + int index = (Integer) v.getTag(); + mCallback.onCrumbSelection(mCrumbs.get(index), index); + } + } + + public static class SavedStateWrapper implements Parcelable { + + public final int mActive; + public final List mCrumbs; + public final int mVisibility; + + public SavedStateWrapper(BreadCrumbLayout view) { + mActive = view.mActive; + mCrumbs = view.mCrumbs; + mVisibility = view.getVisibility(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.mActive); + dest.writeTypedList(mCrumbs); + dest.writeInt(this.mVisibility); + } + + protected SavedStateWrapper(Parcel in) { + this.mActive = in.readInt(); + this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR); + this.mVisibility = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public SavedStateWrapper createFromParcel(Parcel source) { + return new SavedStateWrapper(source); + } + + public SavedStateWrapper[] newArray(int size) { + return new SavedStateWrapper[size]; + } + }; + } + + public SavedStateWrapper getStateWrapper() { + return new SavedStateWrapper(this); + } + + public void restoreFromStateWrapper(SavedStateWrapper mSavedState) { + if (mSavedState != null) { + mActive = mSavedState.mActive; + for (Crumb c : mSavedState.mCrumbs) { + addCrumb(c, false); + } + requestLayout(); + setVisibility(mSavedState.mVisibility); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_folder_special_white_24dp.xml b/app/src/main/res/drawable/ic_folder_special_white_24dp.xml new file mode 100644 index 00000000..2c6f7961 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_special_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_insert_drive_file_white_24dp.xml b/app/src/main/res/drawable/ic_insert_drive_file_white_24dp.xml new file mode 100644 index 00000000..0a8dd41e --- /dev/null +++ b/app/src/main/res/drawable/ic_insert_drive_file_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml new file mode 100644 index 00000000..0793e1d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_music_note_white_24dp.xml b/app/src/main/res/drawable/ic_music_note_white_24dp.xml new file mode 100644 index 00000000..645f5822 --- /dev/null +++ b/app/src/main/res/drawable/ic_music_note_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/bread_crumb.xml b/app/src/main/res/layout/bread_crumb.xml new file mode 100644 index 00000000..dfb1904d --- /dev/null +++ b/app/src/main/res/layout/bread_crumb.xml @@ -0,0 +1,38 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_folder.xml b/app/src/main/res/layout/fragment_folder.xml index 5bde048f..45427b57 100644 --- a/app/src/main/res/layout/fragment_folder.xml +++ b/app/src/main/res/layout/fragment_folder.xml @@ -1,50 +1,87 @@ - + + android:orientation="vertical" + tools:context="com.kabouzeid.gramophone.ui.fragments.FolderFragment"> - + android:layout_height="wrap_content" + android:elevation="0dp" + tools:ignore="UnusedAttribute"> + - + + - - - + android:layout_height="wrap_content" + app:layout_scrollFlags="scroll|enterAlways"> + + + + + + + + + + + + + + + + + + - + - - - - - \ No newline at end of file + diff --git a/app/src/main/res/menu/menu_folders.xml b/app/src/main/res/menu/menu_folders.xml new file mode 100644 index 00000000..2859c54a --- /dev/null +++ b/app/src/main/res/menu/menu_folders.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_cannot_delete_single_songs_playlist_song.xml b/app/src/main/res/menu/menu_item_cannot_delete_single_songs_playlist_song.xml index 351f0306..dd2b985c 100644 --- a/app/src/main/res/menu/menu_item_cannot_delete_single_songs_playlist_song.xml +++ b/app/src/main/res/menu/menu_item_cannot_delete_single_songs_playlist_song.xml @@ -38,7 +38,7 @@ android:title="@string/action_set_as_ringtone" app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_playing_queue_song.xml b/app/src/main/res/menu/menu_item_playing_queue_song.xml index 33d7eaf7..1e8a14ae 100644 --- a/app/src/main/res/menu/menu_item_playing_queue_song.xml +++ b/app/src/main/res/menu/menu_item_playing_queue_song.xml @@ -38,7 +38,7 @@ android:title="@string/action_set_as_ringtone" app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_playlist_song.xml b/app/src/main/res/menu/menu_item_playlist_song.xml index 3840ff9a..b738dbf7 100644 --- a/app/src/main/res/menu/menu_item_playlist_song.xml +++ b/app/src/main/res/menu/menu_item_playlist_song.xml @@ -42,7 +42,7 @@ android:title="@string/action_set_as_ringtone" app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_song.xml b/app/src/main/res/menu/menu_item_song.xml index 351f0306..dd2b985c 100644 --- a/app/src/main/res/menu/menu_item_song.xml +++ b/app/src/main/res/menu/menu_item_song.xml @@ -38,7 +38,7 @@ android:title="@string/action_set_as_ringtone" app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_media_selection.xml b/app/src/main/res/menu/menu_media_selection.xml index cd84b1f3..26a0068f 100644 --- a/app/src/main/res/menu/menu_media_selection.xml +++ b/app/src/main/res/menu/menu_media_selection.xml @@ -15,7 +15,7 @@ app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d81f701..cf41ffc6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,6 +96,7 @@ Shuffle artist Shuffle playlist Clear playing queue + Go to standard folder Last opened Light Dark