Genres tab

This commit is contained in:
Eugene Cheung 2017-09-02 19:54:57 -04:00
commit 944b5fc1fa
No known key found for this signature in database
GPG key ID: E1FD745328866B0A
16 changed files with 739 additions and 28 deletions

View file

@ -94,6 +94,7 @@
</activity> </activity>
<activity android:name=".ui.activities.AlbumDetailActivity" /> <activity android:name=".ui.activities.AlbumDetailActivity" />
<activity android:name=".ui.activities.ArtistDetailActivity" /> <activity android:name=".ui.activities.ArtistDetailActivity" />
<activity android:name=".ui.activities.GenreDetailActivity" />
<activity android:name=".ui.activities.PlaylistDetailActivity" /> <activity android:name=".ui.activities.PlaylistDetailActivity" />
<activity <activity
android:name=".ui.activities.tageditor.SongTagEditorActivity" android:name=".ui.activities.tageditor.SongTagEditorActivity"

View file

@ -0,0 +1,88 @@
package com.kabouzeid.gramophone.adapter;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder;
import com.kabouzeid.gramophone.model.Genre;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.NavigationUtil;
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import java.util.ArrayList;
public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder> implements FastScrollRecyclerView.SectionedAdapter {
public static final String TAG = GenreAdapter.class.getSimpleName();
@NonNull
private final AppCompatActivity activity;
private ArrayList<Genre> dataSet;
private int itemLayoutRes;
public GenreAdapter(@NonNull AppCompatActivity activity, ArrayList<Genre> dataSet, @LayoutRes int itemLayoutRes) {
this.activity = activity;
this.dataSet = dataSet;
this.itemLayoutRes = itemLayoutRes;
}
public ArrayList<Genre> getDataSet() {
return dataSet;
}
public void swapDataSet(ArrayList<Genre> 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);
}
}
}

View file

@ -12,6 +12,7 @@ import android.view.ViewGroup;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.AlbumsFragment; 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.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.PlaylistsFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.pager.SongsFragment; 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.songs),
context.getResources().getString(R.string.albums), context.getResources().getString(R.string.albums),
context.getResources().getString(R.string.artists), context.getResources().getString(R.string.artists),
context.getResources().getString(R.string.genres),
context.getResources().getString(R.string.playlists) context.getResources().getString(R.string.playlists)
}; };
final MusicFragments[] fragments = MusicFragments.values(); final MusicFragments[] fragments = MusicFragments.values();
@ -110,6 +112,7 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
SONG(SongsFragment.class), SONG(SongsFragment.class),
ALBUM(AlbumsFragment.class), ALBUM(AlbumsFragment.class),
ARTIST(ArtistsFragment.class), ARTIST(ArtistsFragment.class),
GENRES(GenresFragment.class),
PLAYLIST(PlaylistsFragment.class); PLAYLIST(PlaylistsFragment.class);
private final Class<? extends Fragment> mFragmentClass; private final Class<? extends Fragment> mFragmentClass;
@ -121,12 +124,10 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
public Class<? extends Fragment> getFragmentClass() { public Class<? extends Fragment> getFragmentClass() {
return mFragmentClass; return mFragmentClass;
} }
} }
private final static class Holder { private final static class Holder {
String mClassName; String mClassName;
Bundle mParams; Bundle mParams;
} }
} }

View file

@ -153,4 +153,15 @@ public final class SortOrder {
+ " ASC"; + " 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";
}
} }

View file

@ -7,10 +7,12 @@ public interface LoaderIds {
int ALBUM_DETAIL_ACTIVITY = 1; int ALBUM_DETAIL_ACTIVITY = 1;
int ARTIST_DETAIL_ACTIVITY = 2; int ARTIST_DETAIL_ACTIVITY = 2;
int PLAYLIST_DETAIL_ACTIVITY = 3; int PLAYLIST_DETAIL_ACTIVITY = 3;
int SEARCH_ACTIVITY = 4; int GENRE_DETAIL_ACTIVITY = 4;
int FOLDERS_FRAGMENT = 5; int SEARCH_ACTIVITY = 5;
int ALBUMS_FRAGMENT = 6; int FOLDERS_FRAGMENT = 6;
int ARTISTS_FRAGMENT = 7; int ALBUMS_FRAGMENT = 7;
int PLAYLISTS_FRAGMENT = 8; int ARTISTS_FRAGMENT = 8;
int SONGS_FRAGMENT = 9; int PLAYLISTS_FRAGMENT = 9;
int GENRES_FRAGMENT = 10;
int SONGS_FRAGMENT = 11;
} }

View file

@ -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<Genre> 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<Song> 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<Genre> getGenresFromCursor(@NonNull final Context context, @Nullable final Cursor cursor) {
final ArrayList<Genre> 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<Song> getSongsWithNoGenre(@NonNull final Context context) {
final Cursor cursor = makeAllSongsWithGenreCursor(context);
ArrayList<Song> 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;
}
}
}

View file

@ -12,12 +12,26 @@ import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.PreferenceUtil; import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public class SongLoader { public class SongLoader {
protected static final String BASE_SELECTION = AudioColumns.IS_MUSIC + "=1" + " AND " + AudioColumns.TITLE + " != ''"; 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 @NonNull
public static ArrayList<Song> getAllSongs(@NonNull Context context) { public static ArrayList<Song> getAllSongs(@NonNull Context context) {
@ -31,6 +45,13 @@ public class SongLoader {
return getSongs(cursor); return getSongs(cursor);
} }
@NonNull
public static ArrayList<Song> 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 @NonNull
public static Song getSong(@NonNull final Context context, final int queryId) { public static Song getSong(@NonNull final Context context, final int queryId) {
Cursor cursor = makeSongCursor(context, AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)}); Cursor cursor = makeSongCursor(context, AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)});
@ -96,22 +117,13 @@ public class SongLoader {
try { try {
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{ BASE_PROJECTION, baseSelection, selectionValues, sortOrder);
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);
} catch (SecurityException e) { } catch (SecurityException e) {
return null; return null;
} }
} }
private static String convertArrayToQueryList(final int[] array) {
return "(" + Arrays.toString(array).replaceAll("\\[|\\]|\\s", "") + ")";
}
} }

View file

@ -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<Genre> CREATOR = new Creator<Genre>() {
public Genre createFromParcel(Parcel source) {
return new Genre(source);
}
public Genre[] newArray(int size) {
return new Genre[size];
}
};
}

View file

@ -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<ArrayList<Song>> {
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<Song>(), 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<ArrayList<Song>> onCreateLoader(int id, Bundle args) {
return new GenreDetailActivity.AsyncGenreSongLoader(this, genre);
}
@Override
public void onLoadFinished(Loader<ArrayList<Song>> loader, ArrayList<Song> data) {
if (adapter != null)
adapter.swapDataSet(data);
}
@Override
public void onLoaderReset(Loader<ArrayList<Song>> loader) {
if (adapter != null)
adapter.swapDataSet(new ArrayList<Song>());
}
private static class AsyncGenreSongLoader extends WrappedAsyncTaskLoader<ArrayList<Song>> {
private final Genre genre;
public AsyncGenreSongLoader(Context context, Genre genre) {
super(context);
this.genre = genre;
}
@Override
public ArrayList<Song> loadInBackground() {
return GenreLoader.getSongs(getContext(), genre.id);
}
}
}

View file

@ -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<GenreAdapter, LinearLayoutManager> implements LoaderManager.LoaderCallbacks<ArrayList<Genre>> {
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<Genre> dataSet = getAdapter() == null ? new ArrayList<Genre>() : 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<ArrayList<Genre>> onCreateLoader(int id, Bundle args) {
return new GenresFragment.AsyncGenreLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<ArrayList<Genre>> loader, ArrayList<Genre> data) {
getAdapter().swapDataSet(data);
}
@Override
public void onLoaderReset(Loader<ArrayList<Genre>> loader) {
getAdapter().swapDataSet(new ArrayList<Genre>());
}
private static class AsyncGenreLoader extends WrappedAsyncTaskLoader<ArrayList<Genre>> {
public AsyncGenreLoader(Context context) {
super(context);
}
@Override
public ArrayList<Genre> loadInBackground() {
return GenreLoader.getAllGenres(getContext());
}
}
}

View file

@ -12,9 +12,11 @@ import android.widget.Toast;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.model.Genre;
import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity; import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity;
import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity; import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity;
import com.kabouzeid.gramophone.ui.activities.GenreDetailActivity;
import com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity; import com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity;
/** /**
@ -38,6 +40,13 @@ public class NavigationUtil {
activity.startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(activity, sharedElements).toBundle()); 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) { public static void goToPlaylist(@NonNull final Activity activity, final Playlist playlist, @Nullable Pair... sharedElements) {
final Intent intent = new Intent(activity, PlaylistDetailActivity.class); final Intent intent = new Intent(activity, PlaylistDetailActivity.class);
intent.putExtra(PlaylistDetailActivity.EXTRA_PLAYLIST, playlist); intent.putExtra(PlaylistDetailActivity.EXTRA_PLAYLIST, playlist);

View file

@ -27,6 +27,7 @@ public final class PreferenceUtil {
public static final String ALBUM_SORT_ORDER = "album_sort_order"; 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 ALBUM_SONG_SORT_ORDER = "album_song_sort_order";
public static final String SONG_SORT_ORDER = "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 = "album_grid_size";
public static final String ALBUM_GRID_SIZE_LAND = "album_grid_size_land"; public static final String ALBUM_GRID_SIZE_LAND = "album_grid_size_land";
@ -199,13 +200,11 @@ public final class PreferenceUtil {
} }
public final String getArtistSongSortOrder() { public final String getArtistSongSortOrder() {
return mPreferences.getString(ARTIST_SONG_SORT_ORDER, return mPreferences.getString(ARTIST_SONG_SORT_ORDER, SortOrder.ArtistSongSortOrder.SONG_A_Z);
SortOrder.ArtistSongSortOrder.SONG_A_Z);
} }
public final String getArtistAlbumSortOrder() { public final String getArtistAlbumSortOrder() {
return mPreferences.getString(ARTIST_ALBUM_SORT_ORDER, return mPreferences.getString(ARTIST_ALBUM_SORT_ORDER, SortOrder.ArtistAlbumSortOrder.ALBUM_YEAR);
SortOrder.ArtistAlbumSortOrder.ALBUM_YEAR);
} }
public final String getAlbumSortOrder() { public final String getAlbumSortOrder() {
@ -213,14 +212,17 @@ public final class PreferenceUtil {
} }
public final String getAlbumSongSortOrder() { public final String getAlbumSongSortOrder() {
return mPreferences.getString(ALBUM_SONG_SORT_ORDER, return mPreferences.getString(ALBUM_SONG_SORT_ORDER, SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST);
SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST);
} }
public final String getSongSortOrder() { public final String getSongSortOrder() {
return mPreferences.getString(SONG_SORT_ORDER, SortOrder.SongSortOrder.SONG_A_Z); 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() { public long getLastAddedCutoff() {
final CalendarUtil calendarUtil = new CalendarUtil(); final CalendarUtil calendarUtil = new CalendarUtil();
long interval; long interval;

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionGroup="true"
tools:ignore="UnusedAttribute">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/status_bar" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorPrimary"
android:elevation="@dimen/toolbar_elevation"
tools:ignore="UnusedAttribute">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar"
android:background="@android:color/transparent" />
<ViewStub
android:id="@+id/cab_stub"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</FrameLayout>
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="none" />
</LinearLayout>
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="sans-serif-light"
android:text="@string/playlist_empty_text"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/empty_text_size"
android:visibility="gone" />
</FrameLayout>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp"
android:foreground="?attr/rectSelector"
tools:ignore="UnusedAttribute">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:fontFamily="sans-serif"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<View
android:id="@+id/separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:background="?attr/dividerColor" />
</RelativeLayout>

View file

@ -0,0 +1,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.kabouzeid.gramophone.ui.activities.GenreDetailActivity">
<item
android:id="@+id/action_shuffle_genre"
android:icon="@drawable/ic_shuffle_white_24dp"
android:title="@string/action_shuffle_all"
app:showAsAction="ifRoom" />
</menu>

View file

@ -30,6 +30,7 @@
<string name="action_set_as_start_directory">Set as start directory</string> <string name="action_set_as_start_directory">Set as start directory</string>
<string name="albums">Albums</string> <string name="albums">Albums</string>
<string name="artists">Artists</string> <string name="artists">Artists</string>
<string name="genres">Genres</string>
<string name="songs">Songs</string> <string name="songs">Songs</string>
<string name="playlists">Playlists</string> <string name="playlists">Playlists</string>
<string name="unplayable_file">Couldn\u2019t play this song.</string> <string name="unplayable_file">Couldn\u2019t play this song.</string>
@ -41,6 +42,7 @@
<string name="album">Album</string> <string name="album">Album</string>
<string name="artist">Artist</string> <string name="artist">Artist</string>
<string name="genre">Genre</string> <string name="genre">Genre</string>
<string name="unknown_genre">Unknown genre</string>
<string name="album_artist">Album artist</string> <string name="album_artist">Album artist</string>
<string name="year">Year</string> <string name="year">Year</string>
<string name="track_hint">"Track (2 for track 2 or 3004 for CD3 track 4)"</string> <string name="track_hint">"Track (2 for track 2 or 3004 for CD3 track 4)"</string>
@ -150,6 +152,7 @@
<string name="no_albums">No albums</string> <string name="no_albums">No albums</string>
<string name="no_songs">No songs</string> <string name="no_songs">No songs</string>
<string name="no_artists">No artists</string> <string name="no_artists">No artists</string>
<string name="no_genres">No genres</string>
<string name="empty">Empty</string> <string name="empty">Empty</string>
<string name="playlist_name_empty">Playlist name</string> <string name="playlist_name_empty">Playlist name</string>
<string name="song">Song</string> <string name="song">Song</string>