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 extends Fragment> mFragmentClass;
@@ -121,12 +124,10 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
public Class extends Fragment> 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