From 944b5fc1fa2b136d71f8ce5763c8659d9de6453d Mon Sep 17 00:00:00 2001 From: Eugene Cheung Date: Sat, 2 Sep 2017 19:54:57 -0400 Subject: [PATCH 1/2] Genres tab --- app/src/main/AndroidManifest.xml | 1 + .../gramophone/adapter/GenreAdapter.java | 88 ++++++++ .../adapter/MusicLibraryPagerAdapter.java | 5 +- .../gramophone/helper/SortOrder.java | 11 + .../gramophone/interfaces/LoaderIds.java | 14 +- .../gramophone/loader/GenreLoader.java | 129 +++++++++++ .../gramophone/loader/SongLoader.java | 40 ++-- .../com/kabouzeid/gramophone/model/Genre.java | 72 ++++++ .../ui/activities/GenreDetailActivity.java | 206 ++++++++++++++++++ .../library/pager/GenresFragment.java | 79 +++++++ .../gramophone/util/NavigationUtil.java | 9 + .../gramophone/util/PreferenceUtil.java | 14 +- .../main/res/layout/activity_genre_detail.xml | 55 +++++ app/src/main/res/layout/item_list_simple.xml | 29 +++ app/src/main/res/menu/menu_genre_detail.xml | 12 + app/src/main/res/values/strings.xml | 3 + 16 files changed, 739 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/adapter/GenreAdapter.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/model/Genre.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/ui/activities/GenreDetailActivity.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/library/pager/GenresFragment.java create mode 100644 app/src/main/res/layout/activity_genre_detail.xml create mode 100644 app/src/main/res/layout/item_list_simple.xml create mode 100644 app/src/main/res/menu/menu_genre_detail.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d2f06caf..7fadfd0f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -94,6 +94,7 @@ + implements FastScrollRecyclerView.SectionedAdapter { + + public static final String TAG = GenreAdapter.class.getSimpleName(); + + @NonNull + private final AppCompatActivity activity; + private ArrayList dataSet; + private int itemLayoutRes; + + public GenreAdapter(@NonNull AppCompatActivity activity, ArrayList dataSet, @LayoutRes int itemLayoutRes) { + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + } + + public ArrayList getDataSet() { + return dataSet; + } + + public void swapDataSet(ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).hashCode(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + final Genre genre = dataSet.get(position); + + if (holder.title != null) { + holder.title.setText(genre.name); + } + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @NonNull + @Override + public String getSectionName(int position) { + final Genre genre = dataSet.get(position); + return genre.id == -1 ? "" : MusicUtil.getSectionName(dataSet.get(position).name); + } + + public class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(@NonNull View itemView) { + super(itemView); + } + + @Override + public void onClick(View view) { + Genre genre = dataSet.get(getAdapterPosition()); + NavigationUtil.goToGenre(activity, genre); + } + } +} 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 20c57414..ee292b4f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/MusicLibraryPagerAdapter.java @@ -12,6 +12,7 @@ import android.view.ViewGroup; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.AlbumsFragment; import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.ArtistsFragment; +import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.GenresFragment; import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.PlaylistsFragment; import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.SongsFragment; @@ -39,6 +40,7 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter { context.getResources().getString(R.string.songs), context.getResources().getString(R.string.albums), context.getResources().getString(R.string.artists), + context.getResources().getString(R.string.genres), context.getResources().getString(R.string.playlists) }; final MusicFragments[] fragments = MusicFragments.values(); @@ -110,6 +112,7 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter { SONG(SongsFragment.class), ALBUM(AlbumsFragment.class), ARTIST(ArtistsFragment.class), + GENRES(GenresFragment.class), PLAYLIST(PlaylistsFragment.class); private final Class mFragmentClass; @@ -121,12 +124,10 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter { public Class getFragmentClass() { return mFragmentClass; } - } private final static class Holder { String mClassName; - Bundle mParams; } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/SortOrder.java b/app/src/main/java/com/kabouzeid/gramophone/helper/SortOrder.java index 638e950a..ae805959 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/SortOrder.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/SortOrder.java @@ -153,4 +153,15 @@ public final class SortOrder { + " ASC"; } + /** + * Genre sort order entries. + */ + public interface GenreSortOrder { + /* Genre sort order A-Z */ + String GENRE_A_Z = MediaStore.Audio.Genres.DEFAULT_SORT_ORDER; + + /* Genre sort order Z-A */ + String ALBUM_Z_A = GENRE_A_Z + " DESC"; + } + } diff --git a/app/src/main/java/com/kabouzeid/gramophone/interfaces/LoaderIds.java b/app/src/main/java/com/kabouzeid/gramophone/interfaces/LoaderIds.java index dc6ad0c3..c0770e5e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/interfaces/LoaderIds.java +++ b/app/src/main/java/com/kabouzeid/gramophone/interfaces/LoaderIds.java @@ -7,10 +7,12 @@ public interface LoaderIds { int ALBUM_DETAIL_ACTIVITY = 1; int ARTIST_DETAIL_ACTIVITY = 2; int PLAYLIST_DETAIL_ACTIVITY = 3; - int SEARCH_ACTIVITY = 4; - int FOLDERS_FRAGMENT = 5; - int ALBUMS_FRAGMENT = 6; - int ARTISTS_FRAGMENT = 7; - int PLAYLISTS_FRAGMENT = 8; - int SONGS_FRAGMENT = 9; + int GENRE_DETAIL_ACTIVITY = 4; + int SEARCH_ACTIVITY = 5; + int FOLDERS_FRAGMENT = 6; + int ALBUMS_FRAGMENT = 7; + int ARTISTS_FRAGMENT = 8; + int PLAYLISTS_FRAGMENT = 9; + int GENRES_FRAGMENT = 10; + int SONGS_FRAGMENT = 11; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java new file mode 100644 index 00000000..59a71f76 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java @@ -0,0 +1,129 @@ +package com.kabouzeid.gramophone.loader; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore.Audio.Genres; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.model.Genre; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.util.PreferenceUtil; + +import java.util.ArrayList; + +public class GenreLoader { + + @NonNull + public static ArrayList getAllGenres(@NonNull final Context context) { + final String[] projection = new String[]{ + Genres._ID, + Genres.NAME + }; + final String selection = Genres._ID + " IN" + + " (SELECT " + Genres.Members.GENRE_ID + " FROM audio_genres_map WHERE " + Genres.Members.AUDIO_ID + " IN" + + " (SELECT " + Genres._ID + " FROM audio_meta WHERE " + SongLoader.BASE_SELECTION + "))"; + + final Cursor cursor = context.getContentResolver().query( + Genres.EXTERNAL_CONTENT_URI, + projection, selection, null, PreferenceUtil.getInstance(context).getGenreSortOrder()); + + return getGenresFromCursor(context, cursor); + } + + @NonNull + public static ArrayList getSongs(@NonNull final Context context, final int genreId) { + // The genres table only stores songs that have a genre specified, + // so we need to get songs without a genre a different way. + if (genreId == -1) { + return getSongsWithNoGenre(context); + } + + final Cursor cursor = context.getContentResolver().query( + Genres.Members.getContentUri("external", genreId), + SongLoader.BASE_PROJECTION, SongLoader.BASE_SELECTION, null, null); + + return SongLoader.getSongs(cursor); + } + + @NonNull + private static ArrayList getGenresFromCursor(@NonNull final Context context, @Nullable final Cursor cursor) { + final ArrayList genres = new ArrayList<>(); + + if (hasSongsWithNoGenre(context)) { + genres.add(new Genre(context.getResources().getString(R.string.unknown_genre))); + } + + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + genres.add(getGenreFromCursor(cursor)); + } while (cursor.moveToNext()); + } + cursor.close(); + } + return genres; + } + + @NonNull + private static Genre getGenreFromCursor(@NonNull final Cursor cursor) { + final int id = cursor.getInt(0); + final String name = cursor.getString(1); + return new Genre(id, name); + } + + @NonNull + private static ArrayList getSongsWithNoGenre(@NonNull final Context context) { + final Cursor cursor = makeAllSongsWithGenreCursor(context); + ArrayList songs = new ArrayList<>(); + final int[] songIds = getSongIdsFromCursor(cursor); + if (songIds.length > 0) { + songs = SongLoader.getSongsNotIn(context, songIds); + } + return songs; + } + + private static int[] getSongIdsFromCursor(@Nullable final Cursor cursor) { + if (cursor == null) { + return new int[]{}; + } + + int[] songIds = new int[cursor.getCount()]; + if (cursor.moveToFirst()) { + int i = 0; + do { + songIds[i] = cursor.getInt(0); + i++; + } while (cursor.moveToNext()); + } + cursor.close(); + return songIds; + } + + private static boolean hasSongsWithNoGenre(@NonNull final Context context) { + final Cursor allSongsCursor = SongLoader.makeSongCursor(context, null, null); + final Cursor allSongsWithGenreCursor = makeAllSongsWithGenreCursor(context); + + if (allSongsCursor == null || allSongsWithGenreCursor == null) { + return false; + } + + final boolean hasSongsWithNoGenre = allSongsCursor.getCount() > allSongsWithGenreCursor.getCount(); + allSongsCursor.close(); + allSongsWithGenreCursor.close(); + return hasSongsWithNoGenre; + } + + @Nullable + private static Cursor makeAllSongsWithGenreCursor(@NonNull final Context context) { + try { + return context.getContentResolver().query( + Uri.parse("content://media/external/audio/genres/all/members"), + new String[]{Genres.Members.AUDIO_ID}, null, null, null); + } catch (SecurityException e) { + return null; + } + } +} 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 4c3c65bc..e1690441 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java @@ -12,12 +12,26 @@ import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.util.PreferenceUtil; import java.util.ArrayList; +import java.util.Arrays; /** * @author Karim Abou Zeid (kabouzeid) */ public class SongLoader { protected static final String BASE_SELECTION = AudioColumns.IS_MUSIC + "=1" + " AND " + AudioColumns.TITLE + " != ''"; + protected static final String[] BASE_PROJECTION = new String[]{ + BaseColumns._ID, // 0 + AudioColumns.TITLE, // 1 + AudioColumns.TRACK, // 2 + AudioColumns.YEAR, // 3 + AudioColumns.DURATION, // 4 + AudioColumns.DATA, // 5 + AudioColumns.DATE_MODIFIED, // 6 + AudioColumns.ALBUM_ID, // 7 + AudioColumns.ALBUM, // 8 + AudioColumns.ARTIST_ID, // 9 + AudioColumns.ARTIST, // 10 + }; @NonNull public static ArrayList getAllSongs(@NonNull Context context) { @@ -31,6 +45,13 @@ public class SongLoader { return getSongs(cursor); } + @NonNull + public static ArrayList getSongsNotIn(@NonNull final Context context, final int[] query) { + final String ids = convertArrayToQueryList(query); + Cursor cursor = makeSongCursor(context, AudioColumns._ID + " NOT IN " + ids, null); + return getSongs(cursor); + } + @NonNull public static Song getSong(@NonNull final Context context, final int queryId) { Cursor cursor = makeSongCursor(context, AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)}); @@ -96,22 +117,13 @@ public class SongLoader { try { return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - new String[]{ - BaseColumns._ID,// 0 - AudioColumns.TITLE,// 1 - AudioColumns.TRACK,// 2 - AudioColumns.YEAR,// 3 - AudioColumns.DURATION,// 4 - AudioColumns.DATA,// 5 - AudioColumns.DATE_MODIFIED,// 6 - AudioColumns.ALBUM_ID,// 7 - AudioColumns.ALBUM,// 8 - AudioColumns.ARTIST_ID,// 9 - AudioColumns.ARTIST,// 10 - - }, baseSelection, selectionValues, sortOrder); + BASE_PROJECTION, baseSelection, selectionValues, sortOrder); } catch (SecurityException e) { return null; } } + + private static String convertArrayToQueryList(final int[] array) { + return "(" + Arrays.toString(array).replaceAll("\\[|\\]|\\s", "") + ")"; + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/Genre.java b/app/src/main/java/com/kabouzeid/gramophone/model/Genre.java new file mode 100644 index 00000000..10b177c1 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/model/Genre.java @@ -0,0 +1,72 @@ +package com.kabouzeid.gramophone.model; + +import android.os.Parcel; +import android.os.Parcelable; + +public class Genre implements Parcelable { + public final int id; + public final String name; + + public Genre(final int id, final String name) { + this.id = id; + this.name = name; + } + + // For unknown genre + public Genre(final String name) { + this.id = -1; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Genre genre = (Genre) o; + + if (id != genre.id) return false; + return name != null ? name.equals(genre.name) : genre.name == null; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Genre{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.id); + dest.writeString(this.name); + } + + protected Genre(Parcel in) { + this.id = in.readInt(); + this.name = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + public Genre createFromParcel(Parcel source) { + return new Genre(source); + } + + public Genre[] newArray(int size) { + return new Genre[size]; + } + }; +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/GenreDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/GenreDetailActivity.java new file mode 100644 index 00000000..9aba61c8 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/GenreDetailActivity.java @@ -0,0 +1,206 @@ +package com.kabouzeid.gramophone.ui.activities; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.afollestad.materialcab.MaterialCab; +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; +import com.kabouzeid.appthemehelper.ThemeStore; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.adapter.song.SongAdapter; +import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.interfaces.CabHolder; +import com.kabouzeid.gramophone.interfaces.LoaderIds; +import com.kabouzeid.gramophone.loader.GenreLoader; +import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader; +import com.kabouzeid.gramophone.model.Genre; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity; +import com.kabouzeid.gramophone.util.PhonographColorUtil; +import com.kabouzeid.gramophone.util.ViewUtil; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class GenreDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder, LoaderManager.LoaderCallbacks> { + + public static final String TAG = GenreDetailActivity.class.getSimpleName(); + private static final int LOADER_ID = LoaderIds.GENRE_DETAIL_ACTIVITY; + + public static final String EXTRA_GENRE = "extra_genre"; + + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(android.R.id.empty) + TextView empty; + + private Genre genre; + + private MaterialCab cab; + private SongAdapter adapter; + + private RecyclerView.Adapter wrappedAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setDrawUnderStatusbar(true); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + + genre = getIntent().getExtras().getParcelable(EXTRA_GENRE); + + setUpRecyclerView(); + + setUpToolBar(); + + getSupportLoaderManager().initLoader(LOADER_ID, null, this); + } + + @Override + protected View createContentView() { + return wrapSlidingMusicPanel(R.layout.activity_genre_detail); + } + + private void setUpRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(this)); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + adapter = new SongAdapter(this, new ArrayList(), R.layout.item_list, false, this); + recyclerView.setAdapter(adapter); + + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + } + }); + } + + private void setUpToolBar() { + toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(genre.name); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_genre_detail, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + switch (id) { + case R.id.action_shuffle_genre: + MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true); + return true; + case android.R.id.home: + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @NonNull + @Override + public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) cab.finish(); + cab = new MaterialCab(this, R.id.cab_stub) + .setMenu(menu) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor(PhonographColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) + .start(callback); + return cab; + } + + @Override + public void onBackPressed() { + if (cab != null && cab.isActive()) cab.finish(); + else { + recyclerView.stopScroll(); + super.onBackPressed(); + } + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + getSupportLoaderManager().restartLoader(LOADER_ID, null, this); + } + + private void checkIsEmpty() { + empty.setVisibility( + adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE + ); + } + + @Override + protected void onDestroy() { + if (recyclerView != null) { + recyclerView.setAdapter(null); + recyclerView = null; + } + + if (wrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(wrappedAdapter); + wrappedAdapter = null; + } + adapter = null; + + super.onDestroy(); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new GenreDetailActivity.AsyncGenreSongLoader(this, genre); + } + + @Override + public void onLoadFinished(Loader> loader, ArrayList data) { + if (adapter != null) + adapter.swapDataSet(data); + } + + @Override + public void onLoaderReset(Loader> loader) { + if (adapter != null) + adapter.swapDataSet(new ArrayList()); + } + + private static class AsyncGenreSongLoader extends WrappedAsyncTaskLoader> { + private final Genre genre; + + public AsyncGenreSongLoader(Context context, Genre genre) { + super(context); + this.genre = genre; + } + + @Override + public ArrayList loadInBackground() { + return GenreLoader.getSongs(getContext(), genre.id); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/library/pager/GenresFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/library/pager/GenresFragment.java new file mode 100644 index 00000000..d51e0fd5 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/library/pager/GenresFragment.java @@ -0,0 +1,79 @@ +package com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v7.widget.LinearLayoutManager; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.adapter.GenreAdapter; +import com.kabouzeid.gramophone.interfaces.LoaderIds; +import com.kabouzeid.gramophone.loader.GenreLoader; +import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader; +import com.kabouzeid.gramophone.model.Genre; + +import java.util.ArrayList; + +public class GenresFragment extends AbsLibraryPagerRecyclerViewFragment implements LoaderManager.LoaderCallbacks> { + + public static final String TAG = GenresFragment.class.getSimpleName(); + + private static final int LOADER_ID = LoaderIds.GENRES_FRAGMENT; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getLoaderManager().initLoader(LOADER_ID, null, this); + } + + @NonNull + @Override + protected LinearLayoutManager createLayoutManager() { + return new LinearLayoutManager(getActivity()); + } + + @NonNull + @Override + protected GenreAdapter createAdapter() { + ArrayList dataSet = getAdapter() == null ? new ArrayList() : getAdapter().getDataSet(); + return new GenreAdapter(getLibraryFragment().getMainActivity(), dataSet, R.layout.item_list_simple); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_genres; + } + + @Override + public void onMediaStoreChanged() { + getLoaderManager().restartLoader(LOADER_ID, null, this); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new GenresFragment.AsyncGenreLoader(getActivity()); + } + + @Override + public void onLoadFinished(Loader> loader, ArrayList data) { + getAdapter().swapDataSet(data); + } + + @Override + public void onLoaderReset(Loader> loader) { + getAdapter().swapDataSet(new ArrayList()); + } + + private static class AsyncGenreLoader extends WrappedAsyncTaskLoader> { + public AsyncGenreLoader(Context context) { + super(context); + } + + @Override + public ArrayList loadInBackground() { + return GenreLoader.getAllGenres(getContext()); + } + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/NavigationUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/NavigationUtil.java index c036c3c6..66950c72 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/NavigationUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/NavigationUtil.java @@ -12,9 +12,11 @@ import android.widget.Toast; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.model.Genre; import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity; import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity; +import com.kabouzeid.gramophone.ui.activities.GenreDetailActivity; import com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity; /** @@ -38,6 +40,13 @@ public class NavigationUtil { activity.startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(activity, sharedElements).toBundle()); } + public static void goToGenre(@NonNull final Activity activity, final Genre genre, @Nullable Pair... sharedElements) { + final Intent intent = new Intent(activity, GenreDetailActivity.class); + intent.putExtra(GenreDetailActivity.EXTRA_GENRE, genre); + + activity.startActivity(intent); + } + public static void goToPlaylist(@NonNull final Activity activity, final Playlist playlist, @Nullable Pair... sharedElements) { final Intent intent = new Intent(activity, PlaylistDetailActivity.class); intent.putExtra(PlaylistDetailActivity.EXTRA_PLAYLIST, playlist); diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java index 565df23e..e2264827 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java @@ -27,6 +27,7 @@ public final class PreferenceUtil { public static final String ALBUM_SORT_ORDER = "album_sort_order"; public static final String ALBUM_SONG_SORT_ORDER = "album_song_sort_order"; public static final String SONG_SORT_ORDER = "song_sort_order"; + public static final String GENRE_SORT_ORDER = "genre_sort_order"; public static final String ALBUM_GRID_SIZE = "album_grid_size"; public static final String ALBUM_GRID_SIZE_LAND = "album_grid_size_land"; @@ -199,13 +200,11 @@ public final class PreferenceUtil { } public final String getArtistSongSortOrder() { - return mPreferences.getString(ARTIST_SONG_SORT_ORDER, - SortOrder.ArtistSongSortOrder.SONG_A_Z); + return mPreferences.getString(ARTIST_SONG_SORT_ORDER, SortOrder.ArtistSongSortOrder.SONG_A_Z); } public final String getArtistAlbumSortOrder() { - return mPreferences.getString(ARTIST_ALBUM_SORT_ORDER, - SortOrder.ArtistAlbumSortOrder.ALBUM_YEAR); + return mPreferences.getString(ARTIST_ALBUM_SORT_ORDER, SortOrder.ArtistAlbumSortOrder.ALBUM_YEAR); } public final String getAlbumSortOrder() { @@ -213,14 +212,17 @@ public final class PreferenceUtil { } public final String getAlbumSongSortOrder() { - return mPreferences.getString(ALBUM_SONG_SORT_ORDER, - SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST); + return mPreferences.getString(ALBUM_SONG_SORT_ORDER, SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST); } public final String getSongSortOrder() { return mPreferences.getString(SONG_SORT_ORDER, SortOrder.SongSortOrder.SONG_A_Z); } + public final String getGenreSortOrder() { + return mPreferences.getString(GENRE_SORT_ORDER, SortOrder.GenreSortOrder.GENRE_A_Z); + } + public long getLastAddedCutoff() { final CalendarUtil calendarUtil = new CalendarUtil(); long interval; diff --git a/app/src/main/res/layout/activity_genre_detail.xml b/app/src/main/res/layout/activity_genre_detail.xml new file mode 100644 index 00000000..c59b2d6c --- /dev/null +++ b/app/src/main/res/layout/activity_genre_detail.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_list_simple.xml b/app/src/main/res/layout/item_list_simple.xml new file mode 100644 index 00000000..68d972de --- /dev/null +++ b/app/src/main/res/layout/item_list_simple.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_genre_detail.xml b/app/src/main/res/menu/menu_genre_detail.xml new file mode 100644 index 00000000..198ec785 --- /dev/null +++ b/app/src/main/res/menu/menu_genre_detail.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00ba0454..7919e30d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Set as start directory Albums Artists + Genres Songs Playlists Couldn\u2019t play this song. @@ -41,6 +42,7 @@ Album Artist Genre + Unknown genre Album artist Year "Track (2 for track 2 or 3004 for CD3 track 4)" @@ -150,6 +152,7 @@ No albums No songs No artists + No genres Empty Playlist name Song From f51076eeb3abce47d1024cf753c2ba707f162eb5 Mon Sep 17 00:00:00 2001 From: Eugene Cheung Date: Tue, 5 Sep 2017 07:29:38 -0400 Subject: [PATCH 2/2] Better way of getting songs with no genre --- .../gramophone/loader/GenreLoader.java | 29 ++++--------------- .../gramophone/loader/SongLoader.java | 12 -------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java index 59a71f76..4c552761 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/GenreLoader.java @@ -3,6 +3,7 @@ package com.kabouzeid.gramophone.loader; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.provider.BaseColumns; import android.provider.MediaStore.Audio.Genres; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -22,6 +23,7 @@ public class GenreLoader { Genres._ID, Genres.NAME }; + // Genres that actually have songs final String selection = Genres._ID + " IN" + " (SELECT " + Genres.Members.GENRE_ID + " FROM audio_genres_map WHERE " + Genres.Members.AUDIO_ID + " IN" + " (SELECT " + Genres._ID + " FROM audio_meta WHERE " + SongLoader.BASE_SELECTION + "))"; @@ -76,30 +78,9 @@ public class GenreLoader { @NonNull private static ArrayList getSongsWithNoGenre(@NonNull final Context context) { - final Cursor cursor = makeAllSongsWithGenreCursor(context); - ArrayList songs = new ArrayList<>(); - final int[] songIds = getSongIdsFromCursor(cursor); - if (songIds.length > 0) { - songs = SongLoader.getSongsNotIn(context, songIds); - } - return songs; - } - - private static int[] getSongIdsFromCursor(@Nullable final Cursor cursor) { - if (cursor == null) { - return new int[]{}; - } - - int[] songIds = new int[cursor.getCount()]; - if (cursor.moveToFirst()) { - int i = 0; - do { - songIds[i] = cursor.getInt(0); - i++; - } while (cursor.moveToNext()); - } - cursor.close(); - return songIds; + String selection = BaseColumns._ID + " NOT IN " + + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"; + return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null)); } private static boolean hasSongsWithNoGenre(@NonNull final Context context) { 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 e1690441..8e14a448 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java @@ -12,7 +12,6 @@ import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.util.PreferenceUtil; import java.util.ArrayList; -import java.util.Arrays; /** * @author Karim Abou Zeid (kabouzeid) @@ -45,13 +44,6 @@ public class SongLoader { return getSongs(cursor); } - @NonNull - public static ArrayList getSongsNotIn(@NonNull final Context context, final int[] query) { - final String ids = convertArrayToQueryList(query); - Cursor cursor = makeSongCursor(context, AudioColumns._ID + " NOT IN " + ids, null); - return getSongs(cursor); - } - @NonNull public static Song getSong(@NonNull final Context context, final int queryId) { Cursor cursor = makeSongCursor(context, AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)}); @@ -122,8 +114,4 @@ public class SongLoader { return null; } } - - private static String convertArrayToQueryList(final int[] array) { - return "(" + Arrays.toString(array).replaceAll("\\[|\\]|\\s", "") + ")"; - } }